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