Приветствуем вас, уважаемые коллеги! Сегодня мы детально разберем крайне популярную и важную задачу, с которой сталкивается практически каждый разработчик при создании современных интерфейсов в системе «1С:Предприятие 8.3» на управляемых формах. Речь пойдет о сохранении файлов (в нашем случае — картинок) в регистры сведений и последующем их выводе на экран пользователя.
Казалось бы, задача тривиальная: загрузить файл на клиенте, передать на сервер, запаковать в ХранилищеЗначения и записать в базу. Однако на практике разработчики часто совершают грубейшие архитектурные ошибки, которые приводят к нестабильной работе системы, блокировкам файлов или даже сбою серверного процесса при переходе на операционные системы семейства Linux. Мы проанализируем эти ошибки и построим надежное, производительное и современное решение.
Прежде чем перейти к правильной реализации, давайте выясним причину, почему подходы, обсуждаемые на форумах (включая представленный в теме лог), часто не работают или работают некорректно в реальных многопользовательских базах данных.
C:\tmp_fail.jpg, чтобы затем прочитать его на клиенте. Это критическая ошибка. Сервер приложений 1С часто работает под учетной записью с ограниченными правами (например, usr1cv8), у которой нет доступа на запись в корень диска. Более того, при работе в кластере серверов или на Linux-сервере этот код мгновенно вызовет фатальный сбой. Нам категорически нельзя использовать физические пути к файлам на диске сервера для отображения картинок на клиенте!ПередЗаписьюНаСервере основного объекта формы (например, справочника физических лиц) нарушает транзакционную целостность. Если при записи элемента справочника произойдет ошибка (например, сработает проверка заполнения или сгенерируется отказ), то запись в регистре сведений все равно зафиксируется, так как метод менеджера записи работает вне общей транзакции формы. Данные будут рассинхронизированы.НачатьПомещениеФайла и старый диалог выбора файлов постепенно выводятся из поддержки. Нам следует ориентироваться на современные асинхронные вызовы платформы.Давайте построим правильную и отказоустойчивую схему работы с изображениями без записи промежуточных файлов на диск.
Для хранения изображений мы будем использовать подчиненный регистр сведений. Рассмотрим его структуру. Создадим регистр сведений с именем Изображения со следующими характеристиками:
Объект с типом ссылки на наш основной справочник (например, СправочникСсылка.ФизическиеЛица). Установим свойство "Ведущее", чтобы при удалении физлица автоматически удалялась и связанная с ним картинка.Изображение с типом данных ХранилищеЗначения. Именно в этом поле будут храниться сжатые двоичные данные картинки.Для вывода картинки на форму нам не понадобятся декорации и сложные манипуляции с файлами. Платформа «1С:Предприятие» умеет самостоятельно отображать изображения, если ей передать правильный адрес во временном хранилище или навигационную ссылку.
Выполним следующие действия в конфигураторе:
АдресКартинки и типом данных Строка.АдресКартинки в окно элементов формы.Теперь элемент управления на форме ожидает, что в реквизит АдресКартинки будет помещен адрес временного хранилища или навигационная ссылка на базу данных. Как только туда запишется адрес, картинка отобразится автоматически.
Рассмотрим современный асинхронный способ выбора файла на клиенте и помещения его во временное хранилище. Для платформ версии 8.3.15 и выше рекомендуется использовать метод ПоместитьФайлНаСерверАсинх, который работает через обещания и делает код линейным.
Создадим на клиенте команду формы ВыбратьФайлКартинки и привяжем ее к кнопке на форме:
&НаКлиенте
Асинх Процедура ВыбратьФайлКартинки(Команда)
// Настроим параметры диалога выбора файла
ПараметрыДиалога = Новый ПараметрыДиалогаПомещенияФайлов;
ПараметрыДиалога.Фильтр = "Изображения (*.jpg;*.png;*.bmp)|*.jpg;*.png;*.bmp";
ПараметрыДиалога.Заголовок = "Выберите файл изображения";
// Запускаем асинхронное помещение файла во временное хранилище формы
РезультатЗагрузки = Ждать ПоместитьФайлНаСерверАсинх(,,, ПараметрыДиалога, УникальныйИдентификатор);
Если РезультатЗагрузки <> Неопределено И Не РезультатЗагрузки.ПомещениеФайлаОтменено Тогда
// Ссылка на временное хранилище находится в РезультатЗагрузки.Адрес
АдресКартинки = РезультатЗагрузки.Адрес;
// Помечаем форму как измененную, чтобы платформа предложила сохранить изменения
Модифицированность = Истина;
КонецЕсли;
КонецПроцедуры
Обратите внимание на параметр УникальныйИдентификатор формы, передаваемый в метод. Это критически важно: временное хранилище будет "жить" ровно столько, сколько открыта наша управляемая форма. Если его не указать, данные могут быть удалены сборщиком мусора до момента записи объекта.
Чтобы не нарушать транзакционную целостность базы данных, запись подчиненного регистра сведений нужно производить в обработчике события формы ПриЗаписиНаСервере. В этот момент основной объект (например, физическое лицо) уже успешно записан в базу данных, ссылка на него существует и валидна, но транзакция еще не зафиксирована.
Проанализируем серверный код сохранения:
&НаСервере
Процедура ПриЗаписиНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
// Проверяем, действительно ли адрес ведет во временное хранилище
// (если картинка не менялась, там будет навигационная ссылка или пустота)
Если ЭтоАдресВременногоХранилища(АдресКартинки) Тогда
// Получаем двоичные данные из временного хранилища по адресу
ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресКартинки);
Если ТипЗнч(ДвоичныеДанные) = Тип("ДвоичныеДанные") Тогда
// Создаем менеджер записи регистра сведений для перезаписи данных
ЗаписьРегистра = РегистрыСведений.Изображения.СоздатьМенеджерЗаписи();
ЗаписьРегистра.Объект = ТекущийОбъект.Ссылка;
// Сначала пытаемся прочитать существующую запись
ЗаписьРегистра.Прочитать();
// Если записи нет, измерения заполнятся автоматически из инициализации менеджера
ЗаписьРегистра.Объект = ТекущийОбъект.Ссылка;
// Помещаем двоичные данные в ХранилищеЗначения
ЗаписьРегистра.Изображение = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
Попытка
// Записываем данные в рамках текущей транзакции
ЗаписьРегистра.Записать(Истина);
Исключение
Отказ = Истина;
ВызватьИсключение "Не удалось сохранить изображение в регистр сведений. Ошибка: " + ОписаниеОшибки();
КонецПопытки;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Использование параметра Новый СжатиеДанных(9) при инициализации ХранилищеЗначения позволяет максимально эффективно упаковать картинку перед сохранением в СУБД, экономя дисковое пространство на сервере баз данных.
Теперь настроим чтение картинки из регистра при открытии карточки объекта. Для этого воспользуемся обработчиком ПриСозданииНаСервере. Мы рассмотрим два альтернативных способа решения этой задачи.
Этот метод подходит в большинстве случаев. Мы извлекаем данные из базы, распаковываем их и помещаем во временное хранилище управляемой формы.
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Проверим, что объект уже сохранен в базе (не новый)
Если Не Объект.Ссылка.Пустая() Тогда
// Формируем отбор по текущему объекту
ЗаписьРегистра = РегистрыСведений.Изображения.СоздатьМенеджерЗаписи();
ЗаписьРегистра.Объект = Объект.Ссылка;
ЗаписьРегистра.Прочитать();
Если ЗаписьРегистра.Выбран() Тогда
// Получаем двоичные данные из ХранилищаЗначения
ДвоичныеДанные = ЗаписьРегистра.Изображение.Получить();
Если ТипЗнч(ДвоичныеДанные) = Тип("ДвоичныеДанные") Тогда
// Помещаем данные во временное хранилище и привязываем к УИД формы
АдресКартинки = ПоместитьВоВременноеХранилище(ДвоичныеДанные, УникальныйИдентификатор);
КонецЕсли;
КонецЕсли;
КонецЕсли;
КонецПроцедуры
Если мы хотим добиться максимальной производительности интерфейса (особенно при работе с большими файлами картинок), то считывание двоичных данных в память сервера 1С и передача их во временное хранилище может оказаться ресурсоемкой операцией.
Платформа предоставляет механизм получения навигационных ссылок прямо к ресурсам регистров сведений. В этом случае сервер 1С не тратит ресурсы на распаковку картинки, а тонкий клиент обращается к СУБД напрямую. Посмотрим на этот лаконичный код:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
Если Не Объект.Ссылка.Пустая() Тогда
// Создаем ключ записи для точной идентификации строки регистра
ПараметрыКлюча = Новый Структура("Объект", Объект.Ссылка);
КлючЗаписиРегистра = РегистрыСведений.Изображения.СоздатьКлючЗаписи(ПараметрыКлюча);
// Получаем прямую навигационную ссылку на ресурс "Изображение"
АдресКартинки = ПолучитьНавигационнуюСсылку(КлючЗаписиРегистра, "Изображение");
КонецЕсли;
КонецПроцедуры
Этот способ является наиболее предпочтительным с точки зрения стандартов разработки фирмы «1С». При его использовании нагрузка на сервер приложений сводится к минимуму, а отрисовка интерфейса происходит значительно быстрее.
Таким образом, мы полностью исключили из цепочки промежуточные файлы на жестких дисках сервера, обеспечили стабильную кроссплатформенную работу системы и защитили транзакционную целостность данных при записи.