mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
Merge PR #507: add rate limit recovery notifications
This commit is contained in:
@@ -490,20 +490,29 @@ class ClaudeConsoleAccountService {
|
|||||||
errorMessage: ''
|
errorMessage: ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hadAutoStop = accountData.rateLimitAutoStopped === 'true'
|
||||||
|
|
||||||
// 只恢复因限流而自动停止的账户
|
// 只恢复因限流而自动停止的账户
|
||||||
if (accountData.rateLimitAutoStopped === 'true' && accountData.schedulable === 'false') {
|
if (hadAutoStop && accountData.schedulable === 'false') {
|
||||||
updateData.schedulable = 'true' // 恢复调度
|
updateData.schedulable = 'true' // 恢复调度
|
||||||
// 删除限流自动停止标记
|
|
||||||
await client.hdel(accountKey, 'rateLimitAutoStopped')
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`✅ Auto-resuming scheduling for Claude Console account ${accountId} after rate limit cleared`
|
`✅ Auto-resuming scheduling for Claude Console account ${accountId} after rate limit cleared`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hadAutoStop) {
|
||||||
|
await client.hdel(accountKey, 'rateLimitAutoStopped')
|
||||||
|
}
|
||||||
|
|
||||||
await client.hset(accountKey, updateData)
|
await client.hset(accountKey, updateData)
|
||||||
logger.success(`✅ Rate limit removed and account re-enabled: ${accountId}`)
|
logger.success(`✅ Rate limit removed and account re-enabled: ${accountId}`)
|
||||||
}
|
}
|
||||||
} else {
|
} 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}`)
|
logger.success(`✅ Rate limit removed for Claude Console account: ${accountId}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -110,9 +110,6 @@ class RateLimitCleanupService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 清空已清理账户列表
|
|
||||||
this.clearedAccounts = []
|
|
||||||
|
|
||||||
// 记录错误
|
// 记录错误
|
||||||
const allErrors = [
|
const allErrors = [
|
||||||
...results.openai.errors,
|
...results.openai.errors,
|
||||||
@@ -125,6 +122,8 @@ class RateLimitCleanupService {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('❌ Rate limit cleanup failed:', error)
|
logger.error('❌ Rate limit cleanup failed:', error)
|
||||||
} finally {
|
} finally {
|
||||||
|
// 确保无论成功或失败都重置列表,避免重复通知
|
||||||
|
this.clearedAccounts = []
|
||||||
this.isRunning = false
|
this.isRunning = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,8 +198,12 @@ class RateLimitCleanupService {
|
|||||||
typeof account.rateLimitStatus === 'object' &&
|
typeof account.rateLimitStatus === 'object' &&
|
||||||
account.rateLimitStatus.status === 'limited')
|
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++
|
result.checked++
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -208,6 +211,9 @@ class RateLimitCleanupService {
|
|||||||
const isStillLimited = await claudeAccountService.isAccountRateLimited(account.id)
|
const isStillLimited = await claudeAccountService.isAccountRateLimited(account.id)
|
||||||
|
|
||||||
if (!isStillLimited) {
|
if (!isStillLimited) {
|
||||||
|
if (!isRateLimited && autoStopped) {
|
||||||
|
await claudeAccountService.removeAccountRateLimit(account.id)
|
||||||
|
}
|
||||||
result.cleared++
|
result.cleared++
|
||||||
logger.info(
|
logger.info(
|
||||||
`🧹 Auto-cleared expired rate limit for Claude account: ${account.name} (${account.id})`
|
`🧹 Auto-cleared expired rate limit for Claude account: ${account.name} (${account.id})`
|
||||||
@@ -286,10 +292,13 @@ class RateLimitCleanupService {
|
|||||||
typeof account.rateLimitStatus === 'object' &&
|
typeof account.rateLimitStatus === 'object' &&
|
||||||
account.rateLimitStatus.status === 'limited')
|
account.rateLimitStatus.status === 'limited')
|
||||||
|
|
||||||
|
const autoStopped = account.rateLimitAutoStopped === 'true'
|
||||||
|
const needsAutoStopRecovery = autoStopped && account.schedulable === 'false'
|
||||||
|
|
||||||
// 检查两种状态字段:rateLimitStatus 和 status
|
// 检查两种状态字段:rateLimitStatus 和 status
|
||||||
const hasStatusRateLimited = account.status === 'rate_limited'
|
const hasStatusRateLimited = account.status === 'rate_limited'
|
||||||
|
|
||||||
if (isRateLimited || hasStatusRateLimited) {
|
if (isRateLimited || hasStatusRateLimited || needsAutoStopRecovery) {
|
||||||
result.checked++
|
result.checked++
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -299,6 +308,9 @@ class RateLimitCleanupService {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (!isStillLimited) {
|
if (!isStillLimited) {
|
||||||
|
if (!isRateLimited && autoStopped) {
|
||||||
|
await claudeConsoleAccountService.removeAccountRateLimit(account.id)
|
||||||
|
}
|
||||||
result.cleared++
|
result.cleared++
|
||||||
|
|
||||||
// 如果 status 字段是 rate_limited,需要额外清理
|
// 如果 status 字段是 rate_limited,需要额外清理
|
||||||
|
|||||||
@@ -18,7 +18,17 @@ class WebhookConfigService {
|
|||||||
// 返回默认配置
|
// 返回默认配置
|
||||||
return this.getDefaultConfig()
|
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) {
|
} catch (error) {
|
||||||
logger.error('获取webhook配置失败:', error)
|
logger.error('获取webhook配置失败:', error)
|
||||||
return this.getDefaultConfig()
|
return this.getDefaultConfig()
|
||||||
@@ -30,6 +40,13 @@ class WebhookConfigService {
|
|||||||
*/
|
*/
|
||||||
async saveConfig(config) {
|
async saveConfig(config) {
|
||||||
try {
|
try {
|
||||||
|
const defaultConfig = this.getDefaultConfig()
|
||||||
|
|
||||||
|
config.notificationTypes = {
|
||||||
|
...defaultConfig.notificationTypes,
|
||||||
|
...(config.notificationTypes || {})
|
||||||
|
}
|
||||||
|
|
||||||
// 验证配置
|
// 验证配置
|
||||||
this.validateConfig(config)
|
this.validateConfig(config)
|
||||||
|
|
||||||
@@ -312,6 +329,7 @@ class WebhookConfigService {
|
|||||||
quotaWarning: true, // 配额警告
|
quotaWarning: true, // 配额警告
|
||||||
systemError: true, // 系统错误
|
systemError: true, // 系统错误
|
||||||
securityAlert: true, // 安全警报
|
securityAlert: true, // 安全警报
|
||||||
|
rateLimitRecovery: true, // 限流恢复
|
||||||
test: true // 测试通知
|
test: true // 测试通知
|
||||||
},
|
},
|
||||||
retrySettings: {
|
retrySettings: {
|
||||||
|
|||||||
@@ -1255,15 +1255,18 @@ const testingConnection = ref(false)
|
|||||||
const savingPlatform = ref(false)
|
const savingPlatform = ref(false)
|
||||||
|
|
||||||
// Webhook 配置
|
// Webhook 配置
|
||||||
const webhookConfig = ref({
|
const DEFAULT_WEBHOOK_NOTIFICATION_TYPES = {
|
||||||
enabled: false,
|
|
||||||
platforms: [],
|
|
||||||
notificationTypes: {
|
|
||||||
accountAnomaly: true,
|
accountAnomaly: true,
|
||||||
quotaWarning: true,
|
quotaWarning: true,
|
||||||
systemError: true,
|
systemError: true,
|
||||||
securityAlert: true
|
securityAlert: true,
|
||||||
},
|
rateLimitRecovery: true
|
||||||
|
}
|
||||||
|
|
||||||
|
const webhookConfig = ref({
|
||||||
|
enabled: false,
|
||||||
|
platforms: [],
|
||||||
|
notificationTypes: { ...DEFAULT_WEBHOOK_NOTIFICATION_TYPES },
|
||||||
retrySettings: {
|
retrySettings: {
|
||||||
maxRetries: 3,
|
maxRetries: 3,
|
||||||
retryDelay: 1000,
|
retryDelay: 1000,
|
||||||
@@ -1475,7 +1478,14 @@ const loadWebhookConfig = async () => {
|
|||||||
signal: abortController.value.signal
|
signal: abortController.value.signal
|
||||||
})
|
})
|
||||||
if (response.success && isMounted.value) {
|
if (response.success && isMounted.value) {
|
||||||
webhookConfig.value = response.config
|
const config = response.config || {}
|
||||||
|
webhookConfig.value = {
|
||||||
|
...config,
|
||||||
|
notificationTypes: {
|
||||||
|
...DEFAULT_WEBHOOK_NOTIFICATION_TYPES,
|
||||||
|
...(config.notificationTypes || {})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.name === 'AbortError') return
|
if (error.name === 'AbortError') return
|
||||||
@@ -1489,10 +1499,19 @@ const loadWebhookConfig = async () => {
|
|||||||
const saveWebhookConfig = async () => {
|
const saveWebhookConfig = async () => {
|
||||||
if (!isMounted.value) return
|
if (!isMounted.value) return
|
||||||
try {
|
try {
|
||||||
const response = await apiClient.post('/admin/webhook/config', webhookConfig.value, {
|
const payload = {
|
||||||
|
...webhookConfig.value,
|
||||||
|
notificationTypes: {
|
||||||
|
...DEFAULT_WEBHOOK_NOTIFICATION_TYPES,
|
||||||
|
...(webhookConfig.value.notificationTypes || {})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await apiClient.post('/admin/webhook/config', payload, {
|
||||||
signal: abortController.value.signal
|
signal: abortController.value.signal
|
||||||
})
|
})
|
||||||
if (response.success && isMounted.value) {
|
if (response.success && isMounted.value) {
|
||||||
|
webhookConfig.value = payload
|
||||||
showToast('配置已保存', 'success')
|
showToast('配置已保存', 'success')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -1930,6 +1949,7 @@ const getNotificationTypeName = (type) => {
|
|||||||
quotaWarning: '配额警告',
|
quotaWarning: '配额警告',
|
||||||
systemError: '系统错误',
|
systemError: '系统错误',
|
||||||
securityAlert: '安全警报',
|
securityAlert: '安全警报',
|
||||||
|
rateLimitRecovery: '限流恢复',
|
||||||
test: '测试通知'
|
test: '测试通知'
|
||||||
}
|
}
|
||||||
return names[type] || type
|
return names[type] || type
|
||||||
@@ -1941,6 +1961,7 @@ const getNotificationTypeDescription = (type) => {
|
|||||||
quotaWarning: 'API调用配额不足警告',
|
quotaWarning: 'API调用配额不足警告',
|
||||||
systemError: '系统运行错误和故障',
|
systemError: '系统运行错误和故障',
|
||||||
securityAlert: '安全相关的警报通知',
|
securityAlert: '安全相关的警报通知',
|
||||||
|
rateLimitRecovery: '限流状态恢复时发送提醒',
|
||||||
test: '用于测试Webhook连接是否正常'
|
test: '用于测试Webhook连接是否正常'
|
||||||
}
|
}
|
||||||
return descriptions[type] || ''
|
return descriptions[type] || ''
|
||||||
|
|||||||
Reference in New Issue
Block a user