471 lines
13 KiB
Go
471 lines
13 KiB
Go
|
|
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[:])
|
||
|
|
}
|