From 0abe322cca0da59069531efb8de499cd2caf4355 Mon Sep 17 00:00:00 2001 From: shaw Date: Wed, 24 Dec 2025 15:44:45 +0800 Subject: [PATCH] =?UTF-8?q?feat(accounts):=20=E8=B4=A6=E6=88=B7=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=98=BE=E7=A4=BA=E5=AE=9E=E6=97=B6=E5=B9=B6=E5=8F=91?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在账户列表 API 返回中添加 current_concurrency 字段 - 合并平台和类型列为 PlatformTypeBadge 组件,节省表格空间 - 新增并发状态列,显示 当前/最大 并发数,支持颜色编码 --- backend/cmd/server/wire_gen.go | 6 +- .../internal/handler/admin/account_handler.go | 34 ++++++++- .../internal/service/concurrency_service.go | 17 +++++ .../components/common/PlatformTypeBadge.vue | 75 +++++++++++++++++++ frontend/src/i18n/locales/en.ts | 2 + frontend/src/i18n/locales/zh.ts | 2 + frontend/src/types/index.ts | 1 + frontend/src/views/admin/AccountsView.vue | 49 ++++++------ 8 files changed, 157 insertions(+), 29 deletions(-) create mode 100644 frontend/src/components/common/PlatformTypeBadge.vue diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go index 41804775..3c1e8ce4 100644 --- a/backend/cmd/server/wire_gen.go +++ b/backend/cmd/server/wire_gen.go @@ -84,7 +84,9 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { accountUsageService := service.NewAccountUsageService(accountRepository, usageLogRepository, claudeUsageFetcher) httpUpstream := repository.NewHTTPUpstream(configConfig) accountTestService := service.NewAccountTestService(accountRepository, oAuthService, openAIOAuthService, httpUpstream) - accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, rateLimitService, accountUsageService, accountTestService) + concurrencyCache := repository.NewConcurrencyCache(client) + concurrencyService := service.NewConcurrencyService(concurrencyCache) + accountHandler := admin.NewAccountHandler(adminService, oAuthService, openAIOAuthService, rateLimitService, accountUsageService, accountTestService, concurrencyService) oAuthHandler := admin.NewOAuthHandler(oAuthService) openAIOAuthHandler := admin.NewOpenAIOAuthHandler(openAIOAuthService, adminService) proxyHandler := admin.NewProxyHandler(adminService) @@ -108,8 +110,6 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) { identityCache := repository.NewIdentityCache(client) identityService := service.NewIdentityService(identityCache) gatewayService := service.NewGatewayService(accountRepository, usageLogRepository, userRepository, userSubscriptionRepository, gatewayCache, configConfig, billingService, rateLimitService, billingCacheService, identityService, httpUpstream) - concurrencyCache := repository.NewConcurrencyCache(client) - concurrencyService := service.NewConcurrencyService(concurrencyCache) gatewayHandler := handler.NewGatewayHandler(gatewayService, userService, concurrencyService, billingCacheService) openAIGatewayService := service.NewOpenAIGatewayService(accountRepository, usageLogRepository, userRepository, userSubscriptionRepository, gatewayCache, configConfig, billingService, rateLimitService, billingCacheService, httpUpstream) openAIGatewayHandler := handler.NewOpenAIGatewayHandler(openAIGatewayService, concurrencyService, billingCacheService) diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go index 74b26187..13ce453b 100644 --- a/backend/internal/handler/admin/account_handler.go +++ b/backend/internal/handler/admin/account_handler.go @@ -3,6 +3,7 @@ package admin import ( "strconv" + "sub2api/internal/model" "sub2api/internal/pkg/claude" "sub2api/internal/pkg/openai" "sub2api/internal/pkg/response" @@ -32,10 +33,11 @@ type AccountHandler struct { rateLimitService *service.RateLimitService accountUsageService *service.AccountUsageService accountTestService *service.AccountTestService + concurrencyService *service.ConcurrencyService } // 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, concurrencyService *service.ConcurrencyService) *AccountHandler { return &AccountHandler{ adminService: adminService, oauthService: oauthService, @@ -43,6 +45,7 @@ func NewAccountHandler(adminService service.AdminService, oauthService *service. rateLimitService: rateLimitService, accountUsageService: accountUsageService, accountTestService: accountTestService, + concurrencyService: concurrencyService, } } @@ -73,6 +76,12 @@ type UpdateAccountRequest struct { GroupIDs *[]int64 `json:"group_ids"` } +// AccountWithConcurrency extends Account with real-time concurrency info +type AccountWithConcurrency struct { + *model.Account + CurrentConcurrency int `json:"current_concurrency"` +} + // List handles listing all accounts with pagination // GET /api/v1/admin/accounts func (h *AccountHandler) List(c *gin.Context) { @@ -88,7 +97,28 @@ func (h *AccountHandler) List(c *gin.Context) { return } - response.Paginated(c, accounts, total, page, pageSize) + // Get current concurrency counts for all accounts + accountIDs := make([]int64, len(accounts)) + for i, acc := range accounts { + accountIDs[i] = acc.ID + } + + concurrencyCounts, err := h.concurrencyService.GetAccountConcurrencyBatch(c.Request.Context(), accountIDs) + if err != nil { + // Log error but don't fail the request, just use 0 for all + concurrencyCounts = make(map[int64]int) + } + + // Build response with concurrency info + result := make([]AccountWithConcurrency, len(accounts)) + for i := range accounts { + result[i] = AccountWithConcurrency{ + Account: &accounts[i], + CurrentConcurrency: concurrencyCounts[accounts[i].ID], + } + } + + response.Paginated(c, result, total, page, pageSize) } // GetByID handles getting an account by ID diff --git a/backend/internal/service/concurrency_service.go b/backend/internal/service/concurrency_service.go index c54167da..c554624c 100644 --- a/backend/internal/service/concurrency_service.go +++ b/backend/internal/service/concurrency_service.go @@ -147,3 +147,20 @@ func CalculateMaxWait(userConcurrency int) int { } return userConcurrency + defaultExtraWaitSlots } + +// GetAccountConcurrencyBatch gets current concurrency counts for multiple accounts +// Returns a map of accountID -> current concurrency count +func (s *ConcurrencyService) GetAccountConcurrencyBatch(ctx context.Context, accountIDs []int64) (map[int64]int, error) { + result := make(map[int64]int) + + for _, accountID := range accountIDs { + count, err := s.cache.GetAccountConcurrency(ctx, accountID) + if err != nil { + // If key doesn't exist in Redis, count is 0 + count = 0 + } + result[accountID] = count + } + + return result, nil +} diff --git a/frontend/src/components/common/PlatformTypeBadge.vue b/frontend/src/components/common/PlatformTypeBadge.vue new file mode 100644 index 00000000..d7ed6714 --- /dev/null +++ b/frontend/src/components/common/PlatformTypeBadge.vue @@ -0,0 +1,75 @@ + + + diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index a81f6706..1d063f57 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -702,8 +702,10 @@ export default { }, columns: { name: 'Name', + platformType: 'Platform/Type', platform: 'Platform', type: 'Type', + concurrencyStatus: 'Concurrency', status: 'Status', schedulable: 'Schedule', todayStats: "Today's Stats", diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index 4cd4214b..d71148ae 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -796,8 +796,10 @@ export default { failedToToggleSchedulable: '切换调度状态失败', columns: { name: '名称', + platformType: '平台/类型', platform: '平台', type: '类型', + concurrencyStatus: '并发', priority: '优先级', weight: '权重', status: '状态', diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 7fea7ad9..17b3b9db 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -319,6 +319,7 @@ export interface Account { extra?: CodexUsageSnapshot & Record; // Extra fields including Codex usage proxy_id: number | null; concurrency: number; + current_concurrency?: number; // Real-time concurrency count from Redis priority: number; status: 'active' | 'inactive' | 'error'; error_message: string | null; diff --git a/frontend/src/views/admin/AccountsView.vue b/frontend/src/views/admin/AccountsView.vue index fca29988..b9903a38 100644 --- a/frontend/src/views/admin/AccountsView.vue +++ b/frontend/src/views/admin/AccountsView.vue @@ -73,29 +73,30 @@ {{ value }} -