mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
feat: 新增 OpenAI-Responses 账户管理功能和独立自动停止标记机制
## 功能新增 - 实现 OpenAI-Responses 账户服务(openaiResponsesAccountService.js) - 支持使用账户内置 API Key 进行请求转发 - 实现每日额度管理和重置机制 - 支持代理配置和优先级设置 - 实现 OpenAI-Responses 中继服务(openaiResponsesRelayService.js) - 处理请求转发和响应流处理 - 自动记录使用统计信息 - 支持流式和非流式响应 - 新增管理界面的 OpenAI-Responses 账户管理功能 - 完整的 CRUD 操作支持 - 实时额度监控和状态管理 - 支持手动重置限流和每日额度 ## 架构改进 - 引入独立的自动停止标记机制,区分不同原因的自动停止 - rateLimitAutoStopped: 限流自动停止 - fiveHourAutoStopped: 5小时限制自动停止 - tempErrorAutoStopped: 临时错误自动停止 - quotaAutoStopped: 额度耗尽自动停止 - 修复手动修改调度状态时自动恢复的问题 - 统一清理逻辑,防止状态冲突 ## 其他优化 - getAccountUsageStats 支持不同账户类型参数 - 统一调度器支持 OpenAI-Responses 账户类型 - WebHook 通知增强,支持新账户类型的事件 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -603,6 +603,25 @@ class ClaudeAccountService {
|
||||
|
||||
updatedData.updatedAt = new Date().toISOString()
|
||||
|
||||
// 如果是手动修改调度状态,清除所有自动停止相关的字段
|
||||
if (Object.prototype.hasOwnProperty.call(updates, 'schedulable')) {
|
||||
// 清除所有自动停止的标记,防止自动恢复
|
||||
delete updatedData.rateLimitAutoStopped
|
||||
delete updatedData.fiveHourAutoStopped
|
||||
delete updatedData.fiveHourStoppedAt
|
||||
delete updatedData.tempErrorAutoStopped
|
||||
// 兼容旧的标记(逐步迁移)
|
||||
delete updatedData.autoStoppedAt
|
||||
delete updatedData.stoppedReason
|
||||
|
||||
// 如果是手动启用调度,记录日志
|
||||
if (updates.schedulable === true || updates.schedulable === 'true') {
|
||||
logger.info(`✅ Manually enabled scheduling for account ${accountId}`)
|
||||
} else {
|
||||
logger.info(`⛔ Manually disabled scheduling for account ${accountId}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否手动禁用了账号,如果是则发送webhook通知
|
||||
if (updates.isActive === 'false' && accountData.isActive === 'true') {
|
||||
try {
|
||||
@@ -1088,7 +1107,9 @@ class ClaudeAccountService {
|
||||
updatedAccountData.rateLimitedAt = new Date().toISOString()
|
||||
updatedAccountData.rateLimitStatus = 'limited'
|
||||
// 限流时停止调度,与 OpenAI 账号保持一致
|
||||
updatedAccountData.schedulable = false
|
||||
updatedAccountData.schedulable = 'false'
|
||||
// 使用独立的限流自动停止标记,避免与其他自动停止冲突
|
||||
updatedAccountData.rateLimitAutoStopped = 'true'
|
||||
|
||||
// 如果提供了准确的限流重置时间戳(来自API响应头)
|
||||
if (rateLimitResetTimestamp) {
|
||||
@@ -1173,13 +1194,16 @@ class ClaudeAccountService {
|
||||
delete accountData.rateLimitedAt
|
||||
delete accountData.rateLimitStatus
|
||||
delete accountData.rateLimitEndAt // 清除限流结束时间
|
||||
// 恢复可调度状态,与 OpenAI 账号保持一致
|
||||
accountData.schedulable = true
|
||||
|
||||
// 只恢复因限流而自动停止的账户
|
||||
if (accountData.rateLimitAutoStopped === 'true' && accountData.schedulable === 'false') {
|
||||
accountData.schedulable = 'true'
|
||||
delete accountData.rateLimitAutoStopped
|
||||
logger.info(`✅ Auto-resuming scheduling for account ${accountId} after rate limit cleared`)
|
||||
}
|
||||
await redis.setClaudeAccount(accountId, accountData)
|
||||
|
||||
logger.success(
|
||||
`✅ Rate limit removed for account: ${accountData.name} (${accountId}), schedulable restored`
|
||||
)
|
||||
logger.success(`✅ Rate limit removed for account: ${accountData.name} (${accountId})`)
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
@@ -1331,17 +1355,13 @@ class ClaudeAccountService {
|
||||
}
|
||||
|
||||
// 如果账户因为5小时限制被自动停止,现在恢复调度
|
||||
if (
|
||||
accountData.autoStoppedAt &&
|
||||
accountData.schedulable === 'false' &&
|
||||
accountData.stoppedReason === '5小时使用量接近限制,自动停止调度'
|
||||
) {
|
||||
if (accountData.fiveHourAutoStopped === 'true' && accountData.schedulable === 'false') {
|
||||
logger.info(
|
||||
`✅ Auto-resuming scheduling for account ${accountData.name} (${accountId}) - new session window started`
|
||||
)
|
||||
accountData.schedulable = 'true'
|
||||
delete accountData.stoppedReason
|
||||
delete accountData.autoStoppedAt
|
||||
delete accountData.fiveHourAutoStopped
|
||||
delete accountData.fiveHourStoppedAt
|
||||
|
||||
// 发送Webhook通知
|
||||
try {
|
||||
@@ -1823,8 +1843,16 @@ class ClaudeAccountService {
|
||||
updatedAccountData.status = 'created'
|
||||
}
|
||||
|
||||
// 恢复可调度状态
|
||||
// 恢复可调度状态(管理员手动重置时恢复调度是合理的)
|
||||
updatedAccountData.schedulable = 'true'
|
||||
// 清除所有自动停止相关的标记
|
||||
delete updatedAccountData.rateLimitAutoStopped
|
||||
delete updatedAccountData.fiveHourAutoStopped
|
||||
delete updatedAccountData.fiveHourStoppedAt
|
||||
delete updatedAccountData.tempErrorAutoStopped
|
||||
// 兼容旧的标记
|
||||
delete updatedAccountData.autoStoppedAt
|
||||
delete updatedAccountData.stoppedReason
|
||||
|
||||
// 清除错误相关字段
|
||||
delete updatedAccountData.errorMessage
|
||||
@@ -1850,7 +1878,15 @@ class ClaudeAccountService {
|
||||
'rateLimitEndAt',
|
||||
'tempErrorAt',
|
||||
'sessionWindowStart',
|
||||
'sessionWindowEnd'
|
||||
'sessionWindowEnd',
|
||||
// 新的独立标记
|
||||
'rateLimitAutoStopped',
|
||||
'fiveHourAutoStopped',
|
||||
'fiveHourStoppedAt',
|
||||
'tempErrorAutoStopped',
|
||||
// 兼容旧的标记
|
||||
'autoStoppedAt',
|
||||
'stoppedReason'
|
||||
]
|
||||
await redis.client.hdel(`claude:account:${accountId}`, ...fieldsToDelete)
|
||||
|
||||
@@ -1901,13 +1937,22 @@ class ClaudeAccountService {
|
||||
// 如果临时错误状态超过指定时间,尝试重新激活
|
||||
if (minutesSinceTempError > TEMP_ERROR_RECOVERY_MINUTES) {
|
||||
account.status = 'active' // 恢复为 active 状态
|
||||
account.schedulable = 'true' // 恢复为可调度
|
||||
// 只恢复因临时错误而自动停止的账户
|
||||
if (account.tempErrorAutoStopped === 'true') {
|
||||
account.schedulable = 'true' // 恢复为可调度
|
||||
delete account.tempErrorAutoStopped
|
||||
}
|
||||
delete account.errorMessage
|
||||
delete account.tempErrorAt
|
||||
await redis.setClaudeAccount(account.id, account)
|
||||
|
||||
// 显式从 Redis 中删除这些字段(因为 HSET 不会删除现有字段)
|
||||
await redis.client.hdel(`claude:account:${account.id}`, 'errorMessage', 'tempErrorAt')
|
||||
await redis.client.hdel(
|
||||
`claude:account:${account.id}`,
|
||||
'errorMessage',
|
||||
'tempErrorAt',
|
||||
'tempErrorAutoStopped'
|
||||
)
|
||||
|
||||
// 同时清除500错误计数
|
||||
await this.clearInternalErrors(account.id)
|
||||
@@ -1992,6 +2037,8 @@ class ClaudeAccountService {
|
||||
updatedAccountData.schedulable = 'false' // 设置为不可调度
|
||||
updatedAccountData.errorMessage = 'Account temporarily disabled due to consecutive 500 errors'
|
||||
updatedAccountData.tempErrorAt = new Date().toISOString()
|
||||
// 使用独立的临时错误自动停止标记
|
||||
updatedAccountData.tempErrorAutoStopped = 'true'
|
||||
|
||||
// 保存更新后的账户数据
|
||||
await redis.setClaudeAccount(accountId, updatedAccountData)
|
||||
@@ -2010,7 +2057,11 @@ class ClaudeAccountService {
|
||||
if (minutesSince >= 5) {
|
||||
// 恢复账户
|
||||
account.status = 'active'
|
||||
account.schedulable = 'true'
|
||||
// 只恢复因临时错误而自动停止的账户
|
||||
if (account.tempErrorAutoStopped === 'true') {
|
||||
account.schedulable = 'true'
|
||||
delete account.tempErrorAutoStopped
|
||||
}
|
||||
delete account.errorMessage
|
||||
delete account.tempErrorAt
|
||||
|
||||
@@ -2020,7 +2071,8 @@ class ClaudeAccountService {
|
||||
await redis.client.hdel(
|
||||
`claude:account:${accountId}`,
|
||||
'errorMessage',
|
||||
'tempErrorAt'
|
||||
'tempErrorAt',
|
||||
'tempErrorAutoStopped'
|
||||
)
|
||||
|
||||
// 清除 500 错误计数
|
||||
@@ -2108,8 +2160,9 @@ class ClaudeAccountService {
|
||||
`⚠️ Account ${accountData.name} (${accountId}) approaching 5h limit, auto-stopping scheduling`
|
||||
)
|
||||
accountData.schedulable = 'false'
|
||||
accountData.stoppedReason = '5小时使用量接近限制,自动停止调度'
|
||||
accountData.autoStoppedAt = new Date().toISOString()
|
||||
// 使用独立的5小时限制自动停止标记
|
||||
accountData.fiveHourAutoStopped = 'true'
|
||||
accountData.fiveHourStoppedAt = new Date().toISOString()
|
||||
|
||||
// 发送Webhook通知
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user