Анкета участника M2M: отправка в НРД через ИШ пакетом #M2MPF
НРД открыл приём анкеты (M2MTransferParticipantForm) в пром-контуре. По формату НРД анкета — отдельный тип ЭД M2MPF: тег <package> = "#" + первые 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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XG3zNMMf2LMoDpXo22mwMX
This commit is contained in:
@@ -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 `<label>` + label + `<input name="` + name + `" value="` + html.EscapeString(val) + `" placeholder="` + ph + `"></label>`
|
||||
@@ -301,6 +334,11 @@ func participantFormRender(w http.ResponseWriter, d participantFormData, errs []
|
||||
}
|
||||
flashHTML = `<div class="` + cls + `">` + html.EscapeString(flash) + `</div>`
|
||||
}
|
||||
igwBtn := `<button type="submit" name="action" value="send_igw" class="primary">Отправить через шлюз (боевой ИШ)</button>`
|
||||
igwStatus := `<p class="note">Боевой ИШ: <b style="color:#2a7a2a">настроен</b> — анкету можно отправить через шлюз (#M2MPF).</p>`
|
||||
if !igwReady {
|
||||
igwStatus = `<p class="note">Боевой ИШ: <b style="color:#b00">не настроен</b> (раздел «НРД», профиль <code>prod-*</code>). Пока доступны «Скачать» и «Отправить письмом».</p>`
|
||||
}
|
||||
page := `<!doctype html><html lang="ru"><head><meta charset="utf-8"><title>Регистрация участника M2M</title>
|
||||
<style>body{font:14px/1.5 system-ui,Arial;margin:24px;max-width:900px;color:#111}
|
||||
h1{font-size:20px}label{display:block;margin:8px 0}label input{display:block;width:100%;padding:6px;margin-top:2px;box-sizing:border-box}
|
||||
@@ -314,9 +352,10 @@ pre{background:#f6f8fa;border:1px solid #ddd;border-radius:8px;padding:12px;over
|
||||
.note{color:#555;font-size:13px}</style></head><body>
|
||||
<h1>Регистрация участника сервиса M2M (МОСТ)</h1>
|
||||
<p class="note">Требование НРД при подключении: зарегистрировать организацию в справочнике участников M2M.
|
||||
Без регистрации сервис отбивает запросы кодом <b>M2M14</b>. Отправляется <b>обычным каналом</b> — письмом
|
||||
на <code>` + participantFormRecipient + `</code> с вложенной анкетой (не через M2M-шлюз). Поля предзаполнены
|
||||
из автоопределения DEPO — проверьте юридические реквизиты.</p>
|
||||
Без регистрации сервис отбивает запросы кодом <b>M2M14</b>. В боевом контуре анкета подаётся <b>через ИШ</b>
|
||||
пакетом <code>#M2MPF</code> (businessLine=MOST, подпись боевым контейнером на стороне ИШ); письмо на
|
||||
<code>` + participantFormRecipient + `</code> оставлено запасным каналом. Поля предзаполнены из
|
||||
автоопределения DEPO — проверьте юридические реквизиты.</p>
|
||||
` + flashHTML + vErr + `
|
||||
<form method="post">
|
||||
<fieldset><legend>Организация</legend>
|
||||
@@ -332,12 +371,13 @@ pre{background:#f6f8fa;border:1px solid #ddd;border-radius:8px;padding:12px;over
|
||||
<div class="row">` + inp("depo", "Код депозитария (DepositoryPlace)", d.DepoCode, "MC0000000000") + inp("broker", "Код брокера (BrokerPlace, если брокер)", d.BrokerCode, "оставьте пустым, если не брокер") + `</div>
|
||||
</fieldset>
|
||||
<fieldset><legend>Отправка</legend>
|
||||
` + inp("to", "Кому (НРД)", participantFormRecipient, participantFormRecipient) + `
|
||||
` + igwStatus + inp("to", "Кому (НРД), запасной канал", participantFormRecipient, participantFormRecipient) + `
|
||||
</fieldset>
|
||||
<div class="btns">
|
||||
<button type="submit" name="action" value="preview">Проверить</button>
|
||||
<button type="submit" name="action" value="download" formtarget="_blank">Скачать анкету (XML)</button>
|
||||
<button type="submit" name="action" value="send" class="primary">Отправить в НРД (письмом)</button>
|
||||
` + igwBtn + `
|
||||
<button type="submit" name="action" value="send">Отправить письмом (запасной канал)</button>
|
||||
</div>
|
||||
</form>
|
||||
<h3>Предпросмотр анкеты (windows-1251)</h3>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user