This commit is contained in:
2025-09-12 01:15:09 +08:00
parent c563b2266b
commit e05ad9e223
103 changed files with 20034 additions and 1041 deletions

View File

@@ -3,6 +3,8 @@ package repositories
import (
"context"
"errors"
"strings"
"time"
"tyapi-server/internal/domains/finance/entities"
domain_finance_repo "tyapi-server/internal/domains/finance/repositories"
"tyapi-server/internal/shared/database"
@@ -110,7 +112,14 @@ func (r *GormRechargeRecordRepository) List(ctx context.Context, options interfa
if options.Filters != nil {
for key, value := range options.Filters {
query = query.Where(key+" = ?", value)
// 特殊处理 user_ids 过滤器
if key == "user_ids" {
if userIds, ok := value.(string); ok && userIds != "" {
query = query.Where("user_id IN ?", strings.Split(userIds, ","))
}
} else {
query = query.Where(key+" = ?", value)
}
}
}
@@ -175,4 +184,144 @@ func (r *GormRechargeRecordRepository) SoftDelete(ctx context.Context, id string
func (r *GormRechargeRecordRepository) Restore(ctx context.Context, id string) error {
return r.RestoreEntity(ctx, id, &entities.RechargeRecord{})
}
}
// GetTotalAmountByUserId 获取用户总充值金额
func (r *GormRechargeRecordRepository) GetTotalAmountByUserId(ctx context.Context, userId string) (float64, error) {
var total float64
err := r.GetDB(ctx).Model(&entities.RechargeRecord{}).
Select("COALESCE(SUM(amount), 0)").
Where("user_id = ? AND status = ?", userId, entities.RechargeStatusSuccess).
Scan(&total).Error
return total, err
}
// GetTotalAmountByUserIdAndDateRange 按用户ID和日期范围获取总充值金额
func (r *GormRechargeRecordRepository) GetTotalAmountByUserIdAndDateRange(ctx context.Context, userId string, startDate, endDate time.Time) (float64, error) {
var total float64
err := r.GetDB(ctx).Model(&entities.RechargeRecord{}).
Select("COALESCE(SUM(amount), 0)").
Where("user_id = ? AND status = ? AND created_at >= ? AND created_at < ?", userId, entities.RechargeStatusSuccess, startDate, endDate).
Scan(&total).Error
return total, err
}
// GetDailyStatsByUserId 获取用户每日充值统计
func (r *GormRechargeRecordRepository) GetDailyStatsByUserId(ctx context.Context, userId string, startDate, endDate time.Time) ([]map[string]interface{}, error) {
var results []map[string]interface{}
// 构建SQL查询 - 使用PostgreSQL语法使用具体的日期范围
sql := `
SELECT
DATE(created_at) as date,
COALESCE(SUM(amount), 0) as amount
FROM recharge_records
WHERE user_id = $1
AND status = $2
AND DATE(created_at) >= $3
AND DATE(created_at) <= $4
GROUP BY DATE(created_at)
ORDER BY date ASC
`
err := r.GetDB(ctx).Raw(sql, userId, entities.RechargeStatusSuccess, startDate.Format("2006-01-02"), endDate.Format("2006-01-02")).Scan(&results).Error
if err != nil {
return nil, err
}
return results, nil
}
// GetMonthlyStatsByUserId 获取用户每月充值统计
func (r *GormRechargeRecordRepository) GetMonthlyStatsByUserId(ctx context.Context, userId string, startDate, endDate time.Time) ([]map[string]interface{}, error) {
var results []map[string]interface{}
// 构建SQL查询 - 使用PostgreSQL语法使用具体的日期范围
sql := `
SELECT
TO_CHAR(created_at, 'YYYY-MM') as month,
COALESCE(SUM(amount), 0) as amount
FROM recharge_records
WHERE user_id = $1
AND status = $2
AND created_at >= $3
AND created_at <= $4
GROUP BY TO_CHAR(created_at, 'YYYY-MM')
ORDER BY month ASC
`
err := r.GetDB(ctx).Raw(sql, userId, entities.RechargeStatusSuccess, startDate, endDate).Scan(&results).Error
if err != nil {
return nil, err
}
return results, nil
}
// GetSystemTotalAmount 获取系统总充值金额
func (r *GormRechargeRecordRepository) GetSystemTotalAmount(ctx context.Context) (float64, error) {
var total float64
err := r.GetDB(ctx).Model(&entities.RechargeRecord{}).
Where("status = ?", entities.RechargeStatusSuccess).
Select("COALESCE(SUM(amount), 0)").
Scan(&total).Error
return total, err
}
// GetSystemAmountByDateRange 获取系统指定时间范围内的充值金额
func (r *GormRechargeRecordRepository) GetSystemAmountByDateRange(ctx context.Context, startDate, endDate time.Time) (float64, error) {
var total float64
err := r.GetDB(ctx).Model(&entities.RechargeRecord{}).
Where("status = ? AND created_at >= ? AND created_at <= ?", entities.RechargeStatusSuccess, startDate, endDate).
Select("COALESCE(SUM(amount), 0)").
Scan(&total).Error
return total, err
}
// GetSystemDailyStats 获取系统每日充值统计
func (r *GormRechargeRecordRepository) GetSystemDailyStats(ctx context.Context, startDate, endDate time.Time) ([]map[string]interface{}, error) {
var results []map[string]interface{}
sql := `
SELECT
DATE(created_at) as date,
COALESCE(SUM(amount), 0) as amount
FROM recharge_records
WHERE status = $1
AND DATE(created_at) >= $2
AND DATE(created_at) <= $3
GROUP BY DATE(created_at)
ORDER BY date ASC
`
err := r.GetDB(ctx).Raw(sql, entities.RechargeStatusSuccess, startDate.Format("2006-01-02"), endDate.Format("2006-01-02")).Scan(&results).Error
if err != nil {
return nil, err
}
return results, nil
}
// GetSystemMonthlyStats 获取系统每月充值统计
func (r *GormRechargeRecordRepository) GetSystemMonthlyStats(ctx context.Context, startDate, endDate time.Time) ([]map[string]interface{}, error) {
var results []map[string]interface{}
sql := `
SELECT
TO_CHAR(created_at, 'YYYY-MM') as month,
COALESCE(SUM(amount), 0) as amount
FROM recharge_records
WHERE status = $1
AND created_at >= $2
AND created_at <= $3
GROUP BY TO_CHAR(created_at, 'YYYY-MM')
ORDER BY month ASC
`
err := r.GetDB(ctx).Raw(sql, entities.RechargeStatusSuccess, startDate, endDate).Scan(&results).Error
if err != nil {
return nil, err
}
return results, nil
}

View File

@@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"time"
"tyapi-server/internal/domains/finance/entities"
domain_finance_repo "tyapi-server/internal/domains/finance/repositories"
"tyapi-server/internal/shared/database"
@@ -184,31 +185,102 @@ func (r *GormWalletRepository) GetByUserID(ctx context.Context, userID string) (
return &wallet, nil
}
// UpdateBalanceWithVersionRetry 乐观锁自动重试最大重试maxRetry次
func (r *GormWalletRepository) UpdateBalanceWithVersion(ctx context.Context, walletID string, newBalance string, oldVersion int64) (bool, error) {
// UpdateBalanceWithVersion 乐观锁自动重试最大重试maxRetry次
func (r *GormWalletRepository) UpdateBalanceWithVersion(ctx context.Context, walletID string, amount decimal.Decimal, operation string) (bool, error) {
maxRetry := 10
for i := 0; i < maxRetry; i++ {
result := r.GetDB(ctx).Model(&entities.Wallet{}).
Where("id = ? AND version = ?", walletID, oldVersion).
Updates(map[string]interface{}{
"balance": newBalance,
"version": oldVersion + 1,
})
if result.Error != nil {
return false, result.Error
}
if result.RowsAffected == 1 {
return true, nil
}
// 并发冲突重试前重新查version
// 每次重试都重新获取最新的钱包信息
var wallet entities.Wallet
err := r.GetDB(ctx).Where("id = ?", walletID).First(&wallet).Error
if err != nil {
return false, err
return false, fmt.Errorf("获取钱包信息失败: %w", err)
}
oldVersion = wallet.Version
// 重新计算新余额
var newBalance decimal.Decimal
switch operation {
case "add":
newBalance = wallet.Balance.Add(amount)
case "subtract":
newBalance = wallet.Balance.Sub(amount)
default:
return false, fmt.Errorf("不支持的操作类型: %s", operation)
}
// 乐观锁更新
result := r.GetDB(ctx).Model(&entities.Wallet{}).
Where("id = ? AND version = ?", walletID, wallet.Version).
Updates(map[string]interface{}{
"balance": newBalance.String(),
"version": wallet.Version + 1,
})
if result.Error != nil {
return false, fmt.Errorf("更新钱包余额失败: %w", result.Error)
}
if result.RowsAffected == 1 {
return true, nil
}
// 乐观锁冲突,继续重试
// 注意这里可以添加日志记录但需要确保logger可用
}
return false, fmt.Errorf("高并发下余额变动失败,请重试")
return false, fmt.Errorf("高并发下余额变动失败,已达到最大重试次数 %d", maxRetry)
}
// UpdateBalanceByUserID 乐观锁更新通过用户ID直接更新使用原生SQL
func (r *GormWalletRepository) UpdateBalanceByUserID(ctx context.Context, userID string, amount decimal.Decimal, operation string) (bool, error) {
maxRetry := 20 // 增加重试次数
baseDelay := 1 // 基础延迟毫秒
for i := 0; i < maxRetry; i++ {
// 每次重试都重新获取最新的钱包信息
var wallet entities.Wallet
err := r.GetDB(ctx).Where("user_id = ?", userID).First(&wallet).Error
if err != nil {
return false, fmt.Errorf("获取钱包信息失败: %w", err)
}
// 重新计算新余额
var newBalance decimal.Decimal
switch operation {
case "add":
newBalance = wallet.Balance.Add(amount)
case "subtract":
newBalance = wallet.Balance.Sub(amount)
default:
return false, fmt.Errorf("不支持的操作类型: %s", operation)
}
// 使用原生SQL进行乐观锁更新
newVersion := wallet.Version + 1
result := r.GetDB(ctx).Exec(`
UPDATE wallets
SET balance = ?, version = ?, updated_at = NOW()
WHERE user_id = ? AND version = ?
`, newBalance.String(), newVersion, userID, wallet.Version)
if result.Error != nil {
return false, fmt.Errorf("更新钱包余额失败: %w", result.Error)
}
if result.RowsAffected == 1 {
return true, nil
}
// 乐观锁冲突,添加指数退避延迟
if i < maxRetry-1 {
delay := baseDelay * (1 << i) // 指数退避: 1ms, 2ms, 4ms, 8ms...
if delay > 50 {
delay = 50 // 最大延迟50ms
}
time.Sleep(time.Duration(delay) * time.Millisecond)
}
}
return false, fmt.Errorf("高并发下余额变动失败,已达到最大重试次数 %d", maxRetry)
}
func (r *GormWalletRepository) UpdateBalance(ctx context.Context, walletID string, balance string) error {

View File

@@ -2,6 +2,7 @@ package repositories
import (
"context"
"strings"
"time"
"tyapi-server/internal/domains/finance/entities"
domain_finance_repo "tyapi-server/internal/domains/finance/repositories"
@@ -150,6 +151,81 @@ func (r *GormWalletTransactionRepository) CountByUserId(ctx context.Context, use
return r.CountWhere(ctx, &entities.WalletTransaction{}, "user_id = ?", userId)
}
// CountByUserIdAndDateRange 按用户ID和日期范围统计钱包交易次数
func (r *GormWalletTransactionRepository) CountByUserIdAndDateRange(ctx context.Context, userId string, startDate, endDate time.Time) (int64, error) {
return r.CountWhere(ctx, &entities.WalletTransaction{}, "user_id = ? AND created_at >= ? AND created_at < ?", userId, startDate, endDate)
}
// GetTotalAmountByUserId 获取用户总消费金额
func (r *GormWalletTransactionRepository) GetTotalAmountByUserId(ctx context.Context, userId string) (float64, error) {
var total float64
err := r.GetDB(ctx).Model(&entities.WalletTransaction{}).
Select("COALESCE(SUM(amount), 0)").
Where("user_id = ?", userId).
Scan(&total).Error
return total, err
}
// GetTotalAmountByUserIdAndDateRange 按用户ID和日期范围获取总消费金额
func (r *GormWalletTransactionRepository) GetTotalAmountByUserIdAndDateRange(ctx context.Context, userId string, startDate, endDate time.Time) (float64, error) {
var total float64
err := r.GetDB(ctx).Model(&entities.WalletTransaction{}).
Select("COALESCE(SUM(amount), 0)").
Where("user_id = ? AND created_at >= ? AND created_at < ?", userId, startDate, endDate).
Scan(&total).Error
return total, err
}
// GetDailyStatsByUserId 获取用户每日消费统计
func (r *GormWalletTransactionRepository) GetDailyStatsByUserId(ctx context.Context, userId string, startDate, endDate time.Time) ([]map[string]interface{}, error) {
var results []map[string]interface{}
// 构建SQL查询 - 使用PostgreSQL语法使用具体的日期范围
sql := `
SELECT
DATE(created_at) as date,
COALESCE(SUM(amount), 0) as amount
FROM wallet_transactions
WHERE user_id = $1
AND DATE(created_at) >= $2
AND DATE(created_at) <= $3
GROUP BY DATE(created_at)
ORDER BY date ASC
`
err := r.GetDB(ctx).Raw(sql, userId, startDate.Format("2006-01-02"), endDate.Format("2006-01-02")).Scan(&results).Error
if err != nil {
return nil, err
}
return results, nil
}
// GetMonthlyStatsByUserId 获取用户每月消费统计
func (r *GormWalletTransactionRepository) GetMonthlyStatsByUserId(ctx context.Context, userId string, startDate, endDate time.Time) ([]map[string]interface{}, error) {
var results []map[string]interface{}
// 构建SQL查询 - 使用PostgreSQL语法使用具体的日期范围
sql := `
SELECT
TO_CHAR(created_at, 'YYYY-MM') as month,
COALESCE(SUM(amount), 0) as amount
FROM wallet_transactions
WHERE user_id = $1
AND created_at >= $2
AND created_at <= $3
GROUP BY TO_CHAR(created_at, 'YYYY-MM')
ORDER BY month ASC
`
err := r.GetDB(ctx).Raw(sql, userId, startDate, endDate).Scan(&results).Error
if err != nil {
return nil, err
}
return results, nil
}
// 实现interfaces.Repository接口的其他方法
func (r *GormWalletTransactionRepository) Delete(ctx context.Context, id string) error {
return r.DeleteEntity(ctx, id, &entities.WalletTransaction{})
@@ -391,4 +467,153 @@ func (r *GormWalletTransactionRepository) ListWithFiltersAndProductName(ctx cont
}
return productNameMap, transactions, total, nil
}
}
// ExportWithFiltersAndProductName 导出钱包交易记录(包含产品名称和企业信息)
func (r *GormWalletTransactionRepository) ExportWithFiltersAndProductName(ctx context.Context, filters map[string]interface{}) ([]*entities.WalletTransaction, error) {
var transactionsWithProduct []WalletTransactionWithProduct
// 构建查询
query := r.GetDB(ctx).Table("wallet_transactions wt").
Select("wt.*, p.name as product_name").
Joins("LEFT JOIN product p ON wt.product_id = p.id")
// 构建WHERE条件
var whereConditions []string
var whereArgs []interface{}
// 用户ID筛选
if userIds, ok := filters["user_ids"].(string); ok && userIds != "" {
whereConditions = append(whereConditions, "wt.user_id IN (?)")
whereArgs = append(whereArgs, strings.Split(userIds, ","))
} else if userId, ok := filters["user_id"].(string); ok && userId != "" {
whereConditions = append(whereConditions, "wt.user_id = ?")
whereArgs = append(whereArgs, userId)
}
// 时间范围筛选
if startTime, ok := filters["start_time"].(time.Time); ok {
whereConditions = append(whereConditions, "wt.created_at >= ?")
whereArgs = append(whereArgs, startTime)
}
if endTime, ok := filters["end_time"].(time.Time); ok {
whereConditions = append(whereConditions, "wt.created_at <= ?")
whereArgs = append(whereArgs, endTime)
}
// 交易ID筛选
if transactionId, ok := filters["transaction_id"].(string); ok && transactionId != "" {
whereConditions = append(whereConditions, "wt.transaction_id LIKE ?")
whereArgs = append(whereArgs, "%"+transactionId+"%")
}
// 产品名称筛选
if productName, ok := filters["product_name"].(string); ok && productName != "" {
whereConditions = append(whereConditions, "p.name LIKE ?")
whereArgs = append(whereArgs, "%"+productName+"%")
}
// 产品ID列表筛选
if productIds, ok := filters["product_ids"].(string); ok && productIds != "" {
whereConditions = append(whereConditions, "wt.product_id IN (?)")
whereArgs = append(whereArgs, strings.Split(productIds, ","))
}
// 金额范围筛选
if minAmount, ok := filters["min_amount"].(string); ok && minAmount != "" {
whereConditions = append(whereConditions, "wt.amount >= ?")
whereArgs = append(whereArgs, minAmount)
}
if maxAmount, ok := filters["max_amount"].(string); ok && maxAmount != "" {
whereConditions = append(whereConditions, "wt.amount <= ?")
whereArgs = append(whereArgs, maxAmount)
}
// 应用WHERE条件
if len(whereConditions) > 0 {
query = query.Where(strings.Join(whereConditions, " AND "), whereArgs...)
}
// 排序
query = query.Order("wt.created_at DESC")
// 执行查询
err := query.Find(&transactionsWithProduct).Error
if err != nil {
return nil, err
}
// 转换为entities.WalletTransaction
var transactions []*entities.WalletTransaction
for _, t := range transactionsWithProduct {
transaction := t.WalletTransaction
transactions = append(transactions, &transaction)
}
return transactions, nil
}
// GetSystemTotalAmount 获取系统总消费金额
func (r *GormWalletTransactionRepository) GetSystemTotalAmount(ctx context.Context) (float64, error) {
var total float64
err := r.GetDB(ctx).Model(&entities.WalletTransaction{}).
Select("COALESCE(SUM(amount), 0)").
Scan(&total).Error
return total, err
}
// GetSystemAmountByDateRange 获取系统指定时间范围内的消费金额
func (r *GormWalletTransactionRepository) GetSystemAmountByDateRange(ctx context.Context, startDate, endDate time.Time) (float64, error) {
var total float64
err := r.GetDB(ctx).Model(&entities.WalletTransaction{}).
Where("created_at >= ? AND created_at <= ?", startDate, endDate).
Select("COALESCE(SUM(amount), 0)").
Scan(&total).Error
return total, err
}
// GetSystemDailyStats 获取系统每日消费统计
func (r *GormWalletTransactionRepository) GetSystemDailyStats(ctx context.Context, startDate, endDate time.Time) ([]map[string]interface{}, error) {
var results []map[string]interface{}
sql := `
SELECT
DATE(created_at) as date,
COALESCE(SUM(amount), 0) as amount
FROM wallet_transactions
WHERE DATE(created_at) >= $1
AND DATE(created_at) <= $2
GROUP BY DATE(created_at)
ORDER BY date ASC
`
err := r.GetDB(ctx).Raw(sql, startDate.Format("2006-01-02"), endDate.Format("2006-01-02")).Scan(&results).Error
if err != nil {
return nil, err
}
return results, nil
}
// GetSystemMonthlyStats 获取系统每月消费统计
func (r *GormWalletTransactionRepository) GetSystemMonthlyStats(ctx context.Context, startDate, endDate time.Time) ([]map[string]interface{}, error) {
var results []map[string]interface{}
sql := `
SELECT
TO_CHAR(created_at, 'YYYY-MM') as month,
COALESCE(SUM(amount), 0) as amount
FROM wallet_transactions
WHERE created_at >= $1
AND created_at <= $2
GROUP BY TO_CHAR(created_at, 'YYYY-MM')
ORDER BY month ASC
`
err := r.GetDB(ctx).Raw(sql, startDate, endDate).Scan(&results).Error
if err != nil {
return nil, err
}
return results, nil
}