Загрузка файлов в базу 1С из кода: диалог, HTTP, COM и общая папка
Содержание
Чтобы добавить файл
в 1С программно, его сначала превращают в ДвоичныеДанные на сервере, а потом записывают в реквизит типа ХранилищеЗначения или в справочник файлов БСП. С клиента файл попадает на сервер через временное хранилище (НачатьПомещениеФайла или ПоместитьФайлНаСерверАсинх), из внешних систем — через HTTP-сервис, COM-соединение или общую папку.
Ниже код для каждого канала на платформе 8.3 и приёмы, на которых чаще всего спотыкаются: пути на сервере, права службы, лимиты веб-сервера и запись ошибок в журнал регистрации.
Где живёт файл: клиент, сервер и временное хранилище
Главное правило программной работы с файлами в 1С: клиент и сервер — разные машины. Путь C:\Users\buh\Desktop\act.pdf существует только на компьютере пользователя. Серверная процедура его не увидит, даже если база файловая и всё крутится на одном ПК, а вы потом переедете на клиент-сервер.
Как данные переходят между сторонами:
- 📦
ДвоичныеДанные— содержимое файла в памяти. Создаётся конструкторомНовый ДвоичныеДанные(ПолноеИмяФайла)там, где файл физически лежит. - 🔄 Временное хранилище — мост между клиентом и сервером.
ПоместитьВоВременноеХранилище()возвращает строку-адрес,ПолучитьИзВременногоХранилища(Адрес)возвращает данные. - 💾
ХранилищеЗначения— тип реквизита, в котором файл хранится в базе постоянно. - 📁 Объект
Файл— разбирает путь (Имя,Расширение,ПолноеИмя) и проверяет наличие файла методомСуществует().
Если временное хранилище привязано к уникальному идентификатору формы, данные живут, пока открыта форма. Без идентификатора — только до конца текущего серверного вызова. Поэтому из диалога файл помещают с параметром УникальныйИдентификатор.
Какой канал загрузки выбрать:
| Канал | Когда подходит | На что смотреть |
|---|---|---|
| Диалог выбора файла | Пользователь сам прикладывает скан, договор, выписку | Работает и в веб-клиенте |
| HTTP-сервис | Сайт, мобильное приложение, другая учётная система | Публикация на веб-сервере, лимит размера тела |
| COM-соединение | Скрипт или программа под Windows на той же сети | Разрядность, регистрация comcntr.dll |
| Общая папка | Сканер, ЭДО-шлюз, выгрузка из чужой системы файлами | Права служебного пользователя сервера 1С |
Как добавить файл из диалога выбора программно
Классическая задача: кнопка «Прикрепить» на форме документа. Файл выбирается на клиенте, уходит во временное хранилище, а запись в базу выполняет сервер.
Вариант с описанием оповещения
Работает без модальных окон, поэтому подходит для конфигураций с режимом «Не использовать» модальности:
&НаКлиенте
Процедура ПрикрепитьФайл(Команда)
Оповещение = Новый ОписаниеОповещения("ПослеПомещенияФайла", ЭтотОбъект);
НачатьПомещениеФайла(Оповещение, , , Истина, УникальныйИдентификатор);
КонецПроцедуры
&НаКлиенте
Процедура ПослеПомещенияФайла(Результат, Адрес, ВыбранноеИмяФайла, ДополнительныеПараметры) Экспорт
Если Не Результат Тогда
Возврат; // пользователь закрыл диалог
КонецЕсли;
ИмяФайла = Новый Файл(ВыбранноеИмяФайла).Имя;
СохранитьНаСервере(Адрес, ИмяФайла);
КонецПроцедуры
&НаСервере
Процедура СохранитьНаСервере(Адрес, ИмяФайла)
Данные = ПолучитьИзВременногоХранилища(Адрес);
ЗагрузкаФайлов.СохранитьФайл(ИмяФайла, Данные);
УдалитьИзВременногоХранилища(Адрес);
КонецПроцедуры
Имя файла вычисляйте на клиенте. Если сервер 1С работает под Linux, объект Файл на сервере не разберёт путь с обратными слешами, и в Имя попадёт строка целиком.
Вариант с Асинх и Ждать
С платформы 8.3.18 тот же сценарий короче:
&НаКлиенте
Асинх Процедура ПрикрепитьФайл(Команда)
Описание = Ждать ПоместитьФайлНаСерверАсинх(, , , УникальныйИдентификатор);
Если Описание = Неопределено Или Описание.ПомещениеФайлаОтменено Тогда
Возврат;
КонецЕсли;
СохранитьНаСервере(Описание.Адрес, Описание.СсылкаНаФайл.Имя);
КонецПроцедуры
В типовых конфигурациях на БСП есть готовая обёртка ФайловаяСистемаКлиент.ЗагрузитьФайл. Она сама показывает диалог и учитывает веб-клиент. В своей доработке типовой лучше вызывать её, чем писать помещение вручную.
Общий модуль для записи
Все примеры дальше пишут файл одной функцией. Пусть это серверный общий модуль ЗагрузкаФайлов и справочник ВходящиеФайлы с реквизитами ИмяФайла (строка), Размер (число) и Данные (ХранилищеЗначения):
Функция СохранитьФайл(ИмяФайла, ДвоичныеДанные) Экспорт
Элемент = Справочники.ВходящиеФайлы.СоздатьЭлемент();
Элемент.Наименование = ИмяФайла;
Элемент.ИмяФайла = ИмяФайла;
Элемент.Размер = ДвоичныеДанные.Размер();
Элемент.Данные = Новый ХранилищеЗначения(ДвоичныеДанные, Новый СжатиеДанных(9));
Элемент.Записать();
Возврат Элемент.Ссылка;
КонецФункции
Сжатие уровня 9 заметно уменьшает XML и текстовые файлы. PDF, JPEG и архивы уже сжаты, на них выигрыш близок к нулю.
Загрузка файла в 1С через HTTP-сервис
HTTP-сервис — самый удобный способ принять файл извне: отправитель не ставит ничего, кроме обычного HTTP-клиента. Сервис создаётся в конфигураторе в ветке Общие → HTTP-сервисы, база публикуется на веб-сервере (IIS или Apache) с флагом публикации HTTP-сервисов.
Настройка сервиса
- Создайте HTTP-сервис, корневой URL — например,
files. - Добавьте шаблон URL
/upload/{name}. - В шаблоне создайте метод
POSTи обработчикЗагрузитьPOST. - Опубликуйте базу. Адрес сервиса будет вида
https://srv.example.ru/buh/hs/files/upload/act.pdf, гдеbuh— имя публикации.
Обработчик на стороне 1С
Файл передаётся «сырым» телом запроса, без multipart. Платформа не разбирает multipart/form-data сама, а с бинарным телом всё делается одной строкой:
Функция ЗагрузитьPOST(Запрос)
ИмяФайла = Запрос.ПараметрыURL.Получить("name");
Данные = Запрос.ПолучитьТелоКакДвоичныеДанные();
Если Данные = Неопределено Или Данные.Размер() = 0 Тогда
Ответ = Новый HTTPСервисОтвет(400);
Ответ.УстановитьТелоИзСтроки("Пустое тело запроса");
Возврат Ответ;
КонецЕсли;
Попытка
Ссылка = ЗагрузкаФайлов.СохранитьФайл(ИмяФайла, Данные);
Исключение
ЗаписьЖурналаРегистрации("ЗагрузкаФайлов.HTTP", УровеньЖурналаРегистрации.Ошибка, , ИмяФайла,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат Новый HTTPСервисОтвет(500);
КонецПопытки;
Ответ = Новый HTTPСервисОтвет(201);
Ответ.УстановитьТелоИзСтроки(Строка(Ссылка.УникальныйИдентификатор()));
Возврат Ответ;
КонецФункции
Проверить сервис можно из командной строки:
curl -u exchange:Пароль --data-binary @act.pdf -H "Content-Type: application/octet-stream" https://srv.example.ru/buh/hs/files/upload/act.pdf
Отправка файла из другой базы 1С
Если отправитель — тоже 1С, используйте HTTPСоединение и УстановитьИмяФайлаТела. Файл читается с диска потоком, без загрузки в память целиком:
Соединение = Новый HTTPСоединение("srv.example.ru", 443, "exchange", Пароль, , 60,
Новый ЗащищенноеСоединениеOpenSSL);
Адрес = "/buh/hs/files/upload/" + КодироватьСтроку(ИмяФайла, СпособКодированияСтроки.КодировкаURL);
Запрос = Новый HTTPЗапрос(Адрес);
Запрос.Заголовки.Вставить("Content-Type", "application/octet-stream");
Запрос.УстановитьИмяФайлаТела(ПолноеИмяФайла);
Ответ = Соединение.ВызватьHTTPМетод("POST", Запрос);
Если Ответ.КодСостояния <> 201 Тогда
ВызватьИсключение "Сервер вернул код " + Формат(Ответ.КодСостояния, "ЧГ=0");
КонецЕсли;
IIS по умолчанию режет тело запроса больше 30 000 000 байт (примерно 28,6 МБ): параметр maxAllowedContentLength в фильтрации запросов. В Apache размер ограничивает директива LimitRequestBody. Отказ происходит до 1С, поэтому в журнале регистрации вы его не увидите — только код 404.13 или 413 у отправителя.
Добавление файла через COM-соединение
COM-соединение подходит, когда файл приходит в программу или скрипт под Windows: робот разбора почты, утилита сканера, скрипт на Python или PowerShell. Внешнее приложение подключается к базе и вызывает экспортную функцию общего модуля.
Подготовка
- ⚙️ Зарегистрируйте компоненту от имени администратора:
regsvr32 "C:\Program Files\1cv8\8.3.xx.xxxx\bin\comcntr.dll"(подставьте свою версию платформы). - 🔢 Разрядность платформы и вызывающего процесса должна совпадать. 64-битный Python не создаст объект из 32-битной платформы.
- ☑️ У общего модуля включите флаги «Сервер» и «Внешнее соединение», иначе его функции не будут видны через COM.
Почему передавать Base64, а не путь
Передать путь к файлу заманчиво, но опасно. В клиент-серверной базе серверный код выполняется на сервере 1С, и путь D:\in\act.pdf машины со скриптом там не существует. Надёжнее прочитать файл во внешней программе, перевести в Base64 и отдать строкой:
// Общий модуль ЗагрузкаФайлов, флаги «Сервер» и «Внешнее соединение»
Функция ДобавитьИзBase64(ИмяФайла, СтрокаBase64) Экспорт
Данные = Base64Значение(СтрокаBase64);
Ссылка = СохранитьФайл(ИмяФайла, Данные);
Возврат Строка(Ссылка.УникальныйИдентификатор());
КонецФункции
Вызов из Python (пакет pywin32):
import base64
import win32com.client
connector = win32com.client.Dispatch("V83.COMConnector")
conn = connector.Connect('Srvr="srv1c";Ref="buh";Usr="exchange";Pwd="secret";')
with open(r"D:\in\act.pdf", "rb") as f:
data = base64.b64encode(f.read()).decode("ascii")
uid = conn.ЗагрузкаФайлов.ДобавитьИзBase64("act.pdf", data)
print(uid)
Для файловой базы строка подключения выглядит как File="D:\Bases\buh";Usr="exchange";Pwd="secret";.
Base64 раздувает данные примерно на треть. Для файлов по 100 МБ и больше COM неудобен, лучше HTTP-сервис или общая папка.
Каждое COM-соединение — полноценный сеанс и занимает клиентскую лицензию. Не открывайте соединение на каждый файл: подключитесь один раз и загрузите пачку.
Программная загрузка файлов из общей папки
Самый простой канал для сканеров и сторонних выгрузок: файлы падают в папку, регламентное задание раз в несколько минут их забирает.
Права и пути
Регламентное задание выполняется на сервере 1С от имени служебной учётной записи (при установке по умолчанию USR1CV8). Значит:
- Путь указывайте в формате UNC:
\\fs01\obmen\in\. Подключённые диски вродеZ:есть только в сеансе пользователя Windows, служба их не видит. - Дайте служебной учётной записи права на чтение, запись и удаление в папке и подпапках.
- Заранее создайте подпапки
work,doneиerror.
Код регламентного задания
Файл сначала переносится в work, а уже потом читается. Так недописанный файл не попадёт в базу дважды: если перенос не удался, файл ещё занят, и задание вернётся к нему в следующий запуск.
Процедура ЗагрузитьИзПапки() Экспорт
Каталог = "\\fs01\obmen\in\";
Файлы = НайтиФайлы(Каталог, "*.pdf", Ложь);
Для Каждого Файл Из Файлы Цикл
ВРаботе = Каталог + "work\" + Файл.Имя;
Попытка
ПереместитьФайл(Файл.ПолноеИмя, ВРаботе);
Исключение
Продолжить; // файл ещё пишется или занят
КонецПопытки;
Попытка
Данные = Новый ДвоичныеДанные(ВРаботе);
ЗагрузкаФайлов.СохранитьФайл(Файл.Имя, Данные);
ПереместитьФайл(ВРаботе, Каталог + "done\" + Файл.Имя);
Исключение
ЗаписьЖурналаРегистрации("ЗагрузкаФайлов.Папка", УровеньЖурналаРегистрации.Ошибка, , Файл.Имя,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ПереместитьФайл(ВРаботе, Каталог + "error\" + Файл.Имя);
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Если сервер 1С под Linux, папку монтируют в файловую систему сервера (например, через CIFS), а в коде указывают локальный путь точки монтирования, /mnt/obmen/in/.
Если в папку регулярно приходят файлы с одинаковыми именами (scan.pdf, export.xml), добавляйте к имени в done и error дату и время через Формат(ТекущаяДатаСеанса(), "ДФ=yyyyMMddHHmmss"). Иначе новый файл столкнётся со старым, и разбираться, какой из них уже в базе, придётся вручную.
Как хранить добавленный файл в базе 1С
После загрузки файл где-то должен жить. Вариантов три, и выбор влияет на размер базы и скорость резервного копирования.
| Способ | Плюсы | Минусы |
|---|---|---|
Реквизит ХранилищеЗначения в своём объекте | Просто, файл едет вместе с выгрузкой .dt | База растёт, бэкап тяжелеет |
| Присоединённые файлы БСП | Версии, права, электронная подпись, просмотр из карточки | Нужно разобраться в API подсистемы |
| Тома на диске (настройка БСП) | База не пухнет от сканов | Тома надо копировать отдельно от базы |
Файл в собственном реквизите
Пример выше уже пишет файл в ХранилищеЗначения. Обратно данные получают методом Получить():
Данные = Ссылка.Данные.Получить(); // ДвоичныеДанные
ИмяВременного = ПолучитьИмяВременногоФайла("pdf");
Данные.Записать(ИмяВременного);
На форму реквизит ХранилищеЗначения не выводится и на клиент не передаётся. Чтобы отдать файл пользователю, положите данные во временное хранилище и вызовите на клиенте получение файла по адресу.
Присоединённые файлы в типовых конфигурациях
В конфигурациях на БСП 3.x (Бухгалтерия 3.0, ЗУП 3.1, УТ 11) правильнее не изобретать свой справочник, а прикрепить файл
к объекту штатно. Для этого в общем модуле РаботаСФайлами есть функция ДобавитьФайл и конструктор параметров ПараметрыДобавленияФайла():
Параметры = РаботаСФайлами.ПараметрыДобавленияФайла();
Параметры.ВладелецФайлов = ДокументСсылка;
Параметры.ИмяБезРасширения = "Акт сверки";
Параметры.РасширениеБезТочки = "pdf";
Адрес = ПоместитьВоВременноеХранилище(Данные);
ФайлСсылка = РаботаСФайлами.ДобавитьФайл(Параметры, Адрес);
Тогда файл появится в карточке документа по кнопке со скрепкой, а пользователь сможет открыть, подписать и отправить его штатными средствами. Состав полей структуры параметров зависит от версии БСП, перед вызовом посмотрите описание функции в самом общем модуле.
Тома хранения включаются в типовых конфигурациях в настройках работы с файлами раздела Администрирование. Ваш код при этом не меняется: ДобавитьФайл сам решит, класть файл в базу или в том.
Ошибки при загрузке и журнал регистрации
Программная загрузка обычно идёт без пользователя, поэтому ошибку никто не увидит, пока её не запишут. Правило одно: каждый канал оборачивает запись в Попытка…Исключение и пишет подробности в журнал регистрации.
Как писать в журнал
Исключение
Текст = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
ЗаписьЖурналаРегистрации("ЗагрузкаФайлов", УровеньЖурналаРегистрации.Ошибка, , ИмяФайла, Текст);
ВызватьИсключение; // если вызывающему коду нужно знать о сбое
КонецПопытки;
Имя события делайте иерархическим через точку: ЗагрузкаФайлов.HTTP, ЗагрузкаФайлов.Папка. Тогда в журнале регистрации (Администрирование → Обслуживание → Журнал регистрации в типовых) фильтр по событию соберёт все сбои канала. На платформе 8.3.17 и новее вместо глобальной функции можно вызывать ОбработкаОшибок.ПодробноеПредставлениеОшибки().