From af6027741127fd03079d46cf84fdc571f2559880 Mon Sep 17 00:00:00 2001
From: fontvielle
Date: Fri, 16 Oct 2026 13:07:40 +0300
Subject: [PATCH] =?UTF-8?q?=D0=90=D0=BD=D0=BA=D0=B5=D1=82=D0=B0=20=D1=83?=
=?UTF-8?q?=D1=87=D0=B0=D1=81=D1=82=D0=BD=D0=B8=D0=BA=D0=B0=20M2M:=20?=
=?UTF-8?q?=D0=BE=D1=82=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B0=20=D0=B2=20?=
=?UTF-8?q?=D0=9D=D0=A0=D0=94=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=98?=
=?UTF-8?q?=D0=A8=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82=D0=BE=D0=BC=20#M2MPF?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
НРД открыл приём анкеты (M2MTransferParticipantForm) в пром-контуре.
По формату НРД анкета — отдельный тип ЭД M2MPF: тег = "#" +
первые 5 символов имени типа ЭД → "#M2MPF", businessLine=MOST (как и
остальные M2M-сообщения; на ИШ <107.0.0.904 businessLine опускается).
Подпись — боевым ГОСТ-контейнером на стороне ИШ.
- nsdadapter/router.go: тип PackageM2MParticipantForm "#M2MPF";
KindParticipantForm теперь маппится на него (был ошибочно
Assets_investment).
- lkgateway/participant_form.go: действие send_igw — упаковка анкеты
через igw.PackXML(..., "#M2MPF") и отправка igwClient.SendPackage в
канал профиля (в боевом контуре — prod-gost). Кнопка «Отправить через
шлюз (боевой ИШ)»; письмо на M2MOST@nsd.ru оставлено запасным каналом.
Статус готовности ИШ на странице.
- lkgateway/server.go: проброс igwClient/igwChannel в registerParticipantForm.
Образец — sendHandbookRequest (#M2MHR) через тот же ИШ.
Co-Authored-By: Claude Opus 4.8
Claude-Session: https://claude.ai/code/session_01XG3zNMMf2LMoDpXo22mwMX
---
internal/lkgateway/participant_form.go | 60 +++++++++++++++++++++-----
internal/lkgateway/server.go | 12 +++---
internal/nsdadapter/router.go | 3 +-
3 files changed, 58 insertions(+), 17 deletions(-)
diff --git a/internal/lkgateway/participant_form.go b/internal/lkgateway/participant_form.go
index b0a95bc..73d73cf 100644
--- a/internal/lkgateway/participant_form.go
+++ b/internal/lkgateway/participant_form.go
@@ -5,10 +5,12 @@ package lkgateway
// участников M2M, иначе сервис МОСТ отбивает запросы кодом M2M14 «Код ЭДО НРД
// отправителя отсутствует в справочнике участников M2M». Здесь bj формирует
// заполненную анкету (предзаполнение из автоопределения DEPO), даёт проверить
-// (валидация + показ XML в windows-1251) и предлагает отправить её в НРД обычным
-// каналом — письмом на M2MOST@nsd.ru с вложением (не через M2M-шлюз).
+// (валидация + показ XML в windows-1251) и отправляет её в НРД: в боевом контуре —
+// через ИШ пакетом #M2MPF (businessLine=MOST, подпись боевым контейнером на стороне
+// ИШ), либо запасным каналом — письмом на M2MOST@nsd.ru с вложением.
import (
+ "context"
"encoding/base64"
"fmt"
"html"
@@ -18,6 +20,8 @@ import (
"time"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m"
+ "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter"
+ "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter/igw"
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdxml"
)
@@ -215,7 +219,7 @@ func sendSMTPAttach(s SMTPSettings, subject, body, filename string, attach []byt
// registerParticipantForm — страница «Регистрация участника M2M»: форма, проверка,
// скачивание анкеты, отправка письмом на M2MOST@nsd.ru.
-func registerParticipantForm(mux *http.ServeMux, rc *RuntimeConfig) {
+func registerParticipantForm(mux *http.ServeMux, rc *RuntimeConfig, igwClient *igw.Client, igwChannel string) {
mux.HandleFunc("/admin/setup/participant-form", func(w http.ResponseWriter, r *http.Request) {
d := participantFormPrefill(rc)
action := ""
@@ -277,12 +281,41 @@ func registerParticipantForm(mux *http.ServeMux, rc *RuntimeConfig) {
}
}
+ if action == "send_igw" {
+ switch {
+ case len(errs) > 0:
+ flash, flashKind = "Анкета не прошла проверку — исправьте поля ниже.", "err"
+ case igwClient == nil || igwChannel == "":
+ flash, flashKind = "Боевой ИШ не настроен (раздел «НРД», профиль prod-*). Отправка через шлюз недоступна — переключитесь в боевой контур.", "err"
+ default:
+ body, encErr := participantFormBytes(d)
+ if encErr != nil {
+ flash, flashKind = "кодировка windows-1251: "+encErr.Error(), "err"
+ break
+ }
+ pkg, _ := nsdadapter.RouteToPackageType(nsdadapter.KindParticipantForm)
+ zipBody, zErr := igw.PackXML(body, "M2MTransferParticipantForm.xml", string(pkg))
+ if zErr != nil {
+ flash, flashKind = "упаковка пакета: "+zErr.Error(), "err"
+ break
+ }
+ ctx, cancel := context.WithTimeout(r.Context(), 20*time.Second)
+ id, sErr := igwClient.SendPackage(ctx, igwChannel, string(pkg), zipBody)
+ cancel()
+ if sErr != nil {
+ flash, flashKind = "отправка через ИШ не удалась: "+sErr.Error(), "err"
+ break
+ }
+ flash, flashKind = "Анкета отправлена через ИШ ("+string(pkg)+", пакет id="+id+"). Ждём подтверждения регистрации от НРД.", "ok"
+ }
+ }
+
xmlPreview := participantFormXML(d)
- participantFormRender(w, d, errs, action, flash, flashKind, xmlPreview)
+ participantFormRender(w, d, errs, action, flash, flashKind, xmlPreview, igwClient != nil && igwChannel != "")
})
}
-func participantFormRender(w http.ResponseWriter, d participantFormData, errs []string, action, flash, flashKind, xmlPreview string) {
+func participantFormRender(w http.ResponseWriter, d participantFormData, errs []string, action, flash, flashKind, xmlPreview string, igwReady bool) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
inp := func(name, label, val, ph string) string {
return ``
@@ -301,6 +334,11 @@ func participantFormRender(w http.ResponseWriter, d participantFormData, errs []
}
flashHTML = `
` + html.EscapeString(flash) + `
`
}
+ igwBtn := ``
+ igwStatus := `
Боевой ИШ: настроен — анкету можно отправить через шлюз (#M2MPF).
`
+ if !igwReady {
+ igwStatus = `
Боевой ИШ: не настроен (раздел «НРД», профиль prod-*). Пока доступны «Скачать» и «Отправить письмом».
`
+ }
page := `Регистрация участника M2M
Регистрация участника сервиса M2M (МОСТ)
Требование НРД при подключении: зарегистрировать организацию в справочнике участников M2M.
-Без регистрации сервис отбивает запросы кодом M2M14. Отправляется обычным каналом — письмом
-на ` + participantFormRecipient + ` с вложенной анкетой (не через M2M-шлюз). Поля предзаполнены
-из автоопределения DEPO — проверьте юридические реквизиты.
+Без регистрации сервис отбивает запросы кодом M2M14. В боевом контуре анкета подаётся через ИШ
+пакетом #M2MPF (businessLine=MOST, подпись боевым контейнером на стороне ИШ); письмо на
+` + participantFormRecipient + ` оставлено запасным каналом. Поля предзаполнены из
+автоопределения DEPO — проверьте юридические реквизиты.
` + flashHTML + vErr + `
Предпросмотр анкеты (windows-1251)
diff --git a/internal/lkgateway/server.go b/internal/lkgateway/server.go
index 913db44..803890c 100644
--- a/internal/lkgateway/server.go
+++ b/internal/lkgateway/server.go
@@ -232,9 +232,9 @@ func NewServer(cfg ServerConfig) (*Server, error) {
registerSetCallback(mux, svc, rc)
registerSeedListing(mux, store)
registerHandbookRequest(mux, igwClient, igwChannel)
- registerChainTest(mux, rc, igwClient != nil) // «Проверить всю цепочку» (мастер, глава «Запуск»)
- registerParticipantForm(mux, rc) // анкета участника M2M (регистрация в НРД, participant_form.go)
- registerClientProfile(mux, rc) // профиль клиента: выгрузка эталона и предзаполнение (client_profile.go)
+ registerChainTest(mux, rc, igwClient != nil) // «Проверить всю цепочку» (мастер, глава «Запуск»)
+ registerParticipantForm(mux, rc, igwClient, igwChannel) // анкета участника M2M (регистрация в НРД, participant_form.go)
+ registerClientProfile(mux, rc) // профиль клиента: выгрузка эталона и предзаполнение (client_profile.go)
return &Server{
cfg: cfg,
@@ -305,10 +305,10 @@ func (s *Server) Run(ctx context.Context) error {
// Все фоновые горутины — под supervise: паника ловится recover(), пишется
// в лог и в ленту, горутина перезапускается. Один сбой не роняет сервис.
go superviseLoop(ctx, "consume-decisions", s.rc, s.consumeDecisions)
- go superviseLoop(ctx, "igw-watch", s.rc, s.igwWatchLoop) // сторож ИШ НРД: оповещения и автоперезапуск
- go superviseLoop(ctx, "units-watch", s.rc, s.unitsWatchLoop) // службы модулей и ключей Валидаты: письмо администратору
+ go superviseLoop(ctx, "igw-watch", s.rc, s.igwWatchLoop) // сторож ИШ НРД: оповещения и автоперезапуск
+ go superviseLoop(ctx, "units-watch", s.rc, s.unitsWatchLoop) // службы модулей и ключей Валидаты: письмо администратору
go superviseLoop(ctx, "igate-share", s.rc, s.igateShareSyncLoop) // папки шлюза «через домен»: список доступа = группы и сотрудники bj
- go handbookLoop(ctx, s.rc) // справочник участников M2M с сервера лицензий (для установок без шлюза НРД)
+ go handbookLoop(ctx, s.rc) // справочник участников M2M с сервера лицензий (для установок без шлюза НРД)
// Поллер входящих от НРД (только в реальном режиме ИШ): забирает
// ответы робота/контрагента и применяет их через ApplyDecision.
diff --git a/internal/nsdadapter/router.go b/internal/nsdadapter/router.go
index 8420868..7f4fdff 100644
--- a/internal/nsdadapter/router.go
+++ b/internal/nsdadapter/router.go
@@ -10,6 +10,7 @@ const (
PackageM2MTransferRequest PackageType = "#M2MTR"
PackageM2MTransferDecision PackageType = "#M2MTD"
PackageM2MTransferError PackageType = "#M2MER"
+ PackageM2MParticipantForm PackageType = "#M2MPF"
PackageSUBBR PackageType = "SUBBR"
PackageSUBER PackageType = "SUBER"
PackageSUB16 PackageType = "SUB16"
@@ -40,7 +41,7 @@ func RouteToPackageType(kind MessageKind) (PackageType, error) {
case KindHandbookRequest:
return PackageAssetsInvestment, nil
case KindParticipantForm:
- return PackageAssetsInvestment, nil
+ return PackageM2MParticipantForm, nil
}
return "", fmt.Errorf("nsdadapter: не задан PackageType для kind=%q", kind)
}