При разработке интеграций между 1С и сторонними системами часто возникает задача организовать выгрузку данных не в типовом формате платформы с конвертом v8msg:Message, а в произвольном виде — например, в собственном формате XML, JSON, CSV или обычном текстовом файле. При этом разработчики хотят сохранить все преимущества механизма планов обмена: регистрацию изменений, инкремент номеров сообщений, защиту от дублирования при повторной выгрузке и возможность подтверждения приема (квитирования).
Стандартный пример выгрузки через ЗаписьСообщенияОбмена требует передачи обязательного параметра типа ЗаписьXML в метод НачатьЗапись(). Выясним причину этой жесткой привязки и разберем практические способы реализации обмена без формирования служебных заголовков платформы.
Проанализируем устройство подсистемы обмена данными в платформе 1С:Предприятие. Платформа строго разделяет два независимых механизма:
_Changes в базе данных), методы менеджера планов обмена ПланыОбмена.ВыбратьИзменения(), ПланыОбмена.УдалитьРегистрациюИзменений() и ПланыОбмена.ЗарегистрироватьИзменения(). Этот механизм универсален и не зависит от формата файлов обмена.ЗаписьСообщенияОбмена и ЧтениеСообщенияОбмена. Они жестко завязаны на формат XML и служебное пространство имен http://v8.1c.ru/messages. Метод НачатьЗапись() выполняет три действия: формирует XML-заголовок, увеличивает номер сообщения узла и устанавливает блокировку на запись узла плана обмена.Ключевой момент заключается в том, что метод ПланыОбмена.ВыбратьИзменения(<Узел>, <НомерСообщения>) абсолютно не требует наличия открытого объекта ЗаписьСообщенияОбмена. Вторым параметром ему передается обычное целое число, которое будет записано в поле НомерСообщения таблицы регистрации для всех невыгруженных изменений (где ранее было значение NULL).
Рассмотрим несколько рабочих вариантов построения процесса выгрузки в собственный формат.
Разберем по шагам, как полностью отказаться от ЗаписьСообщенияОбмена и смоделировать все действия платформы программно. Это наиболее чистый подход при выгрузке в JSON, CSV или собственный формат XML.
Алгоритм действий выглядит следующим образом:
НомерОтправленного + 1.ПланыОбмена.ВыбратьИзменения() с новым номером сообщения. Обратите внимание: этот метод нельзя вызывать внутри явной транзакции 1С, иначе платформа вызовет исключение.Посмотрим на пример программной реализации:
// 1. Блокируем узел от параллельного редактирования и выгрузок
ЗаблокироватьДанныеДляРедактирования(УзелПланаОбмена);
Попытка
// 2. Рассчитываем номер сообщения
НовыйНомерСообщения = УзелПланаОбмена.НомерОтправленного + 1;
// 3. Фиксируем номер сообщения в таблице регистрации изменений
// Внимание: вызов должен быть вне транзакции
Выборка = ПланыОбмена.ВыбратьИзменения(УзелПланаОбмена, НовыйНомерСообщения);
// 4. Формируем целевой файл в произвольном формате (например, JSON)
ЗаписьJSON = Новый ЗаписьJSON;
ЗаписьJSON.ОткрытьФайл(ПутьКФайлу);
ЗаписьJSON.ЗаписатьНачалоОбъекта();
ЗаписьJSON.ЗаписатьИмяСвойства("НомерСообщения");
ЗаписьJSON.ЗаписатьЗначение(НовыйНомерСообщения);
ЗаписьJSON.ЗаписатьИмяСвойства("Данные");
ЗаписьJSON.ЗаписатьНачалоМассива();
Пока Выборка.Следующий() Цикл
ОбъектДанных = Выборка.Получить();
// Заполняем файл собственным форматом
ЗаписатьДанныеВФорматеJSON(ЗаписьJSON, ОбъектДанных);
КонецЦикла;
ЗаписьJSON.ЗаписатьКонецМассива();
ЗаписьJSON.ЗаписатьКонецОбъекта();
ЗаписьJSON.Закрыть();
// 5. Фиксируем номер отправленного сообщения в узле плана обмена
УзелОбъект = УзелПланаОбмена.ПолучитьОбъект();
УзелОбъект.НомерОтправленного = НовыйНомерСообщения;
УзелОбъект.Записать();
Исключение
РазблокироватьДанныеДляРедактирования(УзелПланаОбмена);
ВызватьИсключение;
КонецПопытки;
РазблокироватьДанныеДляРедактирования(УзелПланаОбмена);
Когда система-приемник подтвердит получение пакета (например, ответит: «Получен пакет № 15»), очистим подтвержденные изменения:
// При получении квитка от приемника:
ПланыОбмена.УдалитьРегистрациюИзменений(УзелПланаОбмена, ПринятыйНомер);
Платформа удалит только те записи из таблицы изменений, номер сообщения которых меньше либо равен переданному числу и не равен NULL. Если объект во время выгрузки или доставки пакета был изменен пользователем в базе данных, платформа автоматически сбросит его номер сообщения в NULL, благодаря чему объект останется в регистрации и уйдет со следующим пакетом.
Рассмотрим альтернативный способ: если не хочется вручную реализовывать блокировки и инкремент номеров, можно позволить объекту ЗаписьСообщенияОбмена выполнить свою работу в буфере оперативной памяти.
Для этого инициализируем ЗаписьXML методом УстановитьСтроку(). Файл на диске создаваться не будет, а служебный XML-заголовок запишется во внутреннюю строковую переменную, которую можно просто проигнорировать:
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
// Платформа блокирует узел, рассчитывает номер и пишет XML-заголовок в память
ЗаписьСообщения.НачатьЗапись(ЗаписьXML, УзелПланаОбмена);
НомерСообщения = ЗаписьСообщения.НомерСообщения;
Выборка = ПланыОбмена.ВыбратьИзменения(УзелПланаОбмена, НомерСообщения);
// Формируем собственный файл (XML без v8msg, CSV или текст)
ТекстовыйФайл = Новый ЗаписьТекста(ПутьКФайлу, КодировкаТекста.UTF8);
ТекстовыйФайл.ЗаписатьСтроку("HEADER;" + Формат(НомерСообщения, "ЧГ="));
Пока Выборка.Следующий() Цикл
Данные = Выборка.Получить();
ТекстовыйФайл.ЗаписатьСтроку("DATA;" + Данные.Ссылка.УникальныйИдентификатор());
КонецЦикла;
ТекстовыйФайл.Закрыть();
// Завершаем запись сообщения: платформа обновит НомерОтправленного и снимет блокировку
ЗаписьСообщения.ЗакончитьЗапись();
ЗаписьXML.Закрыть();
Этот метод хорош тем, что сохраняет платформенный механизм обработки ошибок: если в цикле выгрузки произойдет сбой, достаточно вызвать ЗаписьСообщения.ПрерватьЗапись(), и номер узла останется неизменным.
В высоконагруженных системах вызов метода ПланыОбмена.ВыбратьИзменения() при большом объеме накопленных данных может приводить к долгим блокировкам таблиц регистрации. Разберем, как реализовать порционную выгрузку.
К таблицам регистрации изменений можно обращаться напрямую через запросы 1С, используя конструкцию [ИмяТаблицы].Изменения:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1000
| Изм.Ссылка КАК Ссылка
|ИЗ
| Справочник.Номенклатура.Изменения КАК Изм
|ГДЕ
| Изм.Узел = &Узел
| И Изм.НомерСообщения ЕСТЬ NULL";
Запрос.УстановитьПараметр("Узел", УзелПланаОбмена);
Результат = Запрос.Выполнить().Выгрузить();
МассивСсылок = Результат.ВыгрузитьКолонку("Ссылка");
// Выгружаем полученную порцию в файл
// ...
// После успешной отправки порции очищаем регистрацию точечно
ПланыОбмена.УдалитьРегистрациюИзменений(УзелПланаОбмена, МассивСсылок);
Такой подход обеспечивает минимальную нагрузку на СУБД, устраняет необходимость управления номерами сообщений и отлично подходит для сценариев односторонней потоковой выгрузки данных во внешние системы.