feat: 实现基于费用的速率限制功能

- 新增 rateLimitCost 字段,支持按费用进行速率限制
- 新增 weeklyOpusCostLimit 字段,支持 Opus 模型周费用限制
- 优化速率限制逻辑,支持费用、请求数、token多维度控制
- 更新前端界面,添加费用限制配置选项
- 增强账户管理功能,支持费用统计和限制
- 改进 Redis 数据模型,支持费用计数器
- 优化价格计算服务,支持更精确的成本核算

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-08-31 17:27:37 +08:00
parent a54622e3d7
commit e84c6a5555
21 changed files with 1662 additions and 161 deletions

View File

@@ -453,6 +453,144 @@ class ClaudeConsoleAccountService {
}
}
// 🚫 标记账号为未授权状态401错误
async markAccountUnauthorized(accountId) {
try {
const client = redis.getClientSafe()
const account = await this.getAccount(accountId)
if (!account) {
throw new Error('Account not found')
}
const updates = {
schedulable: 'false',
status: 'unauthorized',
errorMessage: 'API Key无效或已过期401错误',
unauthorizedAt: new Date().toISOString(),
unauthorizedCount: String((parseInt(account.unauthorizedCount || '0') || 0) + 1)
}
await client.hset(`${this.ACCOUNT_KEY_PREFIX}${accountId}`, updates)
// 发送Webhook通知
try {
const webhookNotifier = require('../utils/webhookNotifier')
await webhookNotifier.sendAccountAnomalyNotification({
accountId,
accountName: account.name || 'Claude Console Account',
platform: 'claude-console',
status: 'error',
errorCode: 'CLAUDE_CONSOLE_UNAUTHORIZED',
reason: 'API Key无效或已过期401错误账户已停止调度',
timestamp: new Date().toISOString()
})
} catch (webhookError) {
logger.error('Failed to send unauthorized webhook notification:', webhookError)
}
logger.warn(
`🚫 Claude Console account marked as unauthorized: ${account.name} (${accountId})`
)
return { success: true }
} catch (error) {
logger.error(`❌ Failed to mark Claude Console account as unauthorized: ${accountId}`, error)
throw error
}
}
// 🚫 标记账号为过载状态529错误
async markAccountOverloaded(accountId) {
try {
const client = redis.getClientSafe()
const account = await this.getAccount(accountId)
if (!account) {
throw new Error('Account not found')
}
const updates = {
overloadedAt: new Date().toISOString(),
overloadStatus: 'overloaded',
errorMessage: '服务过载529错误'
}
await client.hset(`${this.ACCOUNT_KEY_PREFIX}${accountId}`, updates)
// 发送Webhook通知
try {
const webhookNotifier = require('../utils/webhookNotifier')
await webhookNotifier.sendAccountAnomalyNotification({
accountId,
accountName: account.name || 'Claude Console Account',
platform: 'claude-console',
status: 'error',
errorCode: 'CLAUDE_CONSOLE_OVERLOADED',
reason: '服务过载529错误。账户将暂时停止调度',
timestamp: new Date().toISOString()
})
} catch (webhookError) {
logger.error('Failed to send overload webhook notification:', webhookError)
}
logger.warn(`🚫 Claude Console account marked as overloaded: ${account.name} (${accountId})`)
return { success: true }
} catch (error) {
logger.error(`❌ Failed to mark Claude Console account as overloaded: ${accountId}`, error)
throw error
}
}
// ✅ 移除账号的过载状态
async removeAccountOverload(accountId) {
try {
const client = redis.getClientSafe()
await client.hdel(`${this.ACCOUNT_KEY_PREFIX}${accountId}`, 'overloadedAt', 'overloadStatus')
logger.success(`✅ Overload status removed for Claude Console account: ${accountId}`)
return { success: true }
} catch (error) {
logger.error(
`❌ Failed to remove overload status for Claude Console account: ${accountId}`,
error
)
throw error
}
}
// 🔍 检查账号是否处于过载状态
async isAccountOverloaded(accountId) {
try {
const account = await this.getAccount(accountId)
if (!account) {
return false
}
if (account.overloadStatus === 'overloaded' && account.overloadedAt) {
const overloadedAt = new Date(account.overloadedAt)
const now = new Date()
const minutesSinceOverload = (now - overloadedAt) / (1000 * 60)
// 过载状态持续10分钟后自动恢复
if (minutesSinceOverload >= 10) {
await this.removeAccountOverload(accountId)
return false
}
return true
}
return false
} catch (error) {
logger.error(
`❌ Failed to check overload status for Claude Console account: ${accountId}`,
error
)
return false
}
}
// 🚫 标记账号为封锁状态(模型不支持等原因)
async blockAccount(accountId, reason) {
try {