feat(nsd-adapter): REST-клиент ИШ НРД + маршрутизация типов пакетов

- internal/nsdadapter/igw/client.go: REST-клиент ИШ (SendPackage, GetStatus, ListIncoming) с base64-JSON, ретраями на 5xx, 4xx без ретраев
- internal/nsdadapter/router.go: маршрутизация MessageKind -> PackageType ЭДО (#M2MTR, #M2MTD, #M2MER, SUBBR/SUBER/SUB16, Assets_investment)
- internal/nsdadapter/sender.go: реализация m2mcore.NSDSender (Send/SendDecision) через REST ИШ, сериализация Request/Decision в windows-1251
- internal/nsdadapter/config.go: профили guest/test3/prod × gost/rsa (URL ИШ, канал, контейнер ключа, retry)
- internal/nsdadapter/onyx/onyx.go: скелет резервного канала WS ONYX (ждёт PR-6 crypto-service для подписи)
- cmd/nsd-adapter/main.go: HTTP /healthz + фоновый поллер входящих по типам ЭДО; idle-режим без BJ_NSD_PROFILE

make ci зелёный. Без внешних Go-зависимостей.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
fontvielle
2026-05-14 00:55:20 +03:00
parent 9e6e95f431
commit a8cdeeb838
10 changed files with 803 additions and 1 deletions
+73
View File
@@ -0,0 +1,73 @@
// Package nsdadapter реализует транспорт к НРД:
// REST-клиент Интеграционного шлюза (основной канал) и резервный
// WS ONYX. На M1 — REST-клиент + маршрутизация типов пакетов;
// onyx остаётся скелетом до получения КриптоПро JCP (PR-6).
package nsdadapter
import (
"fmt"
"time"
)
// Profile — преднастроенный набор параметров подключения к ИШ НРД.
type Profile struct {
Name string
IGWBaseURL string
Channel string
KeyContainer string
RequestTimeout time.Duration
RetryMax int
RetryBackoff time.Duration
}
// Известные профили: пары среда (guest/test3/prod) x алгоритм (gost/rsa).
var profiles = map[string]Profile{
"guest-gost": {
Name: "guest-gost", IGWBaseURL: "http://localhost:8080", Channel: "GUEST",
KeyContainer: "GUEST_GOST_CONTAINER",
RequestTimeout: 30 * time.Second, RetryMax: 3, RetryBackoff: time.Second,
},
"guest-rsa": {
Name: "guest-rsa", IGWBaseURL: "http://localhost:8080", Channel: "GUEST",
KeyContainer: "GUEST_RSA_CONTAINER",
RequestTimeout: 30 * time.Second, RetryMax: 3, RetryBackoff: time.Second,
},
"test3-gost": {
Name: "test3-gost", IGWBaseURL: "http://localhost:8080", Channel: "TEST3",
KeyContainer: "TEST3_GOST_CONTAINER",
RequestTimeout: 30 * time.Second, RetryMax: 3, RetryBackoff: time.Second,
},
"test3-rsa": {
Name: "test3-rsa", IGWBaseURL: "http://localhost:8080", Channel: "TEST3",
KeyContainer: "TEST3_RSA_CONTAINER",
RequestTimeout: 30 * time.Second, RetryMax: 3, RetryBackoff: time.Second,
},
"prod-gost": {
Name: "prod-gost", IGWBaseURL: "http://localhost:8080", Channel: "PROD",
KeyContainer: "PROD_GOST_CONTAINER",
RequestTimeout: 60 * time.Second, RetryMax: 5, RetryBackoff: 2 * time.Second,
},
"prod-rsa": {
Name: "prod-rsa", IGWBaseURL: "http://localhost:8080", Channel: "PROD",
KeyContainer: "PROD_RSA_CONTAINER",
RequestTimeout: 60 * time.Second, RetryMax: 5, RetryBackoff: 2 * time.Second,
},
}
// LookupProfile находит профиль по имени.
func LookupProfile(name string) (Profile, error) {
p, ok := profiles[name]
if !ok {
return Profile{}, fmt.Errorf("nsdadapter: неизвестный профиль %q", name)
}
return p, nil
}
// AvailableProfiles возвращает имена всех известных профилей.
func AvailableProfiles() []string {
out := make([]string, 0, len(profiles))
for name := range profiles {
out = append(out, name)
}
return out
}