From 4a20a2a8ba38cccdd9a28c6bfafd5c89d066dcf2 Mon Sep 17 00:00:00 2001 From: yangjianbo Date: Sat, 7 Feb 2026 21:18:03 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=87=AD=E8=AF=81=E6=98=8E=E7=BB=86=E4=B8=8E?= =?UTF-8?q?=E7=BC=93=E5=AD=98TTL=E6=8A=96=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BatchUpdateCredentials 返回 success/failed/results 及 success_ids/failed_ids - billing jitteredTTL 改为只减不增,确保TTL不超上界 - crypto/rand 失败时随机ID降级避免 panic - OpenAI SelectAccount 失败日志去重并补充字段 - 修复两处类型断言以通过 errcheck --- .../internal/handler/admin/account_handler.go | 34 ++++++++++++++----- .../handler/openai_gateway_handler.go | 3 +- .../pkg/antigravity/response_transformer.go | 21 +++++++++--- backend/internal/repository/billing_cache.go | 8 +++-- .../service/sse_scanner_buffer_pool.go | 7 +++- .../internal/service/subscription_service.go | 6 +++- 6 files changed, 61 insertions(+), 18 deletions(-) diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go index f1c9f303..55536ba9 100644 --- a/backend/internal/handler/admin/account_handler.go +++ b/backend/internal/handler/admin/account_handler.go @@ -810,20 +810,38 @@ func (h *AccountHandler) BatchUpdateCredentials(c *gin.Context) { updates = append(updates, accountUpdate{ID: accountID, Credentials: account.Credentials}) } - // 阶段二:依次更新,任何失败立即返回(避免部分成功部分失败) + // 阶段二:依次更新,返回每个账号的成功/失败明细,便于调用方重试 + success := 0 + failed := 0 + successIDs := make([]int64, 0, len(updates)) + failedIDs := make([]int64, 0, len(updates)) + results := make([]gin.H, 0, len(updates)) for _, u := range updates { - updateInput := &service.UpdateAccountInput{ - Credentials: u.Credentials, - } + updateInput := &service.UpdateAccountInput{Credentials: u.Credentials} if _, err := h.adminService.UpdateAccount(ctx, u.ID, updateInput); err != nil { - response.Error(c, 500, fmt.Sprintf("Failed to update account %d: %v", u.ID, err)) - return + failed++ + failedIDs = append(failedIDs, u.ID) + results = append(results, gin.H{ + "account_id": u.ID, + "success": false, + "error": err.Error(), + }) + continue } + success++ + successIDs = append(successIDs, u.ID) + results = append(results, gin.H{ + "account_id": u.ID, + "success": true, + }) } response.Success(c, gin.H{ - "success": len(updates), - "failed": 0, + "success": success, + "failed": failed, + "success_ids": successIDs, + "failed_ids": failedIDs, + "results": results, }) } diff --git a/backend/internal/handler/openai_gateway_handler.go b/backend/internal/handler/openai_gateway_handler.go index dba7b70a..56e21690 100644 --- a/backend/internal/handler/openai_gateway_handler.go +++ b/backend/internal/handler/openai_gateway_handler.go @@ -219,9 +219,8 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) { log.Printf("[OpenAI Handler] Selecting account: groupID=%v model=%s", apiKey.GroupID, reqModel) selection, err := h.gatewayService.SelectAccountWithLoadAwareness(c.Request.Context(), apiKey.GroupID, sessionHash, reqModel, failedAccountIDs) if err != nil { - log.Printf("[OpenAI Handler] SelectAccount failed: %v", err) + log.Printf("[OpenAI Handler] SelectAccount failed: groupID=%v model=%s tried=%d err=%v", apiKey.GroupID, reqModel, len(failedAccountIDs), err) if len(failedAccountIDs) == 0 { - log.Printf("[OpenAI Gateway] SelectAccount failed: %v", err) h.handleStreamingAwareError(c, http.StatusServiceUnavailable, "api_error", "Service temporarily unavailable", streamStarted) return } diff --git a/backend/internal/pkg/antigravity/response_transformer.go b/backend/internal/pkg/antigravity/response_transformer.go index 1f58eb8e..69829ab6 100644 --- a/backend/internal/pkg/antigravity/response_transformer.go +++ b/backend/internal/pkg/antigravity/response_transformer.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "strings" + "time" ) // TransformGeminiToClaude 将 Gemini 响应转换为 Claude 格式(非流式) @@ -345,13 +346,25 @@ func buildGroundingText(grounding *GeminiGroundingMetadata) string { // generateRandomID 生成密码学安全的随机 ID func generateRandomID() string { const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" - result := make([]byte, 12) + id := make([]byte, 12) randBytes := make([]byte, 12) if _, err := rand.Read(randBytes); err != nil { - panic("crypto/rand unavailable: " + err.Error()) + // 避免在请求路径里 panic:极端情况下熵源不可用时降级为伪随机。 + // 这里主要用于生成响应/工具调用的临时 ID,安全要求不高但需尽量避免碰撞。 + seed := uint64(time.Now().UnixNano()) + if err != nil { + seed ^= uint64(len(err.Error())) << 32 + } + for i := range id { + seed ^= seed << 13 + seed ^= seed >> 7 + seed ^= seed << 17 + id[i] = chars[int(seed)%len(chars)] + } + return string(id) } for i, b := range randBytes { - result[i] = chars[int(b)%len(chars)] + id[i] = chars[int(b)%len(chars)] } - return string(result) + return string(id) } diff --git a/backend/internal/repository/billing_cache.go b/backend/internal/repository/billing_cache.go index 50ea0da9..370d0672 100644 --- a/backend/internal/repository/billing_cache.go +++ b/backend/internal/repository/billing_cache.go @@ -22,8 +22,12 @@ const ( // jitteredTTL 返回带随机抖动的 TTL,防止缓存雪崩 func jitteredTTL() time.Duration { - jitter := time.Duration(rand.Int63n(int64(2*billingCacheJitter))) - billingCacheJitter - return billingCacheTTL + jitter + // 只做“减法抖动”,确保实际 TTL 不会超过 billingCacheTTL(避免上界预期被打破)。 + if billingCacheJitter <= 0 { + return billingCacheTTL + } + jitter := time.Duration(rand.Int63n(int64(billingCacheJitter))) + return billingCacheTTL - jitter } // billingBalanceKey generates the Redis key for user balance cache. diff --git a/backend/internal/service/sse_scanner_buffer_pool.go b/backend/internal/service/sse_scanner_buffer_pool.go index 7475547f..f1f079b2 100644 --- a/backend/internal/service/sse_scanner_buffer_pool.go +++ b/backend/internal/service/sse_scanner_buffer_pool.go @@ -13,7 +13,12 @@ var sseScannerBuf64KPool = sync.Pool{ } func getSSEScannerBuf64K() *sseScannerBuf64K { - return sseScannerBuf64KPool.Get().(*sseScannerBuf64K) + v := sseScannerBuf64KPool.Get() + buf, ok := v.(*sseScannerBuf64K) + if !ok || buf == nil { + return new(sseScannerBuf64K) + } + return buf } func putSSEScannerBuf64K(buf *sseScannerBuf64K) { diff --git a/backend/internal/service/subscription_service.go b/backend/internal/service/subscription_service.go index 21694d41..92300b11 100644 --- a/backend/internal/service/subscription_service.go +++ b/backend/internal/service/subscription_service.go @@ -483,7 +483,11 @@ func (s *SubscriptionService) GetActiveSubscription(ctx context.Context, userID, return nil, err } // singleflight 返回的也是缓存指针,需要浅拷贝 - cp := *value.(*UserSubscription) + sub, ok := value.(*UserSubscription) + if !ok || sub == nil { + return nil, ErrSubscriptionNotFound + } + cp := *sub return &cp, nil }