254 lines
7.8 KiB
Go
254 lines
7.8 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
"google.golang.org/grpc"
|
|
"hyapp/pkg/grpchealth"
|
|
"hyapp/pkg/healthhttp"
|
|
"hyapp/pkg/logx"
|
|
"hyapp/pkg/rocketmqx"
|
|
serviceapp "hyapp/pkg/servicekit/app"
|
|
"hyapp/pkg/servicekit/grpcserver"
|
|
servicemq "hyapp/pkg/servicekit/mq"
|
|
"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"
|
|
inviteactivityservice "hyapp/services/activity-service/internal/service/inviteactivity"
|
|
luckygiftservice "hyapp/services/activity-service/internal/service/luckygift"
|
|
messageservice "hyapp/services/activity-service/internal/service/message"
|
|
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
|
|
inviteActivityReward *inviteactivityservice.Service
|
|
actionConfirm *messageservice.ActionConfirmService
|
|
broadcastWorkerEnabled bool
|
|
luckyGiftWorkerEnabled bool
|
|
messageActionWorkerEnabled bool
|
|
workerNodeID string
|
|
luckyGiftWorkerOptions luckygiftservice.WorkerOptions
|
|
messageActionWorkerOptions config.MessageActionConfirmWorkerConfig
|
|
messageActionOutboxTopic string
|
|
userLeaderboardRedisClose func() error
|
|
mqConsumers []*rocketmqx.Consumer
|
|
messageActionProducer *rocketmqx.Producer
|
|
userConn *grpc.ClientConn
|
|
walletConn *grpc.ClientConn
|
|
roomConn *grpc.ClientConn
|
|
workers *serviceapp.BackgroundGroup
|
|
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
|
|
var messageActionProducer *rocketmqx.Producer
|
|
var userLeaderboardRedisClose func() error
|
|
cleanup := func() {
|
|
shutdownConsumers(mqConsumers)
|
|
servicemq.ShutdownProducers([]*rocketmqx.Producer{messageActionProducer})
|
|
if userLeaderboardRedisClose != nil {
|
|
_ = userLeaderboardRedisClose()
|
|
}
|
|
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 := grpcserver.New("activity-service")
|
|
userLeaderboardStore, closeLeaderboardRedis, err := openUserLeaderboardStore(startupCtx, cfg.UserLeaderboardWorker)
|
|
if err != nil {
|
|
cleanup()
|
|
return nil, err
|
|
}
|
|
userLeaderboardRedisClose = closeLeaderboardRedis
|
|
services, err := buildServiceBundle(cfg, repository, clients, userLeaderboardStore)
|
|
if err != nil {
|
|
cleanup()
|
|
return nil, err
|
|
}
|
|
registerGRPCServers(server, services)
|
|
|
|
mqConsumers, err = buildMQConsumers(cfg, services)
|
|
if err != nil {
|
|
cleanup()
|
|
return nil, err
|
|
}
|
|
if cfg.RocketMQ.MessageActionOutbox.Enabled {
|
|
messageActionProducer, err = rocketmqx.NewProducer(messageActionProducerConfig(cfg.RocketMQ))
|
|
if err != nil {
|
|
cleanup()
|
|
return nil, err
|
|
}
|
|
}
|
|
health, healthHTTP, err := newHealthServers(cfg, server, repository)
|
|
if err != nil {
|
|
cleanup()
|
|
return nil, err
|
|
}
|
|
|
|
return &App{
|
|
server: server,
|
|
listener: listener,
|
|
health: health,
|
|
healthHTTP: healthHTTP,
|
|
mysqlRepo: repository,
|
|
broadcast: services.broadcast,
|
|
luckyGift: services.luckyGift,
|
|
firstRechargeReward: services.firstRechargeReward,
|
|
cumulativeRecharge: services.cumulativeRecharge,
|
|
inviteActivityReward: services.inviteActivityReward,
|
|
actionConfirm: services.actionConfirm,
|
|
broadcastWorkerEnabled: cfg.Broadcast.Enabled,
|
|
luckyGiftWorkerEnabled: cfg.LuckyGiftWorker.Enabled,
|
|
messageActionWorkerEnabled: cfg.MessageActionConfirmWorker.Enabled,
|
|
workerNodeID: cfg.NodeID,
|
|
luckyGiftWorkerOptions: luckyGiftWorkerOptions(cfg.NodeID, cfg.LuckyGiftWorker),
|
|
messageActionWorkerOptions: cfg.MessageActionConfirmWorker,
|
|
messageActionOutboxTopic: cfg.RocketMQ.MessageActionOutbox.Topic,
|
|
userLeaderboardRedisClose: userLeaderboardRedisClose,
|
|
mqConsumers: mqConsumers,
|
|
messageActionProducer: messageActionProducer,
|
|
userConn: clients.userConn,
|
|
walletConn: clients.walletConn,
|
|
roomConn: clients.roomConn,
|
|
workers: serviceapp.NewBackground(context.Background()),
|
|
}, 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.workers != nil {
|
|
a.workers.Go(func(ctx context.Context) {
|
|
a.broadcast.RunWorker(ctx, broadcastservice.WorkerOptions{})
|
|
})
|
|
}
|
|
if a.luckyGiftWorkerEnabled && a.luckyGift != nil && a.workers != nil {
|
|
a.workers.Go(func(ctx context.Context) {
|
|
a.luckyGift.RunWorker(ctx, a.luckyGiftWorkerOptions)
|
|
})
|
|
}
|
|
if a.messageActionWorkerEnabled && a.actionConfirm != nil && a.messageActionProducer != nil && a.workers != nil {
|
|
a.workers.Go(func(ctx context.Context) {
|
|
a.runMessageActionOutboxWorker(ctx)
|
|
})
|
|
}
|
|
err := a.server.Serve(a.listener)
|
|
if a.workers != nil {
|
|
a.workers.StopAndWait()
|
|
}
|
|
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.workers != nil {
|
|
a.workers.StopAndWait()
|
|
}
|
|
a.shutdownMQ()
|
|
grpcserver.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()
|
|
}
|
|
if a.userLeaderboardRedisClose != nil {
|
|
_ = a.userLeaderboardRedisClose()
|
|
}
|
|
})
|
|
}
|
|
|
|
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 {
|
|
if err := servicemq.StartProducers([]*rocketmqx.Producer{a.messageActionProducer}); err != nil {
|
|
return err
|
|
}
|
|
return servicemq.StartConsumers(a.mqConsumers)
|
|
}
|
|
|
|
func (a *App) shutdownMQ() {
|
|
servicemq.ShutdownConsumers(a.mqConsumers)
|
|
servicemq.ShutdownProducers([]*rocketmqx.Producer{a.messageActionProducer})
|
|
}
|