From 12b33f4ea41ac5ac959f3fb9d858f863254aa927 Mon Sep 17 00:00:00 2001 From: erio Date: Fri, 6 Mar 2026 04:27:41 +0800 Subject: [PATCH] fix: address load_factor code review findings - Fix bulk edit: send 0 instead of null/NaN to clear load_factor - Fix edit modal: explicit NaN check instead of implicit falsy - Fix create modal: use ?? instead of || for load_factor - Add load_factor upper limit validation (max 10000) - Add //go:build unit tag and self-contained intPtrHelper in test - Add design intent comments on WaitPlan.MaxConcurrency --- .../service/account_load_factor_test.go | 12 ++++++++---- backend/internal/service/admin_service.go | 5 +++++ .../service/openai_account_scheduler.go | 2 ++ .../account/BulkEditAccountModal.vue | 4 +++- .../components/account/CreateAccountModal.vue | 18 +++++++++--------- .../components/account/EditAccountModal.vue | 5 +++-- 6 files changed, 30 insertions(+), 16 deletions(-) diff --git a/backend/internal/service/account_load_factor_test.go b/backend/internal/service/account_load_factor_test.go index 1cd1b17c..a4d78a4b 100644 --- a/backend/internal/service/account_load_factor_test.go +++ b/backend/internal/service/account_load_factor_test.go @@ -1,3 +1,5 @@ +//go:build unit + package service import ( @@ -6,6 +8,8 @@ import ( "github.com/stretchr/testify/require" ) +func intPtrHelper(v int) *int { return &v } + func TestEffectiveLoadFactor_NilAccount(t *testing.T) { var a *Account require.Equal(t, 1, a.EffectiveLoadFactor()) @@ -22,21 +26,21 @@ func TestEffectiveLoadFactor_NilLoadFactor_ZeroConcurrency(t *testing.T) { } func TestEffectiveLoadFactor_PositiveLoadFactor(t *testing.T) { - a := &Account{Concurrency: 5, LoadFactor: intPtr(20)} + a := &Account{Concurrency: 5, LoadFactor: intPtrHelper(20)} require.Equal(t, 20, a.EffectiveLoadFactor()) } func TestEffectiveLoadFactor_ZeroLoadFactor_FallbackToConcurrency(t *testing.T) { - a := &Account{Concurrency: 5, LoadFactor: intPtr(0)} + a := &Account{Concurrency: 5, LoadFactor: intPtrHelper(0)} require.Equal(t, 5, a.EffectiveLoadFactor()) } func TestEffectiveLoadFactor_NegativeLoadFactor_FallbackToConcurrency(t *testing.T) { - a := &Account{Concurrency: 3, LoadFactor: intPtr(-1)} + a := &Account{Concurrency: 3, LoadFactor: intPtrHelper(-1)} require.Equal(t, 3, a.EffectiveLoadFactor()) } func TestEffectiveLoadFactor_ZeroLoadFactor_ZeroConcurrency(t *testing.T) { - a := &Account{Concurrency: 0, LoadFactor: intPtr(0)} + a := &Account{Concurrency: 0, LoadFactor: intPtrHelper(0)} require.Equal(t, 1, a.EffectiveLoadFactor()) } diff --git a/backend/internal/service/admin_service.go b/backend/internal/service/admin_service.go index e2d4b458..e5ceac07 100644 --- a/backend/internal/service/admin_service.go +++ b/backend/internal/service/admin_service.go @@ -1417,6 +1417,9 @@ func (s *adminServiceImpl) CreateAccount(ctx context.Context, input *CreateAccou account.RateMultiplier = input.RateMultiplier } if input.LoadFactor != nil && *input.LoadFactor > 0 { + if *input.LoadFactor > 10000 { + return nil, errors.New("load_factor must be <= 10000") + } account.LoadFactor = input.LoadFactor } if err := s.accountRepo.Create(ctx, account); err != nil { @@ -1492,6 +1495,8 @@ func (s *adminServiceImpl) UpdateAccount(ctx context.Context, id int64, input *U if input.LoadFactor != nil { if *input.LoadFactor <= 0 { account.LoadFactor = nil // 0 或负数表示清除 + } else if *input.LoadFactor > 10000 { + return nil, errors.New("load_factor must be <= 10000") } else { account.LoadFactor = input.LoadFactor } diff --git a/backend/internal/service/openai_account_scheduler.go b/backend/internal/service/openai_account_scheduler.go index 9cc6aae1..cf4bc26e 100644 --- a/backend/internal/service/openai_account_scheduler.go +++ b/backend/internal/service/openai_account_scheduler.go @@ -342,6 +342,7 @@ func (s *defaultOpenAIAccountScheduler) selectBySessionHash( } cfg := s.service.schedulingConfig() + // WaitPlan.MaxConcurrency 使用 Concurrency(非 EffectiveLoadFactor),因为 WaitPlan 控制的是 Redis 实际并发槽位等待。 if s.service.concurrencyService != nil { return &AccountSelectionResult{ Account: account, @@ -703,6 +704,7 @@ func (s *defaultOpenAIAccountScheduler) selectByLoadBalance( } cfg := s.service.schedulingConfig() + // WaitPlan.MaxConcurrency 使用 Concurrency(非 EffectiveLoadFactor),因为 WaitPlan 控制的是 Redis 实际并发槽位等待。 candidate := selectionOrder[0] return &AccountSelectionResult{ Account: candidate.account, diff --git a/frontend/src/components/account/BulkEditAccountModal.vue b/frontend/src/components/account/BulkEditAccountModal.vue index d7853b23..12ca5a70 100644 --- a/frontend/src/components/account/BulkEditAccountModal.vue +++ b/frontend/src/components/account/BulkEditAccountModal.vue @@ -1227,7 +1227,9 @@ const buildUpdatePayload = (): Record | null => { } if (enableLoadFactor.value) { - updates.load_factor = loadFactor.value + // 空值/NaN/0 时发送 0(后端约定 <= 0 表示清除) + const lf = loadFactor.value + updates.load_factor = (lf != null && !Number.isNaN(lf) && lf > 0) ? lf : 0 } if (enablePriority.value) { diff --git a/frontend/src/components/account/CreateAccountModal.vue b/frontend/src/components/account/CreateAccountModal.vue index a1113267..c5420ca4 100644 --- a/frontend/src/components/account/CreateAccountModal.vue +++ b/frontend/src/components/account/CreateAccountModal.vue @@ -3491,7 +3491,7 @@ const handleImportAccessToken = async (accessTokenInput: string) => { extra: soraExtra, proxy_id: form.proxy_id, concurrency: form.concurrency, - load_factor: form.load_factor || undefined, + load_factor: form.load_factor ?? undefined, priority: form.priority, rate_multiplier: form.rate_multiplier, group_ids: form.group_ids, @@ -3551,7 +3551,7 @@ const createAccountAndFinish = async ( extra, proxy_id: form.proxy_id, concurrency: form.concurrency, - load_factor: form.load_factor || undefined, + load_factor: form.load_factor ?? undefined, priority: form.priority, rate_multiplier: form.rate_multiplier, group_ids: form.group_ids, @@ -3607,7 +3607,7 @@ const handleOpenAIExchange = async (authCode: string) => { extra, proxy_id: form.proxy_id, concurrency: form.concurrency, - load_factor: form.load_factor || undefined, + load_factor: form.load_factor ?? undefined, priority: form.priority, rate_multiplier: form.rate_multiplier, group_ids: form.group_ids, @@ -3637,7 +3637,7 @@ const handleOpenAIExchange = async (authCode: string) => { extra: soraExtra, proxy_id: form.proxy_id, concurrency: form.concurrency, - load_factor: form.load_factor || undefined, + load_factor: form.load_factor ?? undefined, priority: form.priority, rate_multiplier: form.rate_multiplier, group_ids: form.group_ids, @@ -3715,7 +3715,7 @@ const handleOpenAIValidateRT = async (refreshTokenInput: string) => { extra, proxy_id: form.proxy_id, concurrency: form.concurrency, - load_factor: form.load_factor || undefined, + load_factor: form.load_factor ?? undefined, priority: form.priority, rate_multiplier: form.rate_multiplier, group_ids: form.group_ids, @@ -3743,7 +3743,7 @@ const handleOpenAIValidateRT = async (refreshTokenInput: string) => { extra: soraExtra, proxy_id: form.proxy_id, concurrency: form.concurrency, - load_factor: form.load_factor || undefined, + load_factor: form.load_factor ?? undefined, priority: form.priority, rate_multiplier: form.rate_multiplier, group_ids: form.group_ids, @@ -3832,7 +3832,7 @@ const handleSoraValidateST = async (sessionTokenInput: string) => { extra: soraExtra, proxy_id: form.proxy_id, concurrency: form.concurrency, - load_factor: form.load_factor || undefined, + load_factor: form.load_factor ?? undefined, priority: form.priority, rate_multiplier: form.rate_multiplier, group_ids: form.group_ids, @@ -3921,7 +3921,7 @@ const handleAntigravityValidateRT = async (refreshTokenInput: string) => { extra: {}, proxy_id: form.proxy_id, concurrency: form.concurrency, - load_factor: form.load_factor || undefined, + load_factor: form.load_factor ?? undefined, priority: form.priority, rate_multiplier: form.rate_multiplier, group_ids: form.group_ids, @@ -4245,7 +4245,7 @@ const handleCookieAuth = async (sessionKey: string) => { extra, proxy_id: form.proxy_id, concurrency: form.concurrency, - load_factor: form.load_factor || undefined, + load_factor: form.load_factor ?? undefined, priority: form.priority, rate_multiplier: form.rate_multiplier, group_ids: form.group_ids, diff --git a/frontend/src/components/account/EditAccountModal.vue b/frontend/src/components/account/EditAccountModal.vue index 196e346c..b516c4b3 100644 --- a/frontend/src/components/account/EditAccountModal.vue +++ b/frontend/src/components/account/EditAccountModal.vue @@ -2064,8 +2064,9 @@ const handleSubmit = async () => { if (form.expires_at === null) { updatePayload.expires_at = 0 } - // load_factor: 空值/0/NaN 时发送 0(后端约定 0 = 清除) - if (!form.load_factor || form.load_factor <= 0) { + // load_factor: 空值/NaN/0/负数 时发送 0(后端约定 <= 0 = 清除) + const lf = form.load_factor + if (lf == null || Number.isNaN(lf) || lf <= 0) { updatePayload.load_factor = 0 } updatePayload.auto_pause_on_expired = autoPauseOnExpired.value