mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
refactor: 统一账户过期时间字段映射和检查逻辑
主要改进: 1. 创建 mapExpiryField() 工具函数统一处理前后端字段映射(expiresAt -> subscriptionExpiresAt) 2. 统一 subscriptionExpiresAt 初始值为 null(替代空字符串) 3. 规范过期检查方法名为 isSubscriptionExpired(),返回 true 表示已过期 4. 优化过期检查条件判断,只检查 null 而非空字符串 5. 补充 OpenAI-Responses 和调度器中缺失的过期检查逻辑 6. 添加代码评审文档记录未修复问题 影响范围: - 所有 9 种账户服务的过期字段处理 - admin.js 中所有账户更新路由 - 统一调度器的过期账户过滤逻辑 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,26 @@ const ProxyHelper = require('../utils/proxyHelper')
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
// 🛠️ 工具函数:映射前端字段名到后端字段名
|
||||
/**
|
||||
* 映射前端的 expiresAt 字段到后端的 subscriptionExpiresAt 字段
|
||||
* @param {Object} updates - 更新对象
|
||||
* @param {string} accountType - 账户类型 (如 'Claude', 'OpenAI' 等)
|
||||
* @param {string} accountId - 账户 ID
|
||||
* @returns {Object} 映射后的更新对象
|
||||
*/
|
||||
function mapExpiryField(updates, accountType, accountId) {
|
||||
const mappedUpdates = { ...updates }
|
||||
if ('expiresAt' in mappedUpdates) {
|
||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||
delete mappedUpdates.expiresAt
|
||||
logger.info(
|
||||
`Mapping expiresAt to subscriptionExpiresAt for ${accountType} account ${accountId}`
|
||||
)
|
||||
}
|
||||
return mappedUpdates
|
||||
}
|
||||
|
||||
// 👥 用户管理
|
||||
|
||||
// 获取所有用户列表(用于API Key分配)
|
||||
@@ -2399,11 +2419,7 @@ router.put('/claude-accounts/:accountId', authenticateAdmin, async (req, res) =>
|
||||
}
|
||||
|
||||
// 映射字段名:前端的expiresAt -> 后端的subscriptionExpiresAt
|
||||
const mappedUpdates = { ...updates }
|
||||
if ('expiresAt' in mappedUpdates) {
|
||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||
delete mappedUpdates.expiresAt
|
||||
}
|
||||
const mappedUpdates = mapExpiryField(updates, 'Claude', accountId)
|
||||
|
||||
await claudeAccountService.updateAccount(accountId, mappedUpdates)
|
||||
|
||||
@@ -2746,14 +2762,7 @@ router.put('/claude-console-accounts/:accountId', authenticateAdmin, async (req,
|
||||
const updates = req.body
|
||||
|
||||
// ✅ 【新增】映射字段名:前端的 expiresAt -> 后端的 subscriptionExpiresAt
|
||||
const mappedUpdates = { ...updates }
|
||||
if ('expiresAt' in mappedUpdates) {
|
||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||
delete mappedUpdates.expiresAt
|
||||
logger.info(
|
||||
`Mapping expiresAt to subscriptionExpiresAt for Claude Console account ${accountId}`
|
||||
)
|
||||
}
|
||||
const mappedUpdates = mapExpiryField(updates, 'Claude Console', accountId)
|
||||
|
||||
// 验证priority的有效性(1-100)
|
||||
if (
|
||||
@@ -3172,12 +3181,7 @@ router.put('/ccr-accounts/:accountId', authenticateAdmin, async (req, res) => {
|
||||
const updates = req.body
|
||||
|
||||
// ✅ 【新增】映射字段名:前端的 expiresAt -> 后端的 subscriptionExpiresAt
|
||||
const mappedUpdates = { ...updates }
|
||||
if ('expiresAt' in mappedUpdates) {
|
||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||
delete mappedUpdates.expiresAt
|
||||
logger.info(`Mapping expiresAt to subscriptionExpiresAt for CCR account ${accountId}`)
|
||||
}
|
||||
const mappedUpdates = mapExpiryField(updates, 'CCR', accountId)
|
||||
|
||||
// 验证priority的有效性(1-100)
|
||||
if (
|
||||
@@ -3575,12 +3579,7 @@ router.put('/bedrock-accounts/:accountId', authenticateAdmin, async (req, res) =
|
||||
const updates = req.body
|
||||
|
||||
// ✅ 【新增】映射字段名:前端的 expiresAt -> 后端的 subscriptionExpiresAt
|
||||
const mappedUpdates = { ...updates }
|
||||
if ('expiresAt' in mappedUpdates) {
|
||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||
delete mappedUpdates.expiresAt
|
||||
logger.info(`Mapping expiresAt to subscriptionExpiresAt for Bedrock account ${accountId}`)
|
||||
}
|
||||
const mappedUpdates = mapExpiryField(updates, 'Bedrock', accountId)
|
||||
|
||||
// 验证priority的有效性(1-100)
|
||||
if (
|
||||
@@ -4047,12 +4046,7 @@ router.put('/gemini-accounts/:accountId', authenticateAdmin, async (req, res) =>
|
||||
}
|
||||
|
||||
// ✅ 【新增】映射字段名:前端的 expiresAt -> 后端的 subscriptionExpiresAt
|
||||
const mappedUpdates = { ...updates }
|
||||
if ('expiresAt' in mappedUpdates) {
|
||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||
delete mappedUpdates.expiresAt
|
||||
logger.info(`Mapping expiresAt to subscriptionExpiresAt for Gemini account ${accountId}`)
|
||||
}
|
||||
const mappedUpdates = mapExpiryField(updates, 'Gemini', accountId)
|
||||
|
||||
// 处理分组的变更
|
||||
if (mappedUpdates.accountType !== undefined) {
|
||||
@@ -7430,12 +7424,7 @@ router.put('/openai-accounts/:id', authenticateAdmin, async (req, res) => {
|
||||
const updates = req.body
|
||||
|
||||
// ✅ 【新增】映射字段名:前端的 expiresAt -> 后端的 subscriptionExpiresAt
|
||||
const mappedUpdates = { ...updates }
|
||||
if ('expiresAt' in mappedUpdates) {
|
||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||
delete mappedUpdates.expiresAt
|
||||
logger.info(`Mapping expiresAt to subscriptionExpiresAt for OpenAI account ${id}`)
|
||||
}
|
||||
const mappedUpdates = mapExpiryField(updates, 'OpenAI', id)
|
||||
|
||||
const { needsImmediateRefresh, requireRefreshSuccess } = mappedUpdates
|
||||
|
||||
@@ -7988,12 +7977,7 @@ router.put('/azure-openai-accounts/:id', authenticateAdmin, async (req, res) =>
|
||||
const updates = req.body
|
||||
|
||||
// ✅ 【新增】映射字段名:前端的 expiresAt -> 后端的 subscriptionExpiresAt
|
||||
const mappedUpdates = { ...updates }
|
||||
if ('expiresAt' in mappedUpdates) {
|
||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||
delete mappedUpdates.expiresAt
|
||||
logger.info(`Mapping expiresAt to subscriptionExpiresAt for Azure OpenAI account ${id}`)
|
||||
}
|
||||
const mappedUpdates = mapExpiryField(updates, 'Azure OpenAI', id)
|
||||
|
||||
const account = await azureOpenaiAccountService.updateAccount(id, mappedUpdates)
|
||||
|
||||
@@ -8357,12 +8341,7 @@ router.put('/openai-responses-accounts/:id', authenticateAdmin, async (req, res)
|
||||
const updates = req.body
|
||||
|
||||
// ✅ 【新增】映射字段名:前端的 expiresAt -> 后端的 subscriptionExpiresAt
|
||||
const mappedUpdates = { ...updates }
|
||||
if ('expiresAt' in mappedUpdates) {
|
||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||
delete mappedUpdates.expiresAt
|
||||
logger.info(`Mapping expiresAt to subscriptionExpiresAt for OpenAI-Responses account ${id}`)
|
||||
}
|
||||
const mappedUpdates = mapExpiryField(updates, 'OpenAI-Responses', id)
|
||||
|
||||
// 验证priority的有效性(1-100)
|
||||
if (mappedUpdates.priority !== undefined) {
|
||||
@@ -8838,12 +8817,7 @@ router.put('/droid-accounts/:id', authenticateAdmin, async (req, res) => {
|
||||
const updates = { ...req.body }
|
||||
|
||||
// ✅ 【新增】映射字段名:前端的 expiresAt -> 后端的 subscriptionExpiresAt
|
||||
const mappedUpdates = { ...updates }
|
||||
if ('expiresAt' in mappedUpdates) {
|
||||
mappedUpdates.subscriptionExpiresAt = mappedUpdates.expiresAt
|
||||
delete mappedUpdates.expiresAt
|
||||
logger.info(`Mapping expiresAt to subscriptionExpiresAt for Droid account ${id}`)
|
||||
}
|
||||
const mappedUpdates = mapExpiryField(updates, 'Droid', id)
|
||||
|
||||
const { accountType: rawAccountType, groupId, groupIds } = mappedUpdates
|
||||
|
||||
|
||||
@@ -132,7 +132,7 @@ async function createAccount(accountData) {
|
||||
|
||||
// ✅ 新增:账户订阅到期时间(业务字段,手动管理)
|
||||
// 注意:Azure OpenAI 使用 API Key 认证,没有 OAuth token,因此没有 expiresAt
|
||||
subscriptionExpiresAt: accountData.subscriptionExpiresAt || '',
|
||||
subscriptionExpiresAt: accountData.subscriptionExpiresAt || null,
|
||||
|
||||
// 状态字段
|
||||
isActive: accountData.isActive !== false ? 'true' : 'false',
|
||||
@@ -317,7 +317,8 @@ async function getAllAccounts() {
|
||||
schedulable: accountData.schedulable !== 'false',
|
||||
|
||||
// ✅ 前端显示订阅过期时间(业务字段)
|
||||
expiresAt: accountData.subscriptionExpiresAt || null
|
||||
expiresAt: accountData.subscriptionExpiresAt || null,
|
||||
platform: 'azure-openai'
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -351,7 +352,7 @@ async function getSharedAccounts() {
|
||||
* @returns {boolean} - true: 已过期, false: 未过期
|
||||
*/
|
||||
function isSubscriptionExpired(account) {
|
||||
if (!account.subscriptionExpiresAt || account.subscriptionExpiresAt === '') {
|
||||
if (!account.subscriptionExpiresAt) {
|
||||
return false // 未设置视为永不过期
|
||||
}
|
||||
const expiryDate = new Date(account.subscriptionExpiresAt)
|
||||
|
||||
@@ -59,7 +59,7 @@ class BedrockAccountService {
|
||||
|
||||
// ✅ 新增:账户订阅到期时间(业务字段,手动管理)
|
||||
// 注意:Bedrock 使用 AWS 凭证,没有 OAuth token,因此没有 expiresAt
|
||||
subscriptionExpiresAt: options.subscriptionExpiresAt || '',
|
||||
subscriptionExpiresAt: options.subscriptionExpiresAt || null,
|
||||
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
@@ -154,6 +154,7 @@ class BedrockAccountService {
|
||||
createdAt: account.createdAt,
|
||||
updatedAt: account.updatedAt,
|
||||
type: 'bedrock',
|
||||
platform: 'bedrock',
|
||||
hasCredentials: !!account.awsCredentials
|
||||
})
|
||||
}
|
||||
@@ -299,7 +300,7 @@ class BedrockAccountService {
|
||||
|
||||
const availableAccounts = accountsResult.data.filter((account) => {
|
||||
// ✅ 检查账户订阅是否过期
|
||||
if (this._isSubscriptionExpired(account)) {
|
||||
if (this.isSubscriptionExpired(account)) {
|
||||
logger.debug(
|
||||
`⏰ Skipping expired Bedrock account: ${account.name}, expired at ${account.subscriptionExpiresAt || account.expiresAt}`
|
||||
)
|
||||
@@ -380,8 +381,8 @@ class BedrockAccountService {
|
||||
* @param {Object} account - 账户对象
|
||||
* @returns {boolean} - true: 已过期, false: 未过期
|
||||
*/
|
||||
_isSubscriptionExpired(account) {
|
||||
if (!account.subscriptionExpiresAt || account.subscriptionExpiresAt === '') {
|
||||
isSubscriptionExpired(account) {
|
||||
if (!account.subscriptionExpiresAt) {
|
||||
return false // 未设置视为永不过期
|
||||
}
|
||||
const expiryDate = new Date(account.subscriptionExpiresAt)
|
||||
|
||||
@@ -79,7 +79,7 @@ class CcrAccountService {
|
||||
|
||||
// ✅ 新增:账户订阅到期时间(业务字段,手动管理)
|
||||
// 注意:CCR 使用 API Key 认证,没有 OAuth token,因此没有 expiresAt
|
||||
subscriptionExpiresAt: options.subscriptionExpiresAt || '',
|
||||
subscriptionExpiresAt: options.subscriptionExpiresAt || null,
|
||||
|
||||
createdAt: new Date().toISOString(),
|
||||
lastUsedAt: '',
|
||||
|
||||
@@ -787,13 +787,13 @@ class ClaudeAccountService {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查账户是否未过期
|
||||
* 检查账户订阅是否过期
|
||||
* @param {Object} account - 账户对象
|
||||
* @returns {boolean} - 如果未设置过期时间或未过期返回 true
|
||||
* @returns {boolean} - true: 已过期, false: 未过期
|
||||
*/
|
||||
isAccountNotExpired(account) {
|
||||
isSubscriptionExpired(account) {
|
||||
if (!account.subscriptionExpiresAt) {
|
||||
return true // 未设置过期时间,视为永不过期
|
||||
return false // 未设置过期时间,视为永不过期
|
||||
}
|
||||
|
||||
const expiryDate = new Date(account.subscriptionExpiresAt)
|
||||
@@ -803,10 +803,10 @@ class ClaudeAccountService {
|
||||
logger.debug(
|
||||
`⏰ Account ${account.name} (${account.id}) expired at ${account.subscriptionExpiresAt}`
|
||||
)
|
||||
return false
|
||||
return true
|
||||
}
|
||||
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
// 🎯 智能选择可用账户(支持sticky会话和模型过滤)
|
||||
@@ -819,7 +819,7 @@ class ClaudeAccountService {
|
||||
account.isActive === 'true' &&
|
||||
account.status !== 'error' &&
|
||||
account.schedulable !== 'false' &&
|
||||
this.isAccountNotExpired(account)
|
||||
!this.isSubscriptionExpired(account)
|
||||
)
|
||||
|
||||
// 如果请求的是 Opus 模型,过滤掉 Pro 和 Free 账号
|
||||
@@ -915,7 +915,7 @@ class ClaudeAccountService {
|
||||
boundAccount.isActive === 'true' &&
|
||||
boundAccount.status !== 'error' &&
|
||||
boundAccount.schedulable !== 'false' &&
|
||||
this.isAccountNotExpired(boundAccount)
|
||||
!this.isSubscriptionExpired(boundAccount)
|
||||
) {
|
||||
logger.info(
|
||||
`🎯 Using bound dedicated account: ${boundAccount.name} (${apiKeyData.claudeAccountId}) for API key ${apiKeyData.name}`
|
||||
@@ -937,7 +937,7 @@ class ClaudeAccountService {
|
||||
account.status !== 'error' &&
|
||||
account.schedulable !== 'false' &&
|
||||
(account.accountType === 'shared' || !account.accountType) && // 兼容旧数据
|
||||
this.isAccountNotExpired(account)
|
||||
!this.isSubscriptionExpired(account)
|
||||
)
|
||||
|
||||
// 如果请求的是 Opus 模型,过滤掉 Pro 和 Free 账号
|
||||
|
||||
@@ -86,7 +86,7 @@ class ClaudeConsoleAccountService {
|
||||
|
||||
// ✅ 新增:账户订阅到期时间(业务字段,手动管理)
|
||||
// 注意:Claude Console 没有 OAuth token,因此没有 expiresAt(token过期)
|
||||
subscriptionExpiresAt: options.subscriptionExpiresAt || '',
|
||||
subscriptionExpiresAt: options.subscriptionExpiresAt || null,
|
||||
|
||||
// 限流相关
|
||||
rateLimitedAt: '',
|
||||
|
||||
@@ -738,7 +738,7 @@ class DroidAccountService {
|
||||
expiresAt: normalizedExpiresAt || '', // OAuth Token 过期时间(技术字段,自动刷新)
|
||||
|
||||
// ✅ 新增:账户订阅到期时间(业务字段,手动管理)
|
||||
subscriptionExpiresAt: options.subscriptionExpiresAt || '',
|
||||
subscriptionExpiresAt: options.subscriptionExpiresAt || null,
|
||||
|
||||
proxy: proxy ? JSON.stringify(proxy) : '',
|
||||
isActive: isActive.toString(),
|
||||
@@ -828,6 +828,7 @@ class DroidAccountService {
|
||||
|
||||
// ✅ 前端显示订阅过期时间(业务字段)
|
||||
expiresAt: account.subscriptionExpiresAt || null,
|
||||
platform: account.platform || 'droid',
|
||||
|
||||
apiKeyCount: (() => {
|
||||
const parsedCount = this._parseApiKeyEntries(account.apiKeys).length
|
||||
@@ -1276,8 +1277,8 @@ class DroidAccountService {
|
||||
* @param {Object} account - 账户对象
|
||||
* @returns {boolean} - true: 已过期, false: 未过期
|
||||
*/
|
||||
_isSubscriptionExpired(account) {
|
||||
if (!account.subscriptionExpiresAt || account.subscriptionExpiresAt === '') {
|
||||
isSubscriptionExpired(account) {
|
||||
if (!account.subscriptionExpiresAt) {
|
||||
return false // 未设置视为永不过期
|
||||
}
|
||||
const expiryDate = new Date(account.subscriptionExpiresAt)
|
||||
@@ -1330,7 +1331,7 @@ class DroidAccountService {
|
||||
const status = typeof account.status === 'string' ? account.status.toLowerCase() : ''
|
||||
|
||||
// ✅ 检查账户订阅是否过期
|
||||
if (this._isSubscriptionExpired(account)) {
|
||||
if (this.isSubscriptionExpired(account)) {
|
||||
logger.debug(
|
||||
`⏰ Skipping expired Droid account: ${account.name}, expired at ${account.subscriptionExpiresAt}`
|
||||
)
|
||||
|
||||
@@ -389,7 +389,7 @@ async function createAccount(accountData) {
|
||||
scopes: accountData.geminiOauth ? accountData.scopes || OAUTH_SCOPES.join(' ') : '',
|
||||
|
||||
// ✅ 新增:账户订阅到期时间(业务字段,手动管理)
|
||||
subscriptionExpiresAt: accountData.subscriptionExpiresAt || '',
|
||||
subscriptionExpiresAt: accountData.subscriptionExpiresAt || null,
|
||||
|
||||
// 代理设置
|
||||
proxy: accountData.proxy ? JSON.stringify(accountData.proxy) : '',
|
||||
@@ -814,7 +814,7 @@ function isTokenExpired(account) {
|
||||
* @returns {boolean} - true: 已过期, false: 未过期
|
||||
*/
|
||||
function isSubscriptionExpired(account) {
|
||||
if (!account.subscriptionExpiresAt || account.subscriptionExpiresAt === '') {
|
||||
if (!account.subscriptionExpiresAt) {
|
||||
return false // 未设置视为永不过期
|
||||
}
|
||||
const expiryDate = new Date(account.subscriptionExpiresAt)
|
||||
|
||||
@@ -340,7 +340,7 @@ function isTokenExpired(account) {
|
||||
* @returns {boolean} - true: 已过期, false: 未过期
|
||||
*/
|
||||
function isSubscriptionExpired(account) {
|
||||
if (!account.subscriptionExpiresAt || account.subscriptionExpiresAt === '') {
|
||||
if (!account.subscriptionExpiresAt) {
|
||||
return false // 未设置视为永不过期
|
||||
}
|
||||
const expiryDate = new Date(account.subscriptionExpiresAt)
|
||||
@@ -571,7 +571,7 @@ async function createAccount(accountData) {
|
||||
: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(), // OAuth Token 过期时间(技术字段)
|
||||
|
||||
// ✅ 新增:账户订阅到期时间(业务字段,手动管理)
|
||||
subscriptionExpiresAt: accountData.subscriptionExpiresAt || '',
|
||||
subscriptionExpiresAt: accountData.subscriptionExpiresAt || null,
|
||||
|
||||
// 状态字段
|
||||
isActive: accountData.isActive !== false ? 'true' : 'false',
|
||||
|
||||
@@ -78,7 +78,7 @@ class OpenAIResponsesAccountService {
|
||||
|
||||
// ✅ 新增:账户订阅到期时间(业务字段,手动管理)
|
||||
// 注意:OpenAI-Responses 使用 API Key 认证,没有 OAuth token,因此没有 expiresAt
|
||||
subscriptionExpiresAt: options.subscriptionExpiresAt || '',
|
||||
subscriptionExpiresAt: options.subscriptionExpiresAt || null,
|
||||
|
||||
createdAt: new Date().toISOString(),
|
||||
lastUsedAt: '',
|
||||
@@ -225,6 +225,7 @@ class OpenAIResponsesAccountService {
|
||||
|
||||
// ✅ 前端显示订阅过期时间(业务字段)
|
||||
account.expiresAt = account.subscriptionExpiresAt || null
|
||||
account.platform = account.platform || 'openai-responses'
|
||||
|
||||
accounts.push(account)
|
||||
}
|
||||
@@ -274,6 +275,7 @@ class OpenAIResponsesAccountService {
|
||||
|
||||
// ✅ 前端显示订阅过期时间(业务字段)
|
||||
accountData.expiresAt = accountData.subscriptionExpiresAt || null
|
||||
accountData.platform = accountData.platform || 'openai-responses'
|
||||
|
||||
accounts.push(accountData)
|
||||
}
|
||||
@@ -521,6 +523,25 @@ class OpenAIResponsesAccountService {
|
||||
return { success: true, message: 'Account status reset successfully' }
|
||||
}
|
||||
|
||||
// ⏰ 检查账户订阅是否已过期
|
||||
isSubscriptionExpired(account) {
|
||||
if (!account.subscriptionExpiresAt) {
|
||||
return false // 未设置过期时间,视为永不过期
|
||||
}
|
||||
|
||||
const expiryDate = new Date(account.subscriptionExpiresAt)
|
||||
const now = new Date()
|
||||
|
||||
if (expiryDate <= now) {
|
||||
logger.debug(
|
||||
`⏰ OpenAI-Responses Account ${account.name} (${account.id}) subscription expired at ${account.subscriptionExpiresAt}`
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// 获取限流信息
|
||||
_getRateLimitInfo(accountData) {
|
||||
if (accountData.rateLimitStatus !== 'limited') {
|
||||
|
||||
@@ -545,6 +545,18 @@ class UnifiedClaudeScheduler {
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查订阅是否过期
|
||||
if (account.subscriptionExpiresAt) {
|
||||
const expiryDate = new Date(account.subscriptionExpiresAt)
|
||||
const now = new Date()
|
||||
if (expiryDate <= now) {
|
||||
logger.debug(
|
||||
`⏰ Claude Console account ${account.name} (${account.id}) expired at ${account.subscriptionExpiresAt}`
|
||||
)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 主动触发一次额度检查,确保状态即时生效
|
||||
try {
|
||||
await claudeConsoleAccountService.checkQuotaUsage(account.id)
|
||||
@@ -642,6 +654,18 @@ class UnifiedClaudeScheduler {
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查订阅是否过期
|
||||
if (account.subscriptionExpiresAt) {
|
||||
const expiryDate = new Date(account.subscriptionExpiresAt)
|
||||
const now = new Date()
|
||||
if (expiryDate <= now) {
|
||||
logger.debug(
|
||||
`⏰ CCR account ${account.name} (${account.id}) expired at ${account.subscriptionExpiresAt}`
|
||||
)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否被限流
|
||||
const isRateLimited = await ccrAccountService.isAccountRateLimited(account.id)
|
||||
const isQuotaExceeded = await ccrAccountService.isAccountQuotaExceeded(account.id)
|
||||
@@ -774,6 +798,17 @@ class UnifiedClaudeScheduler {
|
||||
) {
|
||||
return false
|
||||
}
|
||||
// 检查订阅是否过期
|
||||
if (account.subscriptionExpiresAt) {
|
||||
const expiryDate = new Date(account.subscriptionExpiresAt)
|
||||
const now = new Date()
|
||||
if (expiryDate <= now) {
|
||||
logger.debug(
|
||||
`⏰ Claude Console account ${account.name} (${accountId}) expired at ${account.subscriptionExpiresAt} (session check)`
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
// 检查是否超额
|
||||
try {
|
||||
await claudeConsoleAccountService.checkQuotaUsage(accountId)
|
||||
@@ -832,6 +867,17 @@ class UnifiedClaudeScheduler {
|
||||
if (!this._isModelSupportedByAccount(account, 'ccr', requestedModel, 'in session check')) {
|
||||
return false
|
||||
}
|
||||
// 检查订阅是否过期
|
||||
if (account.subscriptionExpiresAt) {
|
||||
const expiryDate = new Date(account.subscriptionExpiresAt)
|
||||
const now = new Date()
|
||||
if (expiryDate <= now) {
|
||||
logger.debug(
|
||||
`⏰ CCR account ${account.name} (${accountId}) expired at ${account.subscriptionExpiresAt} (session check)`
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
// 检查是否超额
|
||||
try {
|
||||
await ccrAccountService.checkQuotaUsage(accountId)
|
||||
@@ -1353,6 +1399,18 @@ class UnifiedClaudeScheduler {
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查订阅是否过期
|
||||
if (account.subscriptionExpiresAt) {
|
||||
const expiryDate = new Date(account.subscriptionExpiresAt)
|
||||
const now = new Date()
|
||||
if (expiryDate <= now) {
|
||||
logger.debug(
|
||||
`⏰ CCR account ${account.name} (${account.id}) expired at ${account.subscriptionExpiresAt}`
|
||||
)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否被限流或超额
|
||||
const isRateLimited = await ccrAccountService.isAccountRateLimited(account.id)
|
||||
const isQuotaExceeded = await ccrAccountService.isAccountQuotaExceeded(account.id)
|
||||
|
||||
@@ -211,6 +211,15 @@ class UnifiedOpenAIScheduler {
|
||||
error.statusCode = 403 // Forbidden - 调度被禁止
|
||||
throw error
|
||||
}
|
||||
|
||||
// ⏰ 检查 OpenAI-Responses 专属账户订阅是否过期
|
||||
if (openaiResponsesAccountService.isSubscriptionExpired(boundAccount)) {
|
||||
const errorMsg = `Dedicated account ${boundAccount.name} subscription has expired`
|
||||
logger.warn(`⚠️ ${errorMsg}`)
|
||||
const error = new Error(errorMsg)
|
||||
error.statusCode = 403 // Forbidden - 订阅已过期
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 专属账户:可选的模型检查(只有明确配置了supportedModels且不为空才检查)
|
||||
@@ -461,6 +470,14 @@ class UnifiedOpenAIScheduler {
|
||||
}
|
||||
}
|
||||
|
||||
// ⏰ 检查订阅是否过期
|
||||
if (openaiResponsesAccountService.isSubscriptionExpired(account)) {
|
||||
logger.debug(
|
||||
`⏭️ Skipping OpenAI-Responses account ${account.name} - subscription expired`
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
// OpenAI-Responses 账户默认支持所有模型
|
||||
// 因为它们是第三方兼容 API,模型支持由第三方决定
|
||||
|
||||
@@ -536,6 +553,11 @@ class UnifiedOpenAIScheduler {
|
||||
logger.info(`🚫 OpenAI-Responses account ${accountId} is not schedulable`)
|
||||
return false
|
||||
}
|
||||
// ⏰ 检查订阅是否过期
|
||||
if (openaiResponsesAccountService.isSubscriptionExpired(account)) {
|
||||
logger.info(`🚫 OpenAI-Responses account ${accountId} subscription expired`)
|
||||
return false
|
||||
}
|
||||
// 检查并清除过期的限流状态
|
||||
const isRateLimitCleared =
|
||||
await openaiResponsesAccountService.checkAndClearRateLimit(accountId)
|
||||
|
||||
Reference in New Issue
Block a user