Как положить файл на хранение в базу 1С и забрать его через внешнее соединение?

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

В процессе интеграции различных систем 1С часто возникает необходимость оперативного обмена файлами или текстовыми выгрузками. Самый простой и очевидный путь — сохранить файл на диск (например, в общую сетевую папку) и прочитать его из другой базы. Однако этот подход имеет существенные недостатки: зависимость от прав доступа к сетевым ресурсам в операционной системе, необходимость администрирования этих папок и уязвимость перед сбоями сети.

Альтернативный и более изолированный метод — хранить временные файлы обмена прямо внутри информационной базы 1С, чтобы затем забирать их через внешнее соединение (COM). В рамках этой статьи мы подробно разберем, как правильно реализовать эту задачу, какие подводные камни могут возникнуть при передаче бинарных данных через COM-границу, где именно в конфигурациях Комплексная автоматизация 1.1 (КА 1.1) и Управление торговлей 10.3 (УТ 10.3) лучше хранить такие данные, и как перевести это решение на современные рельсы.

Дилемма: Хранить файлы в базе данных — зло или благо?

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

  1. Когда хранение в базе — это «зло»: Если пользователи регулярно загружают файлы большого объема (сканы договоров по 10–30 Мбайт, архивы, изображения высокого разрешения), объем базы данных начинает расти лавинообразно. Это приводит к раздуванию транзакционных логов, увеличению времени резервного копирования и замедлению работы всей системы из-за фрагментации страниц СУБД. В таких случаях действительно правильнее использовать внешние тома хранения файлов.
  2. Когда хранение в базе полностью оправдано: Если речь идет о служебных текстовых файлах, JSON/XML-пакетах обмена или небольших табличных документах скромного размера (до нескольких мегабайт). Хранение таких объектов внутри базы упрощает создание резервных копий (база всегда переносится целиком со всеми служебными данными) и гарантирует целостность информации.

В нашем случае рассматривается передача небольшого служебного текстового документа, формируемого автоматически. Хранить его внутри базы — абсолютно оправданное и безопасное решение.

Решение 1. Использование Хранилища Значений через Base64 (Для любых типов файлов)

Если вам необходимо передавать произвольные файлы (двоичные данные, картинки, архивы), стандартным механизмом 1С является объект ХранилищеЗначения. Однако при попытке напрямую прочитать этот реквизит через COM-соединение вы гарантированно столкнетесь со специфической ошибкой десериализации. Платформа не умеет напрямую маршалировать тип ХранилищеЗначения через COM-границу.

Чтобы обойти это ограничение, мы применим классический паттерн: сериализуем бинарные данные в строку формата Base64 на стороне внешней базы (источника), передадим эту строку как обычный текст и восстановим файл на стороне принимающей базы. Разберем по шагам, как это реализовать.

Для начала создадим в базе-источнике собственный регистр сведений, например, СлужебныйБуферОбмена со следующим составом реквизитов:

Рассмотрим пример кода для записи произвольного файла в этот регистр на стороне базы-источника:


// Запись файла в Хранилище Значений на стороне источника
ИмяФайлаНаДиске = "C:\Exchange\export_data.xml";
ДвоичныеДанныеФайла = Новый ДвоичныеДанные(ИмяФайлаНаДиске);

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

Теперь проанализируем процесс извлечения этого файла из принимающей базы через COM-соединение. Обратите внимание на использование метода Base64Строка(), выполняемого именно в контексте COM-соединения:


// Код в принимающей базе для извлечения файла через COM
ПараметрыПодключения = "File=""C:\DB\SourceDB"";Usr=""ExchangeUser"";Pwd=""Password"";";
COMConnector = Новый COMОбъект("V83.COMConnector");

Попытка
    ВнешнееСоединение = COMConnector.Connect(ПараметрыПодключения);
