feat: openai账号401自动停止调度

This commit is contained in:
shaw
2025-09-24 11:35:48 +08:00
parent 00faa21e4b
commit f56d1edce0
5 changed files with 364 additions and 14 deletions

View File

@@ -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,

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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 {