refactor(usage): 移动 usage 查询到 services
This commit is contained in:
@@ -17,10 +17,17 @@ linters:
|
|||||||
service-no-repository:
|
service-no-repository:
|
||||||
list-mode: original
|
list-mode: original
|
||||||
files:
|
files:
|
||||||
- internal/service/**
|
- "**/internal/service/**"
|
||||||
deny:
|
deny:
|
||||||
- pkg: sub2api/internal/repository
|
- pkg: sub2api/internal/repository
|
||||||
desc: "service must not import repository"
|
desc: "service must not import repository"
|
||||||
|
handler-no-repository:
|
||||||
|
list-mode: original
|
||||||
|
files:
|
||||||
|
- "**/internal/handler/**"
|
||||||
|
deny:
|
||||||
|
- pkg: sub2api/internal/repository
|
||||||
|
desc: "handler must not import repository"
|
||||||
errcheck:
|
errcheck:
|
||||||
# Report about not checking of errors in type assertions: `a := b.(MyStruct)`.
|
# Report about not checking of errors in type assertions: `a := b.(MyStruct)`.
|
||||||
# Such cases aren't reported by default.
|
# Such cases aren't reported by default.
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService)
|
apiKeyHandler := handler.NewAPIKeyHandler(apiKeyService)
|
||||||
usageLogRepository := repository.NewUsageLogRepository(db)
|
usageLogRepository := repository.NewUsageLogRepository(db)
|
||||||
usageService := service.NewUsageService(usageLogRepository, userRepository)
|
usageService := service.NewUsageService(usageLogRepository, userRepository)
|
||||||
usageHandler := handler.NewUsageHandler(usageService, usageLogRepository, apiKeyService)
|
usageHandler := handler.NewUsageHandler(usageService, apiKeyService)
|
||||||
redeemCodeRepository := repository.NewRedeemCodeRepository(db)
|
redeemCodeRepository := repository.NewRedeemCodeRepository(db)
|
||||||
billingCache := repository.NewBillingCache(client)
|
billingCache := repository.NewBillingCache(client)
|
||||||
billingCacheService := service.NewBillingCacheService(billingCache, userRepository, userSubscriptionRepository)
|
billingCacheService := service.NewBillingCacheService(billingCache, userRepository, userSubscriptionRepository)
|
||||||
@@ -67,7 +67,8 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
redeemService := service.NewRedeemService(redeemCodeRepository, userRepository, subscriptionService, redeemCache, billingCacheService)
|
redeemService := service.NewRedeemService(redeemCodeRepository, userRepository, subscriptionService, redeemCache, billingCacheService)
|
||||||
redeemHandler := handler.NewRedeemHandler(redeemService)
|
redeemHandler := handler.NewRedeemHandler(redeemService)
|
||||||
subscriptionHandler := handler.NewSubscriptionHandler(subscriptionService)
|
subscriptionHandler := handler.NewSubscriptionHandler(subscriptionService)
|
||||||
dashboardHandler := admin.NewDashboardHandler(usageLogRepository)
|
dashboardService := service.NewDashboardService(usageLogRepository)
|
||||||
|
dashboardHandler := admin.NewDashboardHandler(dashboardService)
|
||||||
accountRepository := repository.NewAccountRepository(db)
|
accountRepository := repository.NewAccountRepository(db)
|
||||||
proxyRepository := repository.NewProxyRepository(db)
|
proxyRepository := repository.NewProxyRepository(db)
|
||||||
proxyExitInfoProber := repository.NewProxyExitInfoProber()
|
proxyExitInfoProber := repository.NewProxyExitInfoProber()
|
||||||
@@ -83,7 +84,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher)
|
accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher)
|
||||||
httpUpstream := repository.NewHTTPUpstream(configConfig)
|
httpUpstream := repository.NewHTTPUpstream(configConfig)
|
||||||
accountTestService := service.NewAccountTestService(accountRepository, oAuthService, openAIOAuthService, httpUpstream)
|
accountTestService := service.NewAccountTestService(accountRepository, oAuthService, openAIOAuthService, httpUpstream)
|
||||||
accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, rateLimitService, accountUsageService, accountTestService, usageLogRepository)
|
accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, rateLimitService, accountUsageService, accountTestService)
|
||||||
oAuthHandler := admin.NewOAuthHandler(oAuthService)
|
oAuthHandler := admin.NewOAuthHandler(oAuthService)
|
||||||
openAIOAuthHandler := admin.NewOpenAIOAuthHandler(openAIOAuthService, adminService)
|
openAIOAuthHandler := admin.NewOpenAIOAuthHandler(openAIOAuthService, adminService)
|
||||||
proxyHandler := admin.NewProxyHandler(adminService)
|
proxyHandler := admin.NewProxyHandler(adminService)
|
||||||
@@ -95,7 +96,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
|
|||||||
updateService := service.ProvideUpdateService(updateCache, gitHubReleaseClient, serviceBuildInfo)
|
updateService := service.ProvideUpdateService(updateCache, gitHubReleaseClient, serviceBuildInfo)
|
||||||
systemHandler := handler.ProvideSystemHandler(updateService)
|
systemHandler := handler.ProvideSystemHandler(updateService)
|
||||||
adminSubscriptionHandler := admin.NewSubscriptionHandler(subscriptionService)
|
adminSubscriptionHandler := admin.NewSubscriptionHandler(subscriptionService)
|
||||||
adminUsageHandler := admin.NewUsageHandler(usageLogRepository, apiKeyRepository, usageService, adminService)
|
adminUsageHandler := admin.NewUsageHandler(usageService, apiKeyService, adminService)
|
||||||
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, oAuthHandler, openAIOAuthHandler, proxyHandler, adminRedeemHandler, settingHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler)
|
adminHandlers := handler.ProvideAdminHandlers(dashboardHandler, adminUserHandler, groupHandler, accountHandler, oAuthHandler, openAIOAuthHandler, proxyHandler, adminRedeemHandler, settingHandler, systemHandler, adminSubscriptionHandler, adminUsageHandler)
|
||||||
gatewayCache := repository.NewGatewayCache(client)
|
gatewayCache := repository.NewGatewayCache(client)
|
||||||
pricingRemoteClient := repository.NewPricingRemoteClient()
|
pricingRemoteClient := repository.NewPricingRemoteClient()
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"sub2api/internal/pkg/openai"
|
"sub2api/internal/pkg/openai"
|
||||||
"sub2api/internal/pkg/response"
|
"sub2api/internal/pkg/response"
|
||||||
"sub2api/internal/pkg/timezone"
|
"sub2api/internal/pkg/timezone"
|
||||||
"sub2api/internal/repository"
|
|
||||||
"sub2api/internal/service"
|
"sub2api/internal/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -33,11 +32,10 @@ type AccountHandler struct {
|
|||||||
rateLimitService *service.RateLimitService
|
rateLimitService *service.RateLimitService
|
||||||
accountUsageService *service.AccountUsageService
|
accountUsageService *service.AccountUsageService
|
||||||
accountTestService *service.AccountTestService
|
accountTestService *service.AccountTestService
|
||||||
usageLogRepo *repository.UsageLogRepository
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAccountHandler creates a new admin account handler
|
// 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, usageLogRepo *repository.UsageLogRepository) *AccountHandler {
|
func NewAccountHandler(adminService service.AdminService, oauthService *service.OAuthService, openaiOAuthService *service.OpenAIOAuthService, rateLimitService *service.RateLimitService, accountUsageService *service.AccountUsageService, accountTestService *service.AccountTestService) *AccountHandler {
|
||||||
return &AccountHandler{
|
return &AccountHandler{
|
||||||
adminService: adminService,
|
adminService: adminService,
|
||||||
oauthService: oauthService,
|
oauthService: oauthService,
|
||||||
@@ -45,7 +43,6 @@ func NewAccountHandler(adminService service.AdminService, oauthService *service.
|
|||||||
rateLimitService: rateLimitService,
|
rateLimitService: rateLimitService,
|
||||||
accountUsageService: accountUsageService,
|
accountUsageService: accountUsageService,
|
||||||
accountTestService: accountTestService,
|
accountTestService: accountTestService,
|
||||||
usageLogRepo: usageLogRepo,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,7 +311,7 @@ func (h *AccountHandler) GetStats(c *gin.Context) {
|
|||||||
endTime := timezone.StartOfDay(now.AddDate(0, 0, 1))
|
endTime := timezone.StartOfDay(now.AddDate(0, 0, 1))
|
||||||
startTime := timezone.StartOfDay(now.AddDate(0, 0, -days+1))
|
startTime := timezone.StartOfDay(now.AddDate(0, 0, -days+1))
|
||||||
|
|
||||||
stats, err := h.usageLogRepo.GetAccountUsageStats(c.Request.Context(), accountID, startTime, endTime)
|
stats, err := h.accountUsageService.GetAccountUsageStats(c.Request.Context(), accountID, startTime, endTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.InternalError(c, "Failed to get account stats: "+err.Error())
|
response.InternalError(c, "Failed to get account stats: "+err.Error())
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"sub2api/internal/pkg/response"
|
"sub2api/internal/pkg/response"
|
||||||
"sub2api/internal/pkg/timezone"
|
"sub2api/internal/pkg/timezone"
|
||||||
"sub2api/internal/repository"
|
"sub2api/internal/service"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -12,15 +12,15 @@ import (
|
|||||||
|
|
||||||
// DashboardHandler handles admin dashboard statistics
|
// DashboardHandler handles admin dashboard statistics
|
||||||
type DashboardHandler struct {
|
type DashboardHandler struct {
|
||||||
usageRepo *repository.UsageLogRepository
|
dashboardService *service.DashboardService
|
||||||
startTime time.Time // Server start time for uptime calculation
|
startTime time.Time // Server start time for uptime calculation
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDashboardHandler creates a new admin dashboard handler
|
// NewDashboardHandler creates a new admin dashboard handler
|
||||||
func NewDashboardHandler(usageRepo *repository.UsageLogRepository) *DashboardHandler {
|
func NewDashboardHandler(dashboardService *service.DashboardService) *DashboardHandler {
|
||||||
return &DashboardHandler{
|
return &DashboardHandler{
|
||||||
usageRepo: usageRepo,
|
dashboardService: dashboardService,
|
||||||
startTime: time.Now(),
|
startTime: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ func parseTimeRange(c *gin.Context) (time.Time, time.Time) {
|
|||||||
// GetStats handles getting dashboard statistics
|
// GetStats handles getting dashboard statistics
|
||||||
// GET /api/v1/admin/dashboard/stats
|
// GET /api/v1/admin/dashboard/stats
|
||||||
func (h *DashboardHandler) GetStats(c *gin.Context) {
|
func (h *DashboardHandler) GetStats(c *gin.Context) {
|
||||||
stats, err := h.usageRepo.GetDashboardStats(c.Request.Context())
|
stats, err := h.dashboardService.GetDashboardStats(c.Request.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get dashboard statistics")
|
response.Error(c, 500, "Failed to get dashboard statistics")
|
||||||
return
|
return
|
||||||
@@ -142,7 +142,7 @@ func (h *DashboardHandler) GetUsageTrend(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trend, err := h.usageRepo.GetUsageTrendWithFilters(c.Request.Context(), startTime, endTime, granularity, userID, apiKeyID)
|
trend, err := h.dashboardService.GetUsageTrendWithFilters(c.Request.Context(), startTime, endTime, granularity, userID, apiKeyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get usage trend")
|
response.Error(c, 500, "Failed to get usage trend")
|
||||||
return
|
return
|
||||||
@@ -175,7 +175,7 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := h.usageRepo.GetModelStatsWithFilters(c.Request.Context(), startTime, endTime, userID, apiKeyID, 0)
|
stats, err := h.dashboardService.GetModelStatsWithFilters(c.Request.Context(), startTime, endTime, userID, apiKeyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get model statistics")
|
response.Error(c, 500, "Failed to get model statistics")
|
||||||
return
|
return
|
||||||
@@ -200,7 +200,7 @@ func (h *DashboardHandler) GetApiKeyUsageTrend(c *gin.Context) {
|
|||||||
limit = 5
|
limit = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
trend, err := h.usageRepo.GetApiKeyUsageTrend(c.Request.Context(), startTime, endTime, granularity, limit)
|
trend, err := h.dashboardService.GetApiKeyUsageTrend(c.Request.Context(), startTime, endTime, granularity, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get API key usage trend")
|
response.Error(c, 500, "Failed to get API key usage trend")
|
||||||
return
|
return
|
||||||
@@ -226,7 +226,7 @@ func (h *DashboardHandler) GetUserUsageTrend(c *gin.Context) {
|
|||||||
limit = 12
|
limit = 12
|
||||||
}
|
}
|
||||||
|
|
||||||
trend, err := h.usageRepo.GetUserUsageTrend(c.Request.Context(), startTime, endTime, granularity, limit)
|
trend, err := h.dashboardService.GetUserUsageTrend(c.Request.Context(), startTime, endTime, granularity, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get user usage trend")
|
response.Error(c, 500, "Failed to get user usage trend")
|
||||||
return
|
return
|
||||||
@@ -259,7 +259,7 @@ func (h *DashboardHandler) GetBatchUsersUsage(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := h.usageRepo.GetBatchUserUsageStats(c.Request.Context(), req.UserIDs)
|
stats, err := h.dashboardService.GetBatchUserUsageStats(c.Request.Context(), req.UserIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get user usage stats")
|
response.Error(c, 500, "Failed to get user usage stats")
|
||||||
return
|
return
|
||||||
@@ -287,7 +287,7 @@ func (h *DashboardHandler) GetBatchApiKeysUsage(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := h.usageRepo.GetBatchApiKeyUsageStats(c.Request.Context(), req.ApiKeyIDs)
|
stats, err := h.dashboardService.GetBatchApiKeyUsageStats(c.Request.Context(), req.ApiKeyIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.Error(c, 500, "Failed to get API key usage stats")
|
response.Error(c, 500, "Failed to get API key usage stats")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"sub2api/internal/pkg/pagination"
|
"sub2api/internal/pkg/pagination"
|
||||||
"sub2api/internal/pkg/response"
|
"sub2api/internal/pkg/response"
|
||||||
"sub2api/internal/pkg/timezone"
|
"sub2api/internal/pkg/timezone"
|
||||||
"sub2api/internal/repository"
|
"sub2api/internal/pkg/usagestats"
|
||||||
"sub2api/internal/service"
|
"sub2api/internal/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -15,24 +15,21 @@ import (
|
|||||||
|
|
||||||
// UsageHandler handles admin usage-related requests
|
// UsageHandler handles admin usage-related requests
|
||||||
type UsageHandler struct {
|
type UsageHandler struct {
|
||||||
usageRepo *repository.UsageLogRepository
|
usageService *service.UsageService
|
||||||
apiKeyRepo *repository.ApiKeyRepository
|
apiKeyService *service.ApiKeyService
|
||||||
usageService *service.UsageService
|
adminService service.AdminService
|
||||||
adminService service.AdminService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUsageHandler creates a new admin usage handler
|
// NewUsageHandler creates a new admin usage handler
|
||||||
func NewUsageHandler(
|
func NewUsageHandler(
|
||||||
usageRepo *repository.UsageLogRepository,
|
|
||||||
apiKeyRepo *repository.ApiKeyRepository,
|
|
||||||
usageService *service.UsageService,
|
usageService *service.UsageService,
|
||||||
|
apiKeyService *service.ApiKeyService,
|
||||||
adminService service.AdminService,
|
adminService service.AdminService,
|
||||||
) *UsageHandler {
|
) *UsageHandler {
|
||||||
return &UsageHandler{
|
return &UsageHandler{
|
||||||
usageRepo: usageRepo,
|
usageService: usageService,
|
||||||
apiKeyRepo: apiKeyRepo,
|
apiKeyService: apiKeyService,
|
||||||
usageService: usageService,
|
adminService: adminService,
|
||||||
adminService: adminService,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,14 +81,14 @@ func (h *UsageHandler) List(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
params := pagination.PaginationParams{Page: page, PageSize: pageSize}
|
params := pagination.PaginationParams{Page: page, PageSize: pageSize}
|
||||||
filters := repository.UsageLogFilters{
|
filters := usagestats.UsageLogFilters{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
ApiKeyID: apiKeyID,
|
ApiKeyID: apiKeyID,
|
||||||
StartTime: startTime,
|
StartTime: startTime,
|
||||||
EndTime: endTime,
|
EndTime: endTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
records, result, err := h.usageRepo.ListWithFilters(c.Request.Context(), params, filters)
|
records, result, err := h.usageService.ListWithFilters(c.Request.Context(), params, filters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.InternalError(c, "Failed to list usage records: "+err.Error())
|
response.InternalError(c, "Failed to list usage records: "+err.Error())
|
||||||
return
|
return
|
||||||
@@ -179,7 +176,7 @@ func (h *UsageHandler) Stats(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get global stats
|
// Get global stats
|
||||||
stats, err := h.usageRepo.GetGlobalStats(c.Request.Context(), startTime, endTime)
|
stats, err := h.usageService.GetGlobalStats(c.Request.Context(), startTime, endTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.InternalError(c, "Failed to get usage statistics: "+err.Error())
|
response.InternalError(c, "Failed to get usage statistics: "+err.Error())
|
||||||
return
|
return
|
||||||
@@ -237,7 +234,7 @@ func (h *UsageHandler) SearchApiKeys(c *gin.Context) {
|
|||||||
userID = id
|
userID = id
|
||||||
}
|
}
|
||||||
|
|
||||||
keys, err := h.apiKeyRepo.SearchApiKeys(c.Request.Context(), userID, keyword, 30)
|
keys, err := h.apiKeyService.SearchApiKeys(c.Request.Context(), userID, keyword, 30)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.InternalError(c, "Failed to search API keys: "+err.Error())
|
response.InternalError(c, "Failed to search API keys: "+err.Error())
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"sub2api/internal/pkg/pagination"
|
"sub2api/internal/pkg/pagination"
|
||||||
"sub2api/internal/pkg/response"
|
"sub2api/internal/pkg/response"
|
||||||
"sub2api/internal/pkg/timezone"
|
"sub2api/internal/pkg/timezone"
|
||||||
"sub2api/internal/repository"
|
|
||||||
"sub2api/internal/service"
|
"sub2api/internal/service"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -17,15 +16,13 @@ import (
|
|||||||
// UsageHandler handles usage-related requests
|
// UsageHandler handles usage-related requests
|
||||||
type UsageHandler struct {
|
type UsageHandler struct {
|
||||||
usageService *service.UsageService
|
usageService *service.UsageService
|
||||||
usageRepo *repository.UsageLogRepository
|
|
||||||
apiKeyService *service.ApiKeyService
|
apiKeyService *service.ApiKeyService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUsageHandler creates a new UsageHandler
|
// NewUsageHandler creates a new UsageHandler
|
||||||
func NewUsageHandler(usageService *service.UsageService, usageRepo *repository.UsageLogRepository, apiKeyService *service.ApiKeyService) *UsageHandler {
|
func NewUsageHandler(usageService *service.UsageService, apiKeyService *service.ApiKeyService) *UsageHandler {
|
||||||
return &UsageHandler{
|
return &UsageHandler{
|
||||||
usageService: usageService,
|
usageService: usageService,
|
||||||
usageRepo: usageRepo,
|
|
||||||
apiKeyService: apiKeyService,
|
apiKeyService: apiKeyService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,7 +257,7 @@ func (h *UsageHandler) DashboardStats(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := h.usageRepo.GetUserDashboardStats(c.Request.Context(), user.ID)
|
stats, err := h.usageService.GetUserDashboardStats(c.Request.Context(), user.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.InternalError(c, "Failed to get dashboard statistics")
|
response.InternalError(c, "Failed to get dashboard statistics")
|
||||||
return
|
return
|
||||||
@@ -287,7 +284,7 @@ func (h *UsageHandler) DashboardTrend(c *gin.Context) {
|
|||||||
startTime, endTime := parseUserTimeRange(c)
|
startTime, endTime := parseUserTimeRange(c)
|
||||||
granularity := c.DefaultQuery("granularity", "day")
|
granularity := c.DefaultQuery("granularity", "day")
|
||||||
|
|
||||||
trend, err := h.usageRepo.GetUserUsageTrendByUserID(c.Request.Context(), user.ID, startTime, endTime, granularity)
|
trend, err := h.usageService.GetUserUsageTrendByUserID(c.Request.Context(), user.ID, startTime, endTime, granularity)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.InternalError(c, "Failed to get usage trend")
|
response.InternalError(c, "Failed to get usage trend")
|
||||||
return
|
return
|
||||||
@@ -318,7 +315,7 @@ func (h *UsageHandler) DashboardModels(c *gin.Context) {
|
|||||||
|
|
||||||
startTime, endTime := parseUserTimeRange(c)
|
startTime, endTime := parseUserTimeRange(c)
|
||||||
|
|
||||||
stats, err := h.usageRepo.GetUserModelStats(c.Request.Context(), user.ID, startTime, endTime)
|
stats, err := h.usageService.GetUserModelStats(c.Request.Context(), user.ID, startTime, endTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.InternalError(c, "Failed to get model statistics")
|
response.InternalError(c, "Failed to get model statistics")
|
||||||
return
|
return
|
||||||
@@ -387,7 +384,7 @@ func (h *UsageHandler) DashboardApiKeysUsage(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := h.usageRepo.GetBatchApiKeyUsageStats(c.Request.Context(), validApiKeyIDs)
|
stats, err := h.usageService.GetBatchApiKeyUsageStats(c.Request.Context(), validApiKeyIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
response.InternalError(c, "Failed to get API key usage stats")
|
response.InternalError(c, "Failed to get API key usage stats")
|
||||||
return
|
return
|
||||||
|
|||||||
201
backend/internal/pkg/usagestats/usage_log_types.go
Normal file
201
backend/internal/pkg/usagestats/usage_log_types.go
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
package usagestats
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// DashboardStats 仪表盘统计
|
||||||
|
type DashboardStats struct {
|
||||||
|
// 用户统计
|
||||||
|
TotalUsers int64 `json:"total_users"`
|
||||||
|
TodayNewUsers int64 `json:"today_new_users"` // 今日新增用户数
|
||||||
|
ActiveUsers int64 `json:"active_users"` // 今日有请求的用户数
|
||||||
|
|
||||||
|
// API Key 统计
|
||||||
|
TotalApiKeys int64 `json:"total_api_keys"`
|
||||||
|
ActiveApiKeys int64 `json:"active_api_keys"` // 状态为 active 的 API Key 数
|
||||||
|
|
||||||
|
// 账户统计
|
||||||
|
TotalAccounts int64 `json:"total_accounts"`
|
||||||
|
NormalAccounts int64 `json:"normal_accounts"` // 正常账户数 (schedulable=true, status=active)
|
||||||
|
ErrorAccounts int64 `json:"error_accounts"` // 异常账户数 (status=error)
|
||||||
|
RateLimitAccounts int64 `json:"ratelimit_accounts"` // 限流账户数
|
||||||
|
OverloadAccounts int64 `json:"overload_accounts"` // 过载账户数
|
||||||
|
|
||||||
|
// 累计 Token 使用统计
|
||||||
|
TotalRequests int64 `json:"total_requests"`
|
||||||
|
TotalInputTokens int64 `json:"total_input_tokens"`
|
||||||
|
TotalOutputTokens int64 `json:"total_output_tokens"`
|
||||||
|
TotalCacheCreationTokens int64 `json:"total_cache_creation_tokens"`
|
||||||
|
TotalCacheReadTokens int64 `json:"total_cache_read_tokens"`
|
||||||
|
TotalTokens int64 `json:"total_tokens"`
|
||||||
|
TotalCost float64 `json:"total_cost"` // 累计标准计费
|
||||||
|
TotalActualCost float64 `json:"total_actual_cost"` // 累计实际扣除
|
||||||
|
|
||||||
|
// 今日 Token 使用统计
|
||||||
|
TodayRequests int64 `json:"today_requests"`
|
||||||
|
TodayInputTokens int64 `json:"today_input_tokens"`
|
||||||
|
TodayOutputTokens int64 `json:"today_output_tokens"`
|
||||||
|
TodayCacheCreationTokens int64 `json:"today_cache_creation_tokens"`
|
||||||
|
TodayCacheReadTokens int64 `json:"today_cache_read_tokens"`
|
||||||
|
TodayTokens int64 `json:"today_tokens"`
|
||||||
|
TodayCost float64 `json:"today_cost"` // 今日标准计费
|
||||||
|
TodayActualCost float64 `json:"today_actual_cost"` // 今日实际扣除
|
||||||
|
|
||||||
|
// 系统运行统计
|
||||||
|
AverageDurationMs float64 `json:"average_duration_ms"` // 平均响应时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrendDataPoint represents a single point in trend data
|
||||||
|
type TrendDataPoint struct {
|
||||||
|
Date string `json:"date"`
|
||||||
|
Requests int64 `json:"requests"`
|
||||||
|
InputTokens int64 `json:"input_tokens"`
|
||||||
|
OutputTokens int64 `json:"output_tokens"`
|
||||||
|
CacheTokens int64 `json:"cache_tokens"`
|
||||||
|
TotalTokens int64 `json:"total_tokens"`
|
||||||
|
Cost float64 `json:"cost"` // 标准计费
|
||||||
|
ActualCost float64 `json:"actual_cost"` // 实际扣除
|
||||||
|
}
|
||||||
|
|
||||||
|
// ModelStat represents usage statistics for a single model
|
||||||
|
type ModelStat struct {
|
||||||
|
Model string `json:"model"`
|
||||||
|
Requests int64 `json:"requests"`
|
||||||
|
InputTokens int64 `json:"input_tokens"`
|
||||||
|
OutputTokens int64 `json:"output_tokens"`
|
||||||
|
TotalTokens int64 `json:"total_tokens"`
|
||||||
|
Cost float64 `json:"cost"` // 标准计费
|
||||||
|
ActualCost float64 `json:"actual_cost"` // 实际扣除
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserUsageTrendPoint represents user usage trend data point
|
||||||
|
type UserUsageTrendPoint struct {
|
||||||
|
Date string `json:"date"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Requests int64 `json:"requests"`
|
||||||
|
Tokens int64 `json:"tokens"`
|
||||||
|
Cost float64 `json:"cost"` // 标准计费
|
||||||
|
ActualCost float64 `json:"actual_cost"` // 实际扣除
|
||||||
|
}
|
||||||
|
|
||||||
|
// ApiKeyUsageTrendPoint represents API key usage trend data point
|
||||||
|
type ApiKeyUsageTrendPoint struct {
|
||||||
|
Date string `json:"date"`
|
||||||
|
ApiKeyID int64 `json:"api_key_id"`
|
||||||
|
KeyName string `json:"key_name"`
|
||||||
|
Requests int64 `json:"requests"`
|
||||||
|
Tokens int64 `json:"tokens"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserDashboardStats 用户仪表盘统计
|
||||||
|
type UserDashboardStats struct {
|
||||||
|
// API Key 统计
|
||||||
|
TotalApiKeys int64 `json:"total_api_keys"`
|
||||||
|
ActiveApiKeys int64 `json:"active_api_keys"`
|
||||||
|
|
||||||
|
// 累计 Token 使用统计
|
||||||
|
TotalRequests int64 `json:"total_requests"`
|
||||||
|
TotalInputTokens int64 `json:"total_input_tokens"`
|
||||||
|
TotalOutputTokens int64 `json:"total_output_tokens"`
|
||||||
|
TotalCacheCreationTokens int64 `json:"total_cache_creation_tokens"`
|
||||||
|
TotalCacheReadTokens int64 `json:"total_cache_read_tokens"`
|
||||||
|
TotalTokens int64 `json:"total_tokens"`
|
||||||
|
TotalCost float64 `json:"total_cost"` // 累计标准计费
|
||||||
|
TotalActualCost float64 `json:"total_actual_cost"` // 累计实际扣除
|
||||||
|
|
||||||
|
// 今日 Token 使用统计
|
||||||
|
TodayRequests int64 `json:"today_requests"`
|
||||||
|
TodayInputTokens int64 `json:"today_input_tokens"`
|
||||||
|
TodayOutputTokens int64 `json:"today_output_tokens"`
|
||||||
|
TodayCacheCreationTokens int64 `json:"today_cache_creation_tokens"`
|
||||||
|
TodayCacheReadTokens int64 `json:"today_cache_read_tokens"`
|
||||||
|
TodayTokens int64 `json:"today_tokens"`
|
||||||
|
TodayCost float64 `json:"today_cost"` // 今日标准计费
|
||||||
|
TodayActualCost float64 `json:"today_actual_cost"` // 今日实际扣除
|
||||||
|
|
||||||
|
// 性能统计
|
||||||
|
AverageDurationMs float64 `json:"average_duration_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsageLogFilters represents filters for usage log queries
|
||||||
|
type UsageLogFilters struct {
|
||||||
|
UserID int64
|
||||||
|
ApiKeyID int64
|
||||||
|
StartTime *time.Time
|
||||||
|
EndTime *time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// UsageStats represents usage statistics
|
||||||
|
type UsageStats struct {
|
||||||
|
TotalRequests int64 `json:"total_requests"`
|
||||||
|
TotalInputTokens int64 `json:"total_input_tokens"`
|
||||||
|
TotalOutputTokens int64 `json:"total_output_tokens"`
|
||||||
|
TotalCacheTokens int64 `json:"total_cache_tokens"`
|
||||||
|
TotalTokens int64 `json:"total_tokens"`
|
||||||
|
TotalCost float64 `json:"total_cost"`
|
||||||
|
TotalActualCost float64 `json:"total_actual_cost"`
|
||||||
|
AverageDurationMs float64 `json:"average_duration_ms"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchUserUsageStats represents usage stats for a single user
|
||||||
|
type BatchUserUsageStats struct {
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
TodayActualCost float64 `json:"today_actual_cost"`
|
||||||
|
TotalActualCost float64 `json:"total_actual_cost"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchApiKeyUsageStats represents usage stats for a single API key
|
||||||
|
type BatchApiKeyUsageStats struct {
|
||||||
|
ApiKeyID int64 `json:"api_key_id"`
|
||||||
|
TodayActualCost float64 `json:"today_actual_cost"`
|
||||||
|
TotalActualCost float64 `json:"total_actual_cost"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountUsageHistory represents daily usage history for an account
|
||||||
|
type AccountUsageHistory struct {
|
||||||
|
Date string `json:"date"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Requests int64 `json:"requests"`
|
||||||
|
Tokens int64 `json:"tokens"`
|
||||||
|
Cost float64 `json:"cost"`
|
||||||
|
ActualCost float64 `json:"actual_cost"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountUsageSummary represents summary statistics for an account
|
||||||
|
type AccountUsageSummary struct {
|
||||||
|
Days int `json:"days"`
|
||||||
|
ActualDaysUsed int `json:"actual_days_used"`
|
||||||
|
TotalCost float64 `json:"total_cost"`
|
||||||
|
TotalStandardCost float64 `json:"total_standard_cost"`
|
||||||
|
TotalRequests int64 `json:"total_requests"`
|
||||||
|
TotalTokens int64 `json:"total_tokens"`
|
||||||
|
AvgDailyCost float64 `json:"avg_daily_cost"`
|
||||||
|
AvgDailyRequests float64 `json:"avg_daily_requests"`
|
||||||
|
AvgDailyTokens float64 `json:"avg_daily_tokens"`
|
||||||
|
AvgDurationMs float64 `json:"avg_duration_ms"`
|
||||||
|
Today *struct {
|
||||||
|
Date string `json:"date"`
|
||||||
|
Cost float64 `json:"cost"`
|
||||||
|
Requests int64 `json:"requests"`
|
||||||
|
Tokens int64 `json:"tokens"`
|
||||||
|
} `json:"today"`
|
||||||
|
HighestCostDay *struct {
|
||||||
|
Date string `json:"date"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Cost float64 `json:"cost"`
|
||||||
|
Requests int64 `json:"requests"`
|
||||||
|
} `json:"highest_cost_day"`
|
||||||
|
HighestRequestDay *struct {
|
||||||
|
Date string `json:"date"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Requests int64 `json:"requests"`
|
||||||
|
Cost float64 `json:"cost"`
|
||||||
|
} `json:"highest_request_day"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccountUsageStatsResponse represents the full usage statistics response for an account
|
||||||
|
type AccountUsageStatsResponse struct {
|
||||||
|
History []AccountUsageHistory `json:"history"`
|
||||||
|
Summary AccountUsageSummary `json:"summary"`
|
||||||
|
Models []ModelStat `json:"models"`
|
||||||
|
}
|
||||||
@@ -113,46 +113,7 @@ func (r *UsageLogRepository) GetUserStats(ctx context.Context, userID int64, sta
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DashboardStats 仪表盘统计
|
// DashboardStats 仪表盘统计
|
||||||
type DashboardStats struct {
|
type DashboardStats = usagestats.DashboardStats
|
||||||
// 用户统计
|
|
||||||
TotalUsers int64 `json:"total_users"`
|
|
||||||
TodayNewUsers int64 `json:"today_new_users"` // 今日新增用户数
|
|
||||||
ActiveUsers int64 `json:"active_users"` // 今日有请求的用户数
|
|
||||||
|
|
||||||
// API Key 统计
|
|
||||||
TotalApiKeys int64 `json:"total_api_keys"`
|
|
||||||
ActiveApiKeys int64 `json:"active_api_keys"` // 状态为 active 的 API Key 数
|
|
||||||
|
|
||||||
// 账户统计
|
|
||||||
TotalAccounts int64 `json:"total_accounts"`
|
|
||||||
NormalAccounts int64 `json:"normal_accounts"` // 正常账户数 (schedulable=true, status=active)
|
|
||||||
ErrorAccounts int64 `json:"error_accounts"` // 异常账户数 (status=error)
|
|
||||||
RateLimitAccounts int64 `json:"ratelimit_accounts"` // 限流账户数
|
|
||||||
OverloadAccounts int64 `json:"overload_accounts"` // 过载账户数
|
|
||||||
|
|
||||||
// 累计 Token 使用统计
|
|
||||||
TotalRequests int64 `json:"total_requests"`
|
|
||||||
TotalInputTokens int64 `json:"total_input_tokens"`
|
|
||||||
TotalOutputTokens int64 `json:"total_output_tokens"`
|
|
||||||
TotalCacheCreationTokens int64 `json:"total_cache_creation_tokens"`
|
|
||||||
TotalCacheReadTokens int64 `json:"total_cache_read_tokens"`
|
|
||||||
TotalTokens int64 `json:"total_tokens"`
|
|
||||||
TotalCost float64 `json:"total_cost"` // 累计标准计费
|
|
||||||
TotalActualCost float64 `json:"total_actual_cost"` // 累计实际扣除
|
|
||||||
|
|
||||||
// 今日 Token 使用统计
|
|
||||||
TodayRequests int64 `json:"today_requests"`
|
|
||||||
TodayInputTokens int64 `json:"today_input_tokens"`
|
|
||||||
TodayOutputTokens int64 `json:"today_output_tokens"`
|
|
||||||
TodayCacheCreationTokens int64 `json:"today_cache_creation_tokens"`
|
|
||||||
TodayCacheReadTokens int64 `json:"today_cache_read_tokens"`
|
|
||||||
TodayTokens int64 `json:"today_tokens"`
|
|
||||||
TodayCost float64 `json:"today_cost"` // 今日标准计费
|
|
||||||
TodayActualCost float64 `json:"today_actual_cost"` // 今日实际扣除
|
|
||||||
|
|
||||||
// 系统运行统计
|
|
||||||
AverageDurationMs float64 `json:"average_duration_ms"` // 平均响应时间
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *UsageLogRepository) GetDashboardStats(ctx context.Context) (*DashboardStats, error) {
|
func (r *UsageLogRepository) GetDashboardStats(ctx context.Context) (*DashboardStats, error) {
|
||||||
var stats DashboardStats
|
var stats DashboardStats
|
||||||
@@ -398,47 +359,16 @@ func (r *UsageLogRepository) GetAccountWindowStats(ctx context.Context, accountI
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TrendDataPoint represents a single point in trend data
|
// TrendDataPoint represents a single point in trend data
|
||||||
type TrendDataPoint struct {
|
type TrendDataPoint = usagestats.TrendDataPoint
|
||||||
Date string `json:"date"`
|
|
||||||
Requests int64 `json:"requests"`
|
|
||||||
InputTokens int64 `json:"input_tokens"`
|
|
||||||
OutputTokens int64 `json:"output_tokens"`
|
|
||||||
CacheTokens int64 `json:"cache_tokens"`
|
|
||||||
TotalTokens int64 `json:"total_tokens"`
|
|
||||||
Cost float64 `json:"cost"` // 标准计费
|
|
||||||
ActualCost float64 `json:"actual_cost"` // 实际扣除
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModelStat represents usage statistics for a single model
|
// ModelStat represents usage statistics for a single model
|
||||||
type ModelStat struct {
|
type ModelStat = usagestats.ModelStat
|
||||||
Model string `json:"model"`
|
|
||||||
Requests int64 `json:"requests"`
|
|
||||||
InputTokens int64 `json:"input_tokens"`
|
|
||||||
OutputTokens int64 `json:"output_tokens"`
|
|
||||||
TotalTokens int64 `json:"total_tokens"`
|
|
||||||
Cost float64 `json:"cost"` // 标准计费
|
|
||||||
ActualCost float64 `json:"actual_cost"` // 实际扣除
|
|
||||||
}
|
|
||||||
|
|
||||||
// UserUsageTrendPoint represents user usage trend data point
|
// UserUsageTrendPoint represents user usage trend data point
|
||||||
type UserUsageTrendPoint struct {
|
type UserUsageTrendPoint = usagestats.UserUsageTrendPoint
|
||||||
Date string `json:"date"`
|
|
||||||
UserID int64 `json:"user_id"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Requests int64 `json:"requests"`
|
|
||||||
Tokens int64 `json:"tokens"`
|
|
||||||
Cost float64 `json:"cost"` // 标准计费
|
|
||||||
ActualCost float64 `json:"actual_cost"` // 实际扣除
|
|
||||||
}
|
|
||||||
|
|
||||||
// ApiKeyUsageTrendPoint represents API key usage trend data point
|
// ApiKeyUsageTrendPoint represents API key usage trend data point
|
||||||
type ApiKeyUsageTrendPoint struct {
|
type ApiKeyUsageTrendPoint = usagestats.ApiKeyUsageTrendPoint
|
||||||
Date string `json:"date"`
|
|
||||||
ApiKeyID int64 `json:"api_key_id"`
|
|
||||||
KeyName string `json:"key_name"`
|
|
||||||
Requests int64 `json:"requests"`
|
|
||||||
Tokens int64 `json:"tokens"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetApiKeyUsageTrend returns usage trend data grouped by API key and date
|
// GetApiKeyUsageTrend returns usage trend data grouped by API key and date
|
||||||
func (r *UsageLogRepository) GetApiKeyUsageTrend(ctx context.Context, startTime, endTime time.Time, granularity string, limit int) ([]ApiKeyUsageTrendPoint, error) {
|
func (r *UsageLogRepository) GetApiKeyUsageTrend(ctx context.Context, startTime, endTime time.Time, granularity string, limit int) ([]ApiKeyUsageTrendPoint, error) {
|
||||||
@@ -531,34 +461,7 @@ func (r *UsageLogRepository) GetUserUsageTrend(ctx context.Context, startTime, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UserDashboardStats 用户仪表盘统计
|
// UserDashboardStats 用户仪表盘统计
|
||||||
type UserDashboardStats struct {
|
type UserDashboardStats = usagestats.UserDashboardStats
|
||||||
// API Key 统计
|
|
||||||
TotalApiKeys int64 `json:"total_api_keys"`
|
|
||||||
ActiveApiKeys int64 `json:"active_api_keys"`
|
|
||||||
|
|
||||||
// 累计 Token 使用统计
|
|
||||||
TotalRequests int64 `json:"total_requests"`
|
|
||||||
TotalInputTokens int64 `json:"total_input_tokens"`
|
|
||||||
TotalOutputTokens int64 `json:"total_output_tokens"`
|
|
||||||
TotalCacheCreationTokens int64 `json:"total_cache_creation_tokens"`
|
|
||||||
TotalCacheReadTokens int64 `json:"total_cache_read_tokens"`
|
|
||||||
TotalTokens int64 `json:"total_tokens"`
|
|
||||||
TotalCost float64 `json:"total_cost"` // 累计标准计费
|
|
||||||
TotalActualCost float64 `json:"total_actual_cost"` // 累计实际扣除
|
|
||||||
|
|
||||||
// 今日 Token 使用统计
|
|
||||||
TodayRequests int64 `json:"today_requests"`
|
|
||||||
TodayInputTokens int64 `json:"today_input_tokens"`
|
|
||||||
TodayOutputTokens int64 `json:"today_output_tokens"`
|
|
||||||
TodayCacheCreationTokens int64 `json:"today_cache_creation_tokens"`
|
|
||||||
TodayCacheReadTokens int64 `json:"today_cache_read_tokens"`
|
|
||||||
TodayTokens int64 `json:"today_tokens"`
|
|
||||||
TodayCost float64 `json:"today_cost"` // 今日标准计费
|
|
||||||
TodayActualCost float64 `json:"today_actual_cost"` // 今日实际扣除
|
|
||||||
|
|
||||||
// 性能统计
|
|
||||||
AverageDurationMs float64 `json:"average_duration_ms"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUserDashboardStats 获取用户专属的仪表盘统计
|
// GetUserDashboardStats 获取用户专属的仪表盘统计
|
||||||
func (r *UsageLogRepository) GetUserDashboardStats(ctx context.Context, userID int64) (*UserDashboardStats, error) {
|
func (r *UsageLogRepository) GetUserDashboardStats(ctx context.Context, userID int64) (*UserDashboardStats, error) {
|
||||||
@@ -705,12 +608,7 @@ func (r *UsageLogRepository) GetUserModelStats(ctx context.Context, userID int64
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UsageLogFilters represents filters for usage log queries
|
// UsageLogFilters represents filters for usage log queries
|
||||||
type UsageLogFilters struct {
|
type UsageLogFilters = usagestats.UsageLogFilters
|
||||||
UserID int64
|
|
||||||
ApiKeyID int64
|
|
||||||
StartTime *time.Time
|
|
||||||
EndTime *time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListWithFilters lists usage logs with optional filters (for admin)
|
// ListWithFilters lists usage logs with optional filters (for admin)
|
||||||
func (r *UsageLogRepository) ListWithFilters(ctx context.Context, params pagination.PaginationParams, filters UsageLogFilters) ([]model.UsageLog, *pagination.PaginationResult, error) {
|
func (r *UsageLogRepository) ListWithFilters(ctx context.Context, params pagination.PaginationParams, filters UsageLogFilters) ([]model.UsageLog, *pagination.PaginationResult, error) {
|
||||||
@@ -758,23 +656,10 @@ func (r *UsageLogRepository) ListWithFilters(ctx context.Context, params paginat
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UsageStats represents usage statistics
|
// UsageStats represents usage statistics
|
||||||
type UsageStats struct {
|
type UsageStats = usagestats.UsageStats
|
||||||
TotalRequests int64 `json:"total_requests"`
|
|
||||||
TotalInputTokens int64 `json:"total_input_tokens"`
|
|
||||||
TotalOutputTokens int64 `json:"total_output_tokens"`
|
|
||||||
TotalCacheTokens int64 `json:"total_cache_tokens"`
|
|
||||||
TotalTokens int64 `json:"total_tokens"`
|
|
||||||
TotalCost float64 `json:"total_cost"`
|
|
||||||
TotalActualCost float64 `json:"total_actual_cost"`
|
|
||||||
AverageDurationMs float64 `json:"average_duration_ms"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// BatchUserUsageStats represents usage stats for a single user
|
// BatchUserUsageStats represents usage stats for a single user
|
||||||
type BatchUserUsageStats struct {
|
type BatchUserUsageStats = usagestats.BatchUserUsageStats
|
||||||
UserID int64 `json:"user_id"`
|
|
||||||
TodayActualCost float64 `json:"today_actual_cost"`
|
|
||||||
TotalActualCost float64 `json:"total_actual_cost"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBatchUserUsageStats gets today and total actual_cost for multiple users
|
// GetBatchUserUsageStats gets today and total actual_cost for multiple users
|
||||||
func (r *UsageLogRepository) GetBatchUserUsageStats(ctx context.Context, userIDs []int64) (map[int64]*BatchUserUsageStats, error) {
|
func (r *UsageLogRepository) GetBatchUserUsageStats(ctx context.Context, userIDs []int64) (map[int64]*BatchUserUsageStats, error) {
|
||||||
@@ -834,11 +719,7 @@ func (r *UsageLogRepository) GetBatchUserUsageStats(ctx context.Context, userIDs
|
|||||||
}
|
}
|
||||||
|
|
||||||
// BatchApiKeyUsageStats represents usage stats for a single API key
|
// BatchApiKeyUsageStats represents usage stats for a single API key
|
||||||
type BatchApiKeyUsageStats struct {
|
type BatchApiKeyUsageStats = usagestats.BatchApiKeyUsageStats
|
||||||
ApiKeyID int64 `json:"api_key_id"`
|
|
||||||
TodayActualCost float64 `json:"today_actual_cost"`
|
|
||||||
TotalActualCost float64 `json:"total_actual_cost"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBatchApiKeyUsageStats gets today and total actual_cost for multiple API keys
|
// GetBatchApiKeyUsageStats gets today and total actual_cost for multiple API keys
|
||||||
func (r *UsageLogRepository) GetBatchApiKeyUsageStats(ctx context.Context, apiKeyIDs []int64) (map[int64]*BatchApiKeyUsageStats, error) {
|
func (r *UsageLogRepository) GetBatchApiKeyUsageStats(ctx context.Context, apiKeyIDs []int64) (map[int64]*BatchApiKeyUsageStats, error) {
|
||||||
@@ -1012,53 +893,13 @@ func (r *UsageLogRepository) GetGlobalStats(ctx context.Context, startTime, endT
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AccountUsageHistory represents daily usage history for an account
|
// AccountUsageHistory represents daily usage history for an account
|
||||||
type AccountUsageHistory struct {
|
type AccountUsageHistory = usagestats.AccountUsageHistory
|
||||||
Date string `json:"date"`
|
|
||||||
Label string `json:"label"`
|
|
||||||
Requests int64 `json:"requests"`
|
|
||||||
Tokens int64 `json:"tokens"`
|
|
||||||
Cost float64 `json:"cost"`
|
|
||||||
ActualCost float64 `json:"actual_cost"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AccountUsageSummary represents summary statistics for an account
|
// AccountUsageSummary represents summary statistics for an account
|
||||||
type AccountUsageSummary struct {
|
type AccountUsageSummary = usagestats.AccountUsageSummary
|
||||||
Days int `json:"days"`
|
|
||||||
ActualDaysUsed int `json:"actual_days_used"`
|
|
||||||
TotalCost float64 `json:"total_cost"`
|
|
||||||
TotalStandardCost float64 `json:"total_standard_cost"`
|
|
||||||
TotalRequests int64 `json:"total_requests"`
|
|
||||||
TotalTokens int64 `json:"total_tokens"`
|
|
||||||
AvgDailyCost float64 `json:"avg_daily_cost"`
|
|
||||||
AvgDailyRequests float64 `json:"avg_daily_requests"`
|
|
||||||
AvgDailyTokens float64 `json:"avg_daily_tokens"`
|
|
||||||
AvgDurationMs float64 `json:"avg_duration_ms"`
|
|
||||||
Today *struct {
|
|
||||||
Date string `json:"date"`
|
|
||||||
Cost float64 `json:"cost"`
|
|
||||||
Requests int64 `json:"requests"`
|
|
||||||
Tokens int64 `json:"tokens"`
|
|
||||||
} `json:"today"`
|
|
||||||
HighestCostDay *struct {
|
|
||||||
Date string `json:"date"`
|
|
||||||
Label string `json:"label"`
|
|
||||||
Cost float64 `json:"cost"`
|
|
||||||
Requests int64 `json:"requests"`
|
|
||||||
} `json:"highest_cost_day"`
|
|
||||||
HighestRequestDay *struct {
|
|
||||||
Date string `json:"date"`
|
|
||||||
Label string `json:"label"`
|
|
||||||
Requests int64 `json:"requests"`
|
|
||||||
Cost float64 `json:"cost"`
|
|
||||||
} `json:"highest_request_day"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AccountUsageStatsResponse represents the full usage statistics response for an account
|
// AccountUsageStatsResponse represents the full usage statistics response for an account
|
||||||
type AccountUsageStatsResponse struct {
|
type AccountUsageStatsResponse = usagestats.AccountUsageStatsResponse
|
||||||
History []AccountUsageHistory `json:"history"`
|
|
||||||
Summary AccountUsageSummary `json:"summary"`
|
|
||||||
Models []ModelStat `json:"models"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAccountUsageStats returns comprehensive usage statistics for an account over a time range
|
// GetAccountUsageStats returns comprehensive usage statistics for an account over a time range
|
||||||
func (r *UsageLogRepository) GetAccountUsageStats(ctx context.Context, accountID int64, startTime, endTime time.Time) (*AccountUsageStatsResponse, error) {
|
func (r *UsageLogRepository) GetAccountUsageStats(ctx context.Context, accountID int64, startTime, endTime time.Time) (*AccountUsageStatsResponse, error) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"sub2api/internal/model"
|
"sub2api/internal/model"
|
||||||
|
"sub2api/internal/pkg/usagestats"
|
||||||
"sub2api/internal/service/ports"
|
"sub2api/internal/service/ports"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -176,6 +177,14 @@ func (s *AccountUsageService) GetTodayStats(ctx context.Context, accountID int64
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *AccountUsageService) GetAccountUsageStats(ctx context.Context, accountID int64, startTime, endTime time.Time) (*usagestats.AccountUsageStatsResponse, error) {
|
||||||
|
stats, err := s.usageLogRepo.GetAccountUsageStats(ctx, accountID, startTime, endTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get account usage stats failed: %w", err)
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
// fetchOAuthUsage 从Anthropic API获取OAuth账号的使用量
|
// fetchOAuthUsage 从Anthropic API获取OAuth账号的使用量
|
||||||
func (s *AccountUsageService) fetchOAuthUsage(ctx context.Context, account *model.Account) (*UsageInfo, error) {
|
func (s *AccountUsageService) fetchOAuthUsage(ctx context.Context, account *model.Account) (*UsageInfo, error) {
|
||||||
accessToken := account.GetCredential("access_token")
|
accessToken := account.GetCredential("access_token")
|
||||||
|
|||||||
@@ -455,3 +455,11 @@ func (s *ApiKeyService) canUserBindGroupInternal(user *model.User, group *model.
|
|||||||
// 标准类型分组:使用原有逻辑
|
// 标准类型分组:使用原有逻辑
|
||||||
return user.CanBindGroup(group.ID, group.IsExclusive)
|
return user.CanBindGroup(group.ID, group.IsExclusive)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *ApiKeyService) SearchApiKeys(ctx context.Context, userID int64, keyword string, limit int) ([]model.ApiKey, error) {
|
||||||
|
keys, err := s.apiKeyRepo.SearchApiKeys(ctx, userID, keyword, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("search api keys: %w", err)
|
||||||
|
}
|
||||||
|
return keys, nil
|
||||||
|
}
|
||||||
|
|||||||
77
backend/internal/service/dashboard_service.go
Normal file
77
backend/internal/service/dashboard_service.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"sub2api/internal/pkg/usagestats"
|
||||||
|
"sub2api/internal/service/ports"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DashboardService provides aggregated statistics for admin dashboard.
|
||||||
|
type DashboardService struct {
|
||||||
|
usageRepo ports.UsageLogRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDashboardService(usageRepo ports.UsageLogRepository) *DashboardService {
|
||||||
|
return &DashboardService{
|
||||||
|
usageRepo: usageRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DashboardService) GetDashboardStats(ctx context.Context) (*usagestats.DashboardStats, error) {
|
||||||
|
stats, err := s.usageRepo.GetDashboardStats(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get dashboard stats: %w", err)
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DashboardService) GetUsageTrendWithFilters(ctx context.Context, startTime, endTime time.Time, granularity string, userID, apiKeyID int64) ([]usagestats.TrendDataPoint, error) {
|
||||||
|
trend, err := s.usageRepo.GetUsageTrendWithFilters(ctx, startTime, endTime, granularity, userID, apiKeyID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get usage trend with filters: %w", err)
|
||||||
|
}
|
||||||
|
return trend, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DashboardService) GetModelStatsWithFilters(ctx context.Context, startTime, endTime time.Time, userID, apiKeyID int64) ([]usagestats.ModelStat, error) {
|
||||||
|
stats, err := s.usageRepo.GetModelStatsWithFilters(ctx, startTime, endTime, userID, apiKeyID, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get model stats with filters: %w", err)
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DashboardService) GetApiKeyUsageTrend(ctx context.Context, startTime, endTime time.Time, granularity string, limit int) ([]usagestats.ApiKeyUsageTrendPoint, error) {
|
||||||
|
trend, err := s.usageRepo.GetApiKeyUsageTrend(ctx, startTime, endTime, granularity, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get api key usage trend: %w", err)
|
||||||
|
}
|
||||||
|
return trend, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DashboardService) GetUserUsageTrend(ctx context.Context, startTime, endTime time.Time, granularity string, limit int) ([]usagestats.UserUsageTrendPoint, error) {
|
||||||
|
trend, err := s.usageRepo.GetUserUsageTrend(ctx, startTime, endTime, granularity, limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get user usage trend: %w", err)
|
||||||
|
}
|
||||||
|
return trend, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DashboardService) GetBatchUserUsageStats(ctx context.Context, userIDs []int64) (map[int64]*usagestats.BatchUserUsageStats, error) {
|
||||||
|
stats, err := s.usageRepo.GetBatchUserUsageStats(ctx, userIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get batch user usage stats: %w", err)
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DashboardService) GetBatchApiKeyUsageStats(ctx context.Context, apiKeyIDs []int64) (map[int64]*usagestats.BatchApiKeyUsageStats, error) {
|
||||||
|
stats, err := s.usageRepo.GetBatchApiKeyUsageStats(ctx, apiKeyIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get batch api key usage stats: %w", err)
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
@@ -25,4 +25,25 @@ type UsageLogRepository interface {
|
|||||||
|
|
||||||
GetAccountWindowStats(ctx context.Context, accountID int64, startTime time.Time) (*usagestats.AccountStats, error)
|
GetAccountWindowStats(ctx context.Context, accountID int64, startTime time.Time) (*usagestats.AccountStats, error)
|
||||||
GetAccountTodayStats(ctx context.Context, accountID int64) (*usagestats.AccountStats, error)
|
GetAccountTodayStats(ctx context.Context, accountID int64) (*usagestats.AccountStats, error)
|
||||||
|
|
||||||
|
// Admin dashboard stats
|
||||||
|
GetDashboardStats(ctx context.Context) (*usagestats.DashboardStats, error)
|
||||||
|
GetUsageTrendWithFilters(ctx context.Context, startTime, endTime time.Time, granularity string, userID, apiKeyID int64) ([]usagestats.TrendDataPoint, error)
|
||||||
|
GetModelStatsWithFilters(ctx context.Context, startTime, endTime time.Time, userID, apiKeyID, accountID int64) ([]usagestats.ModelStat, error)
|
||||||
|
GetApiKeyUsageTrend(ctx context.Context, startTime, endTime time.Time, granularity string, limit int) ([]usagestats.ApiKeyUsageTrendPoint, error)
|
||||||
|
GetUserUsageTrend(ctx context.Context, startTime, endTime time.Time, granularity string, limit int) ([]usagestats.UserUsageTrendPoint, error)
|
||||||
|
GetBatchUserUsageStats(ctx context.Context, userIDs []int64) (map[int64]*usagestats.BatchUserUsageStats, error)
|
||||||
|
GetBatchApiKeyUsageStats(ctx context.Context, apiKeyIDs []int64) (map[int64]*usagestats.BatchApiKeyUsageStats, error)
|
||||||
|
|
||||||
|
// User dashboard stats
|
||||||
|
GetUserDashboardStats(ctx context.Context, userID int64) (*usagestats.UserDashboardStats, error)
|
||||||
|
GetUserUsageTrendByUserID(ctx context.Context, userID int64, startTime, endTime time.Time, granularity string) ([]usagestats.TrendDataPoint, error)
|
||||||
|
GetUserModelStats(ctx context.Context, userID int64, startTime, endTime time.Time) ([]usagestats.ModelStat, error)
|
||||||
|
|
||||||
|
// Admin usage listing/stats
|
||||||
|
ListWithFilters(ctx context.Context, params pagination.PaginationParams, filters usagestats.UsageLogFilters) ([]model.UsageLog, *pagination.PaginationResult, error)
|
||||||
|
GetGlobalStats(ctx context.Context, startTime, endTime time.Time) (*usagestats.UsageStats, error)
|
||||||
|
|
||||||
|
// Account stats
|
||||||
|
GetAccountUsageStats(ctx context.Context, accountID int64, startTime, endTime time.Time) (*usagestats.AccountUsageStatsResponse, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"sub2api/internal/model"
|
"sub2api/internal/model"
|
||||||
"sub2api/internal/pkg/pagination"
|
"sub2api/internal/pkg/pagination"
|
||||||
|
"sub2api/internal/pkg/usagestats"
|
||||||
"sub2api/internal/service/ports"
|
"sub2api/internal/service/ports"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -282,3 +283,57 @@ func (s *UsageService) Delete(ctx context.Context, id int64) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetUserDashboardStats returns per-user dashboard summary stats.
|
||||||
|
func (s *UsageService) GetUserDashboardStats(ctx context.Context, userID int64) (*usagestats.UserDashboardStats, error) {
|
||||||
|
stats, err := s.usageRepo.GetUserDashboardStats(ctx, userID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get user dashboard stats: %w", err)
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserUsageTrendByUserID returns per-user usage trend.
|
||||||
|
func (s *UsageService) GetUserUsageTrendByUserID(ctx context.Context, userID int64, startTime, endTime time.Time, granularity string) ([]usagestats.TrendDataPoint, error) {
|
||||||
|
trend, err := s.usageRepo.GetUserUsageTrendByUserID(ctx, userID, startTime, endTime, granularity)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get user usage trend: %w", err)
|
||||||
|
}
|
||||||
|
return trend, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserModelStats returns per-user model usage stats.
|
||||||
|
func (s *UsageService) GetUserModelStats(ctx context.Context, userID int64, startTime, endTime time.Time) ([]usagestats.ModelStat, error) {
|
||||||
|
stats, err := s.usageRepo.GetUserModelStats(ctx, userID, startTime, endTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get user model stats: %w", err)
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBatchApiKeyUsageStats returns today/total actual_cost for given api keys.
|
||||||
|
func (s *UsageService) GetBatchApiKeyUsageStats(ctx context.Context, apiKeyIDs []int64) (map[int64]*usagestats.BatchApiKeyUsageStats, error) {
|
||||||
|
stats, err := s.usageRepo.GetBatchApiKeyUsageStats(ctx, apiKeyIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get batch api key usage stats: %w", err)
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListWithFilters lists usage logs with admin filters.
|
||||||
|
func (s *UsageService) ListWithFilters(ctx context.Context, params pagination.PaginationParams, filters usagestats.UsageLogFilters) ([]model.UsageLog, *pagination.PaginationResult, error) {
|
||||||
|
logs, result, err := s.usageRepo.ListWithFilters(ctx, params, filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("list usage logs with filters: %w", err)
|
||||||
|
}
|
||||||
|
return logs, result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetGlobalStats returns global usage stats for a time range.
|
||||||
|
func (s *UsageService) GetGlobalStats(ctx context.Context, startTime, endTime time.Time) (*usagestats.UsageStats, error) {
|
||||||
|
stats, err := s.usageRepo.GetGlobalStats(ctx, startTime, endTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get global usage stats: %w", err)
|
||||||
|
}
|
||||||
|
return stats, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ var ProviderSet = wire.NewSet(
|
|||||||
NewProxyService,
|
NewProxyService,
|
||||||
NewRedeemService,
|
NewRedeemService,
|
||||||
NewUsageService,
|
NewUsageService,
|
||||||
|
NewDashboardService,
|
||||||
ProvidePricingService,
|
ProvidePricingService,
|
||||||
NewBillingService,
|
NewBillingService,
|
||||||
NewBillingCacheService,
|
NewBillingCacheService,
|
||||||
|
|||||||
Reference in New Issue
Block a user