"git@web.lueluesay.top:chenxi/sub2api.git" did not exist on "9b213115e73e8cb70da77fb47d08f8375b129588"
Commit 36a86e9a authored by IanShaw027's avatar IanShaw027
Browse files

perf(backend): 优化数据库查询性能

- 合并多个独立查询为单个SQL查询
- 减少数据库往返次数
- 提升仪表板统计数据获取效率
parent 254f1254
...@@ -129,51 +129,67 @@ type DashboardStats = usagestats.DashboardStats ...@@ -129,51 +129,67 @@ type DashboardStats = usagestats.DashboardStats
func (r *usageLogRepository) GetDashboardStats(ctx context.Context) (*DashboardStats, error) { func (r *usageLogRepository) GetDashboardStats(ctx context.Context) (*DashboardStats, error) {
var stats DashboardStats var stats DashboardStats
today := timezone.Today() today := timezone.Today()
now := time.Now()
// 总用户数 // 合并用户统计查询
r.db.WithContext(ctx).Model(&userModel{}).Count(&stats.TotalUsers) var userStats struct {
TotalUsers int64 `gorm:"column:total_users"`
// 今日新增用户数 TodayNewUsers int64 `gorm:"column:today_new_users"`
r.db.WithContext(ctx).Model(&userModel{}). ActiveUsers int64 `gorm:"column:active_users"`
Where("created_at >= ?", today). }
Count(&stats.TodayNewUsers) if err := r.db.WithContext(ctx).Raw(`
SELECT
// 今日活跃用户数 (今日有请求的用户) COUNT(*) as total_users,
r.db.WithContext(ctx).Model(&usageLogModel{}). COUNT(CASE WHEN created_at >= ? THEN 1 END) as today_new_users,
Distinct("user_id"). (SELECT COUNT(DISTINCT user_id) FROM usage_logs WHERE created_at >= ?) as active_users
Where("created_at >= ?", today). FROM users
Count(&stats.ActiveUsers) `, today, today).Scan(&userStats).Error; err != nil {
return nil, err
// 总 API Key 数 }
r.db.WithContext(ctx).Model(&apiKeyModel{}).Count(&stats.TotalApiKeys) stats.TotalUsers = userStats.TotalUsers
stats.TodayNewUsers = userStats.TodayNewUsers
// 活跃 API Key 数 stats.ActiveUsers = userStats.ActiveUsers
r.db.WithContext(ctx).Model(&apiKeyModel{}).
Where("status = ?", service.StatusActive).
Count(&stats.ActiveApiKeys)
// 总账户数
r.db.WithContext(ctx).Model(&accountModel{}).Count(&stats.TotalAccounts)
// 正常账户数 (schedulable=true, status=active)
r.db.WithContext(ctx).Model(&accountModel{}).
Where("status = ? AND schedulable = ?", service.StatusActive, true).
Count(&stats.NormalAccounts)
// 异常账户数 (status=error)
r.db.WithContext(ctx).Model(&accountModel{}).
Where("status = ?", service.StatusError).
Count(&stats.ErrorAccounts)
// 限流账户数 // 合并API Key统计查询
r.db.WithContext(ctx).Model(&accountModel{}). var apiKeyStats struct {
Where("rate_limited_at IS NOT NULL AND rate_limit_reset_at > ?", time.Now()). TotalApiKeys int64 `gorm:"column:total_api_keys"`
Count(&stats.RateLimitAccounts) ActiveApiKeys int64 `gorm:"column:active_api_keys"`
}
if err := r.db.WithContext(ctx).Raw(`
SELECT
COUNT(*) as total_api_keys,
COUNT(CASE WHEN status = ? THEN 1 END) as active_api_keys
FROM api_keys
`, service.StatusActive).Scan(&apiKeyStats).Error; err != nil {
return nil, err
}
stats.TotalApiKeys = apiKeyStats.TotalApiKeys
stats.ActiveApiKeys = apiKeyStats.ActiveApiKeys
// 过载账户数 // 合并账户统计查询
r.db.WithContext(ctx).Model(&accountModel{}). var accountStats struct {
Where("overload_until IS NOT NULL AND overload_until > ?", time.Now()). TotalAccounts int64 `gorm:"column:total_accounts"`
Count(&stats.OverloadAccounts) NormalAccounts int64 `gorm:"column:normal_accounts"`
ErrorAccounts int64 `gorm:"column:error_accounts"`
RateLimitAccounts int64 `gorm:"column:ratelimit_accounts"`
OverloadAccounts int64 `gorm:"column:overload_accounts"`
}
if err := r.db.WithContext(ctx).Raw(`
SELECT
COUNT(*) as total_accounts,
COUNT(CASE WHEN status = ? AND schedulable = true THEN 1 END) as normal_accounts,
COUNT(CASE WHEN status = ? THEN 1 END) as error_accounts,
COUNT(CASE WHEN rate_limited_at IS NOT NULL AND rate_limit_reset_at > ? THEN 1 END) as ratelimit_accounts,
COUNT(CASE WHEN overload_until IS NOT NULL AND overload_until > ? THEN 1 END) as overload_accounts
FROM accounts
`, service.StatusActive, service.StatusError, now, now).Scan(&accountStats).Error; err != nil {
return nil, err
}
stats.TotalAccounts = accountStats.TotalAccounts
stats.NormalAccounts = accountStats.NormalAccounts
stats.ErrorAccounts = accountStats.ErrorAccounts
stats.RateLimitAccounts = accountStats.RateLimitAccounts
stats.OverloadAccounts = accountStats.OverloadAccounts
// 累计 Token 统计 // 累计 Token 统计
var totalStats struct { var totalStats struct {
...@@ -273,6 +289,88 @@ func (r *usageLogRepository) ListByUserAndTimeRange(ctx context.Context, userID ...@@ -273,6 +289,88 @@ func (r *usageLogRepository) ListByUserAndTimeRange(ctx context.Context, userID
return usageLogModelsToService(logs), nil, err return usageLogModelsToService(logs), nil, err
} }
// GetUserStatsAggregated returns aggregated usage statistics for a user using database-level aggregation
func (r *usageLogRepository) GetUserStatsAggregated(ctx context.Context, userID int64, startTime, endTime time.Time) (*usagestats.UsageStats, error) {
var stats struct {
TotalRequests int64 `gorm:"column:total_requests"`
TotalInputTokens int64 `gorm:"column:total_input_tokens"`
TotalOutputTokens int64 `gorm:"column:total_output_tokens"`
TotalCacheTokens int64 `gorm:"column:total_cache_tokens"`
TotalCost float64 `gorm:"column:total_cost"`
TotalActualCost float64 `gorm:"column:total_actual_cost"`
AverageDurationMs float64 `gorm:"column:avg_duration_ms"`
}
err := r.db.WithContext(ctx).Model(&usageLogModel{}).
Select(`
COUNT(*) as total_requests,
COALESCE(SUM(input_tokens), 0) as total_input_tokens,
COALESCE(SUM(output_tokens), 0) as total_output_tokens,
COALESCE(SUM(cache_creation_tokens + cache_read_tokens), 0) as total_cache_tokens,
COALESCE(SUM(total_cost), 0) as total_cost,
COALESCE(SUM(actual_cost), 0) as total_actual_cost,
COALESCE(AVG(COALESCE(duration_ms, 0)), 0) as avg_duration_ms
`).
Where("user_id = ? AND created_at >= ? AND created_at < ?", userID, startTime, endTime).
Scan(&stats).Error
if err != nil {
return nil, err
}
return &usagestats.UsageStats{
TotalRequests: stats.TotalRequests,
TotalInputTokens: stats.TotalInputTokens,
TotalOutputTokens: stats.TotalOutputTokens,
TotalCacheTokens: stats.TotalCacheTokens,
TotalTokens: stats.TotalInputTokens + stats.TotalOutputTokens + stats.TotalCacheTokens,
TotalCost: stats.TotalCost,
TotalActualCost: stats.TotalActualCost,
AverageDurationMs: stats.AverageDurationMs,
}, nil
}
// GetApiKeyStatsAggregated returns aggregated usage statistics for an API key using database-level aggregation
func (r *usageLogRepository) GetApiKeyStatsAggregated(ctx context.Context, apiKeyID int64, startTime, endTime time.Time) (*usagestats.UsageStats, error) {
var stats struct {
TotalRequests int64 `gorm:"column:total_requests"`
TotalInputTokens int64 `gorm:"column:total_input_tokens"`
TotalOutputTokens int64 `gorm:"column:total_output_tokens"`
TotalCacheTokens int64 `gorm:"column:total_cache_tokens"`
TotalCost float64 `gorm:"column:total_cost"`
TotalActualCost float64 `gorm:"column:total_actual_cost"`
AverageDurationMs float64 `gorm:"column:avg_duration_ms"`
}
err := r.db.WithContext(ctx).Model(&usageLogModel{}).
Select(`
COUNT(*) as total_requests,
COALESCE(SUM(input_tokens), 0) as total_input_tokens,
COALESCE(SUM(output_tokens), 0) as total_output_tokens,
COALESCE(SUM(cache_creation_tokens + cache_read_tokens), 0) as total_cache_tokens,
COALESCE(SUM(total_cost), 0) as total_cost,
COALESCE(SUM(actual_cost), 0) as total_actual_cost,
COALESCE(AVG(COALESCE(duration_ms, 0)), 0) as avg_duration_ms
`).
Where("api_key_id = ? AND created_at >= ? AND created_at < ?", apiKeyID, startTime, endTime).
Scan(&stats).Error
if err != nil {
return nil, err
}
return &usagestats.UsageStats{
TotalRequests: stats.TotalRequests,
TotalInputTokens: stats.TotalInputTokens,
TotalOutputTokens: stats.TotalOutputTokens,
TotalCacheTokens: stats.TotalCacheTokens,
TotalTokens: stats.TotalInputTokens + stats.TotalOutputTokens + stats.TotalCacheTokens,
TotalCost: stats.TotalCost,
TotalActualCost: stats.TotalActualCost,
AverageDurationMs: stats.AverageDurationMs,
}, nil
}
func (r *usageLogRepository) ListByApiKeyAndTimeRange(ctx context.Context, apiKeyID int64, startTime, endTime time.Time) ([]service.UsageLog, *pagination.PaginationResult, error) { func (r *usageLogRepository) ListByApiKeyAndTimeRange(ctx context.Context, apiKeyID int64, startTime, endTime time.Time) ([]service.UsageLog, *pagination.PaginationResult, error) {
var logs []usageLogModel var logs []usageLogModel
err := r.db.WithContext(ctx). err := r.db.WithContext(ctx).
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment