Присоединённые файлы из кода: БСП, двоичные данные и HTTP

· 9 мин

Содержание

Чтобы программно прикрепить файл к документу в 1С, в типовых конфигурациях на БСП достаточно двух вызовов на сервере: РаботаСФайлами.ПараметрыДобавленияФайла() и РаботаСФайлами.ДобавитьФайл(). Первому передаёте владельца (ссылку на документ), имя и расширение, второму — адрес двоичных данных во временном хранилище. На выходе получаете ссылку на элемент присоединённого файла, который сразу виден в форме документа по кнопке со скрепкой.

Тема общая для платформы 8.3 и любых конфигураций на Библиотеке стандартных подсистем: Бухгалтерии 3.0, ЗУП 3.1, УТ 11, ERP. Если БСП в базе нет, файл хранят в реквизите типа ХранилищеЗначения или в собственном справочнике — этот вариант тоже разобран с кодом.

Где 1С хранит присоединённые файлы

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

Как устроено хранение:

  • 📁 Справочник присоединённых файлов. Для объекта создаётся справочник с именем вида <ИмяОбъекта>ПрисоединенныеФайлы, например РеализацияТоваровУслугПрисоединенныеФайлы. Связь с документом — через реквизит ВладелецФайла.
  • 🗂 Справочник «Файлы». Общая файловая библиотека с папками. Там же живут версии файлов.
  • 💾 Место хранения двоичных данных. Либо в самой информационной базе, либо в томах на диске. Режим задаётся администратором в разделе Администрирование в настройках работы с файлами.
  • 📝 Карточка файла. Имя, расширение, размер, автор, дата изменения, описание, признак подписи ЭП и шифрования.

Код через программный интерфейс БСП не зависит от того, где лежат байты. Вы передаёте данные, а библиотека сама решает, писать их в базу или в том. Поэтому писать напрямую в регистры и служебные справочники подсистемы не нужно: при обновлении БСП их состав меняется.

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

Как прикрепить файл к документу через БСП

Базовый алгоритм из трёх шагов:

  1. Получить двоичные данные файла (с диска сервера, с клиента, из HTTP-ответа).
  2. Поместить их во временное хранилище и получить адрес.
  3. Заполнить параметры добавления и вызвать ДобавитьФайл.

Серверная процедура

Процедура в общем модуле с флажком «Сервер». Файл читается с диска сервера по полному пути.

Функция ПрикрепитьФайлКДокументу(ДокументСсылка, ПолноеИмяФайла) Экспорт

	ФайлНаДиске = Новый Файл(ПолноеИмяФайла);
	Если Не ФайлНаДиске.Существует() Тогда
		ВызватьИсключение СтрШаблон("Файл не найден: %1", ПолноеИмяФайла);
	КонецЕсли;

	ДвоичныеДанные = Новый ДвоичныеДанные(ПолноеИмяФайла);
	Адрес = ПоместитьВоВременноеХранилище(ДвоичныеДанные, Новый УникальныйИдентификатор);

	ПараметрыФайла = РаботаСФайлами.ПараметрыДобавленияФайла();
	ПараметрыФайла.ВладелецФайлов = ДокументСсылка;
	ПараметрыФайла.ИмяБезРасширения = ФайлНаДиске.ИмяБезРасширения;
	ПараметрыФайла.РасширениеБезТочки = СтрЗаменить(ФайлНаДиске.Расширение, ".", "");
	ПараметрыФайла.ВремяИзмененияУниверсальное = ФайлНаДиске.ПолучитьВремяИзмененияУниверсальное();

	ПрисоединенныйФайл = РаботаСФайлами.ДобавитьФайл(ПараметрыФайла, Адрес, , "Загружено обработкой");

	УдалитьИзВременногоХранилища(Адрес);
	Возврат ПрисоединенныйФайл;

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

Четвёртый параметр ДобавитьФайл — описание файла, оно видно в списке присоединённых файлов. Третий — адрес извлечённого текста для полнотекстового поиска, его можно не заполнять.

Расширение у объекта Файл возвращается с точкой (.pdf), а БСП ждёт его без точки. Отсюда СтрЗаменить. Если в имени файла есть ещё точки, например акт.подписан.pdf, это не мешает: свойство Расширение берёт только последнюю часть.

