From ee7d6bc19f0fef3d5c8ec018f96f40b68ce5cad9 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 --- backend/cmd/server/VERSION | 2 +- .../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 +++-- 7 files changed, 31 insertions(+), 17 deletions(-) diff --git a/backend/cmd/server/VERSION b/backend/cmd/server/VERSION index 42c952870a..231369cfef 100644 --- a/backend/cmd/server/VERSION +++ b/backend/cmd/server/VERSION @@ -1 +1 @@ -0.1.91.2 +0.1.91.3 diff --git a/backend/internal/service/account_load_factor_test.go b/backend/internal/service/account_load_factor_test.go index 1cd1b17c9c..a4d78a4bba 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 11f850ee5f..8992e2505d 100644 --- a/backend/internal/service/admin_service.go +++ b/backend/internal/service/admin_service.go @@ -1437,6 +1437,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 { @@ -1516,6 +1519,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 9cc6aae163..cf4bc26eb0 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 5e7d0ae4fe..9763d24ae6 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 8013bbaf46..ad2357b739 100644 --- a/frontend/src/components/account/CreateAccountModal.vue +++ b/frontend/src/components/account/CreateAccountModal.vue @@ -3497,7 +3497,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, @@ -3562,7 +3562,7 @@ const createAccountAndFinish = async ( extra: finalExtra, 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, @@ -3618,7 +3618,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, @@ -3648,7 +3648,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, @@ -3726,7 +3726,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, @@ -3754,7 +3754,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, @@ -3843,7 +3843,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, @@ -3932,7 +3932,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, @@ -4256,7 +4256,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 03f8367633..557c884902 100644 --- a/frontend/src/components/account/EditAccountModal.vue +++ b/frontend/src/components/account/EditAccountModal.vue @@ -2077,8 +2077,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