Files
tyapi-server/internal/domains/finance/services/wallet_aggregate_service.go
2025-09-12 01:15:09 +08:00

156 lines
5.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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))
}
}