При активной работе розничного магазина пользователи часто наполняют карточки товаров фотографиями. В старых конфигурациях, таких как 1С:Розница ред. 1 (работающих на обычных формах и без использования Библиотеки Стандартных Подсистем — БСП), файлы по умолчанию сохраняются непосредственно в базу данных в виде двоичных данных. Это приводит к взрывному росту объема файловой базы 1Cv8.1CD. База начинает зависать, регламентные операции замедляются, а работа через общую сетевую папку (SMB-протокол) становится невыносимо медленной.
В этой статье мы подробно разберем, как спроектировать и внедрить эффективную систему хранения изображений номенклатуры за пределами базы данных 1С, обеспечив при этом надежную идентификацию файлов, защиту от случайного удаления и высокую скорость работы интерфейса.
Для оптимизации работы нам необходимо полностью исключить хранение тяжелых бинарных данных (картинок) внутри таблицы базы данных. Вместо этого мы будем хранить сами файлы в специально выделенном сетевом каталоге (или на веб/FTP-сервере), а в самой 1С будем записывать лишь текстовые ссылки, указывающие на расположение этих файлов на диске.
Для реализации этого механизма нам потребуется решить четыре важные задачи:
Рассмотрим каждый из этих шагов подробно.
Выясним, как правильно называть файлы на диске, чтобы 1С однозначно понимала, какому товару принадлежит конкретное изображение. На форумах часто предлагают два основных варианта: использовать Код номенклатуры или уникальный идентификатор ссылки — GUID.
Проанализируем оба подхода:
00001_1.jpg, 00001_2.jpg).Ссылка.УникальныйИдентификатор() гарантирует абсолютную уникальность в пределах любой базы данных. Имя файла будет выглядеть как длинная строка шестнадцатеричных символов, например: d3b07384-d113-11db-a379-001517154562.jpg.Важная рекомендация по оптимизации файловой системы:
Если вы планируете хранить десятки тысяч изображений, категорически не рекомендуется записывать их все в одну общую папку. Файловые системы (такие как NTFS или FAT32) начинают сильно тормозить при поиске файла в каталоге, если количество объектов в нем превышает 10 000.
Чтобы избежать этой проблемы, применим метод иерархического хранения на основе GUID. Мы будем автоматически создавать подпапки на диске, используя первые символы уникального идентификатора.
Например, если GUID файла: d3b07384-d113-11db-a379-001517154562, мы сохраним его по следующему пути:
\ХранилищеКартинок\d3\b0\d3b07384-d113-11db-a379-001517154562.jpg.
Благодаря этому в каждой папке на диске будет находиться не более пары десятков файлов, и операционная система сервера будет мгновенно находить нужное изображение.
Поскольку у нас конфигурация "1С:Розница 1.0" на обычных формах, мы не можем использовать современные механизмы БСП. Нам потребуется внести минимальные изменения в конфигурацию через режим "Конфигуратор".
Для хранения связей между товаром и его изображениями создадим новый Регистр сведений. Назовем его ИзображенияНоменклатуры.
Настроим свойства регистра:
Номенклатура (Тип: СправочникСсылка.Номенклатура, установим свойство "Ведущее").ИдентификаторФайла (Тип: Строка, длина 36 — здесь мы будем хранить GUID картинки).ОсновноеИзображение (Тип: Булево) — флаг для определения того, какую именно фотографию выводить на форму списка номенклатуры и в прайс-листы по умолчанию.ОригинальноеИмя (Тип: Строка, длина 150) — здесь полезно хранить исходное имя файла (например, "туфли_вид_сбоку.jpg"), чтобы пользователь при выгрузке картинки обратно на компьютер получал красивое человекочитаемое имя.Также нам потребуется создать общую настройку — где именно на сервере физически хранятся эти файлы. Для этого добавим новую Константу с именем ПутьКТомуХраненияФайлов (Тип: Строка, длина 250). В режиме "1С:Предприятие" администратор запишет туда сетевой путь, например: \\SERVER-SRV\1C_Photos\.
Один из главных страхов при выносе картинок на сетевой диск — это то, что пользователи могут случайно зайти в общую папку через Проводник Windows и удалить все фотографии. Разберем три эффективных метода защиты данных.
Это самый простой способ, не требующий написания сложного кода. Мы настраиваем права доступа к сетевой папке на сервере следующим образом:
В результате пользователь, работая в 1С, сможет успешно добавлять новые фотографии (программа от его имени создает файл в каталоге). Но если он попытается зайти на этот диск вручную и нажать клавишу "Delete", операционная система Windows заблокирует это действие.
Более надежный и быстрый способ, исключающий прямой доступ пользователей к дискам. На сервере, где хранятся файлы, разворачивается простейший веб-сервер (например, IIS или Apache).
http://192.168.1.100/photos/d3/b0/d3b07384...jpg.Этот способ работает в разы быстрее классической сетевой папки (особенно при слабом Wi-Fi подключении на кассах магазинов) и гарантирует абсолютную безопасность данных.
Пользователи часто загружают фотографии напрямую со смартфонов или фотоаппаратов. Вес одной такой фотографии может составлять от 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",
УровеньЖурналаРегистрации.Ошибка, , , ОписаниеОшибки());
Возврат Ложь;
КонецПопытки;
КонецФункции
Посмотрим на практический пример того, как связать все элементы воедино. Нам нужно написать процедуру добавления нового изображения в карточке номенклатуры и процедуру его отображения на форме.
Разберем по шагам логику добавления файла:
УникальныйИдентификатор (GUID), который станет именем нового файла.ПутьКТомуХраненияФайлов.СжатьИзображениеWIA, и файл записывается на сетевой диск.ИзображенияНоменклатуры записывается новая строка с привязкой к текущей Номенклатуре.Проанализируем пример кода для загрузки файла:
Процедура ДобавитьИзображениеКТовару(ТоварСсылка)
// Шаг 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" на обычных формах) — задача вполне тривиальная, если подойти к ней системно. Мы избавились от хранения бинарников внутри СУБД, решив главную проблему с падением скорости работы и разрастанием базы данных.
Преимущества внедренного решения: