Files
tyapi-server/internal/domains/finance/services/finance_service.go

471 lines
13 KiB
Go
Raw Normal View History

2025-07-11 21:05:58 +08:00
package services
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"fmt"
"time"
"github.com/shopspring/decimal"
"go.uber.org/zap"
"tyapi-server/internal/domains/finance/dto"
"tyapi-server/internal/domains/finance/entities"
"tyapi-server/internal/domains/finance/repositories"
"tyapi-server/internal/shared/interfaces"
)
// FinanceService 财务服务
type FinanceService struct {
walletRepo repositories.WalletRepository
userSecretsRepo repositories.UserSecretsRepository
responseBuilder interfaces.ResponseBuilder
logger *zap.Logger
}
// NewFinanceService 创建财务服务
func NewFinanceService(
walletRepo repositories.WalletRepository,
userSecretsRepo repositories.UserSecretsRepository,
responseBuilder interfaces.ResponseBuilder,
logger *zap.Logger,
) *FinanceService {
return &FinanceService{
walletRepo: walletRepo,
userSecretsRepo: userSecretsRepo,
responseBuilder: responseBuilder,
logger: logger,
}
}
// CreateWallet 创建钱包
func (s *FinanceService) CreateWallet(ctx context.Context, req *dto.CreateWalletRequest) (*dto.CreateWalletResponse, error) {
s.logger.Info("创建钱包", zap.String("user_id", req.UserID))
// 检查用户是否已有钱包
exists, err := s.walletRepo.ExistsByUserID(ctx, req.UserID)
if err != nil {
return nil, fmt.Errorf("检查钱包存在性失败: %w", err)
}
if exists {
return nil, fmt.Errorf("用户已存在钱包")
}
// 创建钱包
wallet := entities.Wallet{
ID: s.generateID(),
UserID: req.UserID,
IsActive: true,
Balance: decimal.Zero,
}
if err := s.walletRepo.Create(ctx, wallet); err != nil {
return nil, fmt.Errorf("创建钱包失败: %w", err)
}
// 构建响应
walletInfo := dto.WalletInfo{
ID: wallet.ID,
UserID: wallet.UserID,
IsActive: wallet.IsActive,
Balance: wallet.Balance,
CreatedAt: wallet.CreatedAt,
UpdatedAt: wallet.UpdatedAt,
}
s.logger.Info("钱包创建成功", zap.String("wallet_id", wallet.ID))
return &dto.CreateWalletResponse{Wallet: walletInfo}, nil
}
// GetWallet 获取钱包信息
func (s *FinanceService) GetWallet(ctx context.Context, userID string) (*dto.WalletInfo, error) {
s.logger.Info("获取钱包信息", zap.String("user_id", userID))
wallet, err := s.walletRepo.FindByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("钱包不存在")
}
walletInfo := dto.WalletInfo{
ID: wallet.ID,
UserID: wallet.UserID,
IsActive: wallet.IsActive,
Balance: wallet.Balance,
CreatedAt: wallet.CreatedAt,
UpdatedAt: wallet.UpdatedAt,
}
return &walletInfo, nil
}
// UpdateWallet 更新钱包
func (s *FinanceService) UpdateWallet(ctx context.Context, req *dto.UpdateWalletRequest) error {
s.logger.Info("更新钱包", zap.String("user_id", req.UserID))
wallet, err := s.walletRepo.FindByUserID(ctx, req.UserID)
if err != nil {
return fmt.Errorf("钱包不存在")
}
// 更新字段
if !req.Balance.IsZero() {
wallet.Balance = req.Balance
}
if req.IsActive != nil {
wallet.IsActive = *req.IsActive
}
if err := s.walletRepo.Update(ctx, *wallet); err != nil {
return fmt.Errorf("更新钱包失败: %w", err)
}
s.logger.Info("钱包更新成功", zap.String("user_id", req.UserID))
return nil
}
// Recharge 充值
func (s *FinanceService) Recharge(ctx context.Context, req *dto.RechargeRequest) (*dto.RechargeResponse, error) {
s.logger.Info("钱包充值", zap.String("user_id", req.UserID), zap.String("amount", req.Amount.String()))
// 验证金额
if req.Amount.LessThanOrEqual(decimal.Zero) {
return nil, fmt.Errorf("充值金额必须大于0")
}
// 获取钱包
wallet, err := s.walletRepo.FindByUserID(ctx, req.UserID)
if err != nil {
return nil, fmt.Errorf("钱包不存在")
}
// 检查钱包状态
if !wallet.IsActive {
return nil, fmt.Errorf("钱包已被禁用")
}
// 增加余额
if err := s.walletRepo.AddBalance(ctx, req.UserID, req.Amount); err != nil {
return nil, fmt.Errorf("充值失败: %w", err)
}
// 获取更新后的余额
updatedWallet, err := s.walletRepo.FindByUserID(ctx, req.UserID)
if err != nil {
return nil, fmt.Errorf("获取更新后余额失败: %w", err)
}
s.logger.Info("充值成功", zap.String("user_id", req.UserID), zap.String("amount", req.Amount.String()))
return &dto.RechargeResponse{
WalletID: updatedWallet.ID,
Amount: req.Amount,
Balance: updatedWallet.Balance,
}, nil
}
// Withdraw 提现
func (s *FinanceService) Withdraw(ctx context.Context, req *dto.WithdrawRequest) (*dto.WithdrawResponse, error) {
s.logger.Info("钱包提现", zap.String("user_id", req.UserID), zap.String("amount", req.Amount.String()))
// 验证金额
if req.Amount.LessThanOrEqual(decimal.Zero) {
return nil, fmt.Errorf("提现金额必须大于0")
}
// 获取钱包
wallet, err := s.walletRepo.FindByUserID(ctx, req.UserID)
if err != nil {
return nil, fmt.Errorf("钱包不存在")
}
// 检查钱包状态
if !wallet.IsActive {
return nil, fmt.Errorf("钱包已被禁用")
}
// 检查余额是否足够
if wallet.Balance.LessThan(req.Amount) {
return nil, fmt.Errorf("余额不足")
}
// 减少余额
if err := s.walletRepo.SubtractBalance(ctx, req.UserID, req.Amount); err != nil {
return nil, fmt.Errorf("提现失败: %w", err)
}
// 获取更新后的余额
updatedWallet, err := s.walletRepo.FindByUserID(ctx, req.UserID)
if err != nil {
return nil, fmt.Errorf("获取更新后余额失败: %w", err)
}
s.logger.Info("提现成功", zap.String("user_id", req.UserID), zap.String("amount", req.Amount.String()))
return &dto.WithdrawResponse{
WalletID: updatedWallet.ID,
Amount: req.Amount,
Balance: updatedWallet.Balance,
}, nil
}
// CreateUserSecrets 创建用户密钥
func (s *FinanceService) CreateUserSecrets(ctx context.Context, req *dto.CreateUserSecretsRequest) (*dto.CreateUserSecretsResponse, error) {
s.logger.Info("创建用户密钥", zap.String("user_id", req.UserID))
// 检查用户是否已有密钥
exists, err := s.userSecretsRepo.ExistsByUserID(ctx, req.UserID)
if err != nil {
return nil, fmt.Errorf("检查密钥存在性失败: %w", err)
}
if exists {
return nil, fmt.Errorf("用户已存在密钥")
}
// 生成访问ID和密钥
accessID := s.generateAccessID()
accessKey := s.generateAccessKey()
// 创建密钥
secrets := entities.UserSecrets{
ID: s.generateID(),
UserID: req.UserID,
AccessID: accessID,
AccessKey: accessKey,
IsActive: true,
ExpiresAt: req.ExpiresAt,
}
if err := s.userSecretsRepo.Create(ctx, secrets); err != nil {
return nil, fmt.Errorf("创建密钥失败: %w", err)
}
// 构建响应
secretsInfo := dto.UserSecretsInfo{
ID: secrets.ID,
UserID: secrets.UserID,
AccessID: secrets.AccessID,
AccessKey: secrets.AccessKey,
IsActive: secrets.IsActive,
LastUsedAt: secrets.LastUsedAt,
ExpiresAt: secrets.ExpiresAt,
CreatedAt: secrets.CreatedAt,
UpdatedAt: secrets.UpdatedAt,
}
s.logger.Info("用户密钥创建成功", zap.String("user_id", req.UserID))
return &dto.CreateUserSecretsResponse{Secrets: secretsInfo}, nil
}
// GetUserSecrets 获取用户密钥
func (s *FinanceService) GetUserSecrets(ctx context.Context, userID string) (*dto.UserSecretsInfo, error) {
s.logger.Info("获取用户密钥", zap.String("user_id", userID))
secrets, err := s.userSecretsRepo.FindByUserID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("密钥不存在")
}
secretsInfo := dto.UserSecretsInfo{
ID: secrets.ID,
UserID: secrets.UserID,
AccessID: secrets.AccessID,
AccessKey: secrets.AccessKey,
IsActive: secrets.IsActive,
LastUsedAt: secrets.LastUsedAt,
ExpiresAt: secrets.ExpiresAt,
CreatedAt: secrets.CreatedAt,
UpdatedAt: secrets.UpdatedAt,
}
return &secretsInfo, nil
}
// RegenerateAccessKey 重新生成访问密钥
func (s *FinanceService) RegenerateAccessKey(ctx context.Context, req *dto.RegenerateAccessKeyRequest) (*dto.RegenerateAccessKeyResponse, error) {
s.logger.Info("重新生成访问密钥", zap.String("user_id", req.UserID))
// 检查密钥是否存在
secrets, err := s.userSecretsRepo.FindByUserID(ctx, req.UserID)
if err != nil {
return nil, fmt.Errorf("密钥不存在")
}
// 生成新的访问ID和密钥
newAccessID := s.generateAccessID()
newAccessKey := s.generateAccessKey()
// 更新密钥
if err := s.userSecretsRepo.RegenerateAccessKey(ctx, req.UserID, newAccessID, newAccessKey); err != nil {
return nil, fmt.Errorf("重新生成密钥失败: %w", err)
}
// 更新过期时间
if req.ExpiresAt != nil {
secrets.ExpiresAt = req.ExpiresAt
if err := s.userSecretsRepo.Update(ctx, *secrets); err != nil {
s.logger.Error("更新密钥过期时间失败", zap.Error(err))
}
}
s.logger.Info("访问密钥重新生成成功", zap.String("user_id", req.UserID))
return &dto.RegenerateAccessKeyResponse{
AccessID: newAccessID,
AccessKey: newAccessKey,
}, nil
}
// DeactivateUserSecrets 停用用户密钥
func (s *FinanceService) DeactivateUserSecrets(ctx context.Context, req *dto.DeactivateUserSecretsRequest) error {
s.logger.Info("停用用户密钥", zap.String("user_id", req.UserID))
// 检查密钥是否存在
if _, err := s.userSecretsRepo.FindByUserID(ctx, req.UserID); err != nil {
return fmt.Errorf("密钥不存在")
}
// 停用密钥
if err := s.userSecretsRepo.DeactivateByUserID(ctx, req.UserID); err != nil {
return fmt.Errorf("停用密钥失败: %w", err)
}
s.logger.Info("用户密钥停用成功", zap.String("user_id", req.UserID))
return nil
}
// WalletTransaction 钱包交易
func (s *FinanceService) WalletTransaction(ctx context.Context, req *dto.WalletTransactionRequest) (*dto.WalletTransactionResponse, error) {
s.logger.Info("钱包交易",
zap.String("from_user_id", req.FromUserID),
zap.String("to_user_id", req.ToUserID),
zap.String("amount", req.Amount.String()))
// 验证金额
if req.Amount.LessThanOrEqual(decimal.Zero) {
return nil, fmt.Errorf("交易金额必须大于0")
}
// 验证用户不能给自己转账
if req.FromUserID == req.ToUserID {
return nil, fmt.Errorf("不能给自己转账")
}
// 获取转出钱包
fromWallet, err := s.walletRepo.FindByUserID(ctx, req.FromUserID)
if err != nil {
return nil, fmt.Errorf("转出钱包不存在")
}
// 获取转入钱包
toWallet, err := s.walletRepo.FindByUserID(ctx, req.ToUserID)
if err != nil {
return nil, fmt.Errorf("转入钱包不存在")
}
// 检查钱包状态
if !fromWallet.IsActive {
return nil, fmt.Errorf("转出钱包已被禁用")
}
if !toWallet.IsActive {
return nil, fmt.Errorf("转入钱包已被禁用")
}
// 检查余额是否足够
if fromWallet.Balance.LessThan(req.Amount) {
return nil, fmt.Errorf("余额不足")
}
// 执行交易(使用事务)
// 这里简化处理,实际应该使用数据库事务
if err := s.walletRepo.SubtractBalance(ctx, req.FromUserID, req.Amount); err != nil {
return nil, fmt.Errorf("扣款失败: %w", err)
}
if err := s.walletRepo.AddBalance(ctx, req.ToUserID, req.Amount); err != nil {
return nil, fmt.Errorf("入账失败: %w", err)
}
// 获取更新后的余额
updatedFromWallet, err := s.walletRepo.FindByUserID(ctx, req.FromUserID)
if err != nil {
return nil, fmt.Errorf("获取转出后余额失败: %w", err)
}
updatedToWallet, err := s.walletRepo.FindByUserID(ctx, req.ToUserID)
if err != nil {
return nil, fmt.Errorf("获取转入后余额失败: %w", err)
}
s.logger.Info("钱包交易成功",
zap.String("from_user_id", req.FromUserID),
zap.String("to_user_id", req.ToUserID),
zap.String("amount", req.Amount.String()))
return &dto.WalletTransactionResponse{
TransactionID: s.generateID(),
FromUserID: req.FromUserID,
ToUserID: req.ToUserID,
Amount: req.Amount,
FromBalance: updatedFromWallet.Balance,
ToBalance: updatedToWallet.Balance,
Notes: req.Notes,
CreatedAt: time.Now(),
}, nil
}
// GetWalletStats 获取钱包统计
func (s *FinanceService) GetWalletStats(ctx context.Context) (*dto.WalletStatsResponse, error) {
s.logger.Info("获取钱包统计")
// 获取总钱包数
totalWallets, err := s.walletRepo.Count(ctx, interfaces.CountOptions{})
if err != nil {
return nil, fmt.Errorf("获取总钱包数失败: %w", err)
}
// 获取激活钱包数
activeWallets, err := s.walletRepo.GetActiveWalletCount(ctx)
if err != nil {
return nil, fmt.Errorf("获取激活钱包数失败: %w", err)
}
// 获取总余额
totalBalance, err := s.walletRepo.GetTotalBalance(ctx)
if err != nil {
return nil, fmt.Errorf("获取总余额失败: %w", err)
}
// 这里简化处理,实际应该查询交易记录表
todayTransactions := int64(0)
todayVolume := decimal.Zero
return &dto.WalletStatsResponse{
TotalWallets: totalWallets,
ActiveWallets: activeWallets,
TotalBalance: totalBalance.(decimal.Decimal),
TodayTransactions: todayTransactions,
TodayVolume: todayVolume,
}, nil
}
// generateID 生成ID
func (s *FinanceService) generateID() string {
bytes := make([]byte, 16)
rand.Read(bytes)
return hex.EncodeToString(bytes)
}
// generateAccessID 生成访问ID
func (s *FinanceService) generateAccessID() string {
bytes := make([]byte, 20)
rand.Read(bytes)
return hex.EncodeToString(bytes)
}
// generateAccessKey 生成访问密钥
func (s *FinanceService) generateAccessKey() string {
bytes := make([]byte, 32)
rand.Read(bytes)
hash := sha256.Sum256(bytes)
return hex.EncodeToString(hash[:])
}