Как организовать внешнее хранение картинок номенклатуры вне базы данных в 1С:Розница 1?

Программист 1С v8.3 (Обычные формы) 1С:Розница Управленческий учет Торговля и дистрибуция
← На главную

При активной работе розничного магазина пользователи часто наполняют карточки товаров фотографиями. В старых конфигурациях, таких как 1С:Розница ред. 1 (работающих на обычных формах и без использования Библиотеки Стандартных Подсистем — БСП), файлы по умолчанию сохраняются непосредственно в базу данных в виде двоичных данных. Это приводит к взрывному росту объема файловой базы 1Cv8.1CD. База начинает зависать, регламентные операции замедляются, а работа через общую сетевую папку (SMB-протокол) становится невыносимо медленной.

В этой статье мы подробно разберем, как спроектировать и внедрить эффективную систему хранения изображений номенклатуры за пределами базы данных 1С, обеспечив при этом надежную идентификацию файлов, защиту от случайного удаления и высокую скорость работы интерфейса.

В чем заключается основная идея решения?

Для оптимизации работы нам необходимо полностью исключить хранение тяжелых бинарных данных (картинок) внутри таблицы базы данных. Вместо этого мы будем хранить сами файлы в специально выделенном сетевом каталоге (или на веб/FTP-сервере), а в самой 1С будем записывать лишь текстовые ссылки, указывающие на расположение этих файлов на диске.

Для реализации этого механизма нам потребуется решить четыре важные задачи:

  1. Определить уникальный алгоритм именования файлов для связи с карточкой номенклатуры.
  2. Разработать структуру метаданных в конфигурации 1С для связи товаров и картинок (ведь у одной номенклатуры может быть несколько фото).
  3. Организовать безопасный доступ к сетевому хранилищу с защитой от случайного удаления файлов пользователями.
  4. Реализовать сжатие исходных изображений "на лету" перед их сохранением на диск, чтобы не перегружать сеть тяжелыми файлами со смартфонов.

Рассмотрим каждый из этих шагов подробно.

Шаг 1. Идентификация файлов на диске: GUID против Кода

Выясним, как правильно называть файлы на диске, чтобы 1С однозначно понимала, какому товару принадлежит конкретное изображение. На форумах часто предлагают два основных варианта: использовать Код номенклатуры или уникальный идентификатор ссылки — GUID.

Проанализируем оба подхода:

Важная рекомендация по оптимизации файловой системы:
Если вы планируете хранить десятки тысяч изображений, категорически не рекомендуется записывать их все в одну общую папку. Файловые системы (такие как NTFS или FAT32) начинают сильно тормозить при поиске файла в каталоге, если количество объектов в нем превышает 10 000.

Чтобы избежать этой проблемы, применим метод иерархического хранения на основе GUID. Мы будем автоматически создавать подпапки на диске, используя первые символы уникального идентификатора.
Например, если GUID файла: d3b07384-d113-11db-a379-001517154562, мы сохраним его по следующему пути:
\ХранилищеКартинок\d3\b0\d3b07384-d113-11db-a379-001517154562.jpg.
Благодаря этому в каждой папке на диске будет находиться не более пары десятков файлов, и операционная система сервера будет мгновенно находить нужное изображение.

Шаг 2. Разработка структуры метаданных в 1С

Поскольку у нас конфигурация "1С:Розница 1.0" на обычных формах, мы не можем использовать современные механизмы БСП. Нам потребуется внести минимальные изменения в конфигурацию через режим "Конфигуратор".

Для хранения связей между товаром и его изображениями создадим новый Регистр сведений. Назовем его ИзображенияНоменклатуры.

Настроим свойства регистра:

Также нам потребуется создать общую настройку — где именно на сервере физически хранятся эти файлы. Для этого добавим новую Константу с именем ПутьКТомуХраненияФайлов (Тип: Строка, длина 250). В режиме "1С:Предприятие" администратор запишет туда сетевой путь, например: \\SERVER-SRV\1C_Photos\.