Клиентская часть: файл с компьютера пользователя

Если файл выбирает пользователь, данные сначала нужно передать на сервер. В платформе 8.3.18 и выше удобно использовать асинхронный вызов с Ждать:

&НаКлиенте
Асинх Процедура ПрикрепитьФайл(Команда)

	ОписаниеФайла = Ждать ПоместитьФайлНаСерверАсинх(, , , , УникальныйИдентификатор);
	Если ОписаниеФайла = Неопределено Или ОписаниеФайла.ПомещениеФайлаОтменено Тогда
		Возврат;
	КонецЕсли;

	ПрикрепитьНаСервере(ОписаниеФайла.Адрес, ОписаниеФайла.СсылкаНаФайл.Имя);

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

&НаСервере
Процедура ПрикрепитьНаСервере(Адрес, ИмяФайла)

	ФайлИнфо = Новый Файл(ИмяФайла);
	ПараметрыФайла = РаботаСФайлами.ПараметрыДобавленияФайла();
	ПараметрыФайла.ВладелецФайлов = Объект.Ссылка;
	ПараметрыФайла.ИмяБезРасширения = ФайлИнфо.ИмяБезРасширения;
	ПараметрыФайла.РасширениеБезТочки = СтрЗаменить(ФайлИнфо.Расширение, ".", "");

	РаботаСФайлами.ДобавитьФайл(ПараметрыФайла, Адрес);

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

Передавайте УникальныйИдентификатор формы: тогда данные во временном хранилище живут, пока открыта форма, и не пропадут между вызовами.

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

Прикрепление файла к документу без БСП

В самописной конфигурации или маленькой базе без библиотеки есть два рабочих способа.

СпособКогда подходитМинусы
Реквизит ХранилищеЗначения в документеОдин файл на документ, небольшой размерДокумент «тяжелеет», каждое открытие формы тянет данные
Свой справочник с владельцем-документомНесколько файлов, нужен список и поискНадо сделать форму списка и команду открытия
Табличная часть с ХранилищеЗначенияНесколько мелких вложенийТе же минусы, что у реквизита, только сильнее

Реквизит типа ХранилищеЗначения

ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
ДокументОбъект.ФайлВложения = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
ДокументОбъект.ИмяФайлаВложения = ФайлНаДиске.Имя;
ДокументОбъект.Записать(РежимЗаписиДокумента.Запись);

Достать файл обратно: ДвоичныеДанные = ДокументОбъект.ФайлВложения.Получить();. Уже сжатые форматы (PDF, JPG, ZIP) от СжатиеДанных почти не уменьшаются, для них можно передавать Новый СжатиеДанных(0) и не тратить время процессора.

Записывайте проведённый документ в режиме Запись, а не Проведение, если меняете только вложение. Иначе вы перепроведёте документ и перезапишете движения ради одной картинки.

Свой справочник файлов

Создайте справочник ФайлыДокументов с владельцем — нужными документами, реквизитами ИмяФайла (строка), Размер (число) и Данные (ХранилищеЗначения). Тогда документ не меняется вообще:

НовыйФайл = Справочники.ФайлыДокументов.СоздатьЭлемент();
НовыйФайл.Владелец = ДокументСсылка;
НовыйФайл.Наименование = ФайлНаДиске.ИмяБезРасширения;
НовыйФайл.ИмяФайла = ФайлНаДиске.Имя;
НовыйФайл.Размер = ДвоичныеДанные.Размер();
НовыйФайл.Данные = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
НовыйФайл.Записать();
Где хранить вложения в вашей базе?

Двоичные данные, временное хранилище и потоки

Любой путь прикрепления сходится к объекту ДвоичныеДанные. Получить его можно несколькими способами:

  • 📄 Новый ДвоичныеДанные(ПолноеИмяФайла) — с диска сервера;
  • 🔤 Base64Значение(Строка) — из строки Base64, например из JSON;
  • 🌐 HTTPОтвет.ПолучитьТелоКакДвоичныеДанные() — из ответа внешнего сервиса;
  • 🧩 ПотокВПамяти.ЗакрытьИПолучитьДвоичныеДанные() — после сборки файла в памяти;
  • 📦 ПолучитьИзВременногоХранилища(Адрес) — если данные уже передали с клиента.

