Files
Bridge-and-Join-s/cmd/bj-server/main.go
T
fontvielle 978777ff6a refactor(cmd): объединить Go-сервисы в один бинарник bj-server
Под выбранный объём 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) <noreply@anthropic.com>
2026-05-14 13:46:21 +03:00

150 lines
5.5 KiB
Go

// 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
}