Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
36a86e9a
Commit
36a86e9a
authored
Dec 27, 2025
by
IanShaw027
Browse files
perf(backend): 优化数据库查询性能
- 合并多个独立查询为单个SQL查询 - 减少数据库往返次数 - 提升仪表板统计数据获取效率
parent
254f1254
Changes
1
Hide whitespace changes
Inline
Side-by-side
backend/internal/repository/usage_log_repo.go
View file @
36a86e9a
...
@@ -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
)
.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment