В процессе интеграции различных систем 1С часто возникает необходимость оперативного обмена файлами или текстовыми выгрузками. Самый простой и очевидный путь — сохранить файл на диск (например, в общую сетевую папку) и прочитать его из другой базы. Однако этот подход имеет существенные недостатки: зависимость от прав доступа к сетевым ресурсам в операционной системе, необходимость администрирования этих папок и уязвимость перед сбоями сети.
Альтернативный и более изолированный метод — хранить временные файлы обмена прямо внутри информационной базы 1С, чтобы затем забирать их через внешнее соединение (COM). В рамках этой статьи мы подробно разберем, как правильно реализовать эту задачу, какие подводные камни могут возникнуть при передаче бинарных данных через COM-границу, где именно в конфигурациях Комплексная автоматизация 1.1 (КА 1.1) и Управление торговлей 10.3 (УТ 10.3) лучше хранить такие данные, и как перевести это решение на современные рельсы.
Среди разработчиков 1С часто ведется дискуссия о допустимости хранения файлов непосредственно в СУБД. Проанализируем аргументы обеих сторон, чтобы принять взвешенное решение:
В нашем случае рассматривается передача небольшого служебного текстового документа, формируемого автоматически. Хранить его внутри базы — абсолютно оправданное и безопасное решение.
Если вам необходимо передавать произвольные файлы (двоичные данные, картинки, архивы), стандартным механизмом 1С является объект ХранилищеЗначения. Однако при попытке напрямую прочитать этот реквизит через COM-соединение вы гарантированно столкнетесь со специфической ошибкой десериализации. Платформа не умеет напрямую маршалировать тип ХранилищеЗначения через COM-границу.
Чтобы обойти это ограничение, мы применим классический паттерн: сериализуем бинарные данные в строку формата Base64 на стороне внешней базы (источника), передадим эту строку как обычный текст и восстановим файл на стороне принимающей базы. Разберем по шагам, как это реализовать.
Для начала создадим в базе-источнике собственный регистр сведений, например, СлужебныйБуферОбмена со следующим составом реквизитов:
ИдентификаторФайла (Тип: Строка, длина 100).ДанныеФайла (Тип: ХранилищеЗначения).Рассмотрим пример кода для записи произвольного файла в этот регистр на стороне базы-источника:
// Запись файла в Хранилище Значений на стороне источника
ИмяФайлаНаДиске = "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");
ЛокальныеДвоичныеДанные.Записать(ПутьДляПриема);
Сообщить("Файл успешно получен и сохранен во временный каталог: " + ПутьДляПриема);
Иначе
Сообщить("Запись с указанным идентификатором не найдена в базе-источнике!");
КонецЕсли;
Если ваша задача аналогична описанной на форуме — передается исключительно текстовый документ (или XML/JSON-текст), то использование ХранилищеЗначения является избыточным. Мы можем значительно упростить код, снизить нагрузку на процессор (избавившись от сжатия и кодирования Base64) и повысить скорость обмена.
Для этого в нашем служебном регистре сведений достаточно создать ресурс ТекстФайла с типом Строка (неограниченной длины). Строки неограниченной длины отлично и без искажений передаются напрямую через COM-соединение.
Рассмотрим, как выглядит запись текстовых данных на стороне источника:
// Запись текста напрямую в регистр сведений на стороне источника
ТекстовыйДок = Новый ТекстовыйДокумент;
ТекстовыйДок.УстановитьТекст("Это содержимое служебного текстового файла.");
МенеджерЗаписи = РегистрыСведений.СлужебныйБуферОбмена.СоздатьМенеджерЗаписи();
МенеджерЗаписи.ИдентификаторФайла = "СлужебныйТекст_1";
// Извлекаем сырой текст и записываем в строковый ресурс
МенеджерЗаписи.ТекстФайла = ТекстовыйДок.ПолучитьТекст();
МенеджерЗаписи.Записать();
Теперь посмотрим на пример чтения этого текста из принимающей базы. Код становится лаконичным и простым для понимания:
// Чтение текстовых данных напрямую через COM
ЗаписьРегистра = ВнешнееСоединение.РегистрыСведений.СлужебныйБуферОбмена.СоздатьМенеджерЗаписи();
ЗаписьРегистра.ИдентификаторФайла = "СлужебныйТекст_1";
ЗаписьРегистра.Прочитать();
Если ЗаписьРегистра.Выбран() Тогда
// Прямое чтение строки неограниченной длины через COM-соединение
ПолученныйТекст = ЗаписьРегистра.ТекстФайла;
// Восстанавливаем объект ТекстовыйДокумент в локальной базе
ЛокальныйТекстовыйДокумент = Новый ТекстовыйДокумент;
ЛокальныйТекстовыйДокумент.УстановитьТекст(ПолученныйТекст);
// Начинаем парсинг или обработку документа
Сообщить("Успешно прочитано строк: " + ЛокальныйТекстовыйДокумент.КоличествоСтрок());
КонецЕсли;
В оригинальном обсуждении автор упомянул, что использовал для хранения стандартный регистр сведений СохраненныеНастройки. Настоятельно рекомендуем избегать этого решения по следующим причинам:
СохраненныеНастройки предназначен для хранения пользовательских настроек форм, отчетов и обработок. Он не рассчитан на транзакционную нагрузку интеграционных процессов.Выясним, какие архитектурно правильные альтернативы существуют в типовых конфигурациях на обычных формах (КА 1.1 / УТ 10.3):
СлужебныйБуферОбмена (как в наших примерах выше). Это гарантирует, что данные обмена не пересекутся с другими бизнес-процессами компании.Несмотря на то, что конфигурации КА 1.1 и УТ 10.3 являются устаревшими, они практически всегда запускаются на современной платформе 1С:Предприятие 8.3. Это открывает перед нами возможность полностью отказаться от тяжеловесного, нестабильного и привязанного к Windows механизма COM-соединений в пользу HTTP-сервисов.
Почему стоит отказаться от COM?
comcntr.dll на сервере. При обновлении платформы регистрация часто «слетает».Давайте рассмотрим, как просто перевести нашу задачу на рельсы 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-сервисов, что сделает вашу интеграцию быстрой, надежной и кроссплатформенной.