mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:38:02 +00:00
feat: openai账号401自动停止调度
This commit is contained in:
@@ -865,6 +865,49 @@ async function setAccountRateLimited(accountId, isLimited, resetsInSeconds = nul
|
||||
}
|
||||
}
|
||||
|
||||
// 🚫 标记账户为未授权状态(401错误)
|
||||
async function markAccountUnauthorized(accountId, reason = 'OpenAI账号认证失败(401错误)') {
|
||||
const account = await getAccount(accountId)
|
||||
if (!account) {
|
||||
throw new Error('Account not found')
|
||||
}
|
||||
|
||||
const now = new Date().toISOString()
|
||||
const currentCount = parseInt(account.unauthorizedCount || '0', 10)
|
||||
const unauthorizedCount = Number.isFinite(currentCount) ? currentCount + 1 : 1
|
||||
|
||||
const updates = {
|
||||
status: 'unauthorized',
|
||||
schedulable: 'false',
|
||||
errorMessage: reason,
|
||||
unauthorizedAt: now,
|
||||
unauthorizedCount: unauthorizedCount.toString()
|
||||
}
|
||||
|
||||
await updateAccount(accountId, updates)
|
||||
logger.warn(
|
||||
`🚫 Marked OpenAI account ${account.name || accountId} as unauthorized due to 401 error`
|
||||
)
|
||||
|
||||
try {
|
||||
const webhookNotifier = require('../utils/webhookNotifier')
|
||||
await webhookNotifier.sendAccountAnomalyNotification({
|
||||
accountId,
|
||||
accountName: account.name || accountId,
|
||||
platform: 'openai',
|
||||
status: 'unauthorized',
|
||||
errorCode: 'OPENAI_UNAUTHORIZED',
|
||||
reason,
|
||||
timestamp: now
|
||||
})
|
||||
logger.info(
|
||||
`📢 Webhook notification sent for OpenAI account ${account.name} unauthorized state`
|
||||
)
|
||||
} catch (webhookError) {
|
||||
logger.error('Failed to send unauthorized webhook notification:', webhookError)
|
||||
}
|
||||
}
|
||||
|
||||
// 🔄 重置账户所有异常状态
|
||||
async function resetAccountStatus(accountId) {
|
||||
const account = await getAccount(accountId)
|
||||
@@ -1001,6 +1044,7 @@ module.exports = {
|
||||
refreshAccountToken,
|
||||
isTokenExpired,
|
||||
setAccountRateLimited,
|
||||
markAccountUnauthorized,
|
||||
resetAccountStatus,
|
||||
toggleSchedulable,
|
||||
getAccountRateLimitInfo,
|
||||
|
||||
@@ -293,6 +293,48 @@ class OpenAIResponsesAccountService {
|
||||
)
|
||||
}
|
||||
|
||||
// 🚫 标记账户为未授权状态(401错误)
|
||||
async markAccountUnauthorized(accountId, reason = 'OpenAI Responses账号认证失败(401错误)') {
|
||||
const account = await this.getAccount(accountId)
|
||||
if (!account) {
|
||||
return
|
||||
}
|
||||
|
||||
const now = new Date().toISOString()
|
||||
const currentCount = parseInt(account.unauthorizedCount || '0', 10)
|
||||
const unauthorizedCount = Number.isFinite(currentCount) ? currentCount + 1 : 1
|
||||
|
||||
await this.updateAccount(accountId, {
|
||||
status: 'unauthorized',
|
||||
schedulable: 'false',
|
||||
errorMessage: reason,
|
||||
unauthorizedAt: now,
|
||||
unauthorizedCount: unauthorizedCount.toString()
|
||||
})
|
||||
|
||||
logger.warn(
|
||||
`🚫 OpenAI-Responses account ${account.name || accountId} marked as unauthorized due to 401 error`
|
||||
)
|
||||
|
||||
try {
|
||||
const webhookNotifier = require('../utils/webhookNotifier')
|
||||
await webhookNotifier.sendAccountAnomalyNotification({
|
||||
accountId,
|
||||
accountName: account.name || accountId,
|
||||
platform: 'openai',
|
||||
status: 'unauthorized',
|
||||
errorCode: 'OPENAI_UNAUTHORIZED',
|
||||
reason,
|
||||
timestamp: now
|
||||
})
|
||||
logger.info(
|
||||
`📢 Webhook notification sent for OpenAI-Responses account ${account.name || accountId} unauthorized state`
|
||||
)
|
||||
} catch (webhookError) {
|
||||
logger.error('Failed to send unauthorized webhook notification:', webhookError)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查并清除过期的限流状态
|
||||
async checkAndClearRateLimit(accountId) {
|
||||
const account = await this.getAccount(accountId)
|
||||
|
||||
@@ -169,6 +169,61 @@ class OpenAIResponsesRelayService {
|
||||
errorData
|
||||
})
|
||||
|
||||
if (response.status === 401) {
|
||||
let reason = 'OpenAI Responses账号认证失败(401错误)'
|
||||
if (errorData) {
|
||||
if (typeof errorData === 'string' && errorData.trim()) {
|
||||
reason = `OpenAI Responses账号认证失败(401错误):${errorData.trim()}`
|
||||
} else if (
|
||||
errorData.error &&
|
||||
typeof errorData.error.message === 'string' &&
|
||||
errorData.error.message.trim()
|
||||
) {
|
||||
reason = `OpenAI Responses账号认证失败(401错误):${errorData.error.message.trim()}`
|
||||
} else if (typeof errorData.message === 'string' && errorData.message.trim()) {
|
||||
reason = `OpenAI Responses账号认证失败(401错误):${errorData.message.trim()}`
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await unifiedOpenAIScheduler.markAccountUnauthorized(
|
||||
account.id,
|
||||
'openai-responses',
|
||||
sessionHash,
|
||||
reason
|
||||
)
|
||||
} catch (markError) {
|
||||
logger.error(
|
||||
'❌ Failed to mark OpenAI-Responses account unauthorized after 401:',
|
||||
markError
|
||||
)
|
||||
}
|
||||
|
||||
let unauthorizedResponse = errorData
|
||||
if (
|
||||
!unauthorizedResponse ||
|
||||
typeof unauthorizedResponse !== 'object' ||
|
||||
unauthorizedResponse.pipe ||
|
||||
Buffer.isBuffer(unauthorizedResponse)
|
||||
) {
|
||||
const fallbackMessage =
|
||||
typeof errorData === 'string' && errorData.trim() ? errorData.trim() : 'Unauthorized'
|
||||
unauthorizedResponse = {
|
||||
error: {
|
||||
message: fallbackMessage,
|
||||
type: 'unauthorized',
|
||||
code: 'unauthorized'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清理监听器
|
||||
req.removeListener('close', handleClientDisconnect)
|
||||
res.removeListener('close', handleClientDisconnect)
|
||||
|
||||
return res.status(401).json(unauthorizedResponse)
|
||||
}
|
||||
|
||||
// 清理监听器
|
||||
req.removeListener('close', handleClientDisconnect)
|
||||
res.removeListener('close', handleClientDisconnect)
|
||||
@@ -250,6 +305,57 @@ class OpenAIResponsesRelayService {
|
||||
}
|
||||
}
|
||||
|
||||
if (status === 401) {
|
||||
let reason = 'OpenAI Responses账号认证失败(401错误)'
|
||||
if (errorData) {
|
||||
if (typeof errorData === 'string' && errorData.trim()) {
|
||||
reason = `OpenAI Responses账号认证失败(401错误):${errorData.trim()}`
|
||||
} else if (
|
||||
errorData.error &&
|
||||
typeof errorData.error.message === 'string' &&
|
||||
errorData.error.message.trim()
|
||||
) {
|
||||
reason = `OpenAI Responses账号认证失败(401错误):${errorData.error.message.trim()}`
|
||||
} else if (typeof errorData.message === 'string' && errorData.message.trim()) {
|
||||
reason = `OpenAI Responses账号认证失败(401错误):${errorData.message.trim()}`
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await unifiedOpenAIScheduler.markAccountUnauthorized(
|
||||
account.id,
|
||||
'openai-responses',
|
||||
sessionHash,
|
||||
reason
|
||||
)
|
||||
} catch (markError) {
|
||||
logger.error(
|
||||
'❌ Failed to mark OpenAI-Responses account unauthorized in catch handler:',
|
||||
markError
|
||||
)
|
||||
}
|
||||
|
||||
let unauthorizedResponse = errorData
|
||||
if (
|
||||
!unauthorizedResponse ||
|
||||
typeof unauthorizedResponse !== 'object' ||
|
||||
unauthorizedResponse.pipe ||
|
||||
Buffer.isBuffer(unauthorizedResponse)
|
||||
) {
|
||||
const fallbackMessage =
|
||||
typeof errorData === 'string' && errorData.trim() ? errorData.trim() : 'Unauthorized'
|
||||
unauthorizedResponse = {
|
||||
error: {
|
||||
message: fallbackMessage,
|
||||
type: 'unauthorized',
|
||||
code: 'unauthorized'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res.status(401).json(unauthorizedResponse)
|
||||
}
|
||||
|
||||
return res.status(status).json(errorData)
|
||||
}
|
||||
|
||||
|
||||
@@ -356,7 +356,12 @@ class UnifiedOpenAIScheduler {
|
||||
try {
|
||||
if (accountType === 'openai') {
|
||||
const account = await openaiAccountService.getAccount(accountId)
|
||||
if (!account || !account.isActive || account.status === 'error') {
|
||||
if (
|
||||
!account ||
|
||||
!account.isActive ||
|
||||
account.status === 'error' ||
|
||||
account.status === 'unauthorized'
|
||||
) {
|
||||
return false
|
||||
}
|
||||
// 检查是否可调度
|
||||
@@ -370,7 +375,8 @@ class UnifiedOpenAIScheduler {
|
||||
if (
|
||||
!account ||
|
||||
(account.isActive !== true && account.isActive !== 'true') ||
|
||||
account.status === 'error'
|
||||
account.status === 'error' ||
|
||||
account.status === 'unauthorized'
|
||||
) {
|
||||
return false
|
||||
}
|
||||
@@ -500,6 +506,39 @@ class UnifiedOpenAIScheduler {
|
||||
}
|
||||
}
|
||||
|
||||
// 🚫 标记账户为未授权状态
|
||||
async markAccountUnauthorized(
|
||||
accountId,
|
||||
accountType,
|
||||
sessionHash = null,
|
||||
reason = 'OpenAI账号认证失败(401错误)'
|
||||
) {
|
||||
try {
|
||||
if (accountType === 'openai') {
|
||||
await openaiAccountService.markAccountUnauthorized(accountId, reason)
|
||||
} else if (accountType === 'openai-responses') {
|
||||
await openaiResponsesAccountService.markAccountUnauthorized(accountId, reason)
|
||||
} else {
|
||||
logger.warn(
|
||||
`⚠️ Unsupported account type ${accountType} when marking unauthorized for account ${accountId}`
|
||||
)
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
if (sessionHash) {
|
||||
await this._deleteSessionMapping(sessionHash)
|
||||
}
|
||||
|
||||
return { success: true }
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`❌ Failed to mark account as unauthorized: ${accountId} (${accountType})`,
|
||||
error
|
||||
)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 移除账户的限流状态
|
||||
async removeAccountRateLimit(accountId, accountType) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user