mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:06:18 +00:00
feat: add rate limit recovery webhook notifications
添加限流恢复的 webhook 通知功能,当账户从限流状态自动恢复时发送通知。 主要改进: 1. **新增通知类型** (webhookConfigService.js) - 添加 `rateLimitRecovery` 通知类型 - 在配置获取和保存时自动合并默认通知类型 - 确保新增的通知类型有默认值 2. **增强限流清理服务** (rateLimitCleanupService.js) - 改进自动停止账户的检测逻辑 - 在 `finally` 块中确保 `clearedAccounts` 列表被重置,避免重复通知 - 对自动停止的账户显式调用 `removeAccountRateLimit` - 为 Claude 和 Claude Console 账户添加 `autoStopped` 和 `needsAutoStopRecovery` 检测 3. **改进 Claude Console 限流移除** (claudeConsoleAccountService.js) - 检测并恢复因自动停止而禁用调度的账户 - 清理过期的 `rateLimitAutoStopped` 标志 - 增加详细的日志记录 4. **前端 UI 支持** (SettingsView.vue) - 在 Webhook 设置中添加"限流恢复"通知类型选项 - 更新默认通知类型配置 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -490,20 +490,29 @@ class ClaudeConsoleAccountService {
|
||||
errorMessage: ''
|
||||
}
|
||||
|
||||
const hadAutoStop = accountData.rateLimitAutoStopped === 'true'
|
||||
|
||||
// 只恢复因限流而自动停止的账户
|
||||
if (accountData.rateLimitAutoStopped === 'true' && accountData.schedulable === 'false') {
|
||||
if (hadAutoStop && accountData.schedulable === 'false') {
|
||||
updateData.schedulable = 'true' // 恢复调度
|
||||
// 删除限流自动停止标记
|
||||
await client.hdel(accountKey, 'rateLimitAutoStopped')
|
||||
logger.info(
|
||||
`✅ Auto-resuming scheduling for Claude Console account ${accountId} after rate limit cleared`
|
||||
)
|
||||
}
|
||||
|
||||
if (hadAutoStop) {
|
||||
await client.hdel(accountKey, 'rateLimitAutoStopped')
|
||||
}
|
||||
|
||||
await client.hset(accountKey, updateData)
|
||||
logger.success(`✅ Rate limit removed and account re-enabled: ${accountId}`)
|
||||
}
|
||||
} else {
|
||||
if (await client.hdel(accountKey, 'rateLimitAutoStopped')) {
|
||||
logger.info(
|
||||
`ℹ️ Removed stale auto-stop flag for Claude Console account ${accountId} during rate limit recovery`
|
||||
)
|
||||
}
|
||||
logger.success(`✅ Rate limit removed for Claude Console account: ${accountId}`)
|
||||
}
|
||||
|
||||
|
||||
@@ -110,9 +110,6 @@ class RateLimitCleanupService {
|
||||
)
|
||||
}
|
||||
|
||||
// 清空已清理账户列表
|
||||
this.clearedAccounts = []
|
||||
|
||||
// 记录错误
|
||||
const allErrors = [
|
||||
...results.openai.errors,
|
||||
@@ -125,6 +122,8 @@ class RateLimitCleanupService {
|
||||
} catch (error) {
|
||||
logger.error('❌ Rate limit cleanup failed:', error)
|
||||
} finally {
|
||||
// 确保无论成功或失败都重置列表,避免重复通知
|
||||
this.clearedAccounts = []
|
||||
this.isRunning = false
|
||||
}
|
||||
}
|
||||
@@ -199,8 +198,12 @@ class RateLimitCleanupService {
|
||||
typeof account.rateLimitStatus === 'object' &&
|
||||
account.rateLimitStatus.status === 'limited')
|
||||
|
||||
const autoStopped = account.rateLimitAutoStopped === 'true'
|
||||
const needsAutoStopRecovery =
|
||||
autoStopped && (account.rateLimitEndAt || account.schedulable === 'false')
|
||||
|
||||
// 检查所有可能处于限流状态的账号,包括自动停止的账号
|
||||
if (isRateLimited || account.rateLimitedAt || account.rateLimitAutoStopped === 'true') {
|
||||
if (isRateLimited || account.rateLimitedAt || needsAutoStopRecovery) {
|
||||
result.checked++
|
||||
|
||||
try {
|
||||
@@ -208,6 +211,9 @@ class RateLimitCleanupService {
|
||||
const isStillLimited = await claudeAccountService.isAccountRateLimited(account.id)
|
||||
|
||||
if (!isStillLimited) {
|
||||
if (!isRateLimited && autoStopped) {
|
||||
await claudeAccountService.removeAccountRateLimit(account.id)
|
||||
}
|
||||
result.cleared++
|
||||
logger.info(
|
||||
`🧹 Auto-cleared expired rate limit for Claude account: ${account.name} (${account.id})`
|
||||
@@ -286,10 +292,13 @@ class RateLimitCleanupService {
|
||||
typeof account.rateLimitStatus === 'object' &&
|
||||
account.rateLimitStatus.status === 'limited')
|
||||
|
||||
const autoStopped = account.rateLimitAutoStopped === 'true'
|
||||
const needsAutoStopRecovery = autoStopped && account.schedulable === 'false'
|
||||
|
||||
// 检查两种状态字段:rateLimitStatus 和 status
|
||||
const hasStatusRateLimited = account.status === 'rate_limited'
|
||||
|
||||
if (isRateLimited || hasStatusRateLimited) {
|
||||
if (isRateLimited || hasStatusRateLimited || needsAutoStopRecovery) {
|
||||
result.checked++
|
||||
|
||||
try {
|
||||
@@ -299,6 +308,9 @@ class RateLimitCleanupService {
|
||||
)
|
||||
|
||||
if (!isStillLimited) {
|
||||
if (!isRateLimited && autoStopped) {
|
||||
await claudeConsoleAccountService.removeAccountRateLimit(account.id)
|
||||
}
|
||||
result.cleared++
|
||||
|
||||
// 如果 status 字段是 rate_limited,需要额外清理
|
||||
|
||||
@@ -18,7 +18,17 @@ class WebhookConfigService {
|
||||
// 返回默认配置
|
||||
return this.getDefaultConfig()
|
||||
}
|
||||
return JSON.parse(configStr)
|
||||
|
||||
const storedConfig = JSON.parse(configStr)
|
||||
const defaultConfig = this.getDefaultConfig()
|
||||
|
||||
// 合并默认通知类型,确保新增类型有默认值
|
||||
storedConfig.notificationTypes = {
|
||||
...defaultConfig.notificationTypes,
|
||||
...(storedConfig.notificationTypes || {})
|
||||
}
|
||||
|
||||
return storedConfig
|
||||
} catch (error) {
|
||||
logger.error('获取webhook配置失败:', error)
|
||||
return this.getDefaultConfig()
|
||||
@@ -30,6 +40,13 @@ class WebhookConfigService {
|
||||
*/
|
||||
async saveConfig(config) {
|
||||
try {
|
||||
const defaultConfig = this.getDefaultConfig()
|
||||
|
||||
config.notificationTypes = {
|
||||
...defaultConfig.notificationTypes,
|
||||
...(config.notificationTypes || {})
|
||||
}
|
||||
|
||||
// 验证配置
|
||||
this.validateConfig(config)
|
||||
|
||||
@@ -312,6 +329,7 @@ class WebhookConfigService {
|
||||
quotaWarning: true, // 配额警告
|
||||
systemError: true, // 系统错误
|
||||
securityAlert: true, // 安全警报
|
||||
rateLimitRecovery: true, // 限流恢复
|
||||
test: true // 测试通知
|
||||
},
|
||||
retrySettings: {
|
||||
|
||||
Reference in New Issue
Block a user