Как реализовать выгрузку по плану обмена в произвольном формате без ЗаписьСообщенияОбмена

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

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

Стандартный пример выгрузки через ЗаписьСообщенияОбмена требует передачи обязательного параметра типа ЗаписьXML в метод НачатьЗапись(). Выясним причину этой жесткой привязки и разберем практические способы реализации обмена без формирования служебных заголовков платформы.

Архитектурные особенности механизма планов обмена

Проанализируем устройство подсистемы обмена данными в платформе 1С:Предприятие. Платформа строго разделяет два независимых механизма:

  1. Механизм регистрации изменений: таблицы регистрации изменений (суффикс _Changes в базе данных), методы менеджера планов обмена ПланыОбмена.ВыбратьИзменения(), ПланыОбмена.УдалитьРегистрациюИзменений() и ПланыОбмена.ЗарегистрироватьИзменения(). Этот механизм универсален и не зависит от формата файлов обмена.
  2. Инфраструктура формирования сообщений: объекты ЗаписьСообщенияОбмена и ЧтениеСообщенияОбмена. Они жестко завязаны на формат XML и служебное пространство имен http://v8.1c.ru/messages. Метод НачатьЗапись() выполняет три действия: формирует XML-заголовок, увеличивает номер сообщения узла и устанавливает блокировку на запись узла плана обмена.

Ключевой момент заключается в том, что метод ПланыОбмена.ВыбратьИзменения(<Узел>, <НомерСообщения>) абсолютно не требует наличия открытого объекта ЗаписьСообщенияОбмена. Вторым параметром ему передается обычное целое число, которое будет записано в поле НомерСообщения таблицы регистрации для всех невыгруженных изменений (где ранее было значение NULL).

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

Вариант 1. Ручное управление номером сообщения и блокировкой узла

Разберем по шагам, как полностью отказаться от ЗаписьСообщенияОбмена и смоделировать все действия платформы программно. Это наиболее чистый подход при выгрузке в JSON, CSV или собственный формат XML.

Алгоритм действий выглядит следующим образом:

  1. Вычисляем очередной номер сообщения как НомерОтправленного + 1.
  2. Устанавливаем исключительную управляемую блокировку на узел плана обмена или используем пессимистическую объектную блокировку, чтобы предотвратить параллельные запуски фоновых заданий выгрузки.
  3. Вызываем метод ПланыОбмена.ВыбратьИзменения() с новым номером сообщения. Обратите внимание: этот метод нельзя вызывать внутри явной транзакции 1С, иначе платформа вызовет исключение.
  4. Обходим выборку изменений и записываем данные в целевой файл любого формата.
  5. После успешного формирования файла фиксируем отправленный номер в узле плана обмена.

Посмотрим на пример программной реализации:


// 1. Блокируем узел от параллельного редактирования и выгрузок
ЗаблокироватьДанныеДляРедактирования(УзелПланаОбмена);

Попытка
    // 2. Рассчитываем номер сообщения
    НовыйНомерСообщения = УзелПланаОбмена.НомерОтправленного + 1;
    
    // 3. Фиксируем номер сообщения в таблице регистрации изменений
    // Внимание: вызов должен быть вне транзакции
    Выборка = ПланыОбмена.ВыбратьИзменения(УзелПланаОбмена, НовыйНомерСообщения);
    
    // 4. Формируем целевой файл в произвольном формате (например, JSON)
    ЗаписьJSON = Новый ЗаписьJSON;
    ЗаписьJSON.ОткрытьФайл(ПутьКФайлу);
    ЗаписьJSON.ЗаписатьНачалоОбъекта();
    ЗаписьJSON.ЗаписатьИмяСвойства("НомерСообщения");
    ЗаписьJSON.ЗаписатьЗначение(НовыйНомерСообщения);
    ЗаписьJSON.ЗаписатьИмяСвойства("Данные");
    ЗаписьJSON.ЗаписатьНачалоМассива();
    
    Пока Выборка.Следующий() Цикл
        ОбъектДанных = Выборка.Получить();
        // Заполняем файл собственным форматом
        ЗаписатьДанныеВФорматеJSON(ЗаписьJSON, ОбъектДанных);
    КонецЦикла;
    
    ЗаписьJSON.ЗаписатьКонецМассива();
    ЗаписьJSON.ЗаписатьКонецОбъекта();
    ЗаписьJSON.Закрыть();
    
    // 5. Фиксируем номер отправленного сообщения в узле плана обмена
    УзелОбъект = УзелПланаОбмена.ПолучитьОбъект();
    УзелОбъект.НомерОтправленного = НовыйНомерСообщения;
    УзелОбъект.Записать();
    
