Транспортный модуль.
Для межпрограммного обмена информацией используется общая шина. Благодаря ей обеспечивается универсальность обмена и независимость от платформы.
Например, пользователь на сайте купил товар, и необходимо для него подготовить и отправить на почту документы для оплаты и на сайте отобразить информацию о статусе покупки и состоянии документов.
Здесь необходимо взаимодействие разных систем:
-
При покупке товара информация: о пользователе, товаре, количестве и цене отправляется по общей шине в ту базу 1С, которой принадлежит товар. Для этого у каждого товара есть информация об идентификаторе базы 1С. Сайт написан на языке PHP.
-
База 1С получает из общей шины информацию о покупке и покупателе, готовит документы, отправляет документы клиенту на почту и информацию о статусе документов на сайт. Опять по общей шине.
-
Из общей шины 1С получает выписки банка, записывает их в базу, анализирует поступление оплаты от покупателя и при поступлении оплаты отправляет по общей шине сайту информацию об оплате, а складской программе данные для отгрузки товаров.
-
При получении товаров покупателем информация об этом передается по общей шине базе 1С и на сайт для смены статуса на «товар получен».
В этом примере используются несколько разнородных программ (складская, программа 1С, сайт на языке PHP, банковская программа), но у всех этих программ есть общее свойство — они обмениваются информацией друг с другом по общей шине). Используется только один стандарт обмена информацией. Это избавляет разработчиков разных систем изучать различные стандарты обмена и пытаться обмениваться с каждым ПО по своим каналам обмена.
Для обмена по общей шине для программы 1С реализовано расширение, называемое Транспортным модулем. Именно о нем пойдет речь в этой статье.
Что необходимо транспортному модулю для обмена информацией?
Для того чтобы обмениваться информацией модулю необходимо знать:
-
ip-адрес для обмена (хост);
-
имя файла-обработчика транспорта (путь);
-
номер сетевого порта (порт). Порт это условный номер сервиса (обработчика пришедшей информации);
-
имя пользователя системы;
-
пароль пользователя системы;
-
файл для отправки (если производится отправка);
-
команда. Определяет, какое действие требуется от транспортного сервера.
Отправка команды на транспортный сервер.
//Подготавливаем соответствие с командой и пути для xml файла результата
параметры1 = Новый соответствие;
параметры1.вставить("command", "list");
xmlФайл = получитьИмяВременногоФайла("xml");
xsdФайл = получитьИмяВременногоФайла("xsd") ;
//Получаем данные
Попытка
Попытка
данные = suz_http_post(параметры1); //Процедура описана в приложении 1
Исключение
ВызватьИсключение описаниеОшибки();
КонецПопытки;
//Записываем результат в файл
xml = Новый текстовыйДокумент;
xml.установитьТекст(данные);
xml.записать(xmlФайл, кодировкаТекста.UTF8);
Получение результата после отправки команды.
данные = "";
параметры1 = Новый соответствие;
параметры1.вставить("command", "list");
xmlФайл = получитьИмяВременногоФайла("xml");
xsdФайл = получитьИмяВременногоФайла("xsd") ;
Попытка
Попытка
данные = suz_http_post(параметры1);
Исключение
ВызватьИсключение описаниеОшибки();
КонецПопытки;
xml = Новый текстовыйДокумент;
xml.установитьТекст(данные);
xml.записать(xmlФайл, кодировкаТекста.UTF8);
об=реквизитформывзначение("Объект");
xsd = об.получитьМакет("suz_query");
xsd.записать(xsdФайл, кодировкаТекста.UTF8);
ошибкаЧтения = Ложь;
Попытка
xdto = создатьФабрикуXDTO(xsdФайл);
xml = Новый чтениеXML();
xml.открытьФайл(xmlФайл);
Сооб = xdto.прочитатьXML(xml, xdto.тип("http://suz.rp.ru/query", "LIST"));
Исключение
ОписаниеОшибкиЧтения = ОписаниеОшибки();
ошибкаЧтения = Истина;
КонецПопытки;
КонецПопытки;
Запись результатов в документ СУЗ. Сообщение.
Попытка
спис = list();
успешно = истина;
исключение;
успешно = ложь;
вызватьИсключение ОписаниеОшибки();
конецПопытки;
Для Каждого сооб из спис Цикл
Ссылка = Документы.ТМ_СУЗСообщение.ПустаяСсылка();
Если сооб.source = "suz" Тогда продолжить; КонецЕсли; //спам от СУЗ
Ссылка = Документы.ТМ_СУЗСообщение.найтиПоРеквизиту("query_id", сооб.query_id);
найден = ?(Ссылка = Документы.ТМ_СУЗСообщение.пустаяСсылка(), ложь, истина);
Если сооб.source = этотУзел Тогда //исходящие
Попытка
содержание = receive(сооб.query_id); //функция в приложении 1
исключение
//разделим ошибки соединения, https, xml
//ошибки связи и т.п. во внешний протокол
искл = ОписаниеОшибки();
Если не (найти(искл, "### XML Error") > 0) Тогда
//запротоколировать ошибку
продолжить;
КонецЕсли;
ошибкаXML = истина;
конецПопытки;
//создание нового, возможно подчиненного по ref_query_id
Док = создатьОбъект();
//автонумерация
Док.дата = текущаяДата();
Док.Состояние = Перечисления.ТМ_сузСообщенияСостоянияСообщений.Вх_ОжидаетОбработки;
Если ошибкаXML Тогда
Док.вид = Перечисления.ТМ_сузСообщенияВидыСообщений.ошибка;
Док.заполнить(тз2струк(сооб));
//+ залогировать описание ошибки
Иначе
Док.вид = Перечисления.ТМ_сузСообщенияВидыСообщений.входящее;
Док.заполнить(содержание);
Док.content = Новый хранилищеЗначения(содержание.content);
КонецЕсли;
Попытка
записатьИзменения(Док);
исключение;
конецПопытки;
Конеццикла;
Обработка результатов.
Обработка результатов — задача, зависящая от полученных данных. В разные базы приходят разные данные со своими структурами информации. Поэтому в каждой базе есть свои обработчики сообщений. Общий принцип таков: есть в базе документ СУЗ. Сообщение со статусом «Ожидает обработки». Необходимо получить контент (поле content с двоичными данными), преобразовать его в читаемый XML файл, данные из этого файла уже в зависимости от их типа обработать.
Если в результате обработки ошибок не произошло, то необходимо в поле ANSWER документа СУЗ. Сообщение поставить текстовый ответ ОК (английскими буквами). Если произошла ошибка обработки, то необходимо поставить ответ ERROR: <описание ошибки английскими буквами>. После установки ответа меняем статус с «Ожидает обработки» на «Ожидает отправки подтверждения».
В системе работает регламентное задание, которое ищет документы с таким статусом, отправляет ответ и меняет статус на «Обработано».
Обработчик «Вх_ОжидаетОбработки» у каждой базы свой и зависит от приходящих данных.
Процесс обработки выглядит так:
//Вх_ОжидаетОбработки
Запрос = Новый Запрос;
Запрос.текст =
"
|выбрать
| Ссылка, вид, Состояние, query_id, source, destination, code_type, code, answer, ref_query_id, ref_code_type, ref_code, groupe, status, is_disabled, create_date, dead_date, Комментарий
|из
| Документ.ТМ_СУЗСообщение как сооб
|где
| (сооб.Состояние = Значение(перечисление.ТМ_сузСообщенияСостоянияСообщений.Вх_ОжидаетОбработки))
| и (сооб.вид = Значение(перечисление.ТМ_сузСообщенияВидыСообщений.входящее)) И (сооб.ПометкаУдаления = ЛОЖЬ)
|
|упорядочить по query_id
|"
;
Попытка
ТМ_СУЗСообщенияОбработчикиПереопределяемый.Вх_ОжидаетОбработки(Запрос.Выполнить().Выгрузить(),code_type);
Исключение
ВызватьИсключение ОписаниеОшибки();
КонецПопытки;
Приложение 1. Описание процедур.
Функция suz_http_post(параметры, Знач content = неопределено)
протокол = "";
данные = "";
имяНачФайла = получитьИмяВременногоФайла();
имяСодФайла = получитьИмяВременногоФайла();
имяКонФайла = получитьИмяВременногоФайла();
имяФайлаЗапроса = получитьИмяВременногоФайла();
имяФайлаОтвета = получитьИмяВременногоФайла();
ИмяФайлаПС = получитьИмяВременногоФайла();
Попытка
сервис = Справочники.ТМ_СУЗСообщенияПараметрыСоединения.НайтиПоНаименованию("suz_query");
параметры.вставить("service", СокрЛП(сервис.пользовательСервиса));
параметры.вставить("pass", СокрЛП(сервис.парольПользователяСервиса));
boundary = стрЗаменить(строка(Новый уникальныйИдентификатор()), "-", "");
файлы = Новый массив;
файлы.добавить(имяНачФайла);
начФайл = Новый записьТекста(имяНачФайла, кодировкаТекста.ANSI);
Для Каждого пар Из параметры Цикл
начФайл.записатьСтроку("--" + boundary);
начФайл.записатьСтроку("Content-Disposition: form-data; name=""" + пар.ключ + """");
начФайл.записатьСтроку("");
начФайл.записатьСтроку(пар.Значение);
КонецЦикла;
Если (content = неопределено) Тогда
начФайл.закрыть();
Иначе
начФайл.записатьСтроку("--" + boundary);
начФайл.записатьСтроку("Content-Disposition: form-data; name=""content""");
начФайл.записатьСтроку("Content-Type: application/octet-stream");
начФайл.записатьСтроку("");
начФайл.закрыть();
файлы.добавить(имяСодФайла);
содФайл = Новый текстовыйДокумент;
содФайл.установитьТекст(xmlСтрока(content));
содФайл.записать(имяСодФайла, кодировкаТекста.ANSI);
ФайлПС = Новый ЗаписьТекста(ИмяФайлаПС, КодировкаТекста.ANSI);
ФайлПС.ЗаписатьСтроку(""+Символы.ПС);
ФайлПС.закрыть();
Файлы.добавить(ИмяФайлаПС);
КонецЕсли;
файлы.добавить(имяКонФайла);
конФайл = Новый записьТекста(имяКонФайла, кодировкаТекста.ANSI);
конФайл.записатьСтроку("--" + boundary + "--");
конФайл.закрыть();
объединитьФайлы(файлы, имяФайлаЗапроса);
файлЗапроса = Новый файл(имяФайлаЗапроса);
размерФайла = файлЗапроса.размер();
платформа = Новый системнаяИнформация;
Если платформа.версияПриложения = "8.2.14.540"
Тогда
адресРесурса = сервис.путь;
заголовки = Новый соответствие;
заголовки.вставить("Content-Type", "multipart/form-data, boundary=" + boundary);
заголовки.вставить("Content-Length", xmlСтрока(размерФайла));
http = Новый HTTPСоединение(
сервис.хост
, сервис.порт
, сервис.пользовательСистемы
, сервис.парольПользователяСистемы
);
Попытка
http.отправитьДляОбработки(имяФайлаЗапроса, сервис.путь, имяФайлаОтвета, заголовки);
исключение
вызватьИсключение "### Connection Error: " + символы.пс + ОписаниеОшибки();
конецПопытки;
файлОтвета = Новый текстовыйДокумент;
файлОтвета.прочитать(имяФайлаОтвета, кодировкаТекста.UTF8);
данные = файлОтвета.получитьТекст();
Иначе
запрос = вычислить("Новый HTTPЗапрос");
запрос.адресРесурса = сервис.путь;
запрос.заголовки.вставить("Content-Type", "multipart/form-data, boundary=" + boundary);
запрос.заголовки.вставить("Content-Length", xmlСтрока(размерФайла));
запрос.установитьИмяФайлаТела(имяФайлаЗапроса);
http = Новый HTTPСоединение(
сервис.хост
, сервис.порт
, сервис.пользовательСистемы
, сервис.парольПользователяСистемы
, , 60
);
Попытка
Ответ = http.ОтправитьДляОбработки(запрос);
Исключение
ВызватьИсключение "### Connection Error: " + символы.пс + ОписаниеОшибки();
КонецПопытки;
Данные = Ответ.получитьТелоКакСтроку();
//zaa
попытка
если константы.ТМ_ЗаполнятьСодержаниеСообщенияТС.Получить() тогда
//ЗаписьЖурналаРегистрации("ЗаполнятьСодержаниеСообщенияТС", УровеньЖурналаРегистрации.Ошибка,,,"запись включена");
КлючТС = сокрлп(boundary);
для каждого пар из параметры
цикл
КлючТС = КлючТС + "; " + пар.ключ + "; " + пар.значение;
конецЦикла;
нз = регистрыСведений.ТМ_СодержаниеСообщенияТС.СоздатьНаборЗаписей();
нз.отбор.КлючТС.Установить(КлючТС);
нз.Прочитать();
ЗаписьСодержания = нз.Добавить();
ЗаписьСодержания.ПорядокЗаписи = текущаяДата();
ЗаписьСодержания.КлючТС = КлючТС;
ЗаписьСодержания.СодержаниеЗапросаТС = новый хранилищеЗначения(новый двоичныеданные(имяФайлаЗапроса));
ЗаписьСодержания.ОтветТС = новый хранилищеЗначения(данные);
нз.Записать(ложь);
конецесли;
исключение
ЗаписьЖурналаРегистрации("ЗаполнятьСодержаниеСообщенияТС", УровеньЖурналаРегистрации.Ошибка,,,описаниеошибки());
конецпопытки;
//zaa
Если ответ.кодСостояния <> 200 Тогда
вызватьИсключение "### HTTP Error: " + ответ.кодСостояния;
КонецЕсли;
КонецЕсли; //версия приложения
Попытка удалитьФайлы(имяНачФайла); исключение; конецПопытки;
Попытка удалитьФайлы(имяКонФайла); исключение; конецПопытки;
Попытка удалитьФайлы(имяСодФайла); исключение; конецПопытки;
Попытка удалитьФайлы(имяФайлаЗапроса); исключение; конецПопытки;
Попытка удалитьФайлы(имяФайлаОтвета); исключение; конецПопытки;
Попытка удалитьФайлы(ИмяФайлаПС); исключение; конецПопытки;
Возврат данные;
исключение
//zaa
попытка
если константы.ТМ_ЗаполнятьСодержаниеСообщенияТС.Получить() тогда
//ЗаписьЖурналаРегистрации("ЗаполнятьСодержаниеСообщенияТС", УровеньЖурналаРегистрации.Ошибка,,,"запись включена");
КлючТС = сокрлп(boundary);
для каждого пар из параметры
цикл
КлючТС = КлючТС + "; " + пар.ключ + "; " + пар.значение;
конецЦикла;
нз = регистрыСведений.ТМ_СодержаниеСообщенияТС.СоздатьНаборЗаписей();
нз.отбор.КлючТС.Установить(КлючТС);
нз.Прочитать();
ЗаписьСодержания = нз.Добавить();
ЗаписьСодержания.ПорядокЗаписи = текущаяДата();
ЗаписьСодержания.КлючТС = КлючТС;
ЗаписьСодержания.СодержаниеЗапросаТС = новый хранилищеЗначения(новый двоичныеданные(имяФайлаЗапроса));
ЗаписьСодержания.ОтветТС = новый хранилищеЗначения(данные);
нз.Записать(ложь);
конецесли;
исключение
ЗаписьЖурналаРегистрации("ЗаполнятьСодержаниеСообщенияТС", УровеньЖурналаРегистрации.Ошибка,,,описаниеошибки());
конецпопытки;
//zaa
искл = ОписаниеОшибки() + символы.пс + символы.пс + протокол;
Попытка удалитьФайлы(имяНачФайла); исключение; конецПопытки;
Попытка удалитьФайлы(имяКонФайла); исключение; конецПопытки;
Попытка удалитьФайлы(имяСодФайла); исключение; конецПопытки;
Попытка удалитьФайлы(имяФайлаЗапроса); исключение; конецПопытки;
Попытка удалитьФайлы(имяФайлаОтвета); исключение; конецПопытки;
вызватьИсключение искл;
конецПопытки;
КонецФункции
Функция receive(query_id) Экспорт
данные = "";
параметры = Новый соответствие;
параметры.вставить("command", "receive");
параметры.вставить("query_id", xmlСтрока(query_id));
xmlФайл = получитьИмяВременногоФайла("xml");
xsdФайл = получитьИмяВременногоФайла("xsd") ;
Попытка
Попытка
данные = suz_http_post(параметры);
исключение
вызватьИсключение ОписаниеОшибки();
конецПопытки;
xml = Новый текстовыйДокумент;
xml.установитьТекст(данные);
xml.записать(xmlФайл, кодировкаТекста.utf8);
xsd = справочники.ТМ_СУЗСообщенияПакетыXDTO.получитьМакет("suz_query");
xsd.записать(xsdФайл, кодировкаТекста.UTF8);
ошибкаЧтения = ложь;
Попытка
xdto = создатьФабрикуXDTO(xsdФайл);
xml = Новый чтениеXML();
xml.открытьФайл(xmlФайл);
сооб = xdto.прочитатьXML(xml, xdto.тип("http://suz.rp.ru/query", "RECEIVE"));
исключение
ОписаниеОшибкиЧтения = ОписаниеОшибки();
ошибкаЧтения = истина;
конецПопытки;
Если ошибкаЧтения Тогда
Попытка
xdto = создатьФабрикуXDTO(xsdФайл);
xml = Новый чтениеXML();
xml.открытьФайл(xmlФайл);
сооб = xdto.прочитатьXML(xml, xdto.тип("http://suz.rp.ru/query", "ERROR"));
исключение
вызватьИсключение "### XML Error: " + query_id + символы.пс + ОписаниеОшибкиЧтения;
конецПопытки;
протокол = "### SUZ-QUERY Error: " + сооб.number + " => " + СокрЛП(сооб.description);
вызватьИсключение протокол;
КонецЕсли;
Попытка удалитьФайлы(xmlФайл); исключение; конецПопытки;
Попытка удалитьФайлы(xsdФайл); исключение; конецПопытки;
струк = Новый структура;
Для Каждого св из сооб.свойства() Цикл
струк.вставить(св.имя, сооб[св.имя]);
КонецЦикла;
Возврат струк;
исключение
искл = ОписаниеОшибки();
Попытка удалитьФайлы(xmlФайл); исключение; конецПопытки;
Попытка удалитьФайлы(xsdФайл); исключение; конецПопытки;
вызватьИсключение искл;
конецПопытки;
КонецФункции