| 
									
										
										
										
											2025-07-28 01:46:39 +08:00
										 |  |  |  | package services | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | import ( | 
					
						
							|  |  |  |  | 	"context" | 
					
						
							|  |  |  |  | 	"fmt" | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | 	"github.com/shopspring/decimal" | 
					
						
							|  |  |  |  | 	"go.uber.org/zap" | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  |  | 	"gorm.io/gorm" | 
					
						
							| 
									
										
										
										
											2025-07-28 01:46:39 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 	"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 { | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  |  | 	db              *gorm.DB | 
					
						
							| 
									
										
										
										
											2025-07-28 01:46:39 +08:00
										 |  |  |  | 	walletRepo      repositories.WalletRepository | 
					
						
							|  |  |  |  | 	transactionRepo repositories.WalletTransactionRepository | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  |  | 	balanceAlertSvc BalanceAlertService | 
					
						
							| 
									
										
										
										
											2025-07-28 01:46:39 +08:00
										 |  |  |  | 	logger          *zap.Logger | 
					
						
							|  |  |  |  | 	cfg             *config.Config | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | func NewWalletAggregateService( | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  |  | 	db *gorm.DB, | 
					
						
							|  |  |  |  | 	walletRepo repositories.WalletRepository, | 
					
						
							|  |  |  |  | 	transactionRepo repositories.WalletTransactionRepository, | 
					
						
							|  |  |  |  | 	balanceAlertSvc BalanceAlertService, | 
					
						
							|  |  |  |  | 	logger *zap.Logger, | 
					
						
							| 
									
										
										
										
											2025-07-28 01:46:39 +08:00
										 |  |  |  | 	cfg *config.Config, | 
					
						
							|  |  |  |  | ) WalletAggregateService { | 
					
						
							|  |  |  |  | 	return &WalletAggregateServiceImpl{ | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  |  | 		db:              db, | 
					
						
							| 
									
										
										
										
											2025-07-28 01:46:39 +08:00
										 |  |  |  | 		walletRepo:      walletRepo, | 
					
						
							|  |  |  |  | 		transactionRepo: transactionRepo, | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  |  | 		balanceAlertSvc: balanceAlertSvc, | 
					
						
							| 
									
										
										
										
											2025-07-28 01:46:39 +08:00
										 |  |  |  | 		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 | 
					
						
							|  |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  |  | // Recharge 充值 - 使用事务确保一致性 | 
					
						
							| 
									
										
										
										
											2025-07-28 01:46:39 +08:00
										 |  |  |  | func (s *WalletAggregateServiceImpl) Recharge(ctx context.Context, userID string, amount decimal.Decimal) error { | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  |  | 	// 使用数据库事务确保一致性 | 
					
						
							|  |  |  |  | 	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 | 
					
						
							|  |  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2025-07-28 01:46:39 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  |  | // 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 | 
					
						
							|  |  |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2025-07-28 01:46:39 +08:00
										 |  |  |  | } | 
					
						
							|  |  |  |  | 
 | 
					
						
							|  |  |  |  | // 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) | 
					
						
							|  |  |  |  | } | 
					
						
							| 
									
										
										
										
											2025-09-12 01:15:09 +08:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | // 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)) | 
					
						
							|  |  |  |  | 	} | 
					
						
							|  |  |  |  | } |