基础架构
This commit is contained in:
@@ -1,470 +1,24 @@
|
||||
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 财务服务
|
||||
// FinanceService 财务领域服务
|
||||
type FinanceService struct {
|
||||
walletRepo repositories.WalletRepository
|
||||
userSecretsRepo repositories.UserSecretsRepository
|
||||
responseBuilder interfaces.ResponseBuilder
|
||||
logger *zap.Logger
|
||||
walletRepo repositories.WalletRepository
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewFinanceService 创建财务服务
|
||||
// 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,
|
||||
walletRepo: walletRepo,
|
||||
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[:])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user