174 lines
4.7 KiB
Go
174 lines
4.7 KiB
Go
// Package app wires notice-service modules, health endpoints and background workers.
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
"google.golang.org/grpc"
|
|
healthgrpc "google.golang.org/grpc/health/grpc_health_v1"
|
|
"hyapp/pkg/grpchealth"
|
|
"hyapp/pkg/grpcshutdown"
|
|
"hyapp/pkg/healthhttp"
|
|
"hyapp/pkg/logx"
|
|
"hyapp/pkg/tencentim"
|
|
"hyapp/services/notice-service/internal/config"
|
|
"hyapp/services/notice-service/internal/modules/walletnotice"
|
|
mysqlplatform "hyapp/services/notice-service/internal/platform/mysql"
|
|
)
|
|
|
|
// App owns the notice-service process lifecycle.
|
|
type App struct {
|
|
server *grpc.Server
|
|
listener net.Listener
|
|
health *grpchealth.ServingChecker
|
|
healthHTTP *healthhttp.Server
|
|
store *mysqlplatform.Store
|
|
walletNoticeService *walletnotice.Service
|
|
walletWorkerOptions walletnotice.WalletNoticeWorkerOptions
|
|
walletWorkerEnabled bool
|
|
workerCtx context.Context
|
|
workerCancel context.CancelFunc
|
|
workerWG sync.WaitGroup
|
|
closeOnce sync.Once
|
|
}
|
|
|
|
// New initializes MySQL, module repositories and optional Tencent IM publisher.
|
|
func New(cfg config.Config) (*App, error) {
|
|
startupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
store, err := mysqlplatform.Open(startupCtx, cfg.MySQLDSN)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
listener, err := net.Listen("tcp", cfg.GRPCAddr)
|
|
if err != nil {
|
|
_ = store.Close()
|
|
return nil, err
|
|
}
|
|
|
|
walletRepo, err := walletnotice.NewMySQLRepository(store.DB, cfg.WalletDatabase)
|
|
if err != nil {
|
|
_ = listener.Close()
|
|
_ = store.Close()
|
|
return nil, err
|
|
}
|
|
var publisher walletnotice.UserMessagePublisher
|
|
if cfg.TencentIM.Enabled {
|
|
tencentClient, err := tencentim.NewRESTClient(cfg.TencentIM.RESTConfig())
|
|
if err != nil {
|
|
_ = listener.Close()
|
|
_ = store.Close()
|
|
return nil, err
|
|
}
|
|
publisher = tencentClient
|
|
}
|
|
if cfg.WalletNoticeWorker.Enabled && publisher == nil {
|
|
_ = listener.Close()
|
|
_ = store.Close()
|
|
return nil, errors.New("wallet notice worker requires tencent_im.enabled")
|
|
}
|
|
|
|
server := grpc.NewServer(grpc.UnaryInterceptor(logx.UnaryServerInterceptor("notice-service")))
|
|
health := grpchealth.NewServingChecker("notice-service", grpchealth.Dependency{
|
|
Name: "mysql",
|
|
Check: store.Ping,
|
|
})
|
|
healthgrpc.RegisterHealthServer(server, grpchealth.NewServer(health))
|
|
healthHTTP, err := healthhttp.New(cfg.HealthHTTPAddr, cfg.NodeID, health)
|
|
if err != nil {
|
|
_ = listener.Close()
|
|
_ = store.Close()
|
|
return nil, err
|
|
}
|
|
|
|
workerCtx, workerCancel := context.WithCancel(context.Background())
|
|
return &App{
|
|
server: server,
|
|
listener: listener,
|
|
health: health,
|
|
healthHTTP: healthHTTP,
|
|
store: store,
|
|
walletNoticeService: walletnotice.New(walletnotice.Config{NodeID: cfg.NodeID}, walletRepo, publisher),
|
|
walletWorkerOptions: walletNoticeWorkerOptions(cfg.WalletNoticeWorker),
|
|
walletWorkerEnabled: cfg.WalletNoticeWorker.Enabled,
|
|
workerCtx: workerCtx,
|
|
workerCancel: workerCancel,
|
|
}, nil
|
|
}
|
|
|
|
// Run starts health serving, optional workers and the gRPC health endpoint.
|
|
func (a *App) Run() error {
|
|
a.runHealthHTTP()
|
|
a.health.MarkServing()
|
|
defer func() {
|
|
if a.workerCancel != nil {
|
|
a.workerCancel()
|
|
}
|
|
a.workerWG.Wait()
|
|
a.health.MarkStopped()
|
|
}()
|
|
if a.walletWorkerEnabled && a.walletNoticeService != nil {
|
|
a.workerWG.Go(func() {
|
|
a.walletNoticeService.RunWalletBalanceWorker(a.workerCtx, a.walletWorkerOptions)
|
|
})
|
|
}
|
|
err := a.server.Serve(a.listener)
|
|
if errors.Is(err, grpc.ErrServerStopped) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Close drains workers before closing gRPC and MySQL resources.
|
|
func (a *App) Close() {
|
|
a.closeOnce.Do(func() {
|
|
a.health.MarkDraining()
|
|
if a.workerCancel != nil {
|
|
a.workerCancel()
|
|
}
|
|
a.workerWG.Wait()
|
|
grpcshutdown.GracefulStop(a.server, 15*time.Second)
|
|
a.closeHealthHTTP()
|
|
if a.store != nil {
|
|
_ = a.store.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 walletNoticeWorkerOptions(cfg config.WalletNoticeWorkerConfig) walletnotice.WalletNoticeWorkerOptions {
|
|
return walletnotice.WalletNoticeWorkerOptions{
|
|
PollInterval: cfg.PollInterval,
|
|
BatchSize: cfg.BatchSize,
|
|
LockTTL: cfg.LockTTL,
|
|
PublishTimeout: cfg.PublishTimeout,
|
|
MaxRetryCount: cfg.MaxRetryCount,
|
|
InitialBackoff: cfg.InitialBackoff,
|
|
MaxBackoff: cfg.MaxBackoff,
|
|
}
|
|
}
|