Исключение
    ЗаписьЖурналаРегистрации("Интеграция", УровеньЖурналаРегистрации.Ошибка, , , ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
    ВызватьИсключение "Не удалось подключиться к базе-источнику через COM!";
КонецПопытки;

// Ищем запись в регистре внешней базы
ЗаписьРегистра = ВнешнееСоединение.РегистрыСведений.СлужебныйБуферОбмена.СоздатьМенеджерЗаписи();
ЗаписьРегистра.ИдентификаторФайла = "СлужебныйВыгрузка_1";
ЗаписьРегистра.Прочитать();

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

Решение 2. Оптимизированный вариант для текстовых файлов (Без Хранилища Значений)

Если ваша задача аналогична описанной на форуме — передается исключительно текстовый документ (или XML/JSON-текст), то использование ХранилищеЗначения является избыточным. Мы можем значительно упростить код, снизить нагрузку на процессор (избавившись от сжатия и кодирования Base64) и повысить скорость обмена.

Для этого в нашем служебном регистре сведений достаточно создать ресурс ТекстФайла с типом Строка (неограниченной длины). Строки неограниченной длины отлично и без искажений передаются напрямую через COM-соединение.

Рассмотрим, как выглядит запись текстовых данных на стороне источника:


// Запись текста напрямую в регистр сведений на стороне источника
ТекстовыйДок = Новый ТекстовыйДокумент;
ТекстовыйДок.УстановитьТекст("Это содержимое служебного текстового файла.");

МенеджерЗаписи = РегистрыСведений.СлужебныйБуферОбмена.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ИдентификаторФайла = "СлужебныйТекст_1";
// Извлекаем сырой текст и записываем в строковый ресурс
МенеджерЗаписи.ТекстФайла = ТекстовыйДок.ПолучитьТекст();
МенеджерЗаписи.Записать();

Теперь посмотрим на пример чтения этого текста из принимающей базы. Код становится лаконичным и простым для понимания:


// Чтение текстовых данных напрямую через COM
ЗаписьРегистра = ВнешнееСоединение.РегистрыСведений.СлужебныйБуферОбмена.СоздатьМенеджерЗаписи();
ЗаписьРегистра.ИдентификаторФайла = "СлужебныйТекст_1";
ЗаписьРегистра.Прочитать();

Если ЗаписьРегистра.Выбран() Тогда
    // Прямое чтение строки неограниченной длины через COM-соединение
    ПолученныйТекст = ЗаписьРегистра.ТекстФайла;
    
    // Восстанавливаем объект ТекстовыйДокумент в локальной базе
    ЛокальныйТекстовыйДокумент = Новый ТекстовыйДокумент;
    ЛокальныйТекстовыйДокумент.УстановитьТекст(ПолученныйТекст);
    
    // Начинаем парсинг или обработку документа
    Сообщить("Успешно прочитано строк: " + ЛокальныйТекстовыйДокумент.КоличествоСтрок());
КонецЕсли;

Где лучше хранить данные обмена в УТ 10.3 и КА 1.1?

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

Выясним, какие архитектурно правильные альтернативы существуют в типовых конфигурациях на обычных формах (КА 1.1 / УТ 10.3):

  1. Справочник «Хранилище дополнительной информации» — это стандартный объект метаданных в данных конфигурациях. Он спроектирован специально для хранения изображений, договоров и файлов, привязанных к различным объектам системы. Вы можете создать элемент этого справочника, не привязывая его к конкретному владельцу (или привязав к служебному договору/контрагенту), и складывать файлы туда. Это безопасно и не требует изменения структуры метаданных конфигурации.
  2. Собственный регистр сведений — оптимальный выбор с точки зрения производительности и изоляции кода. Если у вас есть возможность вносить изменения в конфигурацию (снять с поддержки с сохранением поддержки), создайте отдельный регистр сведений СлужебныйБуферОбмена (как в наших примерах выше). Это гарантирует, что данные обмена не пересекутся с другими бизнес-процессами компании.

Современная замена COM-соединению: HTTP-сервисы

Несмотря на то, что конфигурации КА 1.1 и УТ 10.3 являются устаревшими, они практически всегда запускаются на современной платформе 1С:Предприятие 8.3. Это открывает перед нами возможность полностью отказаться от тяжеловесного, нестабильного и привязанного к Windows механизма COM-соединений в пользу HTTP-сервисов.

Почему стоит отказаться от COM?

Давайте рассмотрим, как просто перевести нашу задачу на рельсы HTTP-сервисов. В базе-источнике создадим HTTP-сервис с именем ExchangeService и шаблоном URL: get_file/{ID}. Напишем обработчик GET-запроса:


// Обработчик GET-запроса в HTTP-сервисе базы-источника
Функция get_fileGetFile(Запрос)
    
    // Получаем идентификатор файла из пути запроса
    ИдентификаторФайла = Запрос.ПараметрыШаблонаУРЛ.Получить("ID");
    
    // Ищем данные в нашем регистре
    ЗаписьРегистра = РегистрыСведений.СлужебныйБуферОбмена.СоздатьМенеджерЗаписи();
    ЗаписьРегистра.ИдентификаторФайла = ИдентификаторФайла;
    ЗаписьРегистра.Прочитать();
    
    Если Не ЗаписьРегистра.Выбран() Тогда
        // Если файл не найден, возвращаем статус 404
        Ответ = Новый HTTPСервисОтвет(404);
        Ответ.УстановитьТелоИзСтроки("Файл с идентификатором " + ИдентификаторФайла + " не найден.");
        Возврат Ответ;
    КонецЕсли;
    
    // Формируем успешный ответ
    Ответ = Новый HTTPСервисОтвет(200);
    // Отправляем текст напрямую с указанием кодировки UTF-8
    Ответ.УстановитьТелоИзСтроки(ЗаписьРегистра.ТекстФайла, КодировкаТекста.UTF8);
    Ответ.Заголовки.Вставить("Content-Type", "text/plain; charset=utf-8");
    
    Возврат Ответ;
    
КонецФункции

Теперь посмотрим, насколько просто и быстро принимающая база может забрать этот текстовый файл по протоколу HTTP без использования каких-либо внешних библиотек и COM-объектов:


// Код в принимающей базе для получения файла по HTTP
СерверИсточника = "192.168.1.100"; // IP или имя сервера 1С
ПортИсточника = 80;               // Порт публикации веб-сервера
ИмяПубликации = "SourceDB";       // Имя публикации базы в IIS/Apache

// Создаем HTTP-соединение с авторизацией
HTTPСоединение = Новый HTTPСоединение(СерверИсточника, ПортИсточника, "ExchangeUser", "Password");

// Формируем запрос к нашему HTTP-сервису
АдресРесурса = "/" + ИмяПубликации + "/hs/ExchangeService/get_file/СлужебныйТекст_1";
HTTPЗапрос = Новый HTTPЗапрос(АдресРесурса);

Попытка
    HTTPОтвет = HTTPСоединение.Получить(HTTPЗапрос);
Исключение
    ВызватьИсключение "Ошибка сети при обращении к базе-источнику: " + ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
КонецПопытки;

Если HTTPОтвет.КодСостояния = 200 Тогда
    // Читаем текст ответа напрямую
    ПолученныйТекст = HTTPОтвет.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
    
    ЛокальныйТекстовыйДокумент = Новый ТекстовыйДокумент;
    ЛокальныйТекстовыйДокумент.УстановитьТекст(ПолученныйТекст);
    
    Сообщить("Успешно получено строк по HTTP: " + ЛокальныйТекстовыйДокумент.КоличествоСтрок());
Иначе
    Сообщить("Не удалось получить файл. Код ответа сервера: " + HTTPОтвет.КодСостояния);
КонецЕсли;

Подведем итоги

Для решения задачи передачи файлов между базами данных через внешнее соединение мы проанализировали три основных подхода. При работе с произвольными бинарными данными используйте связку ХранилищеЗначения и преобразования в строку Base64Строка, чтобы избежать ошибок COM. Если передаются текстовые данные — пишите их напрямую в реквизиты с типом Строка неограниченной длины. Старайтесь избегать использования системных регистров вроде СохраненныеНастройки, отдавая предпочтение выделенным регистрам сведений или справочнику ХранилищеДополнительнойИнформации. И, наконец, по возможности переходите на использование современных HTTP-сервисов, что сделает вашу интеграцию быстрой, надежной и кроссплатформенной.

← На главную