Исключение
    РазблокироватьДанныеДляРедактирования(УзелПланаОбмена);
    ВызватьИсключение;
КонецПопытки;

РазблокироватьДанныеДляРедактирования(УзелПланаОбмена);

Когда система-приемник подтвердит получение пакета (например, ответит: «Получен пакет № 15»), очистим подтвержденные изменения:


// При получении квитка от приемника:
ПланыОбмена.УдалитьРегистрациюИзменений(УзелПланаОбмена, ПринятыйНомер);

Платформа удалит только те записи из таблицы изменений, номер сообщения которых меньше либо равен переданному числу и не равен NULL. Если объект во время выгрузки или доставки пакета был изменен пользователем в базе данных, платформа автоматически сбросит его номер сообщения в NULL, благодаря чему объект останется в регистрации и уйдет со следующим пакетом.

Вариант 2. Использование ЗаписьСообщенияОбмена как генератора номера в памяти

Рассмотрим альтернативный способ: если не хочется вручную реализовывать блокировки и инкремент номеров, можно позволить объекту ЗаписьСообщенияОбмена выполнить свою работу в буфере оперативной памяти.

Для этого инициализируем ЗаписьXML методом УстановитьСтроку(). Файл на диске создаваться не будет, а служебный XML-заголовок запишется во внутреннюю строковую переменную, которую можно просто проигнорировать:


ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();

ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
// Платформа блокирует узел, рассчитывает номер и пишет XML-заголовок в память
ЗаписьСообщения.НачатьЗапись(ЗаписьXML, УзелПланаОбмена);

НомерСообщения = ЗаписьСообщения.НомерСообщения;

Выборка = ПланыОбмена.ВыбратьИзменения(УзелПланаОбмена, НомерСообщения);

// Формируем собственный файл (XML без v8msg, CSV или текст)
ТекстовыйФайл = Новый ЗаписьТекста(ПутьКФайлу, КодировкаТекста.UTF8);
ТекстовыйФайл.ЗаписатьСтроку("HEADER;" + Формат(НомерСообщения, "ЧГ="));

Пока Выборка.Следующий() Цикл
    Данные = Выборка.Получить();
    ТекстовыйФайл.ЗаписатьСтроку("DATA;" + Данные.Ссылка.УникальныйИдентификатор());
КонецЦикла;

ТекстовыйФайл.Закрыть();

// Завершаем запись сообщения: платформа обновит НомерОтправленного и снимет блокировку
ЗаписьСообщения.ЗакончитьЗапись();
ЗаписьXML.Закрыть();

Этот метод хорош тем, что сохраняет платформенный механизм обработки ошибок: если в цикле выгрузки произойдет сбой, достаточно вызвать ЗаписьСообщения.ПрерватьЗапись(), и номер узла останется неизменным.

Вариант 3. Порционная выборка изменений через язык запросов

В высоконагруженных системах вызов метода ПланыОбмена.ВыбратьИзменения() при большом объеме накопленных данных может приводить к долгим блокировкам таблиц регистрации. Разберем, как реализовать порционную выгрузку.

К таблицам регистрации изменений можно обращаться напрямую через запросы 1С, используя конструкцию [ИмяТаблицы].Изменения:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ ПЕРВЫЕ 1000
    |    Изм.Ссылка КАК Ссылка
    |ИЗ
    |    Справочник.Номенклатура.Изменения КАК Изм
    |ГДЕ
    |    Изм.Узел = &Узел
    |    И Изм.НомерСообщения ЕСТЬ NULL";

Запрос.УстановитьПараметр("Узел", УзелПланаОбмена);
Результат = Запрос.Выполнить().Выгрузить();

МассивСсылок = Результат.ВыгрузитьКолонку("Ссылка");

// Выгружаем полученную порцию в файл
// ...

// После успешной отправки порции очищаем регистрацию точечно
ПланыОбмена.УдалитьРегистрациюИзменений(УзелПланаОбмена, МассивСсылок);

Такой подход обеспечивает минимальную нагрузку на СУБД, устраняет необходимость управления номерами сообщений и отлично подходит для сценариев односторонней потоковой выгрузки данных во внешние системы.

← На главную