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