Вводная:
Организовать обмен 1С с внешней системой со следующими условиями и ограничениями:
- Гарантированная доставка сообщений, когда сообщение не может считаться принятым пока не придет ответ от второй системы
- Сервис обмена должен поддерживать архивы сообщений и быть независим как от 1С, так и от внешней системы
- Внешняя система не умеет работать с HTTP сервисами 1С (да бывает и такое)
- 1 сообщение из 1С может быть предназначено сразу нескольким разным внешним системам
В процессе поиска подходящего решения наткнулся на интересные примеры в демо конфигураций 1С «Обмен данными», где приложены примеры работы с сервером очередей сообщений Microsoft MSMQ.
MSMQ – компонент любой современной ОС от Microsoft даже не серверной. Из преимуществ MSMQ: бесплатный, поддерживает транзакции, имеется архив принятых/переданных сообщений, параллельные очереди для разных систем и случаев, тригеры сообщений, возможность авторизации отправителей и получателей сообщений, включая доменную аутентификацию.
Описание решения
В первую очередь нужно инсталировать компонент MSMQ на том компьютере, где будет висеть служба сервера очередей, это может быть, как локальный компьютер, так и любой в сети.
После добавления компонента он будет доступен в разделе Управление компьютером – Службы и приложения – Очередь сообщений.
Создаем частную очередь – имя очереди на английском, без пробелов и спец символов. Как настроить сервер MSMQ: права доступа, место хранения, транзакции, триггеры, размер сообщений, размер хранилища – см. разделы помощи на сайте Microsoft и внимательно читайте в отладке какие ответы будете получать от сервера MSMQ в 1С. Так выглядит очередь сервера MSMQ в который мы из 1с отправили сообщение.
Как реализовано в 1С:
Регистрацию изменений я сделал на своем Плане обмена 1С с ручной регистрацией изменений. Далее примеры кода в модуле объекта Плана Обмена, когда произошло событие регистрации или по расписанию нужно прочитать изменения с сервера сообщений.
Формируем строку XML и передаем серверу MSMQ.
Поместить Данные Обмена В Очередь MSMQ
Процедура ЗаписатьСообщениеСИзменениями(Знач НастройкиРаботы = Неопределено)
Попытка
РезультатЗаписи = ЗаписатьНовоеСообщениеВСтроку(ФильтрВыборки);
ПоместитьДанныеОбменаВОчередьMSMQ(РезультатЗаписи.СтрокаХМЛ, РезультатЗаписи.НомерСообщения);
Исключение
ОписаниеПроблемы = "Ошибка записи сообщения для Система1: "+ ОписаниеОшибки();
КонецПопытки;
КонецПроцедуры
Функция ЗаписатьНовоеСообщениеВСтроку(Знач ФильтрВыборки = Неопределено)
НомерСообщения = 0;
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
ЗаписьXML.ЗаписатьОбъявлениеXML();
// Создаем новое сообщение
ЗаписьСообщения = ПланыОбмена.СоздатьЗаписьСообщения();
ЗаписьСообщения.НачатьЗапись(ЗаписьXML, Ссылка);
// Для сокращения размера файла сообщения записываем соответствие пространств имен, необязательно
ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("xsd", "http://www.w3.org/2001/XMLSchema");
ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("xsi", "http://www.w3.org/2001/XMLSchema-instance");
ЗаписьXML.ЗаписатьСоответствиеПространстваИмен("v8", "http://v8.1c.ru/data");
НомерСообщения = ЗаписьСообщения.НомерСообщения;
ПолучательСообщения = ЗаписьСообщения.Получатель;
ЗаписьXML.ЗаписатьНачалоЭлемента("Info");
ЗаписьXML.ЗаписатьАтрибут("содержание", "ОписаниеПередаваемыхДанныхДопущений");
ЗаписьXML.ЗаписатьТекст("Произвольная строка для внешней системы");
ЗаписьXML.ЗаписатьКонецЭлемента();
ВыборкаИзменений = ПланыОбмена.ВыбратьИзменения(ЗаписьСообщения.Получатель,НомерСообщения,ФильтрВыборки);
Пока ВыборкаИзменений.Следующий() Цикл
ЗаписатьXML(ЗаписьXML, ВыборкаИзменений.Получить()); // Записываем данные с помощью стандартного метода
КонецЦикла;
// Завершаем запись сообщения, Блокировка с записи узла плана обмена снимается и сообщение считается отправленным
ЗаписьСообщения.ЗакончитьЗапись();
Возврат Новый Структура("НомерСообщения,СтрокаХМЛ",НомерСообщения,ЗаписьXML.Закрыть());
КонецФункции
Процедура ПоместитьДанныеОбменаВОчередьMSMQ(СтрокаXML)
MQ_SEND_ACCESS = 2;
MQ_DENY_NONE = 0;
qinfo = Новый COMОбъект("MSMQ.MSMQQueueInfo");
// Формируем путь к очереди на удаленном компьютере, Вариант по имени
ОчередьОбменаMSMQ = "ИмяУдаленногоЛокальногоКомпьютера\PRIVATE$\ИмяИсходящейОчередиИз1С";
qinfo.FormatName = "DIRECT=OS:"+ОчередьОбменаMSMQ; // по IP строка вида: DIRECT=TCP:192.168.0.1\PRIVATE$\ИмяОчереди
Очередь = qinfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE);
Сообщение = Новый COMОбъект("MSMQ.MSMQMessage");
Сообщение.Label = "Message" + СокрЛП(УзелИсточник.Код) + "_" + СокрЛП(УзелПриемник.Код) + СокрЛП(Постфикс) + ".xml";
Сообщение.Body = СтрокаXML;
Сообщение.Send(Очередь); // Помещаем сообщение в очередь
Очередь.Close();
КонецПроцедуры
Чтение сообщений для 1С с сервера MSMQ похоже, но имеет свои нюансы, т.к. удалять(архивировать) сообщение из очереди мы можем только после успешного его чтения. Сначала читаем очередь, затем читаем сообщение и данные в нем и только после успешного чтения удаляем(архивируем) сообщение из очереди.
Получить Прочитать Сообщение Из Очереди MSMQ
Процедура ПолучитьПрочитатьСообщениеИзОчередиMSMQ()
MQ_DENY_NONE = 0; MQ_RECEIVE_ACCESS = 1; MQ_NO_TRANSACTION = 0;
qinfo = Новый COMОбъект("MSMQ.MSMQQueueInfo");
qinfo.FormatName = "DIRECT=OS:"+"ИмяКомпьютера\PRIVATE$\ИмяВходящейОчередиДля1С";
MSMQQueue = qinfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE);
СообщениеВрем = Новый COMОбъект("MSMQ.MSMQMessage"); // Временный объект необходим для поиска последнего сообщения
счетчик = 0;
СообщениеВрем = MSMQQueue.Peek(0, , 0, 0);
СообщениеВрем = MSMQQueue.PeekCurrent(0,,0,0);
// Если получили сообщение, разбираем его и передаем данные обмена дальше для обработки
Пока (СообщениеВрем <> Неопределено) Цикл
счетчик = счетчик+1;
Сообщение = СообщениеВрем;
Успешно = ПрочитатьНовоеСообщение(Сообщение.Label, Сообщение.Body);
Если Успешно Тогда
// удалить текущее сообщение как успешное
СообщениеВрем = MSMQQueue.ReceiveCurrent(MQ_NO_TRANSACTION, , , 0);
// происходит смещение курсора - нормально
СообщениеВрем = MSMQQueue.ReceiveByLookupId(Сообщение.LookupId, MQ_NO_TRANSACTION, , , 0);
КонецЕсли;
СообщениеВрем = MSMQQueue.PeekNext(0,,0,0);
КонецЦикла;
MSMQQueue.Close();
КонецПроцедуры
Функция ПрочитатьНовоеСообщение(ИмяСообщения, СтрокаДляРазбора)
Успех = Ложь;
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(СтрокаДляРазбора);
ЧтениеСообщения = ПланыОбмена.СоздатьЧтениеСообщения();
Попытка
ЧтениеСообщения.НачатьЧтение(ЧтениеXML);
НомерСообщения = ЧтениеСообщения.НомерСообщения;
Если ЧтениеСообщения.Отправитель <> Ссылка Тогда
ВызватьИсключение "Неверный узел отправителя. Ошибка маршрутизации."; // Сообщение предназначено не для этого узла
КонецЕсли;
Пока ВозможностьЧтенияДанных(ЧтениеXML) Цикл
Данные = ПрочитатьXML(ЧтениеXML); // Пытаемся прочесть значение из объекта ЧтениеXML стандартным образом
Если ЗначениеЗаполнено(Данные) Тогда
ПланыОбмена.УдалитьРегистрациюИзменений(Ссылка, Данные);
КонецЕсли;
КонецЦикла;
ЧтениеСообщения.ЗакончитьЧтение(); //когда заканчиваем успешно чтение счетчик сообщений общих увеличивается +1
Успех = Истина;
Исключение
ОшибкаЧтения = ОписаниеОшибки();
ЧтениеСообщения.ПрерватьЧтение();
Успех = Ложь;
КонецПопытки;
ЧтениеXML.Закрыть();
Возврат Успех;
КонецФункции
Функция ВозможностьЧтенияДанных(ЧтениеXML)
ТипXML = ПолучитьXMLТип(ЧтениеXML); // Получаем тип данных XML, который может быть считан в данный момент
Если ТипXML = Неопределено Тогда
Возврат Ложь;
КонецЕсли;
Возврат ВозможностьЧтенияXML(ЧтениеXML);
КонецФункции
В заключение:
Разработка велась на версиях Платформы 8.3.14.ххх. Приведенные примеры должны работать на любой конфигурации, желательно с БСП 2.4.4.166
Другие мои публикации на Инфостарте: