163 lines
4.4 KiB
Go
163 lines
4.4 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
luckygiftv1 "hyapp.local/api/proto/luckygift/v1"
|
|
walletv1 "hyapp.local/api/proto/wallet/v1"
|
|
"hyapp/pkg/grpchealth"
|
|
"hyapp/pkg/healthhttp"
|
|
"hyapp/pkg/logx"
|
|
serviceapp "hyapp/pkg/servicekit/app"
|
|
servicegrpc "hyapp/pkg/servicekit/grpcserver"
|
|
servicehealth "hyapp/pkg/servicekit/health"
|
|
"hyapp/services/lucky-gift-service/internal/config"
|
|
luckygiftservice "hyapp/services/lucky-gift-service/internal/service/luckygift"
|
|
mysqlstorage "hyapp/services/lucky-gift-service/internal/storage/mysql"
|
|
grpcserver "hyapp/services/lucky-gift-service/internal/transport/grpc"
|
|
)
|
|
|
|
// App 装配 lucky-gift-service 的 owner 进程。
|
|
// 该进程是幸运礼物规则、抽奖、外部 App 接入和 outbox 的唯一写入者;其他服务只持有 gRPC client。
|
|
type App struct {
|
|
cfg config.Config
|
|
server *grpc.Server
|
|
listener net.Listener
|
|
health *grpchealth.ServingChecker
|
|
healthHTTP *healthhttp.Server
|
|
repository *mysqlstorage.Repository
|
|
walletConn *grpc.ClientConn
|
|
service *luckygiftservice.Service
|
|
workers *serviceapp.BackgroundGroup
|
|
closeOnce sync.Once
|
|
}
|
|
|
|
func New(cfg config.Config) (*App, error) {
|
|
startupCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
repository, err := mysqlstorage.Open(startupCtx, cfg.MySQLDSN)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cfg.MySQLAutoMigrate {
|
|
if err := repository.Migrate(startupCtx); err != nil {
|
|
_ = repository.Close()
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
walletConn, err := grpc.Dial(cfg.WalletServiceAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
if err != nil {
|
|
_ = repository.Close()
|
|
return nil, err
|
|
}
|
|
listener, err := net.Listen("tcp", cfg.GRPCAddr)
|
|
if err != nil {
|
|
_ = walletConn.Close()
|
|
_ = repository.Close()
|
|
return nil, err
|
|
}
|
|
|
|
server := servicegrpc.New("lucky-gift-service")
|
|
svc := luckygiftservice.New(repository, luckygiftservice.WithWallet(walletv1.NewWalletServiceClient(walletConn)))
|
|
luckygiftv1.RegisterLuckyGiftServiceServer(server, grpcserver.NewLuckyGiftServer(svc))
|
|
luckygiftv1.RegisterAdminLuckyGiftServiceServer(server, grpcserver.NewAdminLuckyGiftServer(svc))
|
|
health, healthHTTP, err := servicehealth.New(server, servicehealth.Config{
|
|
ServiceName: "lucky-gift-service",
|
|
HTTPAddr: cfg.HealthHTTPAddr,
|
|
NodeID: cfg.NodeID,
|
|
Dependencies: []grpchealth.Dependency{{
|
|
Name: "mysql",
|
|
Check: repository.Ping,
|
|
}},
|
|
})
|
|
if err != nil {
|
|
_ = listener.Close()
|
|
_ = walletConn.Close()
|
|
_ = repository.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return &App{
|
|
cfg: cfg,
|
|
server: server,
|
|
listener: listener,
|
|
health: health,
|
|
healthHTTP: healthHTTP,
|
|
repository: repository,
|
|
walletConn: walletConn,
|
|
service: svc,
|
|
workers: serviceapp.NewBackground(context.Background()),
|
|
}, nil
|
|
}
|
|
|
|
func (a *App) Run() error {
|
|
if a.healthHTTP != nil {
|
|
go func() {
|
|
if err := a.healthHTTP.Run(); err != nil {
|
|
logx.Error(context.Background(), "lucky_gift_health_http_run_failed", err)
|
|
}
|
|
}()
|
|
}
|
|
a.health.MarkServing()
|
|
if a.cfg.LuckyGiftWorker.Enabled && a.service != nil {
|
|
options := luckyGiftWorkerOptions(a.cfg.NodeID, a.cfg.LuckyGiftWorker)
|
|
a.workers.Go(func(ctx context.Context) {
|
|
a.service.RunWorker(ctx, options)
|
|
})
|
|
}
|
|
err := a.server.Serve(a.listener)
|
|
if a.workers != nil {
|
|
a.workers.StopAndWait()
|
|
}
|
|
a.health.MarkStopped()
|
|
if errors.Is(err, grpc.ErrServerStopped) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (a *App) Close() {
|
|
a.closeOnce.Do(func() {
|
|
if a.health != nil {
|
|
a.health.MarkDraining()
|
|
}
|
|
if a.workers != nil {
|
|
a.workers.StopAndWait()
|
|
}
|
|
servicegrpc.GracefulStop(a.server, 15*time.Second)
|
|
if a.healthHTTP != nil {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
|
defer cancel()
|
|
_ = a.healthHTTP.Close(ctx)
|
|
}
|
|
if a.walletConn != nil {
|
|
_ = a.walletConn.Close()
|
|
}
|
|
if a.repository != nil {
|
|
_ = a.repository.Close()
|
|
}
|
|
})
|
|
}
|
|
|
|
func luckyGiftWorkerOptions(nodeID string, cfg config.LuckyGiftWorkerConfig) luckygiftservice.WorkerOptions {
|
|
return luckygiftservice.WorkerOptions{
|
|
WorkerID: nodeID,
|
|
PollInterval: cfg.WorkerPollInterval,
|
|
BatchSize: cfg.WorkerBatchSize,
|
|
Concurrency: cfg.WorkerConcurrency,
|
|
LockTTL: cfg.WorkerLockTTL,
|
|
MaxRetry: cfg.WorkerMaxRetry,
|
|
PublishTimeout: cfg.PublishTimeout,
|
|
StatsInterval: cfg.StatsRefreshInterval,
|
|
StatsBatchSize: cfg.StatsBatchSize,
|
|
}
|
|
}
|