Интеграционная прослойка на Golang и 1С 8.3: HTTP-сервисы для CRUD «Клиентов» и приём JSON из RabbitMQ
В этой части цикла настраиваем обмен с 1С через два опубликованных НТТР-сервиса: CRUD по логическому справочнику «Клиенты» и приём JSON из RabbitMQ. Прослойка middleware на Golang не опрашивает базу по таймеру: изменения идут по HTTP, события из очереди попадают в ту же информационную базу отдельным POST в платформу 1С:Предприятие 8.3.
Где в цикле middleware стоят два НТТР-сервиса 1С
RabbitMQ и контейнер прослойки уже подняты в частях 1–4; здесь только URL вида /hs/… и контракт JSON к справочнику. К НТТР-сервисам платформы подходим там, где внешней системе нужен предсказуемый контракт, а очередь должна доставлять события в ту же базу без постоянного СОМ-соединения и регламентных файлов на шару.
Логическая модель для всех каналов одна: справочник «Клиенты» с реквизитами Идентификатор (строка или UUID по вашему правилу), Имя, Тип, Майл. Прослойка в режиме «1С» ходит в первый сервис методами GET, POST и DELETE. Consumer RabbitMQ в Go (или НТТР-мост брокера, если вы его используете) отправляет JSON клиента методом POST во второй сервис. Оба сервиса пишут в один справочник, поэтому запись из очереди должна появиться в выборке первого сервиса.
На поддерживаемой типовой конфигурации тот же приём переносят в расширение: справочник-аналог или дополнительные реквизиты, НТТР-сервисы только в расширении, публикация с включённым расширением. Объекты не должны конфликтовать с основной конфигурацией по имени - тогда снимать базу с поддержки для эксперимента не требуется.
НТТР-сервис CRUD: JSON, GET, POST, DELETE и отбор без пометки удаления
В конфигураторе 8.3 создайте объект «НТТР-сервис», задайте корневой URL (например, clients) и шаблоны. Каждый шаблон связывается с НТТР-методом и экспортной функцией сервисного общего модуля (сервер, вызов сервера, без клиентских вызовов). Имена функций платформа подставляет сама, если вы указали их в свойствах шаблона; ниже - схема, которую я обычно повторяю на стенде.
GET /list-ClientsListGET(Запрос)- массив клиентов без пометки удаления.GET /item/{id}-ClientsItemGET(Запрос)- один клиент по Идентификатору из URL.POST /item-ClientsItemPOST(Запрос)- создание или обновление по телу JSON.DELETE /item/{id}-ClientsItemDELETE(Запрос)- удаление по идентификатору из URL.
Контракт ответа для списка - массив объектов JSON с полями идентификатора, имени, типа и почты. POST принимает JSON в теле: если элемент с таким Идентификатором уже есть, обновляем реквизиты (в JSON поле «Имя» пишем в Наименование справочника), иначе создаём элемент. DELETE в эталоне физически удаляет элемент; если у вас принята только пометка удаления, GET обязан исключать такие строки тем же правилом, что и запрос списка.
Ответ собираю через HTTPСервисОтвет: код 200 для успешного чтения, 201 для создания, 400 при кривом JSON, 404 если идентификатор не найден, 500 только когда ошибка записи не связана с входными данными. В теле ответа - короткий JSON с полем error, чтобы прослойка на Golang отличила 404 от 500.
Перед записью проверяю Идентификатор и обязательные поля на сервере, без УстановитьПривилегированныйРежим(Истина) без явной причины. Запись от имени пользователя публикации сохраняет RLS и права так, как настроено в роли.
Для ГЕТ-списка отбираю только элементы без пометки удаления.
ВЫБРАТЬ
Клиенты.Идентификатор КАК Идентификатор,
Клиенты.Наименование КАК Имя,
Клиенты.Тип КАК Тип,
Клиенты.Майл КАК Майл
ИЗ
Справочник.Клиенты КАК Клиенты
ГДЕ
НЕ Клиенты.ПометкаУдаления
И НЕ Клиенты.ЭтоГруппа
Результат запроса сериализую в JSON и отдаю в теле ответа. Если в выборку попадёт помеченный элемент, прослойка и автоматические проверки разойдутся с учётом - это первое, что я проверяю после правки отбора.
Фрагмент обработчика списка (идентификатор из URL для других шаблонов берётся из Запрос.ПараметрыURL):
Функция ClientsListGET(Запрос)
ЗапросДанных = Новый Запрос;
ЗапросДанных.Текст =
"ВЫБРАТЬ
| Клиенты.Идентификатор,
| Клиенты.Наименование КАК Имя,
| Клиенты.Тип,
| Клиенты.Майл
|ИЗ
| Справочник.Клиенты КАК Клиенты
|ГДЕ
| НЕ Клиенты.ПометкаУдаления
| И НЕ Клиенты.ЭтоГруппа";
Выборка = ЗапросДанных.Выполнить().Выбрать();
Массив = Новый Массив;
Пока Выборка.Следующий() Цикл
Массив.Добавить(СтрокаКлиентаВСтруктуру(Выборка));
КонецЦикла;
Возврат ОтветJSON(200, ЗаписатьJSONКлиентов(Массив));
КонецФункции
POST оборачиваю в одну транзакцию: прочитал JSON, нашёл ссылку по Идентификатору, записал объект, зафиксировал. При повторной доставке того же JSON из RabbitMQ срабатывает идемпотентность: обновление вместо второго элемента с тем же ключом. Тело разбираю через ЧтениеJSON; пустой Идентификатор отсекаю до транзакции.
Функция ClientsItemPOST(Запрос)
Тело = Запрос.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
Данные = ПрочитатьJSONКлиента(Тело);
Если Не ЗначениеЗаполнено(Данные.Идентификатор) Тогда
Возврат ОтветJSON(400, "{\"error\":\"empty id\"}");
КонецЕсли;
НачатьТранзакцию();
Попытка
Элемент = НайтиИлиСоздатьКлиентаПоИдентификатору(Данные.Идентификатор);
ЗаполнитьРеквизитыКлиента(Элемент, Данные);
Элемент.Записать();
ЗафиксироватьТранзакцию();
Код =?(Элемент.ЭтоНовый(), 201, 200);
Возврат ОтветJSON(Код, ЗаписатьJSONКлиента(Элемент));
Исключение
ОтменитьТранзакцию();
Возврат ОтветJSON(500, "{\"error\":\"write failed\"}");
КонецПопытки;
КонецФункции
DELETE по идентификатору из шаблона URL:
Функция ClientsItemDELETE(Запрос)
Ид = Запрос.ПараметрыURL.Получить("id");
Ссылка = Справочники.Клиенты.НайтиПоРеквизиту("Идентификатор", Ид);
Если Не ЗначениеЗаполнено(Ссылка) Тогда
Возврат ОтветJSON(404, "{\"error\":\"not found\"}");
КонецЕсли;
Объект = Ссылка.ПолучитьОбъект();
Объект.Удалить();
Возврат ОтветJSON(200, "{\"ok\":true}");
КонецФункции
Одинаковую запись в справочник из CRUD и из приёмника очереди удобнее вынести в общий серверный модуль (поиск по Идентификатору, валидация, транзакция), а в НТТР-модулях оставить только разбор запроса и выбор кода ответа - иначе два сервиса быстро разъедутся при правке.
Ответ = Новый HTTPСервисОтвет(200);
Ответ.Заголовки.Вставить("Content-Type", "application/json; charset=utf-8");
Ответ.УстановитьТелоИзСтроки(ТелоJSON, КодировкаТекста.UTF8);
Тестовое тело для Postman и для сообщения в RabbitMQ (один и тот же JSON): {"Идентификатор":"client-1","Имя":"Demo","Тип":"B2B","Майл":"demo@example.local"}.
Публикация на IIS, Apache или встроенном веб-сервере и первые запросы снаружи
НТТР-сервис заработает для middleware только после публикации информационной базы на веб-сервере. В материалах цикла фигурировал IIS; на актуальных стендах часто встречаются Apache или встроенный НТТР-сервис платформы (зависит от релиза 8.3.x и политики ИБ). Сначала публикация ИБ с НТТР-сервисами, затем сверка default.vrd, затем пробный GET с той же учёткой, что у Go.
В default.vrd смотрю, что корневой URL сервиса совпадает с конфигуратором и что база доступна по виртуальному каталогу интегратора. 404 снаружи чаще всего означает неверный путь после /hs/ или опечатку в имени сервиса. 401 на POST из RabbitMQ обычно связан с аутентификацией: Basic или Windows там, где это включено на сайте.
Учётную запись публикации выделяю отдельно, с правами только на справочник «Клиенты». Пароль храним в секретах прослойки, не в репозитории.
Ниже - чек-лист из пяти проверок без повторения установки IIS. Пошаговые инструкции по публикации на IIS и Apache ищите на Infostart по тегам «публикация» и «НТТР-сервис» - здесь только контрольные точки для интегратора.
GET …/hs/clients/list- 200 и JСОН-массив (может быть пустым).POST …/hs/clients/itemс тестовым JSON - 201 или 200.GET …/hs/clients/item/client-1- та же запись.DELETE …/hs/clients/item/client-1- 200, повторный GET - 404.- Тот же URL и заголовок Authorization, что пропишете в consumer RabbitMQ.
На Apache проверяю права на каталог публикации и прокси перед платформой. Встроенный веб-сервер удобен на стенде; для POST с другого хоста смотрю firewall и совпадение URL в consumer с ручным тестом.
Режим «1С» в прослойке на Golang: net/http и слабые места примера
В конфигурации middleware задаём режим работы «1С» и базовый URL опубликованного СРУД-сервиса (схема, хост, порт, виртуальный каталог, /hs/clients/ и хвост шаблона). Пример фрагмента конфига (имена переменных у вас могут отличаться): ONEC_MODE=1c, ONEC_BASE_URL=https://stand.example/local/hs/clients, ONEC_USER=integration, ONEC_PASSWORD=… из секрета, не из git.
Дальше сценарий прямой: GET …/list - список; DELETE …/item/{id} - удаление; POST …/item с телом JSON - изменение или добавление; в конфигураторе или повторным GET убеждаемся, что база 1С изменилась.
На стороне Go достаточно стандартного net/http. GET списка - http.Get с разбором тела через json.Decoder. POST с JSON - http.Post с bytes.NewReader и Content-Type. DELETE и POST с параметрами в query удобнее через http.NewRequest.
client:= &http.Client{Timeout: 30 * time.Second}
req, err:= http.NewRequest(http.MethodDelete, baseURL+"/item/"+id, nil)
if err!= nil {
return err
}
req.SetBasicAuth(user, pass)
resp, err:= client.Do(req)
if err!= nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
body, _:= io.ReadAll(resp.Body)
return fmt.Errorf("delete %s: %d %s", id, resp.StatusCode, string(body))
}
return nil
Ниже - тот же вызов с http.Client{ Timeout: … } и разбором resp.Body при StatusCode от 400 и выше: переиспользуемый клиент, явная проверка кода, чтение тела для лога middleware. На этом этапе связка уже работает при верных URL и авторизации, если не игнорировать код ответа.
Список клиентов разбираю в слайс структур Go с полями под JСОН-контракт 1С. После DELETE проверяю, что элемент исчез из следующего GET.
Второй НТТР-сервис: POST из RabbitMQ в справочник
Постоянный опрос 1С из Go не нужен: входящие сообщения очереди направляем HTTP POST в платформу. Второй НТТР-сервис делаю минимальным: GET - короткий статус для мониторинга, POST - JSON клиента с теми же полями, что в CRUD.
GET /ping-QueuePingGET(Запрос)- текст или JSON «ok».POST /ingest-QueueIngestPOST(Запрос)- вызов общего модуля записи клиента.
Обработчик POST вызывает ту же процедуру записи, что и CRUD: разбор JSON, поиск по Идентификатору, одна транзакция. При ошибке валидации возвращаю 400 с телом {"error":"…"}. Consumer RabbitMQ при 400 обычно не делает бесконечный requeue: сообщение уходит в dead-letter или отдельную очередь разбора, иначе poison message зациклит очередь. При 5xx или обрыве сети - nack с requeue в пределах лимита повторов.
Типовая схема на middleware: consumer подписан на очередь (имя, exchange и routing key задаёте вы при настройке брокера в частях 1–4), при доставке формирует POST на /hs/queue/ingest с тем же JSON. Альтернатива - плагины и НТТР-мост брокера; меньше кода в Go, зато отдельная настройка durability и таймаутов. Если запись в 1С дольше таймаута HTTP, сообщение может уйти в повтор - идемпотентность по Идентификатору на сервере 1С обязательна.
Имя второго сервиса и корневой URL держу отдельно от CRUD, чтобы права на РОСТ-приём ограничить технической учёткой очереди, а к полному CRUD допускать только прослойку администрирования.
Для очереди включаю durable queue и persistent messages, на consumer - manual ack после успешного HTTP 200 или 201 от 1С.
Тестовое сообщение можно отправить из любой конфигурации-отправителя, если она уже публикует JSON в ту же очередь (тот же exchange и routing key, что у consumer). В тестовой базе 1С смотрим элемент справочника и точку останова в модуле НТТР-сервиса - отладка идёт как у обычного серверного кода, без клиентского сеанса.
Согласованность: очередь, СРУД-сервис и прослойка видят одни данные
После POST из RabbitMQ выполняю GET через первый сервис и запрос списка из middleware. Например, в очереди "id":" client-1 ", а в 1С строка без пробелов - в GET пусто; то же с регистром идентификатора.
Отладку НТТР-сервиса веду в конфигураторе: точка останова в функции шаблона, повтор POST из Postman или consumer с одним сообщением. Логи веб-сервера (IIS или Apache) покажут, дошёл ли запрос до платформы, если внутри 1С обработчик не вызвался. Имеет смысл писать отказ валидации в журнал регистрации короткой записью с идентификатором из JSON - без паролей и без полного тела сообщения.
Конфликт двух одновременных POST с одним Идентификатором решается последней успешной транзакцией; если это критично, добавляют версию записи или блокировку в прикладном коде - в базовом эталоне достаточно идемпотентности по ключу.
Когда запись прошла цепочку RabbitMQ - HTTP POST - справочник, её должно быть видно и в режиме «1С» прослойки. Это проверка, что два сервиса не разъехались по правилам отбора и записи.
OData для чтения и репозиторий конфигурации как приложение
Дополнение к кастомному CRUD - чтение того же справочника через стандартный OData. В Go формирую URL к набору по метаданным публикации, например /odata/standard.odata/Catalog_Клиенты?$filter=DeletionMark eq false&$select=Ref_Key,Description,Идентификатор,Тип,Майл - точные имена полей смотрите в $metadata, в типовых публикациях наименование часто идёт как Description.
type ODataClient struct {
RefKey string `json:"Ref_Key"`
Description string `json:"Description"`
ExternalID string `json:"Идентификатор"`
ClientType string `json:"Тип"`
Email string `json:"Майл"`
DeletionMark bool `json:"DeletionMark"`
}
Теги структуры сверяю с ответом $metadata, а не копирую вслепую. OData удобен для отчётного чтения и сверки; изменения и приём из очереди отправляю в НТТР-сервис CRUD или приёмник, где действуют ваши правила валидации и отбор без пометки удаления.
Полный текст модулей НТТР-сервисов и прослойки вынесен в репозиторий Connector_1C_Enterprise на GitHub (автор dmitry-msk777). Я опираюсь на него как на эталон конфигурации и Го-кода, но контракт API и порядок публикации всё равно нужно повторить у себя на стенде 8.3.x.
Когда так делать нельзя
Ограничения: суточные файлы, документы вместо справочника, жёсткая ИБ без DMZ - тогда схему с открытым POST не применяют.
Если запись должна выполняться только через проведение документов с контролем остатков, прямой POST в справочник из очереди обойдёт прикладную логику - нужен другой контур (документ, обработка, регламентное задание).
Если политика ИБ запрещает внешний POST в базу без WAF, mTLS и централизованного журнала, публикацию НТТР-сервисов на открытый URL без этих мер лучше не использовать.
Вступайте в нашу телеграмм-группу Инфостарт
Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».
- 0% комиссии — оплата напрямую исполнителю;
- Исполнители любого масштаба — от отдельных специалистов до команд под проект;
- Прямой обмен контактами между заказчиком и исполнителем;
- Безопасная сделка — при необходимости;
- Рейтинги, кейсы и прозрачная система откликов.