Как прикрепить и открыть один файл в справочнике 1С на управляемых формах?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

В данной статье мы подробно разберем два принципиально разных подхода к реализации этой задачи на платформе 1С v8.3 (Управляемые формы):

  1. Использование стандартной подсистемы БСП «Работа с файлами» — наиболее надежный, масштабируемый и безопасный вариант для типовых конфигураций.
  2. Собственная легковесная реализация через реквизит типа «Хранилище значения» — оптимальное решение для полностью самописных конфигураций с минимальными требованиями, где не хочется перегружать базу лишними метаданными.

Вариант 1. Реализация на основе Библиотеки Стандартных Подсистем (БСП)

Если ваша конфигурация разработана на базе БСП (например, 1С:ERP, 1С:Комплексная автоматизация, 1С:Управление торговлей или УНФ), настоятельно рекомендуется использовать штатную подсистему «Работа с файлами». Это позволит не изобретать велосипед и сразу задействовать механизмы хранения файлов во внешних томах (чтобы не раздувать размер базы данных SQL), версионирование, разграничение прав доступа и проверку безопасности при открытии.

Рассмотрим по шагам, как интегрировать подсистему БСП для нашего справочника-владельца (допустим, он называется ДоговорыКонтрагентов):

Шаг 1. Создание подчиненного справочника файлов

Согласно стандартам разработки БСП, файлы не должны храниться непосредственно в таблице самого справочника-владельца. Это замедляет выборку данных. Вместо этого создается подчиненный справочник. Создадим справочник с именем ДоговорыКонтрагентовПрисоединенныеФайлы.

В качестве родителя (владельца) укажем наш справочник ДоговорыКонтрагентов. Для быстрого создания структуры лучше всего скопировать свойства и реквизиты аналогичного справочника присоединенных файлов из состава БСП (например, ШаблоныСообщенийПрисоединенныеФайлы) или воспользоваться специальным помощником (внедрение БСП).

Шаг 2. Настройка определяемых типов

Для того чтобы подсистемы БСП «узнали» о нашем новом справочнике, необходимо включить его в определяемые типы в конфигураторе:

Шаг 3. Интеграция в модуль формы

Для того чтобы вверху формы элемента отображалась стандартная кнопка «Файлы» (со скрепкой), нам достаточно вывести стандартную параметризуемую команду на форму. БСП предоставляет общую команду ПрисоединенныеФайлыКОбъекту. Мы можем разместить ее в панели навигации формы в группе «Важное».

Как сделать одиночную красивую ссылку вместо целого списка файлов?

Если по условиям задачи нам строго необходимо выводить именно одну прямую ссылку на файл прямо на форму (без перехода в отдельное диалоговое окно со списком), мы можем написать простой программный код при открытии формы. Проанализируем, как получить ссылку на прикрепленный файл и вывести его на форму:


&НаСервере
Процедура ПолучитьСсылкуНаЕдинственныйФайлНаСервере()
    
    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ РАЗРЕШЕННЫЕ ПЕРВЫЕ 1
        |	ПрисоединенныеФайлы.Ссылка КАК ФайлСсылка,
        |	ПрисоединенныеФайлы.Наименование КАК ИмяФайла
        |ИЗ
        |	Справочник.ДоговорыКонтрагентовПрисоединенныеФайлы КАК ПрисоединенныеФайлы
        |ГДЕ
        |	ПрисоединенныеФайлы.ВладелецФайла = &Владелец
        |	И НЕ ПрисоединенныеФайлы.ПометкаУдаления";
    
    Запрос.УстановитьПараметр("Владелец", Объект.Ссылка);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Если Выборка.Следующий() Тогда
        // Заполняем реквизиты формы для отображения гиперссылки
        АдресФайлаБСП = Выборка.ФайлСсылка;
        ИмяФайлаСсылка = Выборка.ИмяФайла;
    Иначе
        ИмяФайлаСсылка = "Прикрепить файл...";
    КонецЕсли;
    
КонецПроцедуры

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


Вариант 2. Легковесный вариант «с нуля» через Хранилище Значения

Выясним, как решить задачу, если конфигурация является самописной или если мы не хотим использовать механизмы БСП ради одного простого XML-файла. В этом случае мы будем использовать реквизит с типом ХранилищеЗначения для непосредственного хранения бинарных данных XML в СУБД.

Шаг 1. Настройка метаданных справочника

В нашем справочнике создадим два реквизита:

  1. ДанныеФайла — тип ХранилищеЗначения. В нем будет храниться сжатый двоичный код XML-документа.
  2. ИмяФайла — тип Строка (длина 150-250 символов). Этот реквизит критически важен: без оригинального имени файла с расширением (например, data_import.xml) операционная система пользователя не поймет, какой программой открывать извлеченные двоичные данные.

Шаг 2. Разработка интерфейса управляемой формы

