From 04c3b786199124f544bd7e889afff4bdc6e5c271 Mon Sep 17 00:00:00 2001 From: MarkDonish <181540269+MarkDonish@users.noreply.github.com> Date: Thu, 4 Jun 2026 23:50:23 +0800 Subject: [PATCH] refactor(group): remove auto copy-accounts-from-group mechanism MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 号池红线:账号的分组归属只能人工手动修改,不允许任何自动化在创建/编辑分组时 自动复制并改写账号-分组绑定关系。 起因:Destiny (Anthropic Key 上游) 只配了 VIP 分组,却反复被自动加进 CloudMask Pro Exclusive 分组。根因是 UpdateGroup/CreateGroup 的 copy_accounts_from_group_ids:保存分组时会先清空目标分组绑定,再把源分组 (如 VIP) 的全部账号同步进来,导致分组归属漂移。 移除范围(纯删除,不影响调度类运行时行为): - backend admin_service.go: CreateGroup/UpdateGroup 的 copy 执行块 + 两个 Input 结构体的 CopyAccountsFromGroupIDs 字段 - backend group_handler.go: 两个请求结构体字段 + 两处赋值 - frontend GroupsView.vue + types/index.ts: 创建/编辑表单的"从分组复制账号"UI、 computed 选项、表单字段与重置逻辑 保留不动:账号临时不可调度、95% 用量自动关停调度等运行时调度自动化。 未本地编译后端(本机无 Go);前端 vue-tsc typecheck 通过。 --- .../internal/handler/admin/group_handler.go | 6 - backend/internal/service/admin_service.go | 133 ----------- frontend/src/types/index.ts | 3 - frontend/src/views/admin/GroupsView.vue | 214 ------------------ 4 files changed, 356 deletions(-) diff --git a/backend/internal/handler/admin/group_handler.go b/backend/internal/handler/admin/group_handler.go index 91ca71a4d37..04b0461302d 100644 --- a/backend/internal/handler/admin/group_handler.go +++ b/backend/internal/handler/admin/group_handler.go @@ -115,8 +115,6 @@ type CreateGroupRequest struct { MessagesDispatchModelConfig service.OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config"` // 分组 RPM 上限(0 = 不限制) RPMLimit int `json:"rpm_limit"` - // 从指定分组复制账号(创建后自动绑定) - CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"` } // UpdateGroupRequest represents update group request @@ -155,8 +153,6 @@ type UpdateGroupRequest struct { MessagesDispatchModelConfig *service.OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config"` // 分组 RPM 上限(0 = 不限制);nil 表示未提供不改动 RPMLimit *int `json:"rpm_limit"` - // 从指定分组复制账号(同步操作:先清空当前分组的账号绑定,再绑定源分组的账号) - CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"` } // List handles listing all groups with pagination @@ -276,7 +272,6 @@ func (h *GroupHandler) Create(c *gin.Context) { DefaultMappedModel: req.DefaultMappedModel, MessagesDispatchModelConfig: req.MessagesDispatchModelConfig, RPMLimit: req.RPMLimit, - CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs, }) if err != nil { response.ErrorFrom(c, err) @@ -331,7 +326,6 @@ func (h *GroupHandler) Update(c *gin.Context) { DefaultMappedModel: req.DefaultMappedModel, MessagesDispatchModelConfig: req.MessagesDispatchModelConfig, RPMLimit: req.RPMLimit, - CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs, }) if err != nil { response.ErrorFrom(c, err) diff --git a/backend/internal/service/admin_service.go b/backend/internal/service/admin_service.go index 6a2aaf5c51d..a34bc8bbc22 100644 --- a/backend/internal/service/admin_service.go +++ b/backend/internal/service/admin_service.go @@ -214,8 +214,6 @@ type CreateGroupInput struct { MessagesDispatchModelConfig OpenAIMessagesDispatchModelConfig // RPMLimit 分组 RPM 上限(0 = 不限制) RPMLimit int - // 从指定分组复制账号(创建分组后在同一事务内绑定) - CopyAccountsFromGroupIDs []int64 } type UpdateGroupInput struct { @@ -254,8 +252,6 @@ type UpdateGroupInput struct { MessagesDispatchModelConfig *OpenAIMessagesDispatchModelConfig // RPMLimit 分组 RPM 上限(0 = 不限制),nil 表示未提供不改动。 RPMLimit *int - // 从指定分组复制账号(同步操作:先清空当前分组的账号绑定,再绑定源分组的账号) - CopyAccountsFromGroupIDs []int64 } type CreateAccountInput struct { @@ -1631,38 +1627,6 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn mcpXMLInject = *input.MCPXMLInject } - // 如果指定了复制账号的源分组,先获取账号 ID 列表 - var accountIDsToCopy []int64 - if len(input.CopyAccountsFromGroupIDs) > 0 { - // 去重源分组 IDs - seen := make(map[int64]struct{}) - uniqueSourceGroupIDs := make([]int64, 0, len(input.CopyAccountsFromGroupIDs)) - for _, srcGroupID := range input.CopyAccountsFromGroupIDs { - if _, exists := seen[srcGroupID]; !exists { - seen[srcGroupID] = struct{}{} - uniqueSourceGroupIDs = append(uniqueSourceGroupIDs, srcGroupID) - } - } - - // 校验源分组的平台是否与新分组一致 - for _, srcGroupID := range uniqueSourceGroupIDs { - srcGroup, err := s.groupRepo.GetByIDLite(ctx, srcGroupID) - if err != nil { - return nil, fmt.Errorf("source group %d not found: %w", srcGroupID, err) - } - if srcGroup.Platform != platform { - return nil, fmt.Errorf("source group %d platform mismatch: expected %s, got %s", srcGroupID, platform, srcGroup.Platform) - } - } - - // 获取所有源分组的账号(去重) - var err error - accountIDsToCopy, err = s.groupRepo.GetAccountIDsByGroupIDs(ctx, uniqueSourceGroupIDs) - if err != nil { - return nil, fmt.Errorf("failed to get accounts from source groups: %w", err) - } - } - group := &Group{ Name: input.Name, Description: input.Description, @@ -1698,35 +1662,6 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn return nil, err } - // require_oauth_only: 过滤掉 apikey 类型账号 - if group.RequireOAuthOnly && (group.Platform == PlatformOpenAI || group.Platform == PlatformAntigravity || group.Platform == PlatformAnthropic || group.Platform == PlatformGemini) && len(accountIDsToCopy) > 0 { - accounts, err := s.accountRepo.GetByIDs(ctx, accountIDsToCopy) - if err != nil { - return nil, fmt.Errorf("failed to fetch accounts for oauth filter: %w", err) - } - oauthIDs := make(map[int64]struct{}, len(accounts)) - for _, acc := range accounts { - if acc.Type != AccountTypeAPIKey { - oauthIDs[acc.ID] = struct{}{} - } - } - var filtered []int64 - for _, aid := range accountIDsToCopy { - if _, ok := oauthIDs[aid]; ok { - filtered = append(filtered, aid) - } - } - accountIDsToCopy = filtered - } - - // 如果有需要复制的账号,绑定到新分组 - if len(accountIDsToCopy) > 0 { - if err := s.groupRepo.BindAccountsToGroup(ctx, group.ID, accountIDsToCopy); err != nil { - return nil, fmt.Errorf("failed to bind accounts to new group: %w", err) - } - group.AccountCount = int64(len(accountIDsToCopy)) - } - return group, nil } @@ -1951,74 +1886,6 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd s.authCacheInvalidator.InvalidateAuthCacheByGroupID(ctx, id) } - // 如果指定了复制账号的源分组,同步绑定(替换当前分组的账号) - if len(input.CopyAccountsFromGroupIDs) > 0 { - // 去重源分组 IDs - seen := make(map[int64]struct{}) - uniqueSourceGroupIDs := make([]int64, 0, len(input.CopyAccountsFromGroupIDs)) - for _, srcGroupID := range input.CopyAccountsFromGroupIDs { - // 校验:源分组不能是自身 - if srcGroupID == id { - return nil, fmt.Errorf("cannot copy accounts from self") - } - // 去重 - if _, exists := seen[srcGroupID]; !exists { - seen[srcGroupID] = struct{}{} - uniqueSourceGroupIDs = append(uniqueSourceGroupIDs, srcGroupID) - } - } - - // 校验源分组的平台是否与当前分组一致 - for _, srcGroupID := range uniqueSourceGroupIDs { - srcGroup, err := s.groupRepo.GetByIDLite(ctx, srcGroupID) - if err != nil { - return nil, fmt.Errorf("source group %d not found: %w", srcGroupID, err) - } - if srcGroup.Platform != group.Platform { - return nil, fmt.Errorf("source group %d platform mismatch: expected %s, got %s", srcGroupID, group.Platform, srcGroup.Platform) - } - } - - // 获取所有源分组的账号(去重) - accountIDsToCopy, err := s.groupRepo.GetAccountIDsByGroupIDs(ctx, uniqueSourceGroupIDs) - if err != nil { - return nil, fmt.Errorf("failed to get accounts from source groups: %w", err) - } - - // 先清空当前分组的所有账号绑定 - if _, err := s.groupRepo.DeleteAccountGroupsByGroupID(ctx, id); err != nil { - return nil, fmt.Errorf("failed to clear existing account bindings: %w", err) - } - - // require_oauth_only: 过滤掉 apikey 类型账号 - if group.RequireOAuthOnly && (group.Platform == PlatformOpenAI || group.Platform == PlatformAntigravity || group.Platform == PlatformAnthropic || group.Platform == PlatformGemini) && len(accountIDsToCopy) > 0 { - accounts, err := s.accountRepo.GetByIDs(ctx, accountIDsToCopy) - if err != nil { - return nil, fmt.Errorf("failed to fetch accounts for oauth filter: %w", err) - } - oauthIDs := make(map[int64]struct{}, len(accounts)) - for _, acc := range accounts { - if acc.Type != AccountTypeAPIKey { - oauthIDs[acc.ID] = struct{}{} - } - } - var filtered []int64 - for _, aid := range accountIDsToCopy { - if _, ok := oauthIDs[aid]; ok { - filtered = append(filtered, aid) - } - } - accountIDsToCopy = filtered - } - - // 再绑定源分组的账号 - if len(accountIDsToCopy) > 0 { - if err := s.groupRepo.BindAccountsToGroup(ctx, id, accountIDsToCopy); err != nil { - return nil, fmt.Errorf("failed to bind accounts to group: %w", err) - } - } - } - return group, nil } diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 81eee5d0f31..1b221eccc5e 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -643,8 +643,6 @@ export interface CreateGroupRequest { supported_model_scopes?: string[] require_oauth_only?: boolean require_privacy_set?: boolean - // 从指定分组复制账号 - copy_accounts_from_group_ids?: number[] } export interface UpdateGroupRequest { @@ -671,7 +669,6 @@ export interface UpdateGroupRequest { supported_model_scopes?: string[] require_oauth_only?: boolean require_privacy_set?: boolean - copy_accounts_from_group_ids?: number[] } // ==================== Account & Proxy Types ==================== diff --git a/frontend/src/views/admin/GroupsView.vue b/frontend/src/views/admin/GroupsView.vue index 773e5ce6485..90f23297200 100644 --- a/frontend/src/views/admin/GroupsView.vue +++ b/frontend/src/views/admin/GroupsView.vue @@ -392,99 +392,9 @@ v-model="createForm.platform" :options="platformOptions" data-tour="group-form-platform" - @change="createForm.copy_accounts_from_group_ids = []" />
{{ t("admin.groups.platformHint") }}
- -- {{ t("admin.groups.copyAccounts.tooltip") }} -
- -{{ t("admin.groups.copyAccounts.hint") }}
-- {{ t("admin.groups.copyAccounts.tooltipEdit") }} -
- -- {{ t("admin.groups.copyAccounts.hintEdit") }} -
-