Шаг 3. Защита файлов от удаления пользователями

Один из главных страхов при выносе картинок на сетевой диск — это то, что пользователи могут случайно зайти в общую папку через Проводник Windows и удалить все фотографии. Разберем три эффективных метода защиты данных.

Метод А. Тонкая настройка прав доступа NTFS (уровень сисадмина)

Это самый простой способ, не требующий написания сложного кода. Мы настраиваем права доступа к сетевой папке на сервере следующим образом:

  1. Для группы пользователей 1С предоставляются права: "Чтение" и "Запись" (создание файлов и папок).
  2. В дополнительных параметрах безопасности NTFS для этой папки накладывается явный ЗАПРЕТ на "Удаление" и "Удаление подпапок и файлов".

В результате пользователь, работая в 1С, сможет успешно добавлять новые фотографии (программа от его имени создает файл в каталоге). Но если он попытается зайти на этот диск вручную и нажать клавишу "Delete", операционная система Windows заблокирует это действие.

Метод Б. Использование локального HTTP-сервера (Рекомендуется)

Более надежный и быстрый способ, исключающий прямой доступ пользователей к дискам. На сервере, где хранятся файлы, разворачивается простейший веб-сервер (например, IIS или Apache).

Этот способ работает в разы быстрее классической сетевой папки (особенно при слабом Wi-Fi подключении на кассах магазинов) и гарантирует абсолютную безопасность данных.

Шаг 4. Оптимизация размера картинок (Сжатие перед отправкой)

Пользователи часто загружают фотографии напрямую со смартфонов или фотоаппаратов. Вес одной такой фотографии может составлять от 5 до 15 Мегабайт. Если загружать такие файлы "как есть", то даже при хранении на диске сеть магазина быстро забьется, а карточки товаров будут открываться крайне медленно.

Поскольку в "Рознице 1" нет готовых инструментов сжатия БСП, мы можем воспользоваться встроенным в операционную систему Windows компонентом — WIA (Windows Image Acquisition). Он присутствует на любом компьютере с ОС Windows и позволяет легко пережимать картинки "на лету" без установки стороннего ПО.

Рассмотрим подробнее пример кода, который сжимает исходное изображение до разрешения 1000х800 пикселей с качеством 80%, превращая файл объемом 10 МБ в качественную картинку весом всего 100 КБ:


// Функция сжимает исходный файл изображения и сохраняет его по новому пути
Функция СжатьИзображениеWIA(ПутьКИсходномуФайлу, ПутьКНовомуФайлу) Экспорт

    Попытка
        // Создаем COM-объекты для работы с изображениями Windows
        Фильтр = Новый COMОбъект("WIA.ImageProcess");
        Изображение = Новый COMОбъект("WIA.ImageFile");
        
        // Загружаем исходную картинку
        Изображение.LoadFile(ПутьКИсходномуФайлу);
        
        // Настраиваем первый фильтр — масштабирование (Scale)
        Фильтр.Filters.Add(Фильтр.FilterInfos("Scale").FilterID);
        Фильтр.Filters(1).Properties("MaximumWidth").Value = 1000;
        Фильтр.Filters(1).Properties("MaximumHeight").Value = 800;
        Фильтр.Filters(1).Properties("PreserveAspectRatio").Value = Истина; // Сохранять пропорции
        
        // Настраиваем второй фильтр — конвертация формата в JPEG и сжатие
        Фильтр.Filters.Add(Фильтр.FilterInfos("Convert").FilterID);
        Фильтр.Filters(2).Properties("FormatID").Value = "{B96B3CAE-0728-11D3-9D7B-0000F81EF32E}"; // GUID формата JPEG
        Фильтр.Filters(2).Properties("Quality").Value = 80; // Качество сжатия в процентах
        
        // Применяем фильтры к изображению
        СжатоеИзображение = Фильтр.Apply(Изображение);
        
        // Удаляем старый файл, если он уже существовал по новому пути
        ФайлНаДиске = Новый Файл(ПутьКНовомуФайлу);
        Если ФайлНаДиске.Существует() Тогда
            УдалитьФайлы(ПутьКНовомуФайлу);
        КонецЕсли;
        
        // Сохраняем сжатый результат
        СжатоеИзображение.SaveFile(ПутьКНовомуФайлу);
        Возврат Истина;
        
    Исключение
        ЗаписьЖурналаРегистрации("Ошибка сжатия изображения WIA", 
            УровеньЖурналаРегистрации.Ошибка, , , ОписаниеОшибки());
        Возврат Ложь;
    КонецПопытки;

