package services import ( "context" "fmt" "github.com/shopspring/decimal" "go.uber.org/zap" "gorm.io/gorm" "tyapi-server/internal/config" "tyapi-server/internal/domains/finance/entities" "tyapi-server/internal/domains/finance/repositories" ) // WalletAggregateService 钱包聚合服务接口 type WalletAggregateService interface { CreateWallet(ctx context.Context, userID string) (*entities.Wallet, error) Recharge(ctx context.Context, userID string, amount decimal.Decimal) error Deduct(ctx context.Context, userID string, amount decimal.Decimal, apiCallID, transactionID, productID string) error GetBalance(ctx context.Context, userID string) (decimal.Decimal, error) LoadWalletByUserId(ctx context.Context, userID string) (*entities.Wallet, error) } // WalletAggregateServiceImpl 实现 // WalletAggregateServiceImpl 钱包聚合服务实现 type WalletAggregateServiceImpl struct { db *gorm.DB walletRepo repositories.WalletRepository transactionRepo repositories.WalletTransactionRepository balanceAlertSvc BalanceAlertService logger *zap.Logger cfg *config.Config } func NewWalletAggregateService( db *gorm.DB, walletRepo repositories.WalletRepository, transactionRepo repositories.WalletTransactionRepository, balanceAlertSvc BalanceAlertService, logger *zap.Logger, cfg *config.Config, ) WalletAggregateService { return &WalletAggregateServiceImpl{ db: db, walletRepo: walletRepo, transactionRepo: transactionRepo, balanceAlertSvc: balanceAlertSvc, logger: logger, cfg: cfg, } } // CreateWallet 创建钱包 func (s *WalletAggregateServiceImpl) CreateWallet(ctx context.Context, userID string) (*entities.Wallet, error) { // 检查是否已存在 w, _ := s.walletRepo.GetByUserID(ctx, userID) if w != nil { return nil, fmt.Errorf("用户已存在钱包") } wallet := entities.NewWallet(userID, decimal.NewFromFloat(s.cfg.Wallet.DefaultCreditLimit)) created, err := s.walletRepo.Create(ctx, *wallet) if err != nil { s.logger.Error("创建钱包失败", zap.Error(err)) return nil, err } s.logger.Info("钱包创建成功", zap.String("user_id", userID), zap.String("wallet_id", created.ID)) return &created, nil } // Recharge 充值 - 使用事务确保一致性 func (s *WalletAggregateServiceImpl) Recharge(ctx context.Context, userID string, amount decimal.Decimal) error { // 使用数据库事务确保一致性 return s.db.Transaction(func(tx *gorm.DB) error { ok, err := s.walletRepo.UpdateBalanceByUserID(ctx, userID, amount, "add") if err != nil { return fmt.Errorf("更新钱包余额失败: %w", err) } if !ok { return fmt.Errorf("高并发下充值失败,请重试") } s.logger.Info("钱包充值成功", zap.String("user_id", userID), zap.String("amount", amount.String())) return nil }) } // Deduct 扣款,含欠费规则 - 使用事务确保一致性 func (s *WalletAggregateServiceImpl) Deduct(ctx context.Context, userID string, amount decimal.Decimal, apiCallID, transactionID, productID string) error { // 使用数据库事务确保一致性 return s.db.Transaction(func(tx *gorm.DB) error { // 1. 使用乐观锁更新余额(通过用户ID直接更新,避免重复查询) ok, err := s.walletRepo.UpdateBalanceByUserID(ctx, userID, amount, "subtract") if err != nil { return fmt.Errorf("更新钱包余额失败: %w", err) } if !ok { return fmt.Errorf("高并发下扣款失败,请重试") } // 2. 创建扣款记录(检查是否已存在) transaction := entities.NewWalletTransaction(userID, apiCallID, transactionID, productID, amount) if err := tx.Create(transaction).Error; err != nil { return fmt.Errorf("创建扣款记录失败: %w", err) } s.logger.Info("钱包扣款成功", zap.String("user_id", userID), zap.String("amount", amount.String()), zap.String("api_call_id", apiCallID), zap.String("transaction_id", transactionID)) // 3. 扣费成功后异步检查余额预警 go s.checkBalanceAlertAsync(context.Background(), userID) return nil }) } // GetBalance 查询余额 func (s *WalletAggregateServiceImpl) GetBalance(ctx context.Context, userID string) (decimal.Decimal, error) { w, err := s.walletRepo.GetByUserID(ctx, userID) if err != nil { return decimal.Zero, fmt.Errorf("钱包不存在") } return w.Balance, nil } func (s *WalletAggregateServiceImpl) LoadWalletByUserId(ctx context.Context, userID string) (*entities.Wallet, error) { return s.walletRepo.GetByUserID(ctx, userID) } // checkBalanceAlertAsync 异步检查余额预警 func (s *WalletAggregateServiceImpl) checkBalanceAlertAsync(ctx context.Context, userID string) { // 获取最新余额 wallet, err := s.walletRepo.GetByUserID(ctx, userID) if err != nil { s.logger.Error("获取钱包余额失败", zap.String("user_id", userID), zap.Error(err)) return } // 检查并发送预警 if err := s.balanceAlertSvc.CheckAndSendAlert(ctx, userID, wallet.Balance); err != nil { s.logger.Error("余额预警检查失败", zap.String("user_id", userID), zap.Error(err)) } }