Временное хранилище: время жизни

Второй параметр ПоместитьВоВременноеХранилище определяет, сколько живут данные:

Второй параметрКогда удаляются данные
Не указанПосле завершения текущего вызова сервера
УникальныйИдентификатор формыПри закрытии формы
Другой уникальный идентификаторВ конце сеанса, если не удалить раньше

При пакетной загрузке сотен файлов вызывайте УдалитьИзВременногоХранилища(Адрес) сразу после ДобавитьФайл. Иначе память рабочего процесса растёт до конца сеанса.

Потоки для больших и собранных файлов

Когда файл нужно сформировать или преобразовать, работайте через потоки, а не через склейку строк:

Поток = Новый ПотокВПамяти;
Запись = Новый ЗаписьДанных(Поток);
Запись.ЗаписатьСтроку("Номер;Дата;Сумма");
Запись.ЗаписатьСтроку(СтрШаблон("%1;%2;%3",
	ДокументСсылка.Номер,
	Формат(ДокументСсылка.Дата, "ДФ=dd.MM.yyyy"),
	Формат(ДокументСсылка.СуммаДокумента, "ЧГ=0; ЧРД=,")));
Запись.Закрыть();
ДвоичныеДанные = Поток.ЗакрытьИПолучитьДвоичныеДанные();

Для чтения большого файла частями есть ДвоичныеДанные.ОткрытьПотокДляЧтения() и ФайловыйПоток. Если внешний сервис отдаёт файл кусками, соберите их функцией СоединитьДвоичныеДанные(МассивЧастей), а для отправки частями — РазделитьДвоичныеДанные(Данные, РазмерЧасти).

Чтобы не прикрепить один и тот же файл дважды, считайте хеш: Хеш = Новый ХешированиеДанных(ХешФункция.SHA256); Хеш.Добавить(ДвоичныеДанные); и сравнивайте Хеш.ХешСумма с сохранённым значением в своём реквизите или регистре.

Загрузка файлов из внешних систем и через HTTP-сервис

Два направления: 1С сама забирает файл по ссылке или внешняя система присылает его в 1С.

1С скачивает файл по URL

Соединение = Новый HTTPСоединение("files.example.ru", 443, , , , 60, Новый ЗащищенноеСоединениеOpenSSL);
Запрос = Новый HTTPЗапрос("/scans/act-1024.pdf");
Ответ = Соединение.Получить(Запрос);

Если Ответ.КодСостояния <> 200 Тогда
	ВызватьИсключение СтрШаблон("Сервер вернул код %1", Формат(Ответ.КодСостояния, "ЧГ=0"));
КонецЕсли;

ДвоичныеДанные = Ответ.ПолучитьТелоКакДвоичныеДанные();

Дальше — тот же ПоместитьВоВременноеХранилище и ДобавитьФайл. Таймаут 60 секунд задан шестым параметром: без него зависший сервер подвесит фоновое задание.

Внешняя система отправляет файл в 1С

Создайте в конфигураторе HTTP-сервис, например с шаблоном /attach/{docid} и методом POST. Тело запроса — сами байты файла, имя — в заголовке или параметре.

Функция AttachPOST(Запрос)

	Идентификатор = Новый УникальныйИдентификатор(Запрос.ПараметрыURL["docid"]);
	ДокументСсылка = Документы.РеализацияТоваровУслуг.ПолучитьСсылку(Идентификатор);
	Если ДокументСсылка.ПолучитьОбъект() = Неопределено Тогда
		Возврат Новый HTTPСервисОтвет(404);
	КонецЕсли;

	ИмяФайла = Запрос.ПараметрыЗапроса.Получить("name");
	Если Не ЗначениеЗаполнено(ИмяФайла) Тогда
		Возврат Новый HTTPСервисОтвет(400);
	КонецЕсли;

	Адрес = ПоместитьВоВременноеХранилище(Запрос.ПолучитьТелоКакДвоичныеДанные(), Новый УникальныйИдентификатор);
	ФайлИнфо = Новый Файл(ИмяФайла);

	ПараметрыФайла = РаботаСФайлами.ПараметрыДобавленияФайла();
	ПараметрыФайла.ВладелецФайлов = ДокументСсылка;
	ПараметрыФайла.ИмяБезРасширения = ФайлИнфо.ИмяБезРасширения;
	ПараметрыФайла.РасширениеБезТочки = СтрЗаменить(ФайлИнфо.Расширение, ".", "");
	РаботаСФайлами.ДобавитьФайл(ПараметрыФайла, Адрес);
	УдалитьИзВременногоХранилища(Адрес);

	Возврат Новый HTTPСервисОтвет(201);

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

