При разработке обработок по оптимизации, сжатию или пакетной загрузке изображений номенклатуры в современных конфигурациях 1С (таких как «1С:Управление торговлей 11», «1С:ERP» или «1С:Комплексная автоматизация») начинающие программисты часто сталкиваются с аппаратной ошибкой записи данных. Попытка напрямую перезаписать реквизит хранилища приводит к ошибке доступа или к тому, что изменения попросту не сохраняются в базе данных.
В этой статье мы подробно разберем, почему возникает эта проблема, проанализируем ошибки объектной модели 1С и рассмотрим два метода решения: классическое исправление объектного пути и современный, надежный способ обновления картинок с использованием Библиотеки стандартных подсистем (БСП) и встроенных средств платформы.
Рассмотрим исходную ситуацию. У нас есть код, в котором разработчик пытается записать сжатую картинку обратно в номенклатуру:
ИзмененнаяПозиция = Выборка.Ссылка.ПолучитьОбъект();
МояКартинка = Новый Картинка(ИмяФайлa3);
Хран = Новый ХранилищеЗначения(МояКартинка);
ИзмененнаяПозиция.ФайлКартинки.ФайлХранилище = Хран; // Точка останова и ошибка
ИзмененнаяПозиция.Записать();
Проанализируем этот участок кода. Здесь кроется фундаментальная ошибка понимания объектной модели платформы 1С. Разработчик получил объект номенклатуры в переменную ИзмененнаяПозиция. Реквизит номенклатуры ФайлКартинки является ссылкой на элемент другого справочника — обычно это СправочникСсылка.НоменклатураПрисоединенныеФайлы.
Когда выполняется выражение ИзмененнаяПозиция.ФайлКартинки.ФайлХранилище = Хран, программа пытается напрямую изменить реквизит у ссылки, а не у объекта. В 1С:Предприятии свойства объектов, доступные через точку от ссылки, открыты только для чтения. Чтобы изменить данные присоединенного файла, нам необходимо получить непосредственный объект этого файла.
Для того чтобы исправить ошибку в рамках логики автора, нам нужно изменить объект самого присоединенного файла, а не номенклатуры. Давайте разберем по шагам, как должен выглядеть работающий код:
ПолучитьОбъект().ХранилищеЗначения в соответствующий реквизит объекта присоединенного файла.Посмотрим на пример реализации этого алгоритма:
// Проверяем, заполнена ли ссылка на картинку у номенклатуры
Если ЗначениеЗаполнено(Выборка.ФайлКартинки) Тогда
// Получаем ОБЪЕКТ присоединенного файла, а не номенклатуры!
ОбъектФайлаКартинки = Выборка.ФайлКартинки.ПолучитьОбъект();
Если ОбъектФайлаКартинки <> Неопределено Тогда
// Создаем объект Картинка из файла на диске
НоваяКартинка = Новый Картинка(ИмяФайла2);
// Помещаем картинку в ХранилищеЗначения
НовоеХранилище = Новый ХранилищеЗначения(НоваяКартинка);
// Записываем хранилище в реквизит объекта файла
ОбъектФайлаКартинки.ФайлХранилище = НовоеХранилище;
// Сохраняем изменения присоединенного файла в базе данных
ОбъектФайлаКартинки.Записать();
Сообщить("Изображение успешно обновлено для номенклатуры: " + Выборка.Ссылка);
КонецЕсли;
КонецЕсли;
Этот метод исправит ошибку записи, но у него есть ряд критических недостатков, если мы работаем в современных конфигурациях на базе БСП (например, УТ 11).
В конфигурации «1С:Управление торговлей 11» файлы не всегда хранятся непосредственно в базе данных в реквизите ФайлХранилище. В настройках системы может быть включена опция «Хранить файлы в томах на диске». В этом случае физические файлы сохраняются в сетевой папке на сервере, а в базе хранятся лишь пути к ним. Если вы попытаетесь перезаписать ФайлХранилище напрямую, файлы на диске не обновятся, и система покажет пользователю старое изображение из кэша или выдаст ошибку.
Также, в современных версиях БСП двоичные данные могут кэшироваться в регистре сведений ДвоичныеДанныеФайлов. Чтобы избежать рассинхронизации данных, мы должны использовать штатные механизмы подсистемы «Работа с файлами».
Рассмотрим подробнее программный код, который корректно обновит картинку независимо от того, где она хранится — в базе данных или на диске:
// 1. Создаем двоичные данные из нового (сжатого) файла изображения
ДвоичныеДанныеНовойКартинки = Новый ДвоичныеДанные(ИмяФайла2);
// 2. Помещаем эти данные во временное хранилище
АдресВременногоХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанныеНовойКартинки);
// 3. Формируем структуру с параметрами файла для БСП
ПараметрыФайла = Новый Структура;
ПараметрыФайла.Вставить("АдресФайлаВоВременномХранилище", АдресВременногоХранилища);
ПараметрыФайла.Вставить("ДатаМодификацииУниверсальная", УниверсальноеВремя(ТекущаяДата()));
// 4. Ссылка на присоединенный файл номенклатуры
СсылкаНаФайл = Выборка.ФайлКартинки;
Если ЗначениеЗаполнено(СсылкаНаФайл) Тогда
// Вызываем стандартный метод БСП для перезаписи файла
РаботаСФайлами.ОбновитьФайл(СсылкаНаФайл, ПараметрыФайла);
Сообщить("Файл успешно обновлен через БСП для: " + Выборка.Ссылка);
КонецЕсли;
Использование метода РаботаСФайлами.ОбновитьФайл гарантирует, что система автоматически определит место хранения картинки, запишет её в нужный том на диске (если используются тома), обновит размер файла в карточке и очистит старый кэш у пользователей.
В исходном коде автора для сжатия изображений запускаются внешние утилиты PhotoResize400.exe и JPGCLN32.EXE через вызов команды ЗапуститьПриложение. Такой подход имеет серьезные минусы: он привязан к операционной системе Windows, требует наличия стороннего софта на сервере или клиенте и значительно замедляет работу системы.
Начиная с версии платформы 1С:Предприятие 8.3.14, разработчикам доступен мощный встроенный инструмент — объект ОбрабатываемаяКартинка. Он позволяет сжимать, масштабировать, обрезать и конвертировать изображения непосредственно в коде 1С.
Проанализируем ситуацию и напишем полностью автономный код, который сжимает исходное изображение номенклатуры до нужных размеров (например, до ширины в 600 пикселей с оптимизацией качества JPEG) и сразу обновляет его в системе:
// Получаем двоичные данные исходной картинки из базы через БСП
ДвоичныеДанныеИсходные = РаботаСФайлами.ДвоичныеДанныеФайла(Выборка.ФайлКартинки);
Если ТипЗнч(ДвоичныеДанныеИсходные) = Тип("ДвоичныеДанные") Тогда
// Создаем объект Картинка
ИсходнаяКартинка = Новый Картинка(ДвоичныеДанныеИсходные);
// Инициализируем инструмент обработки картинок платформы 1С
ОбработкаИзображения = Новый ОбрабатываемаяКартинка(ИсходнаяКартинка);
// Пропорционально уменьшаем картинку до ширины 600 пикселей (высота определится сама)
ОбработкаИзображения.УстановитьРазмер(600, Неопределено);
// Получаем обработанную картинку
СжатаяКартинка = ОбработкаИзображения.ПолучитьКартинку();
// Получаем двоичные данные сжатого изображения в формате JPEG с качеством 75%
ДвоичныеДанныеСжатые = СжатаяКартинка.ПолучитьДвоичныеДанные("image/jpeg", 75);
// Помещаем новые сжатые данные во временное хранилище
АдресХранилища = ПоместитьВоВременноеХранилище(ДвоичныеДанныеСжатые);
// Готовим структуру для обновления БСП
ПараметрыОбновления = Новый Структура;
ПараметрыОбновления.Вставить("АдресФайлаВоВременномХранилище", АдресХранилища);
ПараметрыОбновления.Вставить("ДатаМодификацииУниверсальная", УниверсальноеВремя(ТекущаяДата()));
// Обновляем файл изображения в системе
РаботаСФайлами.ОбновитьФайл(Выборка.ФайлКартинки, ПараметрыОбновления);
Сообщить("Изображение для " + Выборка.Ссылка + " успешно сжато средствами платформы 1С.");
КонецЕсли;
При работе с файлами картинок в современных решениях 1С важно соблюдать следующие правила:
ПолучитьОбъект(), если используете прямую запись в базу.РаботаСФайлами.ОбновитьФайл избавит вас от проблем с правами доступа, кэшированием и томами хранения файлов.ОбрабатываемаяКартинка делает ваш код кроссплатформенным, стабильным и быстрым, исключая необходимость использования файлов .exe.