1
0
Files
nex/backend/internal/repository/stats_repo_impl.go
lanyuanxiaoyao 1522c87c74 fix: 修复 statsRepo 并发竞态条件,使用 upsert 保证原子性
- 使用 GORM clause.OnConflict 替代事务包装
- Record 和 BatchUpdate 方法改用 upsert 模式
- 修复 UsageStats 的 GORM struct tag,确保 AutoMigrate 创建正确的 UNIQUE 约束
- 更新 usage-statistics spec 以反映 upsert 操作

MySQL 并发测试验证:10 并发调用 → request_count = 10
2026-04-23 15:54:56 +08:00

98 lines
2.2 KiB
Go

package repository
import (
"time"
"gorm.io/gorm"
"gorm.io/gorm/clause"
"nex/backend/internal/config"
"nex/backend/internal/domain"
)
type statsRepository struct {
db *gorm.DB
}
func NewStatsRepository(db *gorm.DB) StatsRepository {
return &statsRepository{db: db}
}
func (r *statsRepository) Record(providerID, modelName string) error {
today := time.Now().Format("2006-01-02")
todayTime, _ := time.Parse("2006-01-02", today)
stats := config.UsageStats{
ProviderID: providerID,
ModelName: modelName,
RequestCount: 1,
Date: todayTime,
}
return r.db.Clauses(clause.OnConflict{
Columns: []clause.Column{
{Name: "provider_id"},
{Name: "model_name"},
{Name: "date"},
},
DoUpdates: clause.Assignments(map[string]interface{}{
"request_count": gorm.Expr("request_count + 1"),
}),
}).Create(&stats).Error
}
func (r *statsRepository) BatchUpdate(providerID, modelName string, date time.Time, delta int) error {
stats := config.UsageStats{
ProviderID: providerID,
ModelName: modelName,
RequestCount: delta,
Date: date,
}
return r.db.Clauses(clause.OnConflict{
Columns: []clause.Column{
{Name: "provider_id"},
{Name: "model_name"},
{Name: "date"},
},
DoUpdates: clause.Assignments(map[string]interface{}{
"request_count": gorm.Expr("request_count + ?", delta),
}),
}).Create(&stats).Error
}
func (r *statsRepository) Query(providerID, modelName string, startDate, endDate *time.Time) ([]domain.UsageStats, error) {
var stats []config.UsageStats
query := r.db.Model(&config.UsageStats{})
if providerID != "" {
query = query.Where("provider_id = ?", providerID)
}
if modelName != "" {
query = query.Where("model_name = ?", modelName)
}
if startDate != nil {
query = query.Where("date >= ?", startDate)
}
if endDate != nil {
query = query.Where("date <= ?", endDate)
}
err := query.Order("date DESC").Find(&stats).Error
if err != nil {
return nil, err
}
result := make([]domain.UsageStats, len(stats))
for i := range stats {
result[i] = domain.UsageStats{
ID: stats[i].ID,
ProviderID: stats[i].ProviderID,
ModelName: stats[i].ModelName,
RequestCount: stats[i].RequestCount,
Date: stats[i].Date,
}
}
return result, nil
}