feat: 支持claude单账户开启串行队列

This commit is contained in:
shaw
2025-12-19 22:29:57 +08:00
parent fa2fc2fb16
commit 638d2ff189
5 changed files with 97 additions and 12 deletions

View File

@@ -584,7 +584,8 @@ router.post('/claude-accounts', authenticateAdmin, async (req, res) => {
useUnifiedClientId,
unifiedClientId,
expiresAt,
extInfo
extInfo,
maxConcurrency
} = req.body
if (!name) {
@@ -629,7 +630,8 @@ router.post('/claude-accounts', authenticateAdmin, async (req, res) => {
useUnifiedClientId: useUnifiedClientId === true, // 默认为false
unifiedClientId: unifiedClientId || '', // 统一的客户端标识
expiresAt: expiresAt || null, // 账户订阅到期时间
extInfo: extInfo || null
extInfo: extInfo || null,
maxConcurrency: maxConcurrency || 0 // 账户级串行队列0=使用全局配置,>0=强制启用
})
// 如果是分组类型,将账户添加到分组

View File

@@ -91,7 +91,8 @@ class ClaudeAccountService {
useUnifiedClientId = false, // 是否使用统一的客户端标识
unifiedClientId = '', // 统一的客户端标识
expiresAt = null, // 账户订阅到期时间
extInfo = null // 额外扩展信息
extInfo = null, // 额外扩展信息
maxConcurrency = 0 // 账户级用户消息串行队列0=使用全局配置,>0=强制启用串行
} = options
const accountId = uuidv4()
@@ -136,7 +137,9 @@ class ClaudeAccountService {
// 账户订阅到期时间
subscriptionExpiresAt: expiresAt || '',
// 扩展信息
extInfo: normalizedExtInfo ? JSON.stringify(normalizedExtInfo) : ''
extInfo: normalizedExtInfo ? JSON.stringify(normalizedExtInfo) : '',
// 账户级用户消息串行队列限制
maxConcurrency: maxConcurrency.toString()
}
} else {
// 兼容旧格式
@@ -168,7 +171,9 @@ class ClaudeAccountService {
// 账户订阅到期时间
subscriptionExpiresAt: expiresAt || '',
// 扩展信息
extInfo: normalizedExtInfo ? JSON.stringify(normalizedExtInfo) : ''
extInfo: normalizedExtInfo ? JSON.stringify(normalizedExtInfo) : '',
// 账户级用户消息串行队列限制
maxConcurrency: maxConcurrency.toString()
}
}
@@ -574,7 +579,9 @@ class ClaudeAccountService {
// 添加停止原因
stoppedReason: account.stoppedReason || null,
// 扩展信息
extInfo: parsedExtInfo
extInfo: parsedExtInfo,
// 账户级用户消息串行队列限制
maxConcurrency: parseInt(account.maxConcurrency || '0', 10)
}
})
)
@@ -666,7 +673,8 @@ class ClaudeAccountService {
'useUnifiedClientId',
'unifiedClientId',
'subscriptionExpiresAt',
'extInfo'
'extInfo',
'maxConcurrency'
]
const updatedData = { ...accountData }
let shouldClearAutoStopFields = false
@@ -681,7 +689,7 @@ class ClaudeAccountService {
updatedData[field] = this._encryptSensitiveData(value)
} else if (field === 'proxy') {
updatedData[field] = value ? JSON.stringify(value) : ''
} else if (field === 'priority') {
} else if (field === 'priority' || field === 'maxConcurrency') {
updatedData[field] = value.toString()
} else if (field === 'subscriptionInfo') {
// 处理订阅信息更新

View File

@@ -210,7 +210,17 @@ class ClaudeRelayService {
logger.error('❌ accountId missing for queue lock in relayRequest')
throw new Error('accountId missing for queue lock')
}
const queueResult = await userMessageQueueService.acquireQueueLock(accountId)
// 获取账户信息以检查账户级串行队列配置
const accountForQueue = await claudeAccountService.getAccount(accountId)
const accountConfig = accountForQueue
? { maxConcurrency: parseInt(accountForQueue.maxConcurrency || '0', 10) }
: null
const queueResult = await userMessageQueueService.acquireQueueLock(
accountId,
null,
null,
accountConfig
)
if (!queueResult.acquired && !queueResult.skipped) {
// 区分 Redis 后端错误和队列超时
const isBackendError = queueResult.error === 'queue_backend_error'
@@ -1314,7 +1324,17 @@ class ClaudeRelayService {
logger.error('❌ accountId missing for queue lock in relayStreamRequestWithUsageCapture')
throw new Error('accountId missing for queue lock')
}
const queueResult = await userMessageQueueService.acquireQueueLock(accountId)
// 获取账户信息以检查账户级串行队列配置
const accountForQueue = await claudeAccountService.getAccount(accountId)
const accountConfig = accountForQueue
? { maxConcurrency: parseInt(accountForQueue.maxConcurrency || '0', 10) }
: null
const queueResult = await userMessageQueueService.acquireQueueLock(
accountId,
null,
null,
accountConfig
)
if (!queueResult.acquired && !queueResult.skipped) {
// 区分 Redis 后端错误和队列超时
const isBackendError = queueResult.error === 'queue_backend_error'

View File

@@ -121,12 +121,23 @@ class UserMessageQueueService {
* @param {string} accountId - 账户ID
* @param {string} requestId - 请求ID可选会自动生成
* @param {number} timeoutMs - 超时时间(可选,使用配置默认值)
* @param {Object} accountConfig - 账户级配置(可选),优先级高于全局配置
* @param {number} accountConfig.maxConcurrency - 账户级串行队列开关:>0启用=0使用全局配置
* @returns {Promise<{acquired: boolean, requestId: string, error?: string}>}
*/
async acquireQueueLock(accountId, requestId = null, timeoutMs = null) {
async acquireQueueLock(accountId, requestId = null, timeoutMs = null, accountConfig = null) {
const cfg = await this.getConfig()
if (!cfg.enabled) {
// 账户级配置优先maxConcurrency > 0 时强制启用,忽略全局开关
let queueEnabled = cfg.enabled
if (accountConfig && accountConfig.maxConcurrency > 0) {
queueEnabled = true
logger.debug(
`📬 User message queue: account-level queue enabled for account ${accountId} (maxConcurrency=${accountConfig.maxConcurrency})`
)
}
if (!queueEnabled) {
return { acquired: true, requestId: requestId || uuidv4(), skipped: true }
}