156 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			156 lines
		
	
	
		
			5.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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))
 | ||
| 	}
 | ||
| }
 |