Как программно загружать, отдавать и хранить присоединенные файлы 1С

· 9 мин

Содержание

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

Типичная задача из практики: сканер договоров в отделе продаж или CRM должны сами прикладывать PDF к документу «Реализация», а сайт — выгружать из 1С подписанные счета. Писать напрямую в таблицы базы нельзя: сломаются версии, тома и права. Ниже — схема, рабочий код и подводные камни.

Как устроены присоединенные файлы в конфигурациях на БСП

Почти все типовые конфигурации 8.3 (Бухгалтерия 3.0, ЗУП 3.1, УТ 11, ERP, УНФ) построены на Библиотеке стандартных подсистем. Файлы в них — не реквизит документа, а отдельные элементы справочника.

📎 Как это выглядит в метаданных:

  • для каждого объекта-владельца, к которому можно прикладывать файлы, есть справочник с именем вида <ИмяВладельца>ПрисоединенныеФайлы;
  • у элемента такого справочника есть реквизит ВладелецФайла — ссылка на документ или элемент справочника;
  • в реквизитах хранятся имя, расширение, размер, автор и тип хранения (в базе или в томе на диске);
  • сами байты лежат не в элементе справочника, а в служебном хранилище подсистемы или в файле на томе.

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

УровеньЧем пользоватьсяДля чего
ПлатформаПоместитьВоВременноеХранилище, ДвоичныеДанные, HTTPСервисОтветПередача байтов между клиентом, сервером и внешним миром
БСПРаботаСФайлами, РаботаСФайламиКлиентСоздание, чтение, обновление присоединенных файлов
КонфигурацияHTTP-сервис, обработка, регламентное заданиеВаша прикладная логика интеграции

Перед разработкой посмотрите, какая версия БСП в конфигурации: Справка → О программе показывает версию конфигурации, а в Конфигураторе версию библиотеки видно в свойствах подсистемы СтандартныеПодсистемы. От неё зависит набор параметров функций.

Программная загрузка и выгрузка присоединенных файлов

Работа всегда идёт в два шага. Сначала двоичные данные попадают во временное хранилище, затем серверная функция БСП создаёт из них присоединенный файл.

Добавить файл к объекту на сервере

Универсальная серверная функция, которую удобно положить в общий модуль интеграции:

Функция ПрикрепитьФайлКОбъекту(Владелец, ДвоичныеДанные, ИмяФайла) Экспорт

    ОписаниеИмени = Новый Файл(ИмяФайла);

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

    Адрес = ПоместитьВоВременноеХранилище(ДвоичныеДанные);
    НовыйФайл = РаботаСФайлами.ДобавитьФайл(ПараметрыФайла, Адрес);
    УдалитьИзВременногоХранилища(Адрес);

    Возврат НовыйФайл;

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

Что здесь важно:

  1. Новый Файл(ИмяФайла) не требует, чтобы файл существовал на диске. Объект используется только для разбора имени на части.
  2. Расширение передаётся без точки, поэтому Сред(..., 2) отрезает первый символ строки .pdf.
  3. Тип хранения (база или том) выбирать не нужно: подсистема возьмёт его из настроек программы.
  4. Функция возвращает ссылку на новый элемент справочника присоединенных файлов. Её удобно вернуть внешней системе как идентификатор.

Прочитать файлы владельца

Список файлов документа получают обычным запросом к справочнику, а содержимое — через 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''.

Сопоставление полей
Тело запроса POST→Двоичные данные файла
Заголовок X-File-Name→ИмяБезРасширения + РасширениеБезТочки
Параметр URL {id}→ВладелецФайлов (ссылка на документ)
Пользователь Basic-авторизации→Автор файла
Ссылка на новый файл→fileId в JSON-ответе

Табличные документы и отчёты как присоединенные файлы

Частый сценарий — автоматически сохранять печатную форму или отчёт в PDF и прикладывать к документу. Например, при проведении счёта класть его копию, чтобы менеджер отправил клиенту файл, который точно не поменяется.

Табличный документ сначала записывают во временный файл, потом читают как двоичные данные:

