Анкета участника 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, иначе сервис МОСТ отбивает запросы кодом M2M14 «Код ЭДО НРД
|
||||||
// отправителя отсутствует в справочнике участников M2M». Здесь bj формирует
|
// отправителя отсутствует в справочнике участников M2M». Здесь bj формирует
|
||||||
// заполненную анкету (предзаполнение из автоопределения DEPO), даёт проверить
|
// заполненную анкету (предзаполнение из автоопределения DEPO), даёт проверить
|
||||||
// (валидация + показ XML в windows-1251) и предлагает отправить её в НРД обычным
|
// (валидация + показ XML в windows-1251) и отправляет её в НРД: в боевом контуре —
|
||||||
// каналом — письмом на M2MOST@nsd.ru с вложением (не через M2M-шлюз).
|
// через ИШ пакетом #M2MPF (businessLine=MOST, подпись боевым контейнером на стороне
|
||||||
|
// ИШ), либо запасным каналом — письмом на M2MOST@nsd.ru с вложением.
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
@@ -18,6 +20,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m"
|
"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"
|
"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»: форма, проверка,
|
// registerParticipantForm — страница «Регистрация участника M2M»: форма, проверка,
|
||||||
// скачивание анкеты, отправка письмом на M2MOST@nsd.ru.
|
// скачивание анкеты, отправка письмом на 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) {
|
mux.HandleFunc("/admin/setup/participant-form", func(w http.ResponseWriter, r *http.Request) {
|
||||||
d := participantFormPrefill(rc)
|
d := participantFormPrefill(rc)
|
||||||
action := ""
|
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)
|
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")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
inp := func(name, label, val, ph string) string {
|
inp := func(name, label, val, ph string) string {
|
||||||
return `<label>` + label + `<input name="` + name + `" value="` + html.EscapeString(val) + `" placeholder="` + ph + `"></label>`
|
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>`
|
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>
|
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}
|
<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}
|
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>
|
.note{color:#555;font-size:13px}</style></head><body>
|
||||||
<h1>Регистрация участника сервиса M2M (МОСТ)</h1>
|
<h1>Регистрация участника сервиса M2M (МОСТ)</h1>
|
||||||
<p class="note">Требование НРД при подключении: зарегистрировать организацию в справочнике участников M2M.
|
<p class="note">Требование НРД при подключении: зарегистрировать организацию в справочнике участников M2M.
|
||||||
Без регистрации сервис отбивает запросы кодом <b>M2M14</b>. Отправляется <b>обычным каналом</b> — письмом
|
Без регистрации сервис отбивает запросы кодом <b>M2M14</b>. В боевом контуре анкета подаётся <b>через ИШ</b>
|
||||||
на <code>` + participantFormRecipient + `</code> с вложенной анкетой (не через M2M-шлюз). Поля предзаполнены
|
пакетом <code>#M2MPF</code> (businessLine=MOST, подпись боевым контейнером на стороне ИШ); письмо на
|
||||||
из автоопределения DEPO — проверьте юридические реквизиты.</p>
|
<code>` + participantFormRecipient + `</code> оставлено запасным каналом. Поля предзаполнены из
|
||||||
|
автоопределения DEPO — проверьте юридические реквизиты.</p>
|
||||||
` + flashHTML + vErr + `
|
` + flashHTML + vErr + `
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<fieldset><legend>Организация</legend>
|
<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>
|
<div class="row">` + inp("depo", "Код депозитария (DepositoryPlace)", d.DepoCode, "MC0000000000") + inp("broker", "Код брокера (BrokerPlace, если брокер)", d.BrokerCode, "оставьте пустым, если не брокер") + `</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset><legend>Отправка</legend>
|
<fieldset><legend>Отправка</legend>
|
||||||
` + inp("to", "Кому (НРД)", participantFormRecipient, participantFormRecipient) + `
|
` + igwStatus + inp("to", "Кому (НРД), запасной канал", participantFormRecipient, participantFormRecipient) + `
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="btns">
|
<div class="btns">
|
||||||
<button type="submit" name="action" value="preview">Проверить</button>
|
<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="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>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<h3>Предпросмотр анкеты (windows-1251)</h3>
|
<h3>Предпросмотр анкеты (windows-1251)</h3>
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ func NewServer(cfg ServerConfig) (*Server, error) {
|
|||||||
registerSeedListing(mux, store)
|
registerSeedListing(mux, store)
|
||||||
registerHandbookRequest(mux, igwClient, igwChannel)
|
registerHandbookRequest(mux, igwClient, igwChannel)
|
||||||
registerChainTest(mux, rc, igwClient != nil) // «Проверить всю цепочку» (мастер, глава «Запуск»)
|
registerChainTest(mux, rc, igwClient != nil) // «Проверить всю цепочку» (мастер, глава «Запуск»)
|
||||||
registerParticipantForm(mux, rc) // анкета участника M2M (регистрация в НРД, participant_form.go)
|
registerParticipantForm(mux, rc, igwClient, igwChannel) // анкета участника M2M (регистрация в НРД, participant_form.go)
|
||||||
registerClientProfile(mux, rc) // профиль клиента: выгрузка эталона и предзаполнение (client_profile.go)
|
registerClientProfile(mux, rc) // профиль клиента: выгрузка эталона и предзаполнение (client_profile.go)
|
||||||
|
|
||||||
return &Server{
|
return &Server{
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const (
|
|||||||
PackageM2MTransferRequest PackageType = "#M2MTR"
|
PackageM2MTransferRequest PackageType = "#M2MTR"
|
||||||
PackageM2MTransferDecision PackageType = "#M2MTD"
|
PackageM2MTransferDecision PackageType = "#M2MTD"
|
||||||
PackageM2MTransferError PackageType = "#M2MER"
|
PackageM2MTransferError PackageType = "#M2MER"
|
||||||
|
PackageM2MParticipantForm PackageType = "#M2MPF"
|
||||||
PackageSUBBR PackageType = "SUBBR"
|
PackageSUBBR PackageType = "SUBBR"
|
||||||
PackageSUBER PackageType = "SUBER"
|
PackageSUBER PackageType = "SUBER"
|
||||||
PackageSUB16 PackageType = "SUB16"
|
PackageSUB16 PackageType = "SUB16"
|
||||||
@@ -40,7 +41,7 @@ func RouteToPackageType(kind MessageKind) (PackageType, error) {
|
|||||||
case KindHandbookRequest:
|
case KindHandbookRequest:
|
||||||
return PackageAssetsInvestment, nil
|
return PackageAssetsInvestment, nil
|
||||||
case KindParticipantForm:
|
case KindParticipantForm:
|
||||||
return PackageAssetsInvestment, nil
|
return PackageM2MParticipantForm, nil
|
||||||
}
|
}
|
||||||
return "", fmt.Errorf("nsdadapter: не задан PackageType для kind=%q", kind)
|
return "", fmt.Errorf("nsdadapter: не задан PackageType для kind=%q", kind)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user