Сделаем так, чтобы ссылка выглядела красиво и нативно в интерфейсе «Такси»:

  1. Создадим реквизит формы (не путать с реквизитом объекта!) с именем ИмяФайлаСсылка и типом Строка.
  2. Перетащим его на форму в самую верхнюю часть (например, над основными реквизитами или в командную панель).
  3. В свойствах созданного элемента формы (Поле формы) установим следующие параметры:
    • Вид: Поле надписи.
    • Гиперссылка: Да (Истина).
    • Цвет текста: выберем системный синий цвет ссылки (по желанию).
  4. В палитре свойств элемента найдем событие Нажатие и сгенерируем обработчик на клиенте.

Для загрузки нового файла добавим рядом кнопку или команду «Загрузить файл» (или оформим это в виде контекстного меню ссылки).

Шаг 3. Программный код загрузки XML-файла

Поскольку мы ориентируемся на платформу 1С v8.3.12 и выше, нам необходимо отказаться от устаревших модальных окон и использовать современные асинхронные методы работы с файловой системой. Разберем по шагам код загрузки файла на клиенте и его сохранение на сервере.

Начнем с клиентской процедуры запуска диалога выбора файла:


&НаКлиенте
Процедура ЗагрузитьФайл(Команда)
    
    // Настроим параметры диалога выбора файлов
    ПараметрыДиалога = Новый ПараметрыДиалогаПомещенияФайлов;
    ПараметрыДиалога.Фильтр = "Файлы XML (*.xml)|*.xml|Все файлы (*.*)|*.*";
    ПараметрыДиалога.МножественныйВыбор = Ложь;
    ПараметрыДиалога.Заголовок = "Выберите XML-файл для загрузки";
    
    // Создаем описание оповещения для обработки результата выбора
    ОписаниеОповещения = Новый ОписаниеОповещения("ПослеВыбораИПомещенияФайла", ЭтотОбъект);
    
    // Начинаем асинхронное помещение файла во временное хранилище
    НачатьПомещениеФайлаНаСервер(ОписаниеОповещения, , , , ПараметрыДиалога, УникальныйИдентификатор);
    
КонецПроцедуры

Теперь опишем процедуру обратного вызова (оповещения), которая сработает, когда пользователь выберет файл, и платформа автоматически закачает его во временное хранилище:


&НаКлиенте
Процедура ПослеВыбораИПомещенияФайла(РезультатПомещения, ДополнительныеПараметры) Экспорт
    
    Если РезультатПомещения = Неопределено Тогда
        Возврат; // Пользователь закрыл диалог или отменил выбор
    КонецЕсли;
    
    // Получаем адрес во временном хранилище и имя исходного файла
    АдресВременногоХранилища = РезультатПомещения.Адрес;
    
    // Заполняем реквизиты объекта
    Объект.ИмяФайла = РезультатПомещения.СсылкаНаФайл.Файл.Имя;
    
    // Обновляем надпись нашей красивой гиперссылки на форме
    ИмяФайлаСсылка = Объект.ИмяФайла;
    
    // Сигнализируем платформе, что данные формы изменились и требуют сохранения
    Модифицированность = Истина;
    
    ПоказатьПредупреждение(, "Файл успешно подготовлен к записи. Сохраните элемент справочника.");
    
КонецПроцедуры

Для того чтобы данные из временного хранилища перекочевали в базу данных, нам нужно перехватить событие записи объекта на сервере. Реализуем это в обработчике формы ПередЗаписьюНаСервере:


&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
    
    // Проверяем, загружался ли новый файл в этой сессии
    Если ЭтоАдресВременногоХранилища(АдресВременногоХранилища) Тогда
        
        // Извлекаем двоичные данные из временного хранилища
        ДвоичныеДанные = ПолучитьИзВременногоХранилища(АдресВременногоХранилища);
        
        // Помещаем их в ХранилищеЗначения с максимальным уровнем сжатия
        ТекущийОбъект.ДанныеФайла = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
        
        // Удаляем данные из временного хранилища, чтобы освободить оперативную память сервера
        УдалитьИзВременногоХранилища(АдресВременногоХранилища);
        АдресВременногоХранилища = "";
        
    КонецЕсли;
    
КонецПроцедуры

Не забудем объявить переменную АдресВременногоХранилища в самом верху модуля управляемой формы:


&НаКлиенте
Перем АдресВременногоХранилища;

Вариант 3. Чтение, извлечение и запуск прикрепленного файла

Посмотрим, как реализовать обратный процесс — открытие прикрепленного файла при нажатии на нашу гиперссылку ИмяФайлаСсылка. Нам необходимо получить двоичные данные из базы данных, временно сохранить их локально на компьютере пользователя и запустить ассоциированное приложение.

Разберем клиентский код обработки нажатия на ссылку:


