Транспортный модуль.

    Для межпрограммного обмена информацией используется общая шина. Благодаря ей обеспечивается универсальность обмена и независимость от платформы.

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

 

Здесь необходимо взаимодействие разных систем:

  • При покупке товара информация: о пользователе, товаре, количестве и цене отправляется по общей шине в ту базу 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Файл); исключение; конецПопытки;

вызватьИсключение искл;

конецПопытки;

КонецФункции