ТабДок = СформироватьПечатнуюФорму(Документ); // ваша функция, возвращает ТабличныйДокумент

ИмяВременногоФайла = ПолучитьИмяВременногоФайла("pdf");
ТабДок.Записать(ИмяВременногоФайла, ТипФайлаТабличногоДокумента.PDF);
Данные = Новый ДвоичныеДанные(ИмяВременногоФайла);
УдалитьФайлы(ИмяВременногоФайла);

ИнтеграцияФайлов.ПрикрепитьФайлКОбъекту(Документ, Данные, "Счет " + Документ.Номер + ".pdf");

📄 Что учесть:

  • PDF на сервере под Linux требует установленных шрифтов, которые используются в макете. Без них текст в файле съедет или заменится другим шрифтом;
  • кроме PDF доступны XLSX, DOCX, MXL, ODS — значения того же перечисления ТипФайлаТабличногоДокумента;
  • временный файл обязательно удаляйте в той же процедуре, иначе каталог временных файлов сервера быстро разрастётся;
  • обратная задача тоже решается: загруженный Excel-файл можно открыть методом Прочитать() табличного документа и разобрать по ячейкам.

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

Хранение в базе или в томах: производительность при большом числе файлов

Где лежат байты, настраивается в разделе Администрирование типовой конфигурации, в настройках работы с файлами. Варианты два.

СпособКогда подходитРиски
В информационной базеНебольшой объём, файловая база, простое резервное копированиеРост базы, долгая выгрузка .dt, медленнее бэкап
В томах на дискеДесятки гигабайт сканов, клиент-серверная базаТома нужно бэкапить отдельно, нужны права у службы сервера

Порядок перехода на тома:

  1. Создайте каталог на сервере или в сетевой папке.
  2. Дайте пользователю, от имени которого запущена служба сервера 1С, права на чтение и запись в этот каталог. Права самого пользователя 1С здесь роли не играют.
  3. В настройках работы с файлами включите хранение в томах и заведите том, указав полный путь (для сетевой папки — UNC-путь вида \\fileserver\1c-volumes\base1, а не буква подключённого диска).
  4. Новые файлы пойдут в тома. Старые останутся в базе, пока вы не перенесёте их встроенной командой переноса файлов между хранилищами.

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

⚡ Как не замедлить интеграцию:

  • не тяните двоичные данные в запросах-списках. Сначала получите ссылки и реквизиты, содержимое — по одному файлу и только когда оно нужно;
  • массовую загрузку (сотни сканов за ночь) делайте фоновым или регламентным заданием, а не в HTTP-запросе: веб-сервер может оборвать долгий запрос по таймауту;
  • ограничьте размер тела на стороне веб-сервера и в коде (проверка Данные.Размер() до записи);
  • освобождайте временное хранилище через УдалитьИзВременногоХранилища, если работаете не в контексте формы.

Права доступа и безопасность файлового обмена

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

В HTTP-сервисе этот механизм срабатывает, только если код выполняется от имени конкретного пользователя без привилегированного режима. Отсюда правила:

  • 🔐 заведите отдельного пользователя 1С для интеграции и дайте ему только нужные профили групп доступа;
  • 🚫 не включайте УстановитьПривилегированныйРежим(Истина) в обработчиках сервиса «чтобы заработало»: так вы откроете внешней системе файлы всех документов;
  • 🔒 публикуйте сервис только по HTTPS, Basic-авторизация без шифрования передаёт пароль открытым текстом;
  • 🧾 проверяйте расширение и размер до записи. В настройках работы с файлами БСП есть ограничение максимального размера и список запрещённых расширений; их проверку выполняет подсистема, но лучше отклонить лишний файл раньше, ответом 413 или 415;
  • 📋 пишите в журнал регистрации, кто и какой файл загрузил: ЗаписьЖурналаРегистрации("Интеграция.Файлы", УровеньЖурналаРегистрации.Информация, , НовыйФайл).

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

Ошибки интеграции присоединенных файлов и их причины

Диагностика: выберите симптом

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

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