package app import ( "context" "errors" "log/slog" "net" "sync" "time" "google.golang.org/grpc" "hyapp/pkg/grpchealth" "hyapp/pkg/grpcshutdown" "hyapp/pkg/healthhttp" "hyapp/pkg/logx" "hyapp/pkg/rocketmqx" "hyapp/services/activity-service/internal/config" broadcastservice "hyapp/services/activity-service/internal/service/broadcast" cumulativerechargeservice "hyapp/services/activity-service/internal/service/cumulativerecharge" firstrechargeservice "hyapp/services/activity-service/internal/service/firstrecharge" luckygiftservice "hyapp/services/activity-service/internal/service/luckygift" mysqlstorage "hyapp/services/activity-service/internal/storage/mysql" ) // App 装配 activity-service 的 gRPC 入口、健康检查、后台 worker 和外部连接。 // 具体业务模块的创建和注册都放在独立文件里,避免启动入口继续膨胀成一个难维护的大函数。 type App struct { server *grpc.Server listener net.Listener health *grpchealth.ServingChecker healthHTTP *healthhttp.Server mysqlRepo *mysqlstorage.Repository broadcast *broadcastservice.Service luckyGift *luckygiftservice.Service firstRechargeReward *firstrechargeservice.Service cumulativeRecharge *cumulativerechargeservice.Service broadcastWorkerEnabled bool luckyGiftWorkerEnabled bool workerNodeID string luckyGiftWorkerOptions luckygiftservice.WorkerOptions mqConsumers []*rocketmqx.Consumer userConn *grpc.ClientConn walletConn *grpc.ClientConn roomConn *grpc.ClientConn workerCtx context.Context workerStop context.CancelFunc workerWG sync.WaitGroup closeOnce sync.Once } // New 初始化 activity-service 应用。 func New(cfg config.Config) (*App, error) { startupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() repository, err := openRepository(startupCtx, cfg) if err != nil { return nil, err } var listener net.Listener var clients externalClients var mqConsumers []*rocketmqx.Consumer cleanup := func() { shutdownConsumers(mqConsumers) clients.Close() if listener != nil { _ = listener.Close() } _ = repository.Close() } listener, err = listenGRPC(cfg.GRPCAddr) if err != nil { cleanup() return nil, err } clients, err = dialExternalClients(cfg) if err != nil { cleanup() return nil, err } server := grpc.NewServer(grpc.UnaryInterceptor(logx.UnaryServerInterceptor("activity-service"))) services, err := buildServiceBundle(cfg, repository, clients) if err != nil { cleanup() return nil, err } registerGRPCServers(server, services) mqConsumers, err = buildMQConsumers(cfg, services) if err != nil { cleanup() return nil, err } health, healthHTTP, err := newHealthServers(cfg, server, repository) if err != nil { cleanup() return nil, err } workerCtx, workerStop := context.WithCancel(context.Background()) return &App{ server: server, listener: listener, health: health, healthHTTP: healthHTTP, mysqlRepo: repository, broadcast: services.broadcast, luckyGift: services.luckyGift, firstRechargeReward: services.firstRechargeReward, cumulativeRecharge: services.cumulativeRecharge, broadcastWorkerEnabled: cfg.Broadcast.Enabled, luckyGiftWorkerEnabled: cfg.LuckyGiftWorker.Enabled, workerNodeID: cfg.NodeID, luckyGiftWorkerOptions: luckyGiftWorkerOptions(cfg.NodeID, cfg.LuckyGiftWorker), mqConsumers: mqConsumers, userConn: clients.userConn, walletConn: clients.walletConn, roomConn: clients.roomConn, workerCtx: workerCtx, workerStop: workerStop, }, nil } // Run 启动 gRPC 服务。 func (a *App) Run() error { a.runHealthHTTP() a.health.MarkServing() if err := a.startMQ(); err != nil { a.health.MarkStopped() return err } if a.broadcastWorkerEnabled && a.broadcast != nil && a.workerCtx != nil { a.workerWG.Go(func() { a.broadcast.RunWorker(a.workerCtx, broadcastservice.WorkerOptions{}) }) } if a.luckyGiftWorkerEnabled && a.luckyGift != nil && a.workerCtx != nil { a.workerWG.Go(func() { a.luckyGift.RunWorker(a.workerCtx, a.luckyGiftWorkerOptions) }) } err := a.server.Serve(a.listener) if a.workerStop != nil { a.workerStop() } a.workerWG.Wait() a.shutdownMQ() a.health.MarkStopped() if errors.Is(err, grpc.ErrServerStopped) { return nil } return err } // Close 关闭 gRPC 服务。 func (a *App) Close() { a.closeOnce.Do(func() { a.health.MarkDraining() if a.workerStop != nil { a.workerStop() } a.workerWG.Wait() a.shutdownMQ() grpcshutdown.GracefulStop(a.server, 15*time.Second) a.closeHealthHTTP() if a.userConn != nil { _ = a.userConn.Close() } if a.walletConn != nil { _ = a.walletConn.Close() } if a.roomConn != nil { _ = a.roomConn.Close() } if a.mysqlRepo != nil { // MySQL 连接池最后关闭,保证 drain 中的查询或消费提交能完成。 _ = a.mysqlRepo.Close() } }) } func (a *App) runHealthHTTP() { if a.healthHTTP == nil { return } go func() { if err := a.healthHTTP.Run(); err != nil { logx.Error(context.Background(), "health_http_run_failed", err) } }() } func (a *App) closeHealthHTTP() { if a.healthHTTP == nil { return } ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() _ = a.healthHTTP.Close(ctx) } func (a *App) startMQ() error { for _, consumer := range a.mqConsumers { if err := consumer.Start(); err != nil { a.shutdownMQ() return err } } return nil } func (a *App) shutdownMQ() { for _, consumer := range a.mqConsumers { if err := consumer.Shutdown(); err != nil { logx.Warn(context.Background(), "rocketmq_consumer_shutdown_failed", slog.String("error", err.Error())) } } }