fix: 修复批量更新凭证明细与缓存TTL抖动
- BatchUpdateCredentials 返回 success/failed/results 及 success_ids/failed_ids - billing jitteredTTL 改为只减不增,确保TTL不超上界 - crypto/rand 失败时随机ID降级避免 panic - OpenAI SelectAccount 失败日志去重并补充字段 - 修复两处类型断言以通过 errcheck
This commit is contained in:
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user