Как программно загружать, отдавать и хранить присоединенные файлы 1С
Содержание
Чтобы интегрировать присоединенные файлы в 1С, работайте через программный интерфейс подсистемы «Работа с файлами» из БСП: файл добавляется функцией РаботаСФайлами.ДобавитьФайл(), а читается через РаботаСФайлами.ДвоичныеДанныеФайла(). Внешняя система передаёт и забирает файлы через HTTP-сервис конфигурации, а сам файл между клиентом и сервером передаётся через временное хранилище.
Типичная задача из практики: сканер договоров в отделе продаж или CRM должны сами прикладывать PDF к документу «Реализация», а сайт — выгружать из 1С подписанные счета. Писать напрямую в таблицы базы нельзя: сломаются версии, тома и права. Ниже — схема, рабочий код и подводные камни.
Как устроены присоединенные файлы в конфигурациях на БСП
Почти все типовые конфигурации 8.3 (Бухгалтерия 3.0, ЗУП 3.1, УТ 11, ERP, УНФ) построены на Библиотеке стандартных подсистем. Файлы в них — не реквизит документа, а отдельные элементы справочника.
📎 Как это выглядит в метаданных:
- для каждого объекта-владельца, к которому можно прикладывать файлы, есть справочник с именем вида
<ИмяВладельца>ПрисоединенныеФайлы; - у элемента такого справочника есть реквизит
ВладелецФайла— ссылка на документ или элемент справочника; - в реквизитах хранятся имя, расширение, размер, автор и тип хранения (в базе или в томе на диске);
- сами байты лежат не в элементе справочника, а в служебном хранилище подсистемы или в файле на томе.
Из этого следует главное правило интеграции. Не читайте и не пишите двоичные данные в обход API подсистемы. Структура служебных таблиц меняется от версии к версии БСП, а при хранении в томах в базе вообще нет содержимого файла — только путь к нему.
| Уровень | Чем пользоваться | Для чего |
|---|---|---|
| Платформа | ПоместитьВоВременноеХранилище, ДвоичныеДанные, HTTPСервисОтвет | Передача байтов между клиентом, сервером и внешним миром |
| БСП | РаботаСФайлами, РаботаСФайламиКлиент | Создание, чтение, обновление присоединенных файлов |
| Конфигурация | HTTP-сервис, обработка, регламентное задание | Ваша прикладная логика интеграции |
Перед разработкой посмотрите, какая версия БСП в конфигурации: Справка → О программе показывает версию конфигурации, а в Конфигураторе версию библиотеки видно в свойствах подсистемы СтандартныеПодсистемы. От неё зависит набор параметров функций.
Программная загрузка и выгрузка присоединенных файлов
Работа всегда идёт в два шага. Сначала двоичные данные попадают во временное хранилище, затем серверная функция БСП создаёт из них присоединенный файл.
Добавить файл к объекту на сервере
Универсальная серверная функция, которую удобно положить в общий модуль интеграции:
Функция ПрикрепитьФайлКОбъекту(Владелец, ДвоичныеДанные, ИмяФайла) Экспорт
ОписаниеИмени = Новый Файл(ИмяФайла);
ПараметрыФайла = РаботаСФайлами.ПараметрыДобавленияФайла();
ПараметрыФайла.ВладелецФайлов = Владелец;
ПараметрыФайла.Автор = Пользователи.ТекущийПользователь();
ПараметрыФайла.ИмяБезРасширения = ОписаниеИмени.ИмяБезРасширения;
ПараметрыФайла.РасширениеБезТочки = Сред(ОписаниеИмени.Расширение, 2);
ПараметрыФайла.ВремяИзмененияУниверсальное = ТекущаяУниверсальнаяДата();
Адрес = ПоместитьВоВременноеХранилище(ДвоичныеДанные);
НовыйФайл = РаботаСФайлами.ДобавитьФайл(ПараметрыФайла, Адрес);
УдалитьИзВременногоХранилища(Адрес);
Возврат НовыйФайл;
КонецФункции
Что здесь важно:
Новый Файл(ИмяФайла)не требует, чтобы файл существовал на диске. Объект используется только для разбора имени на части.- Расширение передаётся без точки, поэтому
Сред(..., 2)отрезает первый символ строки.pdf. - Тип хранения (база или том) выбирать не нужно: подсистема возьмёт его из настроек программы.
- Функция возвращает ссылку на новый элемент справочника присоединенных файлов. Её удобно вернуть внешней системе как идентификатор.
Прочитать файлы владельца
Список файлов документа получают обычным запросом к справочнику, а содержимое — через API:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Файлы.Ссылка КАК Ссылка,
| Файлы.Наименование КАК Имя,
| Файлы.Расширение КАК Расширение,
| Файлы.Размер КАК Размер
|ИЗ
| Справочник.РеализацияТоваровУслугПрисоединенныеФайлы КАК Файлы
|ГДЕ
| Файлы.ВладелецФайла = &Владелец
| И НЕ Файлы.ПометкаУдаления";
Запрос.УстановитьПараметр("Владелец", Документ);
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Данные = РаботаСФайлами.ДвоичныеДанныеФайла(Выборка.Ссылка);
// Данные — объект ДвоичныеДанные: отправить, сохранить, посчитать хеш
КонецЦикла;
Имя справочника подставьте своё: посмотрите его в ветке Справочники Конфигуратора поиском по слову «ПрисоединенныеФайлы».
Загрузка с компьютера пользователя
На клиенте файл сначала нужно передать на сервер. В платформе 8.3.18 и выше для этого есть асинхронные методы:
&НаКлиенте
Асинх Процедура ЗагрузитьСкан(Команда)
ОписаниеФайла = Ждать ПоместитьФайлНаСерверАсинх(, , , , УникальныйИдентификатор);
Если ОписаниеФайла = Неопределено Тогда
Возврат; // пользователь отменил выбор
КонецЕсли;
ПрикрепитьНаСервере(ОписаниеФайла.Адрес, ОписаниеФайла.СсылкаНаФайл.Имя);
КонецПроцедуры
На сервере из адреса берут данные функцией ПолучитьИзВременногоХранилища(Адрес) и передают их в ПрикрепитьФайлКОбъекту. Если задача — просто дать пользователю кнопку «Прикрепить», не пишите свою: в БСП для этого есть готовые клиентские процедуры в модуле РаботаСФайламиКлиент и команда «Присоединенные файлы» на форме объекта.
HTTP-сервис для интеграции присоединенных файлов с внешними системами
Для обмена с сайтом, CRM или сканером удобнее всего свой HTTP-сервис. Он работает поверх публикации базы на веб-сервере и не требует COM-соединения.
Структура сервиса
В Конфигураторе (лучше — в расширении, чтобы не снимать типовую с поддержки) создайте HTTP-сервис:
- корневой URL:
files; - шаблон
/docs/{id}/filesс методамиGET(список) иPOST(загрузка); - шаблон
/file/{fileId}с методомGET(скачать содержимое).
После публикации на веб-сервере (Администрирование → Публикация на веб-сервере в Конфигураторе, флажок публикации HTTP-сервисов) адрес будет вида https://server/base/hs/files/docs/<guid>/files.
Приём файла методом POST
Функция ДокументФайлыPOST(Запрос)
Попытка
Ид = Новый УникальныйИдентификатор(Запрос.ПараметрыURL["id"]);
Исключение
Возврат Новый HTTPСервисОтвет(400);
КонецПопытки;
Документ = Документы.РеализацияТоваровУслуг.ПолучитьСсылку(Ид);
Если Не ОбщегоНазначения.СсылкаСуществует(Документ) Тогда
Возврат Новый HTTPСервисОтвет(404);
КонецЕсли;
ИмяФайла = Запрос.Заголовки.Получить("X-File-Name");
Если Не ЗначениеЗаполнено(ИмяФайла) Тогда
Возврат Новый HTTPСервисОтвет(400);
КонецЕсли;
ИмяФайла = РаскодироватьСтроку(ИмяФайла, СпособКодированияСтроки.КодировкаURL);
Данные = Запрос.ПолучитьТелоКакДвоичныеДанные();
НовыйФайл = ИнтеграцияФайлов.ПрикрепитьФайлКОбъекту(Документ, Данные, ИмяФайла);
Ответ = Новый HTTPСервисОтвет(201);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Ответ.УстановитьТелоИзСтроки(
СтрШаблон("{""fileId"":""%1""}", НовыйФайл.УникальныйИдентификатор()),
КодировкаТекста.UTF8);
Возврат Ответ;
КонецФункции
Проверка со стороны внешней системы:
curl -u integration:password -X POST \
-H "X-File-Name: scan-001.pdf" \
--data-binary @scan-001.pdf \
"https://server/base/hs/files/docs/0f1c2a3b-4d5e-6f70-8192-a3b4c5d6e7f8/files"
Отдача файла методом GET
Функция ФайлGET(Запрос)
Попытка
Ид = Новый УникальныйИдентификатор(Запрос.ПараметрыURL["fileId"]);
Исключение
Возврат Новый HTTPСервисОтвет(400);
КонецПопытки;
СсылкаНаФайл = Справочники.РеализацияТоваровУслугПрисоединенныеФайлы.ПолучитьСсылку(Ид);
Если Не ОбщегоНазначения.СсылкаСуществует(СсылкаНаФайл) Тогда
Возврат Новый HTTPСервисОтвет(404);
КонецЕсли;
Реквизиты = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(СсылкаНаФайл, "Наименование, Расширение");
ПолноеИмя = Реквизиты.Наименование + "." + Реквизиты.Расширение;
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "application/octet-stream");
Ответ.Заголовки.Вставить("Content-Disposition", "attachment; filename*=UTF-8''"
+ КодироватьСтроку(ПолноеИмя, СпособКодированияСтроки.КодировкаURL));
Ответ.УстановитьТелоИзДвоичныхДанных(РаботаСФайлами.ДвоичныеДанныеФайла(СсылкаНаФайл));
Возврат Ответ;
КонецФункции
Кириллица в HTTP-заголовках без кодирования приходит в искажённом виде. Поэтому имя файла в X-File-Name клиент кодирует как URL, а в ответе используется форма filename*=UTF-8''.
Табличные документы и отчёты как присоединенные файлы
Частый сценарий — автоматически сохранять печатную форму или отчёт в PDF и прикладывать к документу. Например, при проведении счёта класть его копию, чтобы менеджер отправил клиенту файл, который точно не поменяется.
Табличный документ сначала записывают во временный файл, потом читают как двоичные данные:
ТабДок = СформироватьПечатнуюФорму(Документ); // ваша функция, возвращает ТабличныйДокумент
ИмяВременногоФайла = ПолучитьИмяВременногоФайла("pdf");
ТабДок.Записать(ИмяВременногоФайла, ТипФайлаТабличногоДокумента.PDF);
Данные = Новый ДвоичныеДанные(ИмяВременногоФайла);
УдалитьФайлы(ИмяВременногоФайла);
ИнтеграцияФайлов.ПрикрепитьФайлКОбъекту(Документ, Данные, "Счет " + Документ.Номер + ".pdf");
📄 Что учесть:
- PDF на сервере под Linux требует установленных шрифтов, которые используются в макете. Без них текст в файле съедет или заменится другим шрифтом;
- кроме PDF доступны
XLSX,DOCX,MXL,ODS— значения того же перечисленияТипФайлаТабличногоДокумента; - временный файл обязательно удаляйте в той же процедуре, иначе каталог временных файлов сервера быстро разрастётся;
- обратная задача тоже решается: загруженный Excel-файл можно открыть методом
Прочитать()табличного документа и разобрать по ячейкам.
Если нужно не автоматически, а по кнопке, программировать не придётся. В окне печати типовых конфигураций при сохранении печатной формы можно выбрать вариант прикрепления файла к документу, и подсистема сама создаст присоединенный файл.
Хранение в базе или в томах: производительность при большом числе файлов
Где лежат байты, настраивается в разделе Администрирование типовой конфигурации, в настройках работы с файлами. Варианты два.
| Способ | Когда подходит | Риски |
|---|---|---|
| В информационной базе | Небольшой объём, файловая база, простое резервное копирование | Рост базы, долгая выгрузка .dt, медленнее бэкап |
| В томах на диске | Десятки гигабайт сканов, клиент-серверная база | Тома нужно бэкапить отдельно, нужны права у службы сервера |
Порядок перехода на тома:
- Создайте каталог на сервере или в сетевой папке.
- Дайте пользователю, от имени которого запущена служба сервера 1С, права на чтение и запись в этот каталог. Права самого пользователя 1С здесь роли не играют.
- В настройках работы с файлами включите хранение в томах и заведите том, указав полный путь (для сетевой папки — UNC-путь вида
\\fileserver\1c-volumes\base1, а не буква подключённого диска). - Новые файлы пойдут в тома. Старые останутся в базе, пока вы не перенесёте их встроенной командой переноса файлов между хранилищами.
Перенос десятков тысяч файлов заметно нагружает сервер и долго держит транзакции. Запускайте его вне рабочего времени и только после полной резервной копии базы и каталога томов.
⚡ Как не замедлить интеграцию:
- не тяните двоичные данные в запросах-списках. Сначала получите ссылки и реквизиты, содержимое — по одному файлу и только когда оно нужно;
- массовую загрузку (сотни сканов за ночь) делайте фоновым или регламентным заданием, а не в HTTP-запросе: веб-сервер может оборвать долгий запрос по таймауту;
- ограничьте размер тела на стороне веб-сервера и в коде (проверка
Данные.Размер()до записи); - освобождайте временное хранилище через
УдалитьИзВременногоХранилища, если работаете не в контексте формы.
Права доступа и безопасность файлового обмена
Доступ к присоединенному файлу в БСП определяется правами на его владельца. Кто не видит документ, не увидит и файлы к нему. Ограничения на уровне записей при этом тоже работают.
В HTTP-сервисе этот механизм срабатывает, только если код выполняется от имени конкретного пользователя без привилегированного режима. Отсюда правила:
- 🔐 заведите отдельного пользователя 1С для интеграции и дайте ему только нужные профили групп доступа;
- 🚫 не включайте
УстановитьПривилегированныйРежим(Истина)в обработчиках сервиса «чтобы заработало»: так вы откроете внешней системе файлы всех документов; - 🔒 публикуйте сервис только по HTTPS, Basic-авторизация без шифрования передаёт пароль открытым текстом;
- 🧾 проверяйте расширение и размер до записи. В настройках работы с файлами БСП есть ограничение максимального размера и список запрещённых расширений; их проверку выполняет подсистема, но лучше отклонить лишний файл раньше, ответом 413 или 415;
- 📋 пишите в журнал регистрации, кто и какой файл загрузил:
ЗаписьЖурналаРегистрации("Интеграция.Файлы", УровеньЖурналаРегистрации.Информация, , НовыйФайл).
Для отладки прав зайдите в базу под пользователем интеграции через обычный клиент и откройте документ. Если файлов в форме не видно, сервис их тоже не отдаст.