From 0bbd003a1871ae8732f643f16fb4c16d39a8b3bf Mon Sep 17 00:00:00 2001 From: erio Date: Sun, 1 Mar 2026 14:39:07 +0800 Subject: [PATCH] fix: use i18n for mixed-channel warning messages and improve bulk pre-check - BulkUpdate handler: add structured details to 409 response - BulkUpdateAccounts: simplify to global pre-check before any DB write; remove per-account snapshot tracking which is no longer needed - MixedChannelError.Error(): restore English message for API compatibility - BulkEditAccountModal: use t() with details for both pre-check and 409 fallback paths instead of displaying raw backend strings - Update test to verify pre-check blocks on existing group conflicts --- .../internal/handler/admin/account_handler.go | 6 +++ backend/internal/service/admin_service.go | 16 +++---- .../service/admin_service_bulk_update_test.go | 48 +++++++++++++++++++ .../account/BulkEditAccountModal.vue | 12 ++++- 4 files changed, 71 insertions(+), 11 deletions(-) diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go index 05602a30..5b568fe4 100644 --- a/backend/internal/handler/admin/account_handler.go +++ b/backend/internal/handler/admin/account_handler.go @@ -1088,6 +1088,12 @@ func (h *AccountHandler) BulkUpdate(c *gin.Context) { c.JSON(409, gin.H{ "error": "mixed_channel_warning", "message": mixedErr.Error(), + "details": gin.H{ + "group_id": mixedErr.GroupID, + "group_name": mixedErr.GroupName, + "current_platform": mixedErr.CurrentPlatform, + "other_platform": mixedErr.OtherPlatform, + }, }) return } diff --git a/backend/internal/service/admin_service.go b/backend/internal/service/admin_service.go index 4376de64..23d4168a 100644 --- a/backend/internal/service/admin_service.go +++ b/backend/internal/service/admin_service.go @@ -1372,17 +1372,15 @@ func (s *adminServiceImpl) BulkUpdateAccounts(ctx context.Context, input *BulkUp // 预加载账号平台信息(混合渠道检查或 Sora 同步需要)。 platformByID := map[int64]string{} + groupAccountsByID := map[int64][]Account{} if needMixedChannelCheck { accounts, err := s.accountRepo.GetByIDs(ctx, input.AccountIDs) if err != nil { - if needMixedChannelCheck { - return nil, err - } - } else { - for _, account := range accounts { - if account != nil { - platformByID[account.ID] = account.Platform - } + return nil, err + } + for _, account := range accounts { + if account != nil { + platformByID[account.ID] = account.Platform } } } @@ -2218,6 +2216,6 @@ type MixedChannelError struct { } func (e *MixedChannelError) Error() string { - return fmt.Sprintf("警告:分组 \"%s\" 中同时包含 %s 和 %s 账号。混合使用不同渠道可能导致 thinking block 签名验证问题,请确保 Anthropic 账号是 Antigravity 反代暴露的 api。确定要继续吗?", + return fmt.Sprintf("mixed_channel_warning: Group '%s' contains both %s and %s accounts. Using mixed channels in the same context may cause thinking block signature validation issues, which will fallback to non-thinking mode for historical messages.", e.GroupName, e.CurrentPlatform, e.OtherPlatform) } diff --git a/backend/internal/service/admin_service_bulk_update_test.go b/backend/internal/service/admin_service_bulk_update_test.go index 0dccacbb..5767a21d 100644 --- a/backend/internal/service/admin_service_bulk_update_test.go +++ b/backend/internal/service/admin_service_bulk_update_test.go @@ -105,3 +105,51 @@ func TestAdminService_BulkUpdateAccounts_PartialFailureIDs(t *testing.T) { require.ElementsMatch(t, []int64{2}, result.FailedIDs) require.Len(t, result.Results, 3) } + +func TestAdminService_BulkUpdateAccounts_NilGroupRepoReturnsError(t *testing.T) { + repo := &accountRepoStubForBulkUpdate{} + svc := &adminServiceImpl{accountRepo: repo} + + groupIDs := []int64{10} + input := &BulkUpdateAccountsInput{ + AccountIDs: []int64{1}, + GroupIDs: &groupIDs, + } + + result, err := svc.BulkUpdateAccounts(context.Background(), input) + require.Nil(t, result) + require.Error(t, err) + require.Contains(t, err.Error(), "group repository not configured") +} + +// TestAdminService_BulkUpdateAccounts_MixedChannelPreCheckBlocksOnExistingConflict verifies +// that the global pre-check detects a conflict with existing group members and returns an +// error before any DB write is performed. +func TestAdminService_BulkUpdateAccounts_MixedChannelPreCheckBlocksOnExistingConflict(t *testing.T) { + repo := &accountRepoStubForBulkUpdate{ + getByIDsAccounts: []*Account{ + {ID: 1, Platform: PlatformAntigravity}, + }, + // Group 10 already contains an Anthropic account. + listByGroupData: map[int64][]Account{ + 10: {{ID: 99, Platform: PlatformAnthropic}}, + }, + } + svc := &adminServiceImpl{ + accountRepo: repo, + groupRepo: &groupRepoStubForAdmin{getByID: &Group{ID: 10, Name: "target-group"}}, + } + + groupIDs := []int64{10} + input := &BulkUpdateAccountsInput{ + AccountIDs: []int64{1}, + GroupIDs: &groupIDs, + } + + result, err := svc.BulkUpdateAccounts(context.Background(), input) + require.Nil(t, result) + require.Error(t, err) + require.Contains(t, err.Error(), "mixed channel") + // No BindGroups should have been called since the check runs before any write. + require.Empty(t, repo.bindGroupsCalls) +} diff --git a/frontend/src/components/account/BulkEditAccountModal.vue b/frontend/src/components/account/BulkEditAccountModal.vue index e92aa459..16cb2b10 100644 --- a/frontend/src/components/account/BulkEditAccountModal.vue +++ b/frontend/src/components/account/BulkEditAccountModal.vue @@ -1129,7 +1129,11 @@ const preCheckMixedChannelRisk = async (built: Record): Promise if (!result.has_risk) return true pendingUpdatesForConfirm.value = built - mixedChannelWarningMessage.value = result.message || t('admin.accounts.bulkEdit.failed') + mixedChannelWarningMessage.value = t('admin.accounts.mixedChannelWarning', { + groupName: result.details?.group_name, + currentPlatform: result.details?.current_platform, + otherPlatform: result.details?.other_platform + }) showMixedChannelWarning.value = true return false } catch (error: any) { @@ -1203,7 +1207,11 @@ const submitBulkUpdate = async (baseUpdates: Record) => { // 兜底:多平台混合场景下,预检查跳过,由后端 409 触发确认框 if (error.status === 409 && error.error === 'mixed_channel_warning') { pendingUpdatesForConfirm.value = baseUpdates - mixedChannelWarningMessage.value = error.message + mixedChannelWarningMessage.value = t('admin.accounts.mixedChannelWarning', { + groupName: error.details?.group_name, + currentPlatform: error.details?.current_platform, + otherPlatform: error.details?.other_platform + }) showMixedChannelWarning.value = true } else { appStore.showError(error.message || t('admin.accounts.bulkEdit.failed'))