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:
@@ -9,9 +9,23 @@ import (
|
||||
|
||||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m"
|
||||
"git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2mcore"
|
||||
"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/nsdadapter/mock"
|
||||
)
|
||||
|
||||
// igwClientAdapter адаптирует igw.Client под узкий nsdadapter.IGWClient:
|
||||
// разворачивает (channel, since, type) в igw.ListFilter.
|
||||
type igwClientAdapter struct{ c *igw.Client }
|
||||
|
||||
func (a igwClientAdapter) SendPackage(ctx context.Context, channel, packageType string, body []byte) (string, error) {
|
||||
return a.c.SendPackage(ctx, channel, packageType, body)
|
||||
}
|
||||
|
||||
func (a igwClientAdapter) ListIncoming(ctx context.Context, channel string, since time.Time, packageType string) ([]igw.Package, error) {
|
||||
return a.c.ListIncoming(ctx, igw.ListFilter{Channel: channel, Date: since, Type: packageType})
|
||||
}
|
||||
|
||||
// ServerConfig — конфигурация HTTP-сервера lk-gateway.
|
||||
type ServerConfig struct {
|
||||
Addr string
|
||||
@@ -31,6 +45,12 @@ type Server struct {
|
||||
rc *RuntimeConfig
|
||||
mux *http.ServeMux
|
||||
server *http.Server
|
||||
|
||||
// igwClient/igwChannel заполнены только в реальном режиме (ИШ настроен).
|
||||
// На них работает поллер входящих pollIncoming — забирает ответы НРД
|
||||
// (M2MTransferDecision/Response) и применяет через svc.ApplyDecision.
|
||||
igwClient *igw.Client
|
||||
igwChannel string
|
||||
}
|
||||
|
||||
// NewServer собирает Server с репозиторием, mock NSDSender, SeedStore
|
||||
@@ -45,13 +65,44 @@ func NewServer(cfg ServerConfig) (*Server, error) {
|
||||
if cfg.MockDecisionDelay > 0 {
|
||||
mockCfg.DecisionDelay = cfg.MockDecisionDelay
|
||||
}
|
||||
sender := mock.NewSender(mockCfg)
|
||||
|
||||
rc, err := NewRuntimeConfig(cfg.SetupPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Выбор NSD-сендера: если в runtime-конфиге задан профиль ИШ и URL —
|
||||
// используем реальный nsdadapter поверх REST ИШ; иначе mock-эмуляция.
|
||||
// mockSender остаётся не-nil только в mock-режиме — на нём висит
|
||||
// consumeDecisions (реальные Decision приходят поллером входящих ИШ).
|
||||
var sender m2mcore.NSDSender
|
||||
var mockSender *mock.Sender
|
||||
var igwClient *igw.Client
|
||||
var igwChannel string
|
||||
{
|
||||
s := rc.Snapshot()
|
||||
if s.NSD.IGWBaseURL != "" && s.NSD.Profile != "" {
|
||||
prof, perr := nsdadapter.LookupProfile(s.NSD.Profile)
|
||||
if perr != nil {
|
||||
log.Printf("lk-gateway: профиль ИШ %q неизвестен (%v) — fallback mock", s.NSD.Profile, perr)
|
||||
} else {
|
||||
prof.IGWBaseURL = s.NSD.IGWBaseURL // override URL из setup.json
|
||||
cl := igw.NewClient(s.NSD.IGWBaseURL)
|
||||
sender = nsdadapter.NewSender(prof, igwClientAdapter{c: cl})
|
||||
igwClient = cl
|
||||
// Канал ИШ резолвится по составному коду <канал>+<депонент>.
|
||||
igwChannel = prof.Channel + string(cfg.DefaultSender)
|
||||
log.Printf("lk-gateway: реальный ИШ-адаптер — профиль %s, канал %s, ИШ %s",
|
||||
prof.Name, igwChannel, s.NSD.IGWBaseURL)
|
||||
}
|
||||
}
|
||||
if sender == nil {
|
||||
mockSender = mock.NewSender(mockCfg)
|
||||
sender = mockSender
|
||||
log.Printf("lk-gateway: NSD mock-режим (Decision через эмуляцию)")
|
||||
}
|
||||
}
|
||||
|
||||
// Repository: pgx если DSN указан, иначе in-memory.
|
||||
var repo m2mcore.Repository = m2mcore.NewMemoryRepository()
|
||||
if dsn := rc.Snapshot().Postgres.DSN; dsn != "" {
|
||||
@@ -115,12 +166,14 @@ func NewServer(cfg ServerConfig) (*Server, error) {
|
||||
registerSeedListing(mux, store)
|
||||
|
||||
return &Server{
|
||||
cfg: cfg,
|
||||
svc: svc,
|
||||
mock: sender,
|
||||
store: store,
|
||||
rc: rc,
|
||||
mux: mux,
|
||||
cfg: cfg,
|
||||
svc: svc,
|
||||
mock: mockSender,
|
||||
store: store,
|
||||
rc: rc,
|
||||
mux: mux,
|
||||
igwClient: igwClient,
|
||||
igwChannel: igwChannel,
|
||||
server: &http.Server{
|
||||
Addr: cfg.Addr,
|
||||
Handler: mux,
|
||||
@@ -159,6 +212,15 @@ func (s *Server) Mux() http.Handler { return s.mux }
|
||||
func (s *Server) Run(ctx context.Context) error {
|
||||
go s.consumeDecisions(ctx)
|
||||
|
||||
// Поллер входящих от НРД (только в реальном режиме ИШ): забирает
|
||||
// ответы робота/контрагента и применяет их через ApplyDecision.
|
||||
if s.igwClient != nil && s.igwChannel != "" {
|
||||
go s.pollIncoming(ctx)
|
||||
}
|
||||
|
||||
// Фоновая авто-проверка обновлений из артефактории (если включена).
|
||||
go NewUpdater(s.rc).updateLoop(ctx)
|
||||
|
||||
// Авто-обновление сертификатов УЦ раз в сутки (если оператор включил).
|
||||
stopCACerts := StartCACertsAutoUpdater(s.rc)
|
||||
defer stopCACerts()
|
||||
@@ -193,6 +255,12 @@ func (s *Server) Run(ctx context.Context) error {
|
||||
|
||||
// consumeDecisions слушает Decisions от mock и обновляет соответствующие сделки.
|
||||
func (s *Server) consumeDecisions(ctx context.Context) {
|
||||
if s.mock == nil {
|
||||
// Реальный ИШ-режим: Decision приходят не из mock-канала, а через
|
||||
// поллер входящих пакетов ИШ (отдельный механизм). Здесь нечего слушать.
|
||||
<-ctx.Done()
|
||||
return
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
|
||||
Reference in New Issue
Block a user