248 lines
8.1 KiB
Go
248 lines
8.1 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
walletv1 "hyapp.local/api/proto/wallet/v1"
|
|
"hyapp/pkg/grpchealth"
|
|
"hyapp/pkg/healthhttp"
|
|
"hyapp/pkg/logx"
|
|
"hyapp/pkg/rocketmqx"
|
|
servicegrpc "hyapp/pkg/servicekit/grpcserver"
|
|
servicehealth "hyapp/pkg/servicekit/health"
|
|
"hyapp/services/wallet-service/internal/client"
|
|
"hyapp/services/wallet-service/internal/client/binance"
|
|
"hyapp/services/wallet-service/internal/client/bscusdt"
|
|
"hyapp/services/wallet-service/internal/client/googleplay"
|
|
"hyapp/services/wallet-service/internal/client/mifapay"
|
|
"hyapp/services/wallet-service/internal/client/trongrid"
|
|
"hyapp/services/wallet-service/internal/client/v5pay"
|
|
"hyapp/services/wallet-service/internal/config"
|
|
walletservice "hyapp/services/wallet-service/internal/service/wallet"
|
|
mysqlstorage "hyapp/services/wallet-service/internal/storage/mysql"
|
|
grpcserver "hyapp/services/wallet-service/internal/transport/grpc"
|
|
)
|
|
|
|
// App 装配 wallet-service gRPC 入口。
|
|
type App struct {
|
|
server *grpc.Server
|
|
listener net.Listener
|
|
health *grpchealth.ServingChecker
|
|
healthHTTP *healthhttp.Server
|
|
mysqlRepo *mysqlstorage.Repository
|
|
walletSvc *walletservice.Service
|
|
activityConn *grpc.ClientConn
|
|
outboxProducer *rocketmqx.Producer
|
|
realtimeOutboxProducer *rocketmqx.Producer
|
|
projectionConsumer *rocketmqx.Consumer
|
|
outboxWorkerCfg config.OutboxWorkerConfig
|
|
realtimeOutboxWorkerCfg config.OutboxWorkerConfig
|
|
projectionWorkerCfg config.ProjectionWorkerConfig
|
|
externalRechargeReconcileWorkerCfg config.ExternalRechargeReconcileWorkerConfig
|
|
walletOutboxTopic string
|
|
realtimeWalletOutboxTopic string
|
|
nodeID string
|
|
redPacketExpiryWorkerCfg config.RedPacketExpiryWorkerConfig
|
|
stopWorker context.CancelFunc
|
|
workers sync.WaitGroup
|
|
closeOnce sync.Once
|
|
}
|
|
|
|
// New 初始化 wallet-service。
|
|
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
|
|
}
|
|
|
|
listener, err := net.Listen("tcp", cfg.GRPCAddr)
|
|
if err != nil {
|
|
_ = repository.Close()
|
|
return nil, err
|
|
}
|
|
activityConn, err := grpc.Dial(cfg.ActivityServiceAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
|
if err != nil {
|
|
_ = listener.Close()
|
|
_ = repository.Close()
|
|
return nil, err
|
|
}
|
|
var outboxProducer *rocketmqx.Producer
|
|
if cfg.RocketMQ.WalletOutbox.Enabled {
|
|
outboxProducer, err = rocketmqx.NewProducer(rocketMQProducerConfig(cfg.RocketMQ, cfg.RocketMQ.WalletOutbox.ProducerGroup))
|
|
if err != nil {
|
|
_ = activityConn.Close()
|
|
_ = listener.Close()
|
|
_ = repository.Close()
|
|
return nil, err
|
|
}
|
|
}
|
|
var realtimeOutboxProducer *rocketmqx.Producer
|
|
if cfg.RocketMQ.RealtimeOutbox.Enabled {
|
|
realtimeOutboxProducer, err = rocketmqx.NewProducer(rocketMQProducerConfig(cfg.RocketMQ, cfg.RocketMQ.RealtimeOutbox.ProducerGroup))
|
|
if err != nil {
|
|
shutdownProducers(outboxProducer)
|
|
_ = activityConn.Close()
|
|
_ = listener.Close()
|
|
_ = repository.Close()
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
server := servicegrpc.New("wallet-service")
|
|
svc := walletservice.New(repository, client.NewActivityAchievementClient(activityConn))
|
|
svc.SetExternalRechargeConfig(walletservice.ExternalRechargeConfig{
|
|
USDTTRC20Enabled: cfg.ExternalRecharge.USDTTRC20Enabled,
|
|
USDTTRC20Address: cfg.ExternalRecharge.USDTTRC20Address,
|
|
USDTTRC20Addresses: cfg.ExternalRecharge.USDTTRC20Addresses,
|
|
USDTBEP20Enabled: cfg.ExternalRecharge.USDTBEP20Enabled,
|
|
USDTBEP20Address: cfg.ExternalRecharge.USDTBEP20Address,
|
|
USDTBEP20Addresses: cfg.ExternalRecharge.USDTBEP20Addresses,
|
|
MifaPayNotifyURL: cfg.ExternalRecharge.MifaPayNotifyURL,
|
|
MifaPayReturnURL: cfg.ExternalRecharge.MifaPayReturnURL,
|
|
V5PayNotifyURL: cfg.ExternalRecharge.V5PayNotifyURL,
|
|
V5PayReturnURL: cfg.ExternalRecharge.V5PayReturnURL,
|
|
})
|
|
if cfg.GooglePlay.Enabled {
|
|
googleClient, err := googleplay.New(cfg.GooglePlay)
|
|
if err != nil {
|
|
shutdownProducers(outboxProducer, realtimeOutboxProducer)
|
|
_ = activityConn.Close()
|
|
_ = listener.Close()
|
|
_ = repository.Close()
|
|
return nil, err
|
|
}
|
|
svc.SetGooglePlayClient(googleClient)
|
|
}
|
|
if cfg.MifaPay.Enabled {
|
|
if mifapayConfigReady(cfg.MifaPay) {
|
|
mifaPayClient, err := mifapay.New(cfg.MifaPay)
|
|
if err != nil {
|
|
shutdownProducers(outboxProducer, realtimeOutboxProducer)
|
|
_ = activityConn.Close()
|
|
_ = listener.Close()
|
|
_ = repository.Close()
|
|
return nil, err
|
|
}
|
|
svc.SetMifaPayClient(mifaPayClient)
|
|
} else {
|
|
|
|
logx.Warn(context.Background(), "mifapay_enabled_without_credentials")
|
|
}
|
|
}
|
|
if cfg.V5Pay.Enabled {
|
|
if v5payConfigReady(cfg.V5Pay) {
|
|
svc.SetV5PayClient(v5pay.New(cfg.V5Pay))
|
|
} else {
|
|
|
|
logx.Warn(context.Background(), "v5pay_enabled_without_credentials")
|
|
}
|
|
}
|
|
if cfg.TronGrid.Enabled {
|
|
svc.SetTronUSDTClient(trongrid.New(cfg.TronGrid))
|
|
}
|
|
if cfg.BSCUSDT.Enabled {
|
|
svc.SetBSCUSDTClient(bscusdt.New(cfg.BSCUSDT))
|
|
}
|
|
if cfg.Binance.Enabled {
|
|
if cfg.Binance.HasAnyAccount() {
|
|
svc.SetBinanceClient(binance.New(cfg.Binance))
|
|
} else {
|
|
logx.Warn(context.Background(), "binance_enabled_without_credentials")
|
|
}
|
|
}
|
|
walletv1.RegisterWalletServiceServer(server, grpcserver.NewServer(svc))
|
|
walletv1.RegisterWalletCronServiceServer(server, grpcserver.NewCronServer(svc))
|
|
health, healthHTTP, err := servicehealth.New(server, servicehealth.Config{
|
|
ServiceName: "wallet-service",
|
|
HTTPAddr: cfg.HealthHTTPAddr,
|
|
NodeID: cfg.NodeID,
|
|
Dependencies: []grpchealth.Dependency{{
|
|
Name: "mysql",
|
|
Check: repository.Ping,
|
|
}},
|
|
})
|
|
if err != nil {
|
|
shutdownProducers(outboxProducer, realtimeOutboxProducer)
|
|
_ = activityConn.Close()
|
|
_ = listener.Close()
|
|
_ = repository.Close()
|
|
return nil, err
|
|
}
|
|
projectionConsumer, err := newWalletProjectionConsumer(cfg, svc)
|
|
if err != nil {
|
|
shutdownProducers(outboxProducer, realtimeOutboxProducer)
|
|
_ = activityConn.Close()
|
|
_ = listener.Close()
|
|
_ = repository.Close()
|
|
return nil, err
|
|
}
|
|
|
|
return &App{
|
|
server: server,
|
|
listener: listener,
|
|
health: health,
|
|
healthHTTP: healthHTTP,
|
|
mysqlRepo: repository,
|
|
walletSvc: svc,
|
|
activityConn: activityConn,
|
|
outboxProducer: outboxProducer,
|
|
realtimeOutboxProducer: realtimeOutboxProducer,
|
|
projectionConsumer: projectionConsumer,
|
|
outboxWorkerCfg: cfg.OutboxWorker,
|
|
realtimeOutboxWorkerCfg: cfg.RealtimeOutboxWorker,
|
|
projectionWorkerCfg: cfg.ProjectionWorker,
|
|
externalRechargeReconcileWorkerCfg: cfg.ExternalRechargeReconcileWorker,
|
|
walletOutboxTopic: cfg.RocketMQ.WalletOutbox.Topic,
|
|
realtimeWalletOutboxTopic: cfg.RocketMQ.RealtimeOutbox.Topic,
|
|
nodeID: cfg.NodeID,
|
|
redPacketExpiryWorkerCfg: cfg.RedPacketExpiryWorker,
|
|
}, nil
|
|
}
|
|
|
|
// Run 启动 gRPC 服务。
|
|
func (a *App) Run() error {
|
|
a.runHealthHTTP()
|
|
if err := a.startMQ(); err != nil {
|
|
a.health.MarkStopped()
|
|
return err
|
|
}
|
|
a.runBackgroundWorkers()
|
|
a.health.MarkServing()
|
|
err := a.server.Serve(a.listener)
|
|
a.health.MarkStopped()
|
|
a.closeBackgroundWorkers()
|
|
a.shutdownMQ()
|
|
if errors.Is(err, grpc.ErrServerStopped) {
|
|
return nil
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// Close 关闭 gRPC 服务。
|
|
func (a *App) Close() {
|
|
a.closeOnce.Do(func() {
|
|
a.health.MarkDraining()
|
|
a.closeBackgroundWorkers()
|
|
servicegrpc.GracefulStop(a.server, 15*time.Second)
|
|
a.closeHealthHTTP()
|
|
if a.activityConn != nil {
|
|
_ = a.activityConn.Close()
|
|
}
|
|
a.shutdownMQ()
|
|
if a.mysqlRepo != nil {
|
|
|
|
_ = a.mysqlRepo.Close()
|
|
}
|
|
})
|
|
}
|