&НаКлиенте
Процедура ИмяФайлаСсылкаНажатие(Элемент, СтандартнаяОбработка)
    
    // Отменяем стандартное поведение платформы для поля ввода
    СтандартнаяОбработка = Ложь;
    
    Если Не ЗначениеЗаполнено(Объект.ИмяФайла) Тогда
        ПоказатьПредупреждение(, "Файл еще не прикреплен к данному элементу!");
        Возврат;
    КонецЕсли;
    
    // Получаем навигационную ссылку на реквизит типа ХранилищеЗначения
    НавигационнаяСсылка = ПолучитьНавигационнуюСсылку(Объект.Ссылка, "ДанныеФайла");
    
    // Формируем уникальный путь во временном каталоге локального компьютера пользователя
    ПутьДляСохранения = КаталогВременныхФайлов() + Объект.ИмяФайла;
    
    // Описываем обработчик завершения получения файла с сервера
    ПараметрыОповещения = Новый ОписаниеОповещения("ПослеПолученияФайлаССервера", ЭтотОбъект, ПутьДляСохранения);
    
    // Запускаем асинхронное получение файла
    НачатьПолучениеФайлаССервера(ПараметрыОповещения, НавигационнаяСсылка, ПутьДляСохранения);
    
КонецПроцедуры

После того как платформа успешно скачает файл в локальную папку временных файлов, сработает процедура обратного вызова. Проанализируем ее код:


&НаКлиенте
Процедура ПослеПолученияФайлаССервера(ПолученныйФайл, ПутьДляСохранения) Экспорт
    
    Если ПолученныйФайл = Неопределено Тогда
        Возврат; // Произошла ошибка или операция отменена
    КонецЕсли;
    
    // Открытие файла на компьютере клиента.
    // Обратите внимание на важный аспект безопасности!
    ЗапуститьФайлБезопасно(ПутьДляСохранения);
    
КонецПроцедуры

Важный нюанс безопасности в 1С:Предприятии

Многие разработчики по старой привычке используют прямой метод платформы ЗапуститьПриложение(ПутьДляСохранения) для открытия документов. Внимание: делать так в современных реалиях категорически запрещено!

Если злонамеренный пользователь загрузит под видом XML-файла исполняемый скрипт (например, с расширением .bat, .cmd, .vbs или .exe), метод ЗапуститьПриложение() безропотно выполнит его на компьютере локального пользователя с правами текущей учетной записи ОС. Это классическая уязвимость для внедрения вирусов и шифровальщиков.

Для предотвращения подобных ситуаций используем безопасные механизмы запуска:

  1. Если конфигурация написана с использованием БСП:
    Используйте вызов общего модуля ФайловаяСистемаКлиент. Метод автоматически проверяет расширение запускаемого файла по списку запрещенных (опасных) расширений и блокирует их запуск.
    
    // Безопасный запуск через БСП
    ФайловаяСистемаКлиент.ОткрытьФайл(ПутьДляСохранения);
    
  2. Если конфигурация чистая (без БСП):
    Напишем собственную мини-проверку расширения файла перед его открытием:
    
    &НаКлиенте
    Процедура ЗапуститьФайлБезопасно(Знач ПутьКФайлу)
        
        ФайлПроверки = Новый Файл(ПутьКФайлу);
        Расширение = НРег(ФайлПроверки.Расширение);
        
        // Список потенциально опасных расширений файлов
        ЗапрещенныеРасширения = Новый Массив;
        ЗапрещенныеРасширения.Добавить(".exe");
        ЗапрещенныеРасширения.Добавить(".bat");
        ЗапрещенныеРасширения.Добавить(".cmd");
        ЗапрещенныеРасширения.Добавить(".vbs");
        ЗапрещенныеРасширения.Добавить(".js");
        ЗапрещенныеРасширения.Добавить(".scr");
        
        Если ЗапрещенныеРасширения.Найти(Расширение) <> Неопределено Тогда
            ВызватьИсключение "Запуск файлов данного типа заблокирован системой безопасности!";
        КонецЕсли;
        
        // Если расширение безопасное (например, .xml, .txt, .pdf), запускаем его
        #Если МобильныйКлиент Тогда
            // Для мобильных платформ запуск отличается
            СредстваМультимедиа.ОткрытьДокумент(ПутьКФайлу);
        #Иначе
            ЗапуститьПриложение(ПутьКФайлу);
        #КонецЕсли
        
    КонецПроцедуры
    

Сравнительный анализ решений

Чтобы окончательно определиться с выбором архитектуры для вашей задачи, сопоставим оба подхода в сравнительной таблице:

Критерий сравнения Использование БСП Собственная реализация (Хранилище)
Скорость внедрения Быстро (при наличии опыта интеграции БСП). Очень быстро (несколько строк кода).
Нагрузка на базу данных (размер ИБ) Минимальная (файлы можно хранить в томах на диске). Высокая (каждый файл сохраняется прямо в SQL/файловую СУБД).
Масштабируемость Высокая. Если завтра потребуется прикреплять 2, 5 или более файлов, переписывать код не придется. Низкая. Придется добавлять новые реквизиты или переходить на подчиненный справочник.
Встроенные функции Контроль версий, блокировка от редактирования, подписи, сканирование. Отсутствуют (всё нужно программировать вручную).

Таким образом, если проект крупный и долгосрочный — всегда выбирайте первый вариант с БСП. Если же перед вами стоит задача сделать простую утилиту, внешнюю обработку или небольшую базу «для себя» — смело используйте легкий вариант с ХранилищеЗначения.

← На главную