КонецФункции

Шаг 5. Реализация добавления и просмотра файлов в 1С

Посмотрим на практический пример того, как связать все элементы воедино. Нам нужно написать процедуру добавления нового изображения в карточке номенклатуры и процедуру его отображения на форме.

Разберем по шагам логику добавления файла:

  1. Пользователь нажимает кнопку "Добавить фото" на форме карточки товара и выбирает локальный файл через диалог выбора файлов.
  2. Программа генерирует новый УникальныйИдентификатор (GUID), который станет именем нового файла.
  3. Программа определяет путь к сетевому тому хранения из константы ПутьКТомуХраненияФайлов.
  4. Создается иерархическая структура папок на основе первых символов сгенерированного GUID.
  5. Вызывается функция сжатия изображения СжатьИзображениеWIA, и файл записывается на сетевой диск.
  6. В созданный нами регистр сведений ИзображенияНоменклатуры записывается новая строка с привязкой к текущей Номенклатуре.

Проанализируем пример кода для загрузки файла:


Процедура ДобавитьИзображениеКТовару(ТоварСсылка)

    // Шаг 1. Выбор файла пользователем
    ДиалогВыбора = Новый ДиалогВыбораФайла(РежимДиалогаВыбораФайла.Открытие);
    ДиалогВыбора.Фильтр = "Изображения (*.jpg;*.png)|*.jpg;*.png";
    ДиалогВыбора.МножественныйВыбор = Ложь;
    
    Если НЕ ДиалогВыбора.Выбрать() Тогда
        Возврат; // Пользователь передумал
    КонецЕсли;
    
    ИсходныйПуть = ДиалогВыбора.ВыбранныеФайлы[0];
    ФайлИсточник = Новый Файл(ИсходныйПуть);
    
    // Шаг 2. Получение базового пути хранения картинок из константы
    БазовыйПуть = Константы.ПутьКТомуХраненияФайлов.Получить();
    Если ПустаяСтрока(БазовыйПуть) Тогда
        Предупреждение("Не заполнен путь к хранилищу файлов в настройках системы!");
        Возврат;
    КонецЕсли;
    
    // Обеспечим наличие закрывающего слэша в пути к каталогу
    Если Прав(БазовыйПуть, 1) <> "\" Тогда
        БазовыйПуть = БазовыйПуть + "\";
    КонецЕсли;
    
    // Шаг 3. Генерация GUID и расчет путей хранения
    НовыйGUID = Строка(Новый УникальныйИдентификатор);
    SubFolder1 = Лев(НовыйGUID, 2);
    SubFolder2 = Сред(НовыйGUID, 3, 2);
    
    КаталогНазначения = БазовыйПуть + SubFolder1 + "\" + SubFolder2 + "\";
    
    // Создаем папки на сервере, если их еще нет
    СоздатьКаталог(КаталогНазначения);
    
    ИмяНовогоФайла = НовыйGUID + "." + ФайлИсточник.Расширение;
    ПолныйПутьНазначения = КаталогНазначения + ИмяНовогоФайла;
    
    // Шаг 4. Сжатие и сохранение файла на диск
    УспешноСохранено = СжатьИзображениеWIA(ИсходныйПуть, ПолныйПутьНазначения);
    
    // Если сжатие по какой-то причине не сработало, скопируем оригинальный файл
    Если НЕ УспешноСохранено Тогда
        КопироватьФайл(ИсходныйПуть, ПолныйПутьНазначения);
    КонецЕсли;
    
    // Шаг 5. Запись связи в регистр сведений
    НаборЗаписей = РегистрыСведений.ИзображенияНоменклатуры.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.Номенклатура.Установить(ТоварСсылка);
    НаборЗаписей.Отбор.ИдентификаторФайла.Установить(НовыйGUID);
    
    НоваяЗапись = НаборЗаписей.Добавить();
    НоваяЗапись.Номенклатура = ТоварСсылка;
    НоваяЗапись.ИдентификаторФайла = НовыйGUID;
    НоваяЗапись.ОригинальноеИмя = ФайлИсточник.Имя;
    
    // Проверим, есть ли уже у товара картинки. Если нет - сделаем эту основной
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ 1 1 
        |ИЗ РегистрСведений.ИзображенияНоменклатуры КАК Изображения 
        |ГДЕ Изображения.Номенклатура = &Номенклатура";
    Запрос.УстановитьПараметр("Номенклатура", ТоварСсылка);
    
    НоваяЗапись.ОсновноеИзображение = Запрос.Выполнить().Пустой();
    
    НаборЗаписей.Записать(Ложь);
    Сообщить("Изображение успешно добавлено!");

