From a749ddfede5b182e894bcef303d57d79178fad55 Mon Sep 17 00:00:00 2001 From: QTom <22166516+DaydreamCoding@users.noreply.github.com> Date: Wed, 7 Jan 2026 20:57:49 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=B8=BB=E5=8A=A8=E5=88=B7=E6=96=B0?= =?UTF-8?q?=E7=AD=89=E5=BE=85=E9=87=8D=E7=BD=AE=E7=9A=84=20Claude=20?= =?UTF-8?q?=E8=B4=A6=E6=88=B7=20Token=EF=BC=88=E9=98=B2=E6=AD=A2=205?= =?UTF-8?q?=E5=B0=8F=E6=97=B6/7=E5=A4=A9=20=E7=AD=89=E5=BE=85=E6=9C=9F?= =?UTF-8?q?=E9=97=B4=20Token=20=E8=BF=87=E6=9C=9F=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主动刷新等待重置的 Claude 账户 Token(防止 5小时/7天 等待期间 Token 过期) --- src/services/rateLimitCleanupService.js | 80 +++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) diff --git a/src/services/rateLimitCleanupService.js b/src/services/rateLimitCleanupService.js index ebf8f44b..9e13f889 100644 --- a/src/services/rateLimitCleanupService.js +++ b/src/services/rateLimitCleanupService.js @@ -72,7 +72,8 @@ class RateLimitCleanupService { const results = { openai: { checked: 0, cleared: 0, errors: [] }, claude: { checked: 0, cleared: 0, errors: [] }, - claudeConsole: { checked: 0, cleared: 0, errors: [] } + claudeConsole: { checked: 0, cleared: 0, errors: [] }, + tokenRefresh: { checked: 0, refreshed: 0, errors: [] } } // 清理 OpenAI 账号 @@ -84,21 +85,29 @@ class RateLimitCleanupService { // 清理 Claude Console 账号 await this.cleanupClaudeConsoleAccounts(results.claudeConsole) + // 主动刷新等待重置的 Claude 账户 Token(防止 5小时/7天 等待期间 Token 过期) + await this.proactiveRefreshClaudeTokens(results.tokenRefresh) + const totalChecked = results.openai.checked + results.claude.checked + results.claudeConsole.checked const totalCleared = results.openai.cleared + results.claude.cleared + results.claudeConsole.cleared const duration = Date.now() - startTime - if (totalCleared > 0) { + if (totalCleared > 0 || results.tokenRefresh.refreshed > 0) { logger.info( - `✅ Rate limit cleanup completed: ${totalCleared} accounts cleared out of ${totalChecked} checked (${duration}ms)` + `✅ Rate limit cleanup completed: ${totalCleared}/${totalChecked} accounts cleared, ${results.tokenRefresh.refreshed} tokens refreshed (${duration}ms)` ) logger.info(` OpenAI: ${results.openai.cleared}/${results.openai.checked}`) logger.info(` Claude: ${results.claude.cleared}/${results.claude.checked}`) logger.info( ` Claude Console: ${results.claudeConsole.cleared}/${results.claudeConsole.checked}` ) + if (results.tokenRefresh.checked > 0 || results.tokenRefresh.refreshed > 0) { + logger.info( + ` Token Refresh: ${results.tokenRefresh.refreshed}/${results.tokenRefresh.checked} refreshed` + ) + } // 发送 webhook 恢复通知 if (this.clearedAccounts.length > 0) { @@ -114,7 +123,8 @@ class RateLimitCleanupService { const allErrors = [ ...results.openai.errors, ...results.claude.errors, - ...results.claudeConsole.errors + ...results.claudeConsole.errors, + ...results.tokenRefresh.errors ] if (allErrors.length > 0) { logger.warn(`⚠️ Encountered ${allErrors.length} errors during cleanup:`, allErrors) @@ -348,6 +358,68 @@ class RateLimitCleanupService { } } + /** + * 主动刷新 Claude 账户 Token(防止等待重置期间 Token 过期) + * 仅对等待重置(schedulable=false)且 Token 即将过期的账户执行刷新 + */ + async proactiveRefreshClaudeTokens(result) { + try { + const redis = require('../models/redis') + const accounts = await redis.getAllClaudeAccounts() + const now = Date.now() + const refreshAheadMs = 30 * 60 * 1000 // 提前30分钟刷新 + const recentRefreshMs = 5 * 60 * 1000 // 5分钟内刷新过则跳过 + + for (const account of accounts) { + // 1. 必须激活 + if (account.isActive !== 'true') { + continue + } + + // 2. 必须有 refreshToken + if (!account.refreshToken) { + continue + } + + // 3. 【优化】仅处理等待重置的账户(schedulable=false) + // 正常调度的账户会在请求时自动刷新,无需主动刷新 + if (account.schedulable !== 'false') { + continue + } + + // 4. 【优化】如果最近 5 分钟内已刷新,跳过(避免重复刷新) + const lastRefreshAt = account.lastRefreshAt ? new Date(account.lastRefreshAt).getTime() : 0 + if (now - lastRefreshAt < recentRefreshMs) { + continue + } + + // 5. 检查 Token 是否即将过期(30分钟内) + const expiresAt = parseInt(account.expiresAt) + if (expiresAt && now < expiresAt - refreshAheadMs) { + continue + } + + // 符合条件,执行刷新 + result.checked++ + try { + await claudeAccountService.refreshAccountToken(account.id) + result.refreshed++ + logger.info(`🔄 Proactively refreshed token: ${account.name} (${account.id})`) + } catch (error) { + result.errors.push({ + accountId: account.id, + accountName: account.name, + error: error.message + }) + logger.warn(`⚠️ Proactive refresh failed for ${account.name}: ${error.message}`) + } + } + } catch (error) { + logger.error('Failed to proactively refresh Claude tokens:', error) + result.errors.push({ error: error.message }) + } + } + /** * 手动触发一次清理(供 API 或 CLI 调用) */