This commit is contained in:
liangzai
2025-07-28 01:46:39 +08:00
parent b03129667a
commit 357639462a
219 changed files with 21634 additions and 8138 deletions

View File

@@ -1,160 +0,0 @@
package services
import (
"context"
"fmt"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"tyapi-server/internal/domains/finance/entities"
"tyapi-server/internal/domains/finance/repositories"
)
// FinanceService 财务领域服务
// 负责财务相关的业务逻辑,包括钱包管理、余额操作等
type FinanceService struct {
walletRepo repositories.WalletRepository
logger *zap.Logger
}
// NewFinanceService 创建财务领域服务
func NewFinanceService(
walletRepo repositories.WalletRepository,
logger *zap.Logger,
) *FinanceService {
return &FinanceService{
walletRepo: walletRepo,
logger: logger,
}
}
// CreateWallet 创建钱包
func (s *FinanceService) CreateWallet(ctx context.Context, userID string) (*entities.Wallet, error) {
// 检查用户是否已有钱包
existingWallet, err := s.walletRepo.GetByUserID(ctx, userID)
if err == nil && existingWallet != nil {
return nil, fmt.Errorf("用户已有钱包")
}
// 创建钱包
wallet := &entities.Wallet{
UserID: userID,
Balance: decimal.Zero,
IsActive: true,
WalletType: "MAIN",
}
createdWallet, err := s.walletRepo.Create(ctx, *wallet)
if err != nil {
s.logger.Error("创建钱包失败", zap.Error(err))
return nil, fmt.Errorf("创建钱包失败: %w", err)
}
s.logger.Info("钱包创建成功",
zap.String("wallet_id", createdWallet.ID),
zap.String("user_id", userID),
)
return &createdWallet, nil
}
// GetWallet 获取钱包信息
func (s *FinanceService) GetWallet(ctx context.Context, userID string) (*entities.Wallet, error) {
wallet, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("钱包不存在: %w", err)
}
return wallet, nil
}
// GetWalletByID 根据ID获取钱包
func (s *FinanceService) GetWalletByID(ctx context.Context, walletID string) (*entities.Wallet, error) {
wallet, err := s.walletRepo.GetByID(ctx, walletID)
if err != nil {
return nil, fmt.Errorf("钱包不存在: %w", err)
}
return &wallet, nil
}
// RechargeWallet 充值钱包
func (s *FinanceService) RechargeWallet(ctx context.Context, userID string, amount float64) error {
if amount <= 0 {
return fmt.Errorf("充值金额必须大于0")
}
wallet, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return fmt.Errorf("钱包不存在: %w", err)
}
// 更新余额
amountDecimal := decimal.NewFromFloat(amount)
wallet.AddBalance(amountDecimal)
if err := s.walletRepo.Update(ctx, *wallet); err != nil {
s.logger.Error("充值失败", zap.Error(err))
return fmt.Errorf("充值失败: %w", err)
}
s.logger.Info("钱包充值成功",
zap.String("wallet_id", wallet.ID),
zap.String("user_id", userID),
zap.Float64("amount", amount),
zap.String("new_balance", wallet.GetFormattedBalance()),
)
return nil
}
// DeductWallet 扣减钱包余额
func (s *FinanceService) DeductWallet(ctx context.Context, userID string, amount float64) error {
if amount <= 0 {
return fmt.Errorf("扣减金额必须大于0")
}
wallet, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return fmt.Errorf("钱包不存在: %w", err)
}
amountDecimal := decimal.NewFromFloat(amount)
if err := wallet.SubtractBalance(amountDecimal); err != nil {
return err
}
if err := s.walletRepo.Update(ctx, *wallet); err != nil {
s.logger.Error("扣减失败", zap.Error(err))
return fmt.Errorf("扣减失败: %w", err)
}
s.logger.Info("钱包扣减成功",
zap.String("wallet_id", wallet.ID),
zap.String("user_id", userID),
zap.Float64("amount", amount),
zap.String("new_balance", wallet.GetFormattedBalance()),
)
return nil
}
// GetWalletBalance 获取钱包余额
func (s *FinanceService) GetWalletBalance(ctx context.Context, userID string) (float64, error) {
wallet, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return 0, fmt.Errorf("钱包不存在: %w", err)
}
balance, _ := wallet.Balance.Float64()
return balance, nil
}
// CheckWalletBalance 检查钱包余额是否足够
func (s *FinanceService) CheckWalletBalance(ctx context.Context, userID string, amount float64) (bool, error) {
wallet, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return false, fmt.Errorf("钱包不存在: %w", err)
}
amountDecimal := decimal.NewFromFloat(amount)
return wallet.HasSufficientBalance(amountDecimal), nil
}