КонецПроцедуры

Для отображения картинки в форме элемента справочника Номенклатура нам достаточно разместить на форме элемент управления "Поле картинки" с именем ПолеВводаКартинки. При переходе по записям или открытии карточки мы будем динамически считывать файл с диска и помещать его в форму.

Рассмотрим код вывода картинки на форму:


Процедура ОбновитьКартинкуНаФорме(ТоварСсылка)

    // Инициализируем очистку поля на форме
    ЭлементыФормы.ПолеВводаКартинки.Картинка = Новый Картинка;
    
    // Запрос для получения основного изображения номенклатуры
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ ПЕРВЫЕ 1
        |    Изображения.ИдентификаторФайла
        |ИЗ
        |    РегистрСведений.ИзображенияНоменклатуры КАК Изображения
        |ГДЕ
        |    Изображения.Номенклатура = &Номенклатура
        |    И Изображения.ОсновноеИзображение = ИСТИНА";
        
    Запрос.УстановитьПараметр("Номенклатура", ТоварСсылка);
    РезультатЗапроса = Запрос.Выполнить();
    
    Если РезультатЗапроса.Пустой() Тогда
        Возврат; // У товара нет картинок
    КонецЕсли;
    
    Выборка = РезультатЗапроса.Выбрать();
    Выборка.Следующий();
    
    // Строим путь к файлу на сервере
    БазовыйПуть = Константы.ПутьКТомуХраненияФайлов.Получить();
    Если Прав(БазовыйПуть, 1) <> "\" Тогда
        БазовыйПуть = БазовыйПуть + "\";
    КонецЕсли;
    
    GUIDКартинки = Выборка.ИдентификаторФайла;
    SubFolder1 = Лев(GUIDКартинки, 2);
    SubFolder2 = Сред(GUIDКартинки, 3, 2);
    
    ПолныйПутьКФайлу = БазовыйПуть + SubFolder1 + "\" + SubFolder2 + "\" + GUIDКартинки + ".jpg";
    
    ФайлНаДиске = Новый Файл(ПолныйПутьКФайлу);
    Если ФайлНаДиске.Существует() Тогда
        // Загружаем картинку с диска в форму 1С
        ЭлементыФормы.ПолеВводаКартинки.Картинка = Новый Картинка(ПолныйПутьКФайлу);
    Иначе
        Сообщить("Файл изображения не найден по пути: " + ПолныйПутьКФайлу, СтатусСообщения.Внимание);
    КонецЕсли;

КонецПроцедуры

Подведем итоги

Реализация внешнего хранения файлов для старых версий 1С (таких как "Розница 1.0" на обычных формах) — задача вполне тривиальная, если подойти к ней системно. Мы избавились от хранения бинарников внутри СУБД, решив главную проблему с падением скорости работы и разрастанием базы данных.

Преимущества внедренного решения:

← На главную