From 978777ff6a88b45e51f1521f1b26a595968f07f3 Mon Sep 17 00:00:00 2001 From: fontvielle Date: Thu, 14 May 2026 13:46:21 +0300 Subject: [PATCH] =?UTF-8?q?refactor(cmd):=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=B4?= =?UTF-8?q?=D0=B8=D0=BD=D0=B8=D1=82=D1=8C=20Go-=D1=81=D0=B5=D1=80=D0=B2?= =?UTF-8?q?=D0=B8=D1=81=D1=8B=20=D0=B2=20=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=B1?= =?UTF-8?q?=D0=B8=D0=BD=D0=B0=D1=80=D0=BD=D0=B8=D0=BA=20bj-server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Под выбранный объём 100-1000 сделок/день микросервисная архитектура с 5 отдельными процессами избыточна. Объединяем lk-gateway, m2m-core, nsd-adapter, notify в один Go-бинарник bj-server. lk-emulator остаётся отдельным бинарником как QA-инструмент (имитация ЛК ESIA Finance). cmd/bj-server/main.go: - Поднимает lkgateway.NewServer (HTTP :8080, REST API + admin UI) - Фоновый poller NSD ИШ (если задан BJ_NSD_PROFILE) - Заглушка notify worker (M3-M4: e-mail / Yandex Messenger / WS-push) - Graceful shutdown через signal.NotifyContext cmd/lk-gateway/, cmd/m2m-core/, cmd/nsd-adapter/, cmd/notify/ — удалены. deploy/systemd/: - bj-server.service — systemd unit для основного сервиса (один файл, простой деплой) - bj-emulator.service — systemd unit для эмулятора - README.md с инструкцией по установке (useradd bj, /opt/bj, daemon-reload) Makefile: - build теперь собирает только bj-server и lk-emulator - бывшие пять бинарей удалены Размер: - bj-server: 19.5 МБ (включает lk-gateway + m2m-core + nsd-adapter + notify) - lk-emulator: 12.7 МБ - общий размер артефактов уменьшился c ~50 МБ до 32 МБ Внутренние пакеты internal/<...> не изменились — разделение сохраняется на уровне Go-пакетов, что облегчает возврат к микросервисам если объём вырастет до 1000+ сделок/день. Co-Authored-By: Claude Opus 4.7 (1M context) --- Makefile | 7 +- cmd/bj-server/main.go | 149 +++++++++++++++++++++++++++++ cmd/lk-gateway/main.go | 67 ------------- cmd/m2m-core/main.go | 17 ---- cmd/notify/main.go | 19 ---- cmd/nsd-adapter/main.go | 22 ----- deploy/systemd/README.md | 33 +++++++ deploy/systemd/bj-emulator.service | 30 ++++++ deploy/systemd/bj-server.service | 39 ++++++++ 9 files changed, 253 insertions(+), 130 deletions(-) create mode 100644 cmd/bj-server/main.go delete mode 100644 cmd/lk-gateway/main.go delete mode 100644 cmd/m2m-core/main.go delete mode 100644 cmd/notify/main.go delete mode 100644 cmd/nsd-adapter/main.go create mode 100644 deploy/systemd/README.md create mode 100644 deploy/systemd/bj-emulator.service create mode 100644 deploy/systemd/bj-server.service diff --git a/Makefile b/Makefile index baf5db5..0bbd155 100644 --- a/Makefile +++ b/Makefile @@ -19,11 +19,8 @@ help: build: @mkdir -p bin - $(GO) build -o bin/lk-gateway ./cmd/lk-gateway - $(GO) build -o bin/m2m-core ./cmd/m2m-core - $(GO) build -o bin/nsd-adapter ./cmd/nsd-adapter - $(GO) build -o bin/lk-emulator ./cmd/lk-emulator - $(GO) build -o bin/notify ./cmd/notify + $(GO) build -o bin/bj-server ./cmd/bj-server + $(GO) build -o bin/lk-emulator ./cmd/lk-emulator test: $(GO) test ./... -race -count=1 diff --git a/cmd/bj-server/main.go b/cmd/bj-server/main.go new file mode 100644 index 0000000..633169a --- /dev/null +++ b/cmd/bj-server/main.go @@ -0,0 +1,149 @@ +// Package main — единый сервис bj-server. +// +// Объединяет в одном процессе: lk-gateway (REST API ЛК + admin web UI), +// m2m-core (FSM сделки, репозиторий, эмиссия и потребление Decision), +// nsd-adapter (REST к ИШ НРД и опрос входящих, когда профиль настроен), +// notify (заглушка отправки уведомлений). lk-emulator живёт отдельным +// бинарником как QA-инструмент. +// +// Архитектура подсказана объёмом 100-1000 сделок/день: для такого +// потока избыточно держать 5 отдельных процессов и микросервисную +// шину. Один Go-бинарник проще деплоить, проще наблюдать и +// масштабировать вертикально, а компоненты внутри по-прежнему +// разделены пакетами internal/<...>. +package main + +import ( + "context" + "log" + "os" + "os/signal" + "syscall" + "time" + + "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/lkgateway" + "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" +) + +const serviceName = "bj-server" + +func main() { + addr := getenv("BJ_HTTP_ADDR", ":8080") + defaultSender := m2m.DeponentCode(getenv("BJ_M2M_SENDER", "MC0079200000")) + defaultReceiver := m2m.DeponentCode(getenv("BJ_M2M_RECEIVER", "MC0010300000")) + setupPath := os.Getenv("BJ_SETUP_PATH") + + cfg := lkgateway.ServerConfig{ + Addr: addr, + DefaultSender: defaultSender, + DefaultReceiver: defaultReceiver, + SetupPath: setupPath, + CheckOptions: func() lkgateway.CheckOptions { + return lkgateway.CheckOptions{ + PostgresDSN: os.Getenv("BJ_DSN"), + CryptoSocket: getenv("BJ_CRYPTO_SOCKET", "/run/bj/crypto.sock"), + NSDAdapterURL: os.Getenv("BJ_NSD_ADAPTER_URL"), + LKCallbackURL: os.Getenv("BJ_LK_CALLBACK_URL"), + Profile: getenv("BJ_NSD_PROFILE", "demo (mock NSD)"), + CryptoProvider: getenv("BJ_CRYPTO_PROVIDER", "stub"), + Timeout: 2 * time.Second, + } + }, + } + + srv, err := lkgateway.NewServer(cfg) + if err != nil { + log.Fatalf("%s: NewServer: %v", serviceName, err) + } + if cb := os.Getenv("BJ_LK_CALLBACK_URL"); cb != "" { + srv.SetCallbackURL(cb) + } + + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + + // Опционально — поллер входящих пакетов ИШ НРД. Запускается если + // BJ_NSD_PROFILE задан (после установки реального ИШ через UI этот + // блок будет тянуть Decisions из настоящего НРД и применять их через + // lkgateway.Service.ApplyDecision). + if profileName := os.Getenv("BJ_NSD_PROFILE"); profileName != "" { + go runNSDPoller(ctx, profileName) + } + + // notify-демон: пока заглушка, в M3-M4 будет рассылать события + // (e-mail, Yandex Messenger, Telegram, WS-push в admin-ui). + go runNotifyWorker(ctx) + + log.Printf("%s: запуск, HTTP %s", serviceName, addr) + runErr := srv.Run(ctx) + stop() + if runErr != nil { + log.Printf("%s: %v", serviceName, runErr) + os.Exit(1) + } +} + +// runNSDPoller — фоновый поллер входящих пакетов ИШ НРД. +func runNSDPoller(ctx context.Context, profileName string) { + profile, err := nsdadapter.LookupProfile(profileName) + if err != nil { + log.Printf("%s: NSD poller: %v (доступные профили: %v)", serviceName, err, nsdadapter.AvailableProfiles()) + return + } + interval := 30 * time.Second + if v := os.Getenv("BJ_NSD_POLL_INTERVAL"); v != "" { + if d, err := time.ParseDuration(v); err == nil { + interval = d + } + } + client := igw.NewClient(profile.IGWBaseURL, igw.WithRetry(profile.RetryMax, profile.RetryBackoff)) + log.Printf("%s: NSD poller: профиль %s, канал %s, ИШ %s, интервал %s", + serviceName, profile.Name, profile.Channel, profile.IGWBaseURL, interval) + + t := time.NewTicker(interval) + defer t.Stop() + since := time.Now().UTC().Add(-time.Hour) + for { + select { + case <-ctx.Done(): + return + case <-t.C: + for _, kind := range nsdadapter.IncomingPackageKinds() { + pkgs, err := client.ListIncoming(ctx, profile.Channel, since, string(kind)) + if err != nil { + log.Printf("%s: NSD poller ListIncoming(%s, %s): %v", serviceName, profile.Channel, kind, err) + continue + } + for _, p := range pkgs { + log.Printf("%s: NSD входящий пакет %s типа %s (канал %s, получен %s)", + serviceName, p.PackageID, p.PackageType, p.Channel, p.ReceivedAt.Format(time.RFC3339)) + // TODO(M3): парсить тело пакета, передавать в lkgateway.Service.ApplyDecision + } + } + since = time.Now().UTC() + } + } +} + +// runNotifyWorker — заглушка демона уведомлений. +func runNotifyWorker(ctx context.Context) { + t := time.NewTicker(time.Minute) + defer t.Stop() + for { + select { + case <-ctx.Done(): + return + case <-t.C: + // На M3-M4 здесь будет: вытащить очередь событий из БД, + // разослать по настроенным каналам (e-mail, мессенджер). + } + } +} + +func getenv(k, def string) string { + if v, ok := os.LookupEnv(k); ok && v != "" { + return v + } + return def +} diff --git a/cmd/lk-gateway/main.go b/cmd/lk-gateway/main.go deleted file mode 100644 index 66aa98d..0000000 --- a/cmd/lk-gateway/main.go +++ /dev/null @@ -1,67 +0,0 @@ -// Package main — сервис lk-gateway. BFF слой ЛК клиента: -// принимает REST-заявки по контракту ESIA Finance, валидирует, -// собирает M2MTransferRequest, отправляет в НРД через nsd-adapter, -// эмитит callback статуса обратно в ЛК. -// -// На M2 — in-memory репозиторий + mock NSDSender (имитация принимающей -// стороны через 3 секунды). На M3 переключим на pgx + реальный -// nsd-adapter без изменения контракта. -package main - -import ( - "context" - "log" - "os" - "os/signal" - "syscall" - "time" - - "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/lkgateway" - "git.zetit.ru/zuevav/Bridge-and-Join-s/internal/m2m" -) - -func main() { - addr := getenv("BJ_HTTP_ADDR", ":8080") - defaultSender := m2m.DeponentCode(getenv("BJ_M2M_SENDER", "MC0079200000")) - defaultReceiver := m2m.DeponentCode(getenv("BJ_M2M_RECEIVER", "MC0010300000")) - - cfg := lkgateway.ServerConfig{ - Addr: addr, - DefaultSender: defaultSender, - DefaultReceiver: defaultReceiver, - CheckOptions: func() lkgateway.CheckOptions { - return lkgateway.CheckOptions{ - PostgresDSN: os.Getenv("BJ_DSN"), - CryptoSocket: getenv("BJ_CRYPTO_SOCKET", "/run/bj/crypto.sock"), - NSDAdapterURL: os.Getenv("BJ_NSD_ADAPTER_URL"), - LKCallbackURL: os.Getenv("BJ_LK_CALLBACK_URL"), - Profile: getenv("BJ_NSD_PROFILE", "demo (mock NSD)"), - CryptoProvider: getenv("BJ_CRYPTO_PROVIDER", "stub"), - Timeout: 2 * time.Second, - } - }, - } - - srv, err := lkgateway.NewServer(cfg) - if err != nil { - log.Fatalf("lk-gateway: NewServer: %v", err) - } - if cb := os.Getenv("BJ_LK_CALLBACK_URL"); cb != "" { - srv.SetCallbackURL(cb) - } - - ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) - runErr := srv.Run(ctx) - stop() - if runErr != nil { - log.Printf("lk-gateway: %v", runErr) - os.Exit(1) - } -} - -func getenv(k, def string) string { - if v, ok := os.LookupEnv(k); ok && v != "" { - return v - } - return def -} diff --git a/cmd/m2m-core/main.go b/cmd/m2m-core/main.go deleted file mode 100644 index 0319ec5..0000000 --- a/cmd/m2m-core/main.go +++ /dev/null @@ -1,17 +0,0 @@ -// Package main — сервис m2m-core. Бизнес-логика и FSM сделки M2M-перевода: -// идемпотентность по GUID, валидация по XSD, метрики SLA, ветка ручного -// согласования и таймаут-отказа MOST. -// -// На этапе M1 — заглушка. -package main - -import ( - "fmt" - "os" -) - -const serviceName = "m2m-core" - -func main() { - fmt.Fprintf(os.Stdout, "%s: запуск (заглушка M1)\n", serviceName) -} diff --git a/cmd/notify/main.go b/cmd/notify/main.go deleted file mode 100644 index c21f264..0000000 --- a/cmd/notify/main.go +++ /dev/null @@ -1,19 +0,0 @@ -// Package main — сервис notify. Отправка уведомлений по нескольким каналам: -// e-mail (SMTP), Yandex Messenger (Yandex 360), WebSocket-push в admin-ui, -// плюс расширяемая модель провайдеров-плагинов (smtp, yandex360, telegram, -// mattermost, webhook) под единый интерфейс Notifier — для тиражирования -// продукта другим компаниям. -// -// На этапе M1 — заглушка. -package main - -import ( - "fmt" - "os" -) - -const serviceName = "notify" - -func main() { - fmt.Fprintf(os.Stdout, "%s: запуск (заглушка M1)\n", serviceName) -} diff --git a/cmd/nsd-adapter/main.go b/cmd/nsd-adapter/main.go deleted file mode 100644 index cb39e44..0000000 --- a/cmd/nsd-adapter/main.go +++ /dev/null @@ -1,22 +0,0 @@ -// Package main — сервис nsd-adapter. Транспорт к НРД: -// - Интеграционный шлюз через REST API (основной канал, ИШ сам подписывает); -// - Web-сервис ONYX напрямую (резерв); -// - Файловый шлюз / обменные папки ИШ (fallback). -// -// Сериализация и парсинг XML по схемам M2MSchemas в windows-1251, -// маршрутизация по типам пакетов (#M2MTR / #M2MTD / #M2MER / SUBBR / SUBER / -// SUB16 / Справки / квитанции ЭДО). -// -// На этапе M1 — заглушка. -package main - -import ( - "fmt" - "os" -) - -const serviceName = "nsd-adapter" - -func main() { - fmt.Fprintf(os.Stdout, "%s: запуск (заглушка M1)\n", serviceName) -} diff --git a/deploy/systemd/README.md b/deploy/systemd/README.md new file mode 100644 index 0000000..7c51587 --- /dev/null +++ b/deploy/systemd/README.md @@ -0,0 +1,33 @@ +# deploy/systemd — юниты для деплоя + +Минимальный production-деплой Bridge-and-Join-s — два бинарника + два +systemd-юнита. + +## Состав + +- `bj-server.service` — основной сервис: lk-gateway BFF + admin UI + + m2m-core FSM + nsd-adapter поллер + notify. HTTP `:8080`. +- `bj-emulator.service` — имитация ЛК (QA-инструмент). HTTP `:8083`. + +## Установка + +```bash +sudo useradd --system --no-create-home --shell /usr/sbin/nologin bj +sudo mkdir -p /opt/bj /var/lib/bj /var/log/bj /run/bj +sudo chown bj:bj /var/lib/bj /var/log/bj /run/bj + +# собрать бинарники на dev-ВМ и положить в /opt/bj/ +sudo cp bin/bj-server bin/lk-emulator /opt/bj/ + +# юниты +sudo cp deploy/systemd/bj-server.service deploy/systemd/bj-emulator.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable --now bj-server bj-emulator + +# проверка +systemctl status bj-server bj-emulator +journalctl -u bj-server -f +``` + +Веб-интерфейс: `http://:8080/admin/setup` — настройка PostgreSQL, +КриптоПро CSP, ИШ НРД, callback ЛК. diff --git a/deploy/systemd/bj-emulator.service b/deploy/systemd/bj-emulator.service new file mode 100644 index 0000000..99ebc48 --- /dev/null +++ b/deploy/systemd/bj-emulator.service @@ -0,0 +1,30 @@ +[Unit] +Description=Bridge-and-Join-s — эмулятор ЛК ESIA Finance (QA) +Documentation=https://git.zetit.ru/zuevav/Bridge-and-Join-s +After=network-online.target bj-server.service +Wants=network-online.target + +[Service] +Type=simple +User=bj +Group=bj +WorkingDirectory=/opt/bj +ExecStart=/opt/bj/lk-emulator +Restart=on-failure +RestartSec=5 + +Environment=BJ_HTTP_ADDR=:8083 +Environment=BJ_GATEWAY_URL=http://127.0.0.1:8080 +Environment=BJ_EMULATOR_PUBLIC_URL=http://127.0.0.1:8083 + +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +ProtectKernelTunables=true + +LimitNOFILE=65536 +TasksMax=128 + +[Install] +WantedBy=multi-user.target diff --git a/deploy/systemd/bj-server.service b/deploy/systemd/bj-server.service new file mode 100644 index 0000000..69f1102 --- /dev/null +++ b/deploy/systemd/bj-server.service @@ -0,0 +1,39 @@ +[Unit] +Description=Bridge-and-Join-s — единый сервис M2M-переводов +Documentation=https://git.zetit.ru/zuevav/Bridge-and-Join-s +After=network-online.target postgresql.service +Wants=network-online.target + +[Service] +Type=simple +User=bj +Group=bj +WorkingDirectory=/opt/bj +ExecStart=/opt/bj/bj-server +Restart=on-failure +RestartSec=5 + +# Конфигурация — через ENV или ~/.bj/setup.json (UI /admin/setup). +Environment=BJ_HTTP_ADDR=:8080 +Environment=BJ_SETUP_PATH=/var/lib/bj/setup.json +Environment=BJ_M2M_SENDER=MC0079200000 +Environment=BJ_M2M_RECEIVER=MC0010300000 + +# Безопасность. +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +ReadWritePaths=/var/lib/bj /var/log/bj /run/bj +ProtectKernelTunables=true +ProtectKernelModules=true +ProtectControlGroups=true +RestrictSUIDSGID=true +LockPersonality=true + +# Лимиты. +LimitNOFILE=65536 +TasksMax=512 + +[Install] +WantedBy=multi-user.target