package app import ( "context" "log/slog" "github.com/go-redis/redis/v8" "gorm.io/gorm" "pay-bridge/internal/repository" "pay-bridge/internal/service" "pay-bridge/pkg/config" "pay-bridge/pkg/sequence" ) // App 应用容器,持有所有初始化完成的 service 实例 type App struct { Cfg *config.Config // 对外暴露供 router 使用的 service AdminAuthSvc *service.AdminAuthService AppSvc *service.AppService TradeSvc *service.TradeService RefundSvc *service.RefundService NotifySvc *service.NotifyService MatchSvc *service.PaymentMatchService MerchantSvc *service.MerchantService ReconSvc *service.ReconciliationService ChannelSvc *service.ChannelService // 内部资源 db *gorm.DB rdb *redis.Client } // New 初始化所有基础设施和 service,返回就绪的 App 实例 func New(cfg *config.Config) (*App, error) { a := &App{Cfg: cfg} if err := a.initInfra(); err != nil { return nil, err } a.initServices() return a, nil } // Start 启动后台任务(notify poller 等) func (a *App) Start(ctx context.Context) { a.NotifySvc.StartPoller(ctx, a.Cfg.Notify.PollerInterval, a.Cfg.Notify.PollerBatch) } // Shutdown 优雅关闭:关闭 DB 和 Redis 连接 func (a *App) Shutdown(ctx context.Context) { if a.rdb != nil { if err := a.rdb.Close(); err != nil { slog.Error("redis close error", "err", err) } } if a.db != nil { sqlDB, err := a.db.DB() if err == nil { if err := sqlDB.Close(); err != nil { slog.Error("db close error", "err", err) } } } } // initInfra 初始化 DB 和 Redis func (a *App) initInfra() error { db, err := config.NewDB(a.Cfg.Database) if err != nil { return err } a.db = db rdb, err := config.NewRedis(a.Cfg.Redis) if err != nil { return err } a.rdb = rdb return nil } // initServices 按依赖顺序构建 repo → service func (a *App) initServices() { encKey := a.Cfg.Security.FieldEncryptKey // Repositories adminUserRepo := repository.NewAdminUserRepository(a.db) appRepo := repository.NewAppRepository(a.db) tradeRepo := repository.NewTradeOrderRepository(a.db) refundRepo := repository.NewRefundOrderRepository(a.db) notifyRepo := repository.NewNotifyLogRepository(a.db) channelCfgRepo := repository.NewChannelConfigRepository(a.db) seqRepo := repository.NewSequenceRepository(a.db) profitSharingRepo := repository.NewProfitSharingRepository(a.db) serviceFeeRepo := repository.NewServiceFeeRepository(a.db) matchRepo := repository.NewPaymentMatchRepository(a.db) merchantRepo := repository.NewMerchantRepository(a.db) wechatRepo := repository.NewWechatRepository(a.db) reconRepo := repository.NewReconciliationRepository(a.db) // JWT expire hours jwtExpireHours := a.Cfg.JWT.ExpireHours if jwtExpireHours == 0 { jwtExpireHours = 24 } // Services a.AdminAuthSvc = service.NewAdminAuthService(adminUserRepo, a.Cfg.JWT.Secret, jwtExpireHours) a.ChannelSvc = service.NewChannelService(channelCfgRepo, encKey, a.Cfg.Channels) seqSvc := sequence.NewService(seqRepo) a.AppSvc = service.NewAppService(appRepo, encKey) a.NotifySvc = service.NewNotifyService(notifyRepo, tradeRepo, a.Cfg.Notify.HTTPTimeout) a.MerchantSvc = service.NewMerchantService(merchantRepo, a.ChannelSvc) a.TradeSvc = service.NewTradeService(tradeRepo, a.ChannelSvc, seqSvc, a.rdb, a.NotifySvc, a.MerchantSvc) a.RefundSvc = service.NewRefundService(refundRepo, tradeRepo, a.ChannelSvc, seqSvc, a.NotifySvc) a.MatchSvc = service.NewPaymentMatchService(matchRepo, tradeRepo, a.NotifySvc, a.TradeSvc) a.ReconSvc = service.NewReconciliationService(reconRepo, tradeRepo, a.ChannelSvc, appRepo) // 以下 service 目前未直接暴露给 router,但已初始化供将来扩展使用 _ = service.NewProfitSharingService(profitSharingRepo, tradeRepo, a.ChannelSvc, seqSvc, a.rdb) _ = service.NewServiceFeeService(serviceFeeRepo, tradeRepo, a.ChannelSvc) _ = service.NewWechatService(wechatRepo, encKey) }