feat: 账号管理新增使用统计功能

- 新增账号统计弹窗,展示30天使用数据
- 显示总费用、请求数、日均费用、日均请求等汇总指标
- 显示今日概览、最高费用日、最高请求日
- 包含费用与请求趋势图(双Y轴)
- 复用模型分布图组件展示模型使用分布
- 显示实际扣费和标准计费(标准计费以较淡颜色显示)
This commit is contained in:
shaw
2025-12-23 13:42:33 +08:00
parent d9e27df9af
commit 7b9c3f165e
12 changed files with 939 additions and 35 deletions

View File

@@ -6,6 +6,8 @@ import (
"sub2api/internal/pkg/claude"
"sub2api/internal/pkg/openai"
"sub2api/internal/pkg/response"
"sub2api/internal/pkg/timezone"
"sub2api/internal/repository"
"sub2api/internal/service"
"github.com/gin-gonic/gin"
@@ -31,10 +33,11 @@ type AccountHandler struct {
rateLimitService *service.RateLimitService
accountUsageService *service.AccountUsageService
accountTestService *service.AccountTestService
usageLogRepo *repository.UsageLogRepository
}
// NewAccountHandler creates a new admin account handler
func NewAccountHandler(adminService service.AdminService, oauthService *service.OAuthService, openaiOAuthService *service.OpenAIOAuthService, rateLimitService *service.RateLimitService, accountUsageService *service.AccountUsageService, accountTestService *service.AccountTestService) *AccountHandler {
func NewAccountHandler(adminService service.AdminService, oauthService *service.OAuthService, openaiOAuthService *service.OpenAIOAuthService, rateLimitService *service.RateLimitService, accountUsageService *service.AccountUsageService, accountTestService *service.AccountTestService, usageLogRepo *repository.UsageLogRepository) *AccountHandler {
return &AccountHandler{
adminService: adminService,
oauthService: oauthService,
@@ -42,6 +45,7 @@ func NewAccountHandler(adminService service.AdminService, oauthService *service.
rateLimitService: rateLimitService,
accountUsageService: accountUsageService,
accountTestService: accountTestService,
usageLogRepo: usageLogRepo,
}
}
@@ -297,15 +301,26 @@ func (h *AccountHandler) GetStats(c *gin.Context) {
return
}
// Return mock data for now
_ = accountID
response.Success(c, gin.H{
"total_requests": 0,
"successful_requests": 0,
"failed_requests": 0,
"total_tokens": 0,
"average_response_time": 0,
})
// Parse days parameter (default 30)
days := 30
if daysStr := c.Query("days"); daysStr != "" {
if d, err := strconv.Atoi(daysStr); err == nil && d > 0 && d <= 90 {
days = d
}
}
// Calculate time range
now := timezone.Now()
endTime := timezone.StartOfDay(now.AddDate(0, 0, 1))
startTime := timezone.StartOfDay(now.AddDate(0, 0, -days+1))
stats, err := h.usageLogRepo.GetAccountUsageStats(c.Request.Context(), accountID, startTime, endTime)
if err != nil {
response.InternalError(c, "Failed to get account stats: "+err.Error())
return
}
response.Success(c, stats)
}
// ClearError handles clearing account error

View File

@@ -175,7 +175,7 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
}
}
stats, err := h.usageRepo.GetModelStatsWithFilters(c.Request.Context(), startTime, endTime, userID, apiKeyID)
stats, err := h.usageRepo.GetModelStatsWithFilters(c.Request.Context(), startTime, endTime, userID, apiKeyID, 0)
if err != nil {
response.Error(c, 500, "Failed to get model statistics")
return