feat: 新增Claude Console账户临时封禁处理和错误消息清理

- 新增 CLAUDE_CONSOLE_BLOCKED_HANDLING_MINUTES 配置项,自动处理账户临时禁用的 400 错误(如 "organization has been disabled"、"too many active sessions" 等)。
  - 添加 errorSanitizer 工具模块,自动清理上游错误响应中的供应商特定信息(URL、供应商名称等),避免泄露中转服务商信息。
  - 统一调度器现在会主动检查并恢复已过期的封禁账户,确保账户在临时封禁时长结束后可以立即重新使用。
This commit is contained in:
sususu
2025-10-17 15:27:47 +08:00
parent f6eb077d82
commit b0917b75a4
6 changed files with 548 additions and 59 deletions

View File

@@ -527,68 +527,86 @@ class UnifiedClaudeScheduler {
logger.info(`📋 Found ${consoleAccounts.length} total Claude Console accounts`)
for (const account of consoleAccounts) {
// 主动检查封禁状态并尝试恢复(在过滤之前执行,确保可以恢复被封禁的账户)
const wasBlocked = await claudeConsoleAccountService.isAccountBlocked(account.id)
// 如果账户之前被封禁但现在已恢复,重新获取最新状态
let currentAccount = account
if (wasBlocked === false && account.status === 'account_blocked') {
// 可能刚刚被恢复,重新获取账户状态
const freshAccount = await claudeConsoleAccountService.getAccount(account.id)
if (freshAccount) {
currentAccount = freshAccount
logger.info(`🔄 Account ${account.name} was recovered from blocked status`)
}
}
logger.info(
`🔍 Checking Claude Console account: ${account.name} - isActive: ${account.isActive}, status: ${account.status}, accountType: ${account.accountType}, schedulable: ${account.schedulable}`
`🔍 Checking Claude Console account: ${currentAccount.name} - isActive: ${currentAccount.isActive}, status: ${currentAccount.status}, accountType: ${currentAccount.accountType}, schedulable: ${currentAccount.schedulable}`
)
// 注意getAllAccounts返回的isActive是布尔值
// 注意getAllAccounts返回的isActive是布尔值getAccount返回的也是布尔值
if (
account.isActive === true &&
account.status === 'active' &&
account.accountType === 'shared' &&
this._isSchedulable(account.schedulable)
currentAccount.isActive === true &&
currentAccount.status === 'active' &&
currentAccount.accountType === 'shared' &&
this._isSchedulable(currentAccount.schedulable)
) {
// 检查是否可调度
// 检查模型支持
if (!this._isModelSupportedByAccount(account, 'claude-console', requestedModel)) {
if (!this._isModelSupportedByAccount(currentAccount, 'claude-console', requestedModel)) {
continue
}
// 检查订阅是否过期
if (claudeConsoleAccountService.isSubscriptionExpired(account)) {
if (claudeConsoleAccountService.isSubscriptionExpired(currentAccount)) {
logger.debug(
`⏰ Claude Console account ${account.name} (${account.id}) expired at ${account.subscriptionExpiresAt}`
`⏰ Claude Console account ${currentAccount.name} (${currentAccount.id}) expired at ${currentAccount.subscriptionExpiresAt}`
)
continue
}
// 主动触发一次额度检查,确保状态即时生效
try {
await claudeConsoleAccountService.checkQuotaUsage(account.id)
await claudeConsoleAccountService.checkQuotaUsage(currentAccount.id)
} catch (e) {
logger.warn(
`Failed to check quota for Claude Console account ${account.name}: ${e.message}`
`Failed to check quota for Claude Console account ${currentAccount.name}: ${e.message}`
)
// 继续处理该账号
}
// 检查是否被限流
const isRateLimited = await claudeConsoleAccountService.isAccountRateLimited(account.id)
const isQuotaExceeded = await claudeConsoleAccountService.isAccountQuotaExceeded(account.id)
const isRateLimited = await claudeConsoleAccountService.isAccountRateLimited(
currentAccount.id
)
const isQuotaExceeded = await claudeConsoleAccountService.isAccountQuotaExceeded(
currentAccount.id
)
if (!isRateLimited && !isQuotaExceeded) {
availableAccounts.push({
...account,
accountId: account.id,
...currentAccount,
accountId: currentAccount.id,
accountType: 'claude-console',
priority: parseInt(account.priority) || 50,
lastUsedAt: account.lastUsedAt || '0'
priority: parseInt(currentAccount.priority) || 50,
lastUsedAt: currentAccount.lastUsedAt || '0'
})
logger.info(
`✅ Added Claude Console account to available pool: ${account.name} (priority: ${account.priority})`
`✅ Added Claude Console account to available pool: ${currentAccount.name} (priority: ${currentAccount.priority})`
)
} else {
if (isRateLimited) {
logger.warn(`⚠️ Claude Console account ${account.name} is rate limited`)
logger.warn(`⚠️ Claude Console account ${currentAccount.name} is rate limited`)
}
if (isQuotaExceeded) {
logger.warn(`💰 Claude Console account ${account.name} quota exceeded`)
logger.warn(`💰 Claude Console account ${currentAccount.name} quota exceeded`)
}
}
} else {
logger.info(
`❌ Claude Console account ${account.name} not eligible - isActive: ${account.isActive}, status: ${account.status}, accountType: ${account.accountType}, schedulable: ${account.schedulable}`
`❌ Claude Console account ${currentAccount.name} not eligible - isActive: ${currentAccount.isActive}, status: ${currentAccount.status}, accountType: ${currentAccount.accountType}, schedulable: ${currentAccount.schedulable}`
)
}
}