Статья написана по хроникам внедрения 1С:Шина версии 4.1 в Первом Бите на Спортивной.
Меня зовут Галац Михаил, хочу поделиться реализацией отдельного http м ет ода на при внедрении шины у клиента, у которого 1С-ные и не 1С-ные информационные системы. Статья актуальная версия на декабрь 2024 г.
Сокращения:
- ИС 1С –информация система на базе 1С конфигурация с БСП.
- Шина – 1С:Шина версии 4.1.
- Json – текстовый файл в формате json.
- Транслятор – объект 1С:Шина в котором выполняется преобразование сообщения.
- Канал – объект 1С:Шина, который осуществляется доставку сообщения.
- id – уникальный код в формате uuid.
- РС - регистр сведений.
- Проект - термин 1с:Шина.
- Сообщение - сообщение интеграции.
Допуски:
- У вас имеется развернутая 1С:Шина не ниже версии 4.1
Источники:
- Официальная документация по продукту 1С:Шина. Ссылка.
- Неофициальная группа в tg @esb_1C
Благодарности:
Отдельная благодарность, это наличие Алексея Борисевича, моего технического архитектора по внедрению проектов 1С:Шина, за консультации и помощь в реализации, когда я был в тупике.
Моему руководителю Гейдару Габриэлянцу за корректировки в статье.
Описание задачи
Необходимо сделать http сервис, который будет принимать json, будет прикреплен к статье и его нужно отправить в типовую конфигурацию, где будут создаваться документ. На стороне шины должны выполнять ряд проверок на корректность переданных данных и возвращать расшифровку ошибки в виде json, в случае прохождения всех проверок возвращается json со статусом заявки
Краткое описание реализации:
Шина получает json и направляет его в канал внутри шины.
На стороне шины нужно сделать ряд проверок (проверки на заполнение, операции с датами) и в случае обнаружения дать ответ в виде json Формат 1.
В случае отсутствия ошибок, присвоить в шине данному пакету id и сделать запись в РС «Сообщения для внешних систем», чтобы зафиксировать заявку в виде json Формате 5. При формировании сообщения добавить ему параметры id и тип сообщения.
ИС 1С забирает из канала с ообщение и записывает его во внутренний регистр сведений «Полученные сообщения» и уже на основе сформированной записи из РС «Полученные сообщения» создается документ. При изменении статус документа, выполняется формируется json и кладем в регистр сведений «Отправленные сообщения» в виде отдельной записи, а json записываем в реквизите РС «Тело сообщения».
Далее из РС «Отправленные сообщение» с использованием сервис интеграции отправляется в шину сообщение. Данное сообщение внутри шины преобразуется через транслятор и сохраняется запись в шине в регистр сведений «Сообщения для внешних систем». На рисунке 1, представлено схематичное описание решения, которое рассматривается в данной статье:
Рисунок 1. Пример для Шины-Создание заявки
А теперь более подробно с примерами кода и скринами из приложений, чтобы стало более понятно и вы смогли бы воспроизвести у себя и получить профит от данной статьи.
Предполагается, что у вас уже есть проект, приложение можно создать из архива, который можно скачать в конце статьи. Или более тернистый путь и создать самостоятельно в 1С: Шина в виде отдельного приложения на основе данной статьи.
Процесс интеграции – http сервис – шаблон, ну и в модулях добавить код из статьи. Кстати, в дефолтовой инструкции процесс создания Процесс интеграции – http сервис – шаблон описан подробно.
Обычно для несложных проектов используется одно приложение, такой вариант предлагается, потому что сопровождать проще, выгружать / загружать данные было более удобно. Разработку с использованием git не рассматриваем (но, возможно, кто осветит данный аспект для 1С:Шина), напомню, моя задача показать практический пример, который вы сразу сможете использовать у себя на проекте.
Вся разработка выполняется во встроенной ide шины.
А теперь создадим свой http сервис.
В навигаторе проекта, в контекстном меню создаем новый элемент проекта, см. Рисунок 2, а на рисунке 3 выбираем Элемент проекта.
Рисунок 2 Создание нового элемента проекта
Рисунок 3 Создание Процесса Интеграции
- Права доступа пока ставим "РазрешеноВсем", если требуется ограничивать, то следующий уровень понимания.
- Корневой Url ставим "v1" это наша первая версия сервиса.
Примечание.
url нашего сервиса будет примерно такой http://localhost:9090/applications/<ИмяПроекта>
Внутри сервиса интеграции рисуем схему, по которой будут идти сообщения. У меня получилось так.
Рисунок 4 Схема Процесса Интеграции
Краткое описание по рисунку 4.
json, который присылают на http сервис мы проверим и отправим в Программный Источник и далее Сообщение Интеграции отправляем в канал "ИзВнешнихСистем", далее сервис интеграции в ИС 1С заберет сообщения, которые там будут находиться. Взаимодействия из ИС 1С в шину в данном бизнес процессе не рассматривается.
Запустим приложение, жмем F5 и посмотрим, что получилось для нашего случая
http://localhost:9090/applications/test
Перейдя по ссылке в браузере должно отобразиться так, см. рисунок 5.
Рисунок 5 Пример запущенного тестового приложения
Далее создаем новый шаблон у http сервиса для рассматриваемой задачи, нужен с типом POST.
В свойствах шаблона указываем следующие действия:
Переходим в раздел Шаблоны -> Новый -> Шаблон URL, см рисунок 6.
Рисунок 6 Создание нового шаблона
И заполняем свойства шаблона:
- Имя «Заявка»
- КонтрольДоступа - Вызов - РазрешеноВсем
- Шаблон /orders
Рисунок 7 Свойства шаблона созданного http сервиса
Теперь добавляем, какие запросы этот шаблон должен обрабатывать, у меня это POST:
- имя POST
- Метод: POST
- Обработчик: жмем на лупу или вводим свое имя метода и жмем лупу.
Рисунок 8.Свойства запроса
В дереве объектов получится так
Рисунок 9 Дерево объектов созданного http сервиса
Теперь, когда у нас созданы все объекты, то переходим в метод определенном на рисунке 8 «ОбработкаЗапроса», если в обработчике будет пусто, жмем на лупу, и название присваивается по умолчанию, как моем примере.
Ниже приведен текст метода с комментариями
// Тут описываем структуры, которые есть внутри json
структура КонтактыКлиента
обз пер cargos: Массив<product>
;
метод ОбработкаЗапросаЗаявка(Запрос: HttpСервисЗапрос)
// Область переменных, корые нужно объявить заранее
пер ПустойМассив = новый Массив<Число>()
пер ТелоОтвета: Строка
пер ГуидВызова = новый Ууид()
/*
Так как поток после прочтения закрывается ,поэтому готовим его заранее,
чтобы передавать его в канал
Тело запрос сериализуем в json, далее в строку и создаем поток из строки.
*/
знч ТелоЗапросаИзПотока = Запрос.Тело.ПрочитатьКакСтроку()
пер ТелоЗапросаПоток = ПотокЧтения.ИзСтроки(ТелоЗапросаИзПотока)
пер ТелоЗапроса = СериализацияJson.ПрочитатьОбъект(ТелоЗапросаПоток)
пер ТелоЗапросаСтрокаПоток = ПотокЧтения.ИзСтроки(ТелоЗапросаИзПотока)
// В канал отправляем тело запроса, коорые получилии и добавляем параметры апроса,
// чтобы идентифицировать и маршрутизировать данное сообщение в ИСНазначение
пер Сообщение = новый СообщениеИнтеграции(
{
"id": ГуидВызова.ВСтроку(), "type" : "orders"
}, ТелоЗапросаСтрокаПоток)
пер Грузы = ТелоЗапроса["cargos"] // как Массив<Объект?>
если Грузы == ПустойМассив //ORD107
пер КодОшибки = "ORD107"
пер РасшифровкаТекстОшибки = "Отсутствуют грузы"
пер ТекстОшибки =
{
"errorCode": КодОшибки,
"errorMessage": РасшифровкаТекстОшибки
}
ТелоОтвета = СериализацияJson.ЗаписатьОбъект(ТекстОшибки)
иначе если Грузы.ВСтроку() != ""
для Груз из Грузы
если Груз["places"] == ПустойМассив
пер КодОшибки = "ORD108"
пер РасшифровкаТекстОшибки = "Отсутствуют грузоместа"
пер ТекстОшибки =
{
"errorCode": КодОшибки,
"errorMessage": РасшифровкаТекстОшибки
}
ТелоОтвета = СериализацияJson.ЗаписатьОбъект(ТекстОшибки)
прервать
иначе
пер грузоместа = Груз["places"] // Массив грузомест
для грузоместо из грузоместа
если грузоместо["weight"]["value"] < 0
пер КодОшибки = "ORD113"
пер РасшифровкаТекстОшибки = "Некорректный вес грузового места"
пер ТекстОшибки =
{
"errorCode": КодОшибки,
"errorMessage": РасшифровкаТекстОшибки
}
ТелоОтвета = СериализацияJson.ЗаписатьОбъект(ТекстОшибки)
прервать
;
;
;
если Груз["items"] == ПустойМассив
пер КодОшибки = "ORD109"
пер РасшифровкаТекстОшибки = "Отсутствует номенклатура"
пер ТекстОшибки =
{
"errorCode": КодОшибки,
"errorMessage": РасшифровкаТекстОшибки
}
ТелоОтвета = СериализацияJson.ЗаписатьОбъект(ТекстОшибки)
прервать
;
если (Груз["origin"]["dueDate"]["from"] == "") или (Груз["destination"]["dueDate"]["from"] == "")
пер КодОшибки = "ORD104"
пер РасшифровкаТекстОшибки = "Не указано время 'От' в точке погрузке/выгрузки"
пер ТекстОшибки =
{
"errorCode": КодОшибки,
"errorMessage": РасшифровкаТекстОшибки
}
ТелоОтвета = СериализацияJson.ЗаписатьОбъект(ТекстОшибки)
прервать
иначе
пер ДатаПогрузкиДо = новый Момент(Груз["origin"]["dueDate"]["to"])
пер ДатаПогрузкиОт = новый Момент(Груз["origin"]["dueDate"]["from"])
пер ДатаВыгрузкиДо = новый Момент(Груз["destination"]["dueDate"]["to"])
пер ДатаВыгрузкиОт = новый Момент(Груз["destination"]["dueDate"]["from"])
пер ДопустимоеВремяПогрузки = новый Длительность(4, 0, 0)
знч ОтметкаВремениСейчас = Момент.Сейчас()
если (ДатаПогрузкиОт >= ДатаПогрузкиДо) или (ДатаВыгрузкиОт >= ДатаВыгрузкиДо)
пер КодОшибки = "ORD279"
пер РасшифровкаТекстОшибки = "Дата 'до' ('to') раньше или равна дате 'от' ('from') той же операции"
пер ТекстОшибки =
{
"errorCode": КодОшибки,
"errorMessage": РасшифровкаТекстОшибки
}
ТелоОтвета = СериализацияJson.ЗаписатьОбъект(ТекстОшибки)
прервать
иначе если ДатаВыгрузкиОт <= ДатаПогрузкиОт или ДатаВыгрузкиОт <= ДатаПогрузкиДо
пер КодОшибки = "ORD277"
пер РасшифровкаТекстОшибки = "Дата-время выгрузки 'от' ('from') раньше или равна дате погрузки"
пер ТекстОшибки =
{
"errorCode": КодОшибки,
"errorMessage": РасшифровкаТекстОшибки
}
ТелоОтвета = СериализацияJson.ЗаписатьОбъект(ТекстОшибки)
прервать
иначе если ДатаПогрузкиОт < (ОтметкаВремениСейчас + ДопустимоеВремяПогрузки)
пер КодОшибки = "ORD270"
пер РасшифровкаТекстОшибки = "Недостаточно времени до погрузки"
пер ТекстОшибки =
{
"errorCode": КодОшибки,
"errorMessage": РасшифровкаТекстОшибки
}
ТелоОтвета = СериализацияJson.ЗаписатьОбъект(ТекстОшибки)
прервать
;
;
;
;
// Если нет ошибок в сообщении, то отдаем guid в виде простого json
если ТелоОтвета == ""
пер ЗаявкаСоздана =
{
"id": ГуидВызова
}
ТелоОтвета = СериализацияJson.ЗаписатьОбъект(ЗаявкаСоздана)
;
//Запись в РС Сообщения из внешних систем.
//ГуидВызова - уид для сообщения, ДатаЗаписи - дата и время когда была сделана запись, ИсходноеСообщение - тело которое нам пришло
//ВнешнийКлиент - от какого завода пришло сообщение, пока служебно пишем Внешний клиент
пер Запись =
новый СообщенияДляВнешнихСистем.Запись(Период = Момент.Сейчас() , ГуидВызова = ГуидВызова, ДатаЗаписи = ДатаВремя.Сейчас(
ЧасовойПояс {UTC+3}), ИсходноеСообщение = ТелоЗапросаИзПотока, ВнешнийКлиент = "Внешний клиент", Обработано = Истина)
СообщенияДляВнешнихСистем.Записать(Запись)
// Отправляем сообщение в Узел "ПрограммныйИсточник" из него Шина отправляет в канал
Обмен_ВнешниеСистемы.ОтправитьСообщениеВУзлы(Сообщение, Обмен_ВнешниеСистемы.Схема.Узлы.ПрограммныйИсточник)
// тут мы даем ответ на обращение в http сервис или ошибку или присвоенный guid
Запрос.Ответ.УстановитьТело(ТелоОтвета, "UTF-8")
;
Рекомендую через Postman или аналогичное ПО сформировать post запрос и отправить на вновь созданный http service и в отладке посмотреть какие значения присваиваются и как работает логика.
Сразу предупреждаю оптимальности кода не преследовалась, это пример который вы можете доработать и улучшить
Кстати, намеренно не описал создание РС Сообщения для внешних систем, создайте самостоятельно, документация по 1С:Шина позволяет это сделать без мук и испытаний, единственно отмечу, что делайте его периодическим с периодом МоментВремени.
Запускаем приложение, через Postman отправляете json в формате 1 и получаете ответ в виде ошибки или с id заявки примерно такое:
Рисунок 10 Заявка создана и id присвоен. Скриншот из Postman
Рисунок 11.Сообщение в канале для ИС1С
Создаем но вый Сервис Интеграции.
Изменения теперь выполняем в ИС 1C, чтобы забрать сообщение из канала шины
- Адрес внешнего сервиса интеграции: http://localhost:9090/applications/test
- Имя: Любое.
- В модуле добавляем Процедуру:
Процедура Основной_Обмен_ВнешниеСистемы_ИзВнешнихСистемОбработкаПолученияСообщения(Сообщение, Отказ)
//РазмерСообщения = Сообщение.Параметры.Получить("РазмерСообщения");
РазмерСообщения = Сообщение.РазмерТела;
ТипВходящегоСообщения = Сообщение.Параметры.Получить("type");
ID = Сообщение.Параметры.Получить("id");
ПОпытка
//////////////////////////////////////////
Если РазмерСообщения <> Неопределено Тогда
РазмерБуфера = Число(РазмерСообщения);
Иначе
РазмерБуфера = 1024;
КонецЕсли;
Тело = Новый БуферДвоичныхДанных(0);
Буфер = Новый БуферДвоичныхДанных(РазмерБуфера);
Поток = Сообщение.ПолучитьТелоКакПоток();
Пока Истина Цикл
Прочитано = Поток.Прочитать(Буфер, 0, РазмерБуфера);
Если Прочитано > 0 Тогда
Тело = Тело.Соединить(Буфер);
КонецЕсли;
Если Прочитано < РазмерБуфера Тогда
Прервать;
КонецЕсли;
КонецЦикла;
//////////////////////////////////////
ТелоСтрока = ПолучитьСтрокуИзБуфераДвоичныхДанных(Тело);
Исключение
Возврат;
КонецПопытки;
Если ЗначениеЗаполнено(ТипВходящегоСообщения) Тогда
СтруктураРегистра = Новый Структура;
СтруктураРегистра.Вставить("ИдентификаторСообщения", Новый УникальныйИдентификатор);
СтруктураРегистра.Вставить("Получатель", Перечисления.битПолучателиИнформационныеСистемы.ВнешниеСистемы);
СтруктураРегистра.Вставить("ТелоСообщения", СокрП(ТелоСтрока));
СтруктураРегистра.Вставить("ID", ID);
СтруктураРегистра.Вставить("ДатаПолученияОтвета", ТекущаяДатаСеанса());
СтруктураРегистра.Вставить("ДатаОтправки", ТекущаяДатаСеанса());
СтруктураРегистра.Вставить("ТипЗапроса", ТипВходящегоСообщения);
РегистрыСведений.БитПолученныеСообщения.ЗаписатьВРегистрПолученныеСообщения(СтруктураРегистра);
Иначе
СтруктураРегистра = Новый Структура;
СтруктураРегистра.Вставить("ИдентификаторСообщения", Новый УникальныйИдентификатор);
СтруктураРегистра.Вставить("Получатель", Перечисления.битПолучателиИнформационныеСистемы.ВнешниеСистемы);
СтруктураРегистра.Вставить("ОписаниеОшибки", СокрП(ТелоСтрока));
СтруктураРегистра.Вставить("ID", ID);
СтруктураРегистра.Вставить("ОшибкаОбработки", Истина);
СтруктураРегистра.Вставить("ДатаПолученияОтвета", ТекущаяДатаСеанса());
СтруктураРегистра.Вставить("ДатаОтправки", ТекущаяДатаСеанса());
СтруктураРегистра.Вставить("ТипЗапроса", "error");
РегистрыСведений.БитПолученныеСообщения.ЗаписатьВРегистрПолученныеСообщения(СтруктураРегистра);
КонецЕсли;
КонецПроцедуры
В данной процедуре смотрим на значение параметра сообщения “type”, если они заполнено, то забираем с канала сообщение интеграции и сохраняем его в РС «Полученные сообщения»
В отладке все вопросы должны уйти, если все же останутся вопросы, пишите в комментариях, дополню статью.
Впечатления от использования шины:
Общее впечатление по продукт у 1С:Шина положительное, а теперь подробней.
Удобным назову способ программирования - это встроенная ide, хотя она периодически подглючивает, жмешь F5, чтобы обновить страницу в браузере, а у тебя запускается приложение в отладке.
Сам код хоть и похож на 1С, то таковым не является, синтаксис циклов вообще удивляет (непривычен), все ключевые слова пишутся с маленькой буквы, хотя 1С нас учит другому, но привыкаешь быстро и в итоге для меня оказалось проще писать с маленькой буквы.
Вообще складывается впечатление, что синтаксис 1С Элемента позаимствован из современных web скриптовых языков.
Существуют 2 документации:
- Документация по шине.
- Синтакс помощник.
Документация - это про установку, про описание объектов и примеры кода.
Синтакс-Помощник - это описание методов, которые есть в шине.
Из недостатков:
Документация очень скудная и часто приходится в отладке подбирать синтаксис. А если есть примеры, то они прям очень простые или из идеальной жизни без вариаций.
Честно скажу, это самое напряжное при работе с шиной - отсутствие вменяемой документации и развитого комьюнити, у меня была возможность сходить к опытному товарищу, который помогал с вопросами, это прям ускоряло процесс.
При условии, что я не имел опыта программирования на 1С:Элемент и сам продукт 1С: Шина не щупал руками, на реализацию функционала ушла неделя.