feat: живой цикл M2M с НРД + мастер установки ключа на флешку
Инфраструктура M2M (живой обмен с НРД через ИШ): - обработка M2MTransferResponse: ERROR(M2Mxx) → заявка Отклонена, сохранение ответа; INFO → ждём Decision; идемпотентность поллера - fallback-корреляция ответов с нулевым GUID (M2M14/M2M17) по FIFO - сырой XML ответа НРД в карточке заявки (для пересылки в ТП) - тестовый пакет роботу приведён к эталону m2m_robot_samples (CostInfo=Yes, 4 бумаги, IsolationStatus, DocumentSeries=сценарий); override паспорта - редирект из теста сразу в карточку заявки Мастер установки ключа Валидаты на флешку (admin/setup/keywizard): - пошаговый: загрузка .7z+пароль → выбор флешки → запись → справочник сертификатов (CRL) → перезапуск+проверка ИШ → готово - привилегированный воркер (bj-keymedia) в host-namespace через файл-обмен, bj-server остаётся в песочнице - сохранение структуры профиля архива (spr<N>), перечисление съёмных USB Прочее: - пакет-доказательство для ТП НРД + форма регистрации участника M2M - эталонные образцы робота (DOC/m2m_robot_samples) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
package lkgateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/nsdadapter/igw"
|
||||
)
|
||||
|
||||
// pollIncoming периодически опрашивает ИШ на входящие пакеты от НРД
|
||||
// (M2MTransferDecision / Response) и применяет их через svc.ApplyDecision.
|
||||
// Замыкает цикл: bj-server отправил заявку → ИШ → НРД → робот ответил →
|
||||
// ИШ забрал ответ во входящие → этот поллер применяет Decision (статус
|
||||
// заявки переходит в confirmed/rejected, срабатывает callback в ЛК).
|
||||
//
|
||||
// Дедупликация по id обработанных пакетов: ИШ возвращает их повторно,
|
||||
// пока мы не подтвердим, поэтому держим множество уже обработанных.
|
||||
func (s *Server) pollIncoming(ctx context.Context) {
|
||||
const interval = 30 * time.Second
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
processed := make(map[int]bool)
|
||||
log.Printf("lk-gateway: поллер входящих ИШ запущен (канал %s, интервал %s)", s.igwChannel, interval)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
s.fetchAndApply(ctx, processed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fetchAndApply — один проход поллера: список входящих → для каждого нового
|
||||
// забираем тело, распаковываем, парсим Decision, применяем.
|
||||
func (s *Server) fetchAndApply(ctx context.Context, processed map[int]bool) {
|
||||
cctx, cancel := context.WithTimeout(ctx, 25*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Тип не указываем — ИШ вернёт оба (M2MTD + M2MER). Date=сегодня.
|
||||
pkgs, err := s.igwClient.ListIncoming(cctx, igw.ListFilter{
|
||||
Channel: s.igwChannel,
|
||||
Date: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("lk-gateway: поллер ListIncoming: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, p := range pkgs {
|
||||
if processed[p.ID] {
|
||||
continue
|
||||
}
|
||||
if err := s.applyIncoming(cctx, p); err != nil {
|
||||
log.Printf("lk-gateway: поллер пакет id=%d (%s): %v", p.ID, p.Type, err)
|
||||
continue // не помечаем обработанным — повторим в след. раз
|
||||
}
|
||||
processed[p.ID] = true
|
||||
log.Printf("lk-gateway: поллер применил входящий пакет id=%d тип=%s", p.ID, p.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// applyIncoming забирает тело пакета и применяет M2M-ответ к сделке.
|
||||
// Среди входящих от НРД много служебных пакетов (квитанции ЭДО типа C/Z,
|
||||
// конверты) — они не M2M-документы и пропускаются. Реальные ответы —
|
||||
// M2MTransferDecision (решение принимающей стороны) и M2MTransferResponse
|
||||
// (ответ сервиса МОСТ, в т.ч. ошибки M2Mxx).
|
||||
func (s *Server) applyIncoming(ctx context.Context, p igw.Package) error {
|
||||
zipBytes, err := s.igwClient.GetPackage(ctx, p.ID)
|
||||
if err != nil {
|
||||
return err // сетевая ошибка — повторим в след. раз
|
||||
}
|
||||
unpacked, err := igw.UnpackPackage(zipBytes)
|
||||
if err != nil {
|
||||
// Нет основного .xml — служебный пакет (квитанция/конверт ЭДО).
|
||||
// Не ошибка: помечаем обработанным, чтобы не повторять.
|
||||
log.Printf("lk-gateway: поллер пакет id=%d (%s) — служебный (квитанция/конверт), пропуск", p.ID, p.Type)
|
||||
return nil
|
||||
}
|
||||
doc := string(unpacked.DocXML)
|
||||
switch {
|
||||
case strings.Contains(doc, "M2MTransferDecision"):
|
||||
decision, err := igw.ParseDecision(unpacked.DocXML)
|
||||
if err != nil {
|
||||
log.Printf("lk-gateway: поллер Decision id=%d: разбор: %v", p.ID, err)
|
||||
return nil
|
||||
}
|
||||
return s.svc.ApplyDecision(ctx, decision)
|
||||
case strings.Contains(doc, "M2MTransferResponse"):
|
||||
resp, err := igw.ParseResponse(unpacked.DocXML)
|
||||
if err != nil {
|
||||
log.Printf("lk-gateway: поллер Response id=%d: разбор: %v", p.ID, err)
|
||||
return nil
|
||||
}
|
||||
// Ответ сервиса МОСТ: статус + код (M2Mxx). Применяем к сделке:
|
||||
// INFO — приём в обработку (статус не меняется), ERROR — отказ сервиса
|
||||
// (напр. M2M14 — отправитель не в справочнике), сделка → Отклонена.
|
||||
// Ответ сохраняется в сделке и виден в карточке заявки.
|
||||
var codes string
|
||||
for _, rr := range resp.Responses {
|
||||
codes += string(rr.Code) + " "
|
||||
}
|
||||
log.Printf("lk-gateway: поллер M2MTransferResponse id=%d: статус=%s коды=[%s] GUID=%s",
|
||||
p.ID, resp.StatusCode, strings.TrimSpace(codes), resp.GUID)
|
||||
if err := s.svc.ApplyServiceResponse(ctx, resp, unpacked.DocXML); err != nil {
|
||||
// Сделка может быть не найдена (ответ на чужой/старый GUID) —
|
||||
// логируем, но помечаем обработанным, чтобы не зациклиться.
|
||||
log.Printf("lk-gateway: поллер ApplyServiceResponse id=%d GUID=%s: %v", p.ID, resp.GUID, err)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
log.Printf("lk-gateway: поллер пакет id=%d (%s) — неизвестный M2M-документ, пропуск", p.ID, p.Type)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user