Если файл приходит внутри JSON строкой Base64, превратите её в данные через Base64Значение. Учтите, что Base64 раздувает объём примерно на треть: для файлов в десятки мегабайт лучше передавать байты телом запроса.

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

Версии и история изменений файла

Присоединённый файл документа в типовых конфигурациях хранит одну актуальную копию. Если вызвать ДобавитьФайл повторно с тем же именем, появится второй элемент, а не новая версия первого.

Варианты, если нужна история:

  1. Обновить существующий файл. В БСП есть процедура РаботаСФайлами.ОбновитьФайл, она заменяет данные элемента. Старое содержимое при этом не сохраняется.
  2. Хранить файл в справочнике «Файлы». Там поддерживаются версии: каждая правка создаёт элемент справочника версий, видно автора и дату.
  3. Вести историю самостоятельно. Новый элемент на каждую редакцию, в описании — номер редакции, а старые помечать на удаление.

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

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

Производительность и очистка хранилища

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

Что ускоряет и разгружает базу:

  • ⚙️ Тома на диске. Таблица двоичных данных перестаёт расти, бэкап базы становится в разы меньше. Сами тома при этом нужно копировать отдельно.
  • ⏱ Фоновые задания. Пакетную загрузку запускайте в фоне, а не в форме: пользователь не ждёт, сеанс не падает по таймауту.
  • 🔁 Без внешних запросов в транзакции. Скачивание по HTTP делайте до НачатьТранзакцию(). Иначе блокировки держатся, пока ждёте ответ сервера.
  • 🧹 Сразу освобождайте временное хранилище. Особенно в циклах.
  • 🗜 Не сжимайте сжатое. PDF, JPG и архивы через СжатиеДанных(9) гонять бессмысленно.

Удаление ненужных файлов

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

Перед массовой чисткой учтите:

  1. Сделайте резервную копию базы и каталога томов. Удалённый файл из тома не вернуть без бэкапа.
  2. Удаление помеченных объектов в рабочее время замедляет работу пользователей. Запускайте его вечером или в выходные.
  3. Удаляйте файл программно через пометку УстановитьПометкуУдаления(Истина), а не прямым Удалить(), если в базе включён контроль ссылочной целостности.

Частые вопросы

Можно ли прикрепить файл к документу, который ещё не записан?
Нет. Владельцем присоединённого файла служит ссылка на документ, а у незаписанного документа она пустая. Сначала запишите документ, потом добавляйте файл.
Как прикрепить файл в фоновом задании без формы?
Используйте ПоместитьВоВременноеХранилище с новым уникальным идентификатором и вызывайте РаботаСФайлами.ДобавитьФайл в серверном коде фонового задания. Автором файла станет пользователь, под которым запущено задание.
Почему прикреплённый файл не виден в форме документа?
Чаще всего у пользователя нет прав на справочник присоединённых файлов этого вида документов или файл создан с другим владельцем. Проверьте реквизит ВладелецФайла у созданного элемента.
Есть ли ограничение на размер прикрепляемого файла?
В БСП администратор задаёт максимальный размер файла в настройках работы с файлами. При превышении ДобавитьФайл вызовет исключение, поэтому проверяйте ДвоичныеДанные.Размер() заранее.
Как прикрепить файл в типовой конфигурации, не снимая её с поддержки?
Пишите код в расширении конфигурации или во внешней обработке. Программный интерфейс РаботаСФайлами доступен из обоих вариантов.