View File

@@ -0,0 +1,349 @@
package services
import (
"context"
"fmt"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"tyapi-server/internal/domains/finance/entities"
"tyapi-server/internal/domains/finance/repositories"
"tyapi-server/internal/shared/database"
"tyapi-server/internal/shared/interfaces"
)
// RechargeRecordService 充值记录服务接口
type RechargeRecordService interface {
// 对公转账充值
TransferRecharge(ctx context.Context, userID string, amount decimal.Decimal, transferOrderID, notes string) (*entities.RechargeRecord, error)
// 赠送充值
GiftRecharge(ctx context.Context, userID string, amount decimal.Decimal, operatorID, notes string) (*entities.RechargeRecord, error)
// 支付宝充值
CreateAlipayRecharge(ctx context.Context, userID string, amount decimal.Decimal, alipayOrderID string) (*entities.RechargeRecord, error)
GetRechargeRecordByAlipayOrderID(ctx context.Context, alipayOrderID string) (*entities.RechargeRecord, error)
// 支付宝订单管理
CreateAlipayOrder(ctx context.Context, rechargeID, outTradeNo, subject string, amount decimal.Decimal, platform string) error
HandleAlipayPaymentSuccess(ctx context.Context, outTradeNo string, amount decimal.Decimal, tradeNo string) error
// 通用查询
GetByID(ctx context.Context, id string) (*entities.RechargeRecord, error)
GetByUserID(ctx context.Context, userID string) ([]entities.RechargeRecord, error)
GetByTransferOrderID(ctx context.Context, transferOrderID string) (*entities.RechargeRecord, error)
// 管理员查询
GetAll(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) ([]entities.RechargeRecord, error)
Count(ctx context.Context, filters map[string]interface{}) (int64, error)
}
// RechargeRecordServiceImpl 充值记录服务实现
type RechargeRecordServiceImpl struct {
rechargeRecordRepo repositories.RechargeRecordRepository
alipayOrderRepo repositories.AlipayOrderRepository
walletRepo repositories.WalletRepository
walletService WalletAggregateService
txManager *database.TransactionManager
logger *zap.Logger
}
func NewRechargeRecordService(
rechargeRecordRepo repositories.RechargeRecordRepository,
alipayOrderRepo repositories.AlipayOrderRepository,
walletRepo repositories.WalletRepository,
walletService WalletAggregateService,
txManager *database.TransactionManager,
logger *zap.Logger,
) RechargeRecordService {
return &RechargeRecordServiceImpl{
rechargeRecordRepo: rechargeRecordRepo,
alipayOrderRepo: alipayOrderRepo,
walletRepo: walletRepo,
walletService: walletService,
txManager: txManager,
logger: logger,
}
}
// TransferRecharge 对公转账充值
func (s *RechargeRecordServiceImpl) TransferRecharge(ctx context.Context, userID string, amount decimal.Decimal, transferOrderID, notes string) (*entities.RechargeRecord, error) {
// 检查钱包是否存在
_, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("钱包不存在")
}
// 检查转账订单号是否已存在
existingRecord, _ := s.rechargeRecordRepo.GetByTransferOrderID(ctx, transferOrderID)
if existingRecord != nil {
return nil, fmt.Errorf("转账订单号已存在")
}
var createdRecord entities.RechargeRecord
// 在事务中执行所有更新操作
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
// 创建充值记录
rechargeRecord := entities.NewTransferRechargeRecord(userID, amount, transferOrderID, notes)
record, err := s.rechargeRecordRepo.Create(txCtx, *rechargeRecord)
if err != nil {
s.logger.Error("创建转账充值记录失败", zap.Error(err))
return err
}
createdRecord = record
// 使用钱包聚合服务更新钱包余额
err = s.walletService.Recharge(txCtx, userID, amount)
if err != nil {
return err
}
// 标记充值记录为成功
createdRecord.MarkSuccess()
err = s.rechargeRecordRepo.Update(txCtx, createdRecord)
if err != nil {
s.logger.Error("更新充值记录状态失败", zap.Error(err))
return err
}
return nil
})
if err != nil {
return nil, err
}
s.logger.Info("对公转账充值成功",
zap.String("user_id", userID),
zap.String("amount", amount.String()),
zap.String("transfer_order_id", transferOrderID))
return &createdRecord, nil
}
// GiftRecharge 赠送充值
func (s *RechargeRecordServiceImpl) GiftRecharge(ctx context.Context, userID string, amount decimal.Decimal, operatorID, notes string) (*entities.RechargeRecord, error) {
// 检查钱包是否存在
_, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("钱包不存在")
}
var createdRecord entities.RechargeRecord
// 在事务中执行所有更新操作
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
// 创建赠送充值记录
rechargeRecord := entities.NewGiftRechargeRecord(userID, amount, notes)
record, err := s.rechargeRecordRepo.Create(txCtx, *rechargeRecord)
if err != nil {
s.logger.Error("创建赠送充值记录失败", zap.Error(err))
return err
}
createdRecord = record
// 使用钱包聚合服务更新钱包余额
err = s.walletService.Recharge(txCtx, userID, amount)
if err != nil {
return err
}
return nil
})
if err != nil {
return nil, err
}
s.logger.Info("赠送充值成功",
zap.String("user_id", userID),
zap.String("amount", amount.String()),
zap.String("notes", notes))
return &createdRecord, nil
}
// CreateAlipayRecharge 创建支付宝充值记录
func (s *RechargeRecordServiceImpl) CreateAlipayRecharge(ctx context.Context, userID string, amount decimal.Decimal, alipayOrderID string) (*entities.RechargeRecord, error) {
// 检查钱包是否存在
_, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("钱包不存在")
}
// 检查支付宝订单号是否已存在
existingRecord, _ := s.rechargeRecordRepo.GetByAlipayOrderID(ctx, alipayOrderID)
if existingRecord != nil {
return nil, fmt.Errorf("支付宝订单号已存在")
}
// 创建充值记录
rechargeRecord := entities.NewAlipayRechargeRecord(userID, amount, alipayOrderID)
createdRecord, err := s.rechargeRecordRepo.Create(ctx, *rechargeRecord)
if err != nil {
s.logger.Error("创建支付宝充值记录失败", zap.Error(err))
return nil, err
}
s.logger.Info("支付宝充值记录创建成功",
zap.String("user_id", userID),
zap.String("amount", amount.String()),
zap.String("alipay_order_id", alipayOrderID),
zap.String("recharge_id", createdRecord.ID))
return &createdRecord, nil
}
// CreateAlipayOrder 创建支付宝订单
func (s *RechargeRecordServiceImpl) CreateAlipayOrder(ctx context.Context, rechargeID, outTradeNo, subject string, amount decimal.Decimal, platform string) error {
// 检查充值记录是否存在
_, err := s.rechargeRecordRepo.GetByID(ctx, rechargeID)
if err != nil {
s.logger.Error("充值记录不存在", zap.String("recharge_id", rechargeID), zap.Error(err))
return fmt.Errorf("充值记录不存在")
}
// 检查支付宝订单号是否已存在
existingOrder, _ := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
if existingOrder != nil {
s.logger.Info("支付宝订单已存在,跳过重复创建", zap.String("out_trade_no", outTradeNo))
return nil
}
// 创建支付宝订单
alipayOrder := entities.NewAlipayOrder(rechargeID, outTradeNo, subject, amount, platform)
_, err = s.alipayOrderRepo.Create(ctx, *alipayOrder)
if err != nil {
s.logger.Error("创建支付宝订单失败", zap.Error(err))
return err
}
s.logger.Info("支付宝订单创建成功",
zap.String("recharge_id", rechargeID),
zap.String("out_trade_no", outTradeNo),
zap.String("subject", subject),
zap.String("amount", amount.String()),
zap.String("platform", platform))
return nil
}
// GetRechargeRecordByAlipayOrderID 根据支付宝订单号获取充值记录
func (s *RechargeRecordServiceImpl) GetRechargeRecordByAlipayOrderID(ctx context.Context, alipayOrderID string) (*entities.RechargeRecord, error) {
return s.rechargeRecordRepo.GetByAlipayOrderID(ctx, alipayOrderID)
}
// HandleAlipayPaymentSuccess 处理支付宝支付成功回调
func (s *RechargeRecordServiceImpl) HandleAlipayPaymentSuccess(ctx context.Context, outTradeNo string, amount decimal.Decimal, tradeNo string) error {
// 查找支付宝订单
alipayOrder, err := s.alipayOrderRepo.GetByOutTradeNo(ctx, outTradeNo)
if err != nil {
s.logger.Error("查找支付宝订单失败", zap.String("out_trade_no", outTradeNo), zap.Error(err))
return fmt.Errorf("查找支付宝订单失败: %w", err)
}
if alipayOrder == nil {
s.logger.Error("支付宝订单不存在", zap.String("out_trade_no", outTradeNo))
return fmt.Errorf("支付宝订单不存在")
}
// 检查订单状态
if alipayOrder.Status == entities.AlipayOrderStatusSuccess {
s.logger.Info("支付宝订单已处理成功,跳过重复处理",
zap.String("out_trade_no", outTradeNo),
zap.String("order_id", alipayOrder.ID),
)
return nil
}
// 查找对应的充值记录
rechargeRecord, err := s.rechargeRecordRepo.GetByID(ctx, alipayOrder.RechargeID)
if err != nil {
s.logger.Error("查找充值记录失败", zap.String("recharge_id", alipayOrder.RechargeID), zap.Error(err))
return fmt.Errorf("查找充值记录失败: %w", err)
}
// 检查充值记录状态
if rechargeRecord.Status == entities.RechargeStatusSuccess {
s.logger.Info("充值记录已处理成功,跳过重复处理",
zap.String("recharge_id", rechargeRecord.ID),
)
return nil
}
// 在事务中执行所有更新操作
err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error {
// 更新支付宝订单状态为成功
alipayOrder.MarkSuccess(tradeNo, "", "", amount, amount)
err := s.alipayOrderRepo.Update(txCtx, *alipayOrder)
if err != nil {
s.logger.Error("更新支付宝订单状态失败", zap.Error(err))
return err
}
// 更新充值记录状态为成功
rechargeRecord.MarkSuccess()
err = s.rechargeRecordRepo.Update(txCtx, rechargeRecord)
if err != nil {
s.logger.Error("更新充值记录状态失败", zap.Error(err))
return err
}
// 使用钱包聚合服务更新钱包余额
err = s.walletService.Recharge(txCtx, rechargeRecord.UserID, amount)
if err != nil {
s.logger.Error("更新钱包余额失败", zap.String("user_id", rechargeRecord.UserID), zap.Error(err))
return err
}
return nil
})
if err != nil {
return err
}
s.logger.Info("支付宝支付成功回调处理成功",
zap.String("user_id", rechargeRecord.UserID),
zap.String("amount", amount.String()),
zap.String("out_trade_no", outTradeNo),
zap.String("trade_no", tradeNo),
zap.String("recharge_id", rechargeRecord.ID),
zap.String("order_id", alipayOrder.ID))
return nil
}
// GetByID 根据ID获取充值记录
func (s *RechargeRecordServiceImpl) GetByID(ctx context.Context, id string) (*entities.RechargeRecord, error) {
record, err := s.rechargeRecordRepo.GetByID(ctx, id)
if err != nil {
return nil, err
}
return &record, nil
}
// GetByUserID 根据用户ID获取充值记录列表
func (s *RechargeRecordServiceImpl) GetByUserID(ctx context.Context, userID string) ([]entities.RechargeRecord, error) {
return s.rechargeRecordRepo.GetByUserID(ctx, userID)
}
// GetByTransferOrderID 根据转账订单号获取充值记录
func (s *RechargeRecordServiceImpl) GetByTransferOrderID(ctx context.Context, transferOrderID string) (*entities.RechargeRecord, error) {
return s.rechargeRecordRepo.GetByTransferOrderID(ctx, transferOrderID)
}
// GetAll 获取所有充值记录(管理员功能)
func (s *RechargeRecordServiceImpl) GetAll(ctx context.Context, filters map[string]interface{}, options interfaces.ListOptions) ([]entities.RechargeRecord, error) {
return s.rechargeRecordRepo.List(ctx, options)
}
// Count 统计充值记录数量(管理员功能)
func (s *RechargeRecordServiceImpl) Count(ctx context.Context, filters map[string]interface{}) (int64, error) {
countOptions := interfaces.CountOptions{
Filters: filters,
}
return s.rechargeRecordRepo.Count(ctx, countOptions)
}

View File

@@ -0,0 +1,142 @@
package services
import (
"context"
"fmt"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"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 {
walletRepo repositories.WalletRepository
transactionRepo repositories.WalletTransactionRepository
logger *zap.Logger
cfg *config.Config
}
func NewWalletAggregateService(
walletRepo repositories.WalletRepository,
transactionRepo repositories.WalletTransactionRepository,
logger *zap.Logger,
cfg *config.Config,
) WalletAggregateService {
return &WalletAggregateServiceImpl{
walletRepo: walletRepo,
transactionRepo: transactionRepo,
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 {
w, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return fmt.Errorf("钱包不存在")
}
// 更新钱包余额
w.AddBalance(amount)
ok, err := s.walletRepo.UpdateBalanceWithVersion(ctx, w.ID, w.Balance.String(), w.Version)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("高并发下充值失败,请重试")
}
s.logger.Info("钱包充值成功",
zap.String("user_id", userID),
zap.String("wallet_id", w.ID),
zap.String("amount", amount.String()),
zap.String("balance_after", w.Balance.String()))
return nil
}
// Deduct 扣款,含欠费规则
func (s *WalletAggregateServiceImpl) Deduct(ctx context.Context, userID string, amount decimal.Decimal, apiCallID, transactionID, productID string) error {
w, err := s.walletRepo.GetByUserID(ctx, userID)
if err != nil {
return fmt.Errorf("钱包不存在")
}
// 扣减余额
if err := w.SubtractBalance(amount); err != nil {
return err
}
// 更新钱包余额
ok, err := s.walletRepo.UpdateBalanceWithVersion(ctx, w.ID, w.Balance.String(), w.Version)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("高并发下扣款失败,请重试")
}
// 创建扣款记录
transaction := entities.NewWalletTransaction(userID, apiCallID, transactionID, productID, amount)
_, err = s.transactionRepo.Create(ctx, *transaction)
if err != nil {
s.logger.Error("创建扣款记录失败", zap.Error(err))
// 不返回错误,因为钱包余额已经更新成功
}
s.logger.Info("钱包扣款成功",
zap.String("user_id", userID),
zap.String("wallet_id", w.ID),
zap.String("amount", amount.String()),
zap.String("balance_after", w.Balance.String()),
zap.String("api_call_id", apiCallID))
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)
}