mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
fix: 修复Openai-api账户分组调度设置问题
This commit is contained in:
@@ -31,8 +31,9 @@ router.get('/openai-responses-accounts', authenticateAdmin, async (req, res) =>
|
|||||||
// 根据分组ID筛选
|
// 根据分组ID筛选
|
||||||
if (groupId) {
|
if (groupId) {
|
||||||
const group = await accountGroupService.getGroup(groupId)
|
const group = await accountGroupService.getGroup(groupId)
|
||||||
if (group && group.platform === 'openai' && group.memberIds && group.memberIds.length > 0) {
|
if (group && group.platform === 'openai') {
|
||||||
accounts = accounts.filter((account) => group.memberIds.includes(account.id))
|
const groupMembers = await accountGroupService.getGroupMembers(groupId)
|
||||||
|
accounts = accounts.filter((account) => groupMembers.includes(account.id))
|
||||||
} else {
|
} else {
|
||||||
accounts = []
|
accounts = []
|
||||||
}
|
}
|
||||||
@@ -94,9 +95,13 @@ router.get('/openai-responses-accounts', authenticateAdmin, async (req, res) =>
|
|||||||
logger.info(`OpenAI-Responses account ${account.id} has ${boundCount} bound API keys`)
|
logger.info(`OpenAI-Responses account ${account.id} has ${boundCount} bound API keys`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取分组信息
|
||||||
|
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
||||||
|
|
||||||
const formattedAccount = formatAccountExpiry(account)
|
const formattedAccount = formatAccountExpiry(account)
|
||||||
return {
|
return {
|
||||||
...formattedAccount,
|
...formattedAccount,
|
||||||
|
groupInfos,
|
||||||
boundApiKeysCount: boundCount,
|
boundApiKeysCount: boundCount,
|
||||||
usage: {
|
usage: {
|
||||||
daily: usageStats.daily,
|
daily: usageStats.daily,
|
||||||
@@ -109,6 +114,7 @@ router.get('/openai-responses-accounts', authenticateAdmin, async (req, res) =>
|
|||||||
const formattedAccount = formatAccountExpiry(account)
|
const formattedAccount = formatAccountExpiry(account)
|
||||||
return {
|
return {
|
||||||
...formattedAccount,
|
...formattedAccount,
|
||||||
|
groupInfos: [],
|
||||||
boundApiKeysCount: 0,
|
boundApiKeysCount: 0,
|
||||||
usage: {
|
usage: {
|
||||||
daily: { requests: 0, tokens: 0, allTokens: 0 },
|
daily: { requests: 0, tokens: 0, allTokens: 0 },
|
||||||
@@ -130,7 +136,39 @@ router.get('/openai-responses-accounts', authenticateAdmin, async (req, res) =>
|
|||||||
// 创建 OpenAI-Responses 账户
|
// 创建 OpenAI-Responses 账户
|
||||||
router.post('/openai-responses-accounts', authenticateAdmin, async (req, res) => {
|
router.post('/openai-responses-accounts', authenticateAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const account = await openaiResponsesAccountService.createAccount(req.body)
|
const accountData = req.body
|
||||||
|
|
||||||
|
// 验证分组类型
|
||||||
|
if (
|
||||||
|
accountData.accountType === 'group' &&
|
||||||
|
!accountData.groupId &&
|
||||||
|
(!accountData.groupIds || accountData.groupIds.length === 0)
|
||||||
|
) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Group ID is required for group type accounts'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const account = await openaiResponsesAccountService.createAccount(accountData)
|
||||||
|
|
||||||
|
// 如果是分组类型,处理分组绑定
|
||||||
|
if (accountData.accountType === 'group') {
|
||||||
|
if (accountData.groupIds && accountData.groupIds.length > 0) {
|
||||||
|
// 多分组模式
|
||||||
|
await accountGroupService.setAccountGroups(account.id, accountData.groupIds, 'openai')
|
||||||
|
logger.info(
|
||||||
|
`🏢 Added OpenAI-Responses account ${account.id} to groups: ${accountData.groupIds.join(', ')}`
|
||||||
|
)
|
||||||
|
} else if (accountData.groupId) {
|
||||||
|
// 单分组模式(向后兼容)
|
||||||
|
await accountGroupService.addAccountToGroup(account.id, accountData.groupId, 'openai')
|
||||||
|
logger.info(
|
||||||
|
`🏢 Added OpenAI-Responses account ${account.id} to group: ${accountData.groupId}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const formattedAccount = formatAccountExpiry(account)
|
const formattedAccount = formatAccountExpiry(account)
|
||||||
res.json({ success: true, data: formattedAccount })
|
res.json({ success: true, data: formattedAccount })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -148,6 +186,15 @@ router.put('/openai-responses-accounts/:id', authenticateAdmin, async (req, res)
|
|||||||
const { id } = req.params
|
const { id } = req.params
|
||||||
const updates = req.body
|
const updates = req.body
|
||||||
|
|
||||||
|
// 获取当前账户信息
|
||||||
|
const currentAccount = await openaiResponsesAccountService.getAccount(id)
|
||||||
|
if (!currentAccount) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Account not found'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ✅ 【新增】映射字段名:前端的 expiresAt -> 后端的 subscriptionExpiresAt
|
// ✅ 【新增】映射字段名:前端的 expiresAt -> 后端的 subscriptionExpiresAt
|
||||||
const mappedUpdates = mapExpiryField(updates, 'OpenAI-Responses', id)
|
const mappedUpdates = mapExpiryField(updates, 'OpenAI-Responses', id)
|
||||||
|
|
||||||
@@ -163,12 +210,48 @@ router.put('/openai-responses-accounts/:id', authenticateAdmin, async (req, res)
|
|||||||
mappedUpdates.priority = priority.toString()
|
mappedUpdates.priority = priority.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理分组变更
|
||||||
|
if (mappedUpdates.accountType !== undefined) {
|
||||||
|
// 如果之前是分组类型,需要从所有分组中移除
|
||||||
|
if (currentAccount.accountType === 'group') {
|
||||||
|
const oldGroups = await accountGroupService.getAccountGroups(id)
|
||||||
|
for (const oldGroup of oldGroups) {
|
||||||
|
await accountGroupService.removeAccountFromGroup(id, oldGroup.id)
|
||||||
|
}
|
||||||
|
logger.info(`📤 Removed OpenAI-Responses account ${id} from all groups`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果新类型是分组,处理多分组支持
|
||||||
|
if (mappedUpdates.accountType === 'group') {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(mappedUpdates, 'groupIds')) {
|
||||||
|
if (mappedUpdates.groupIds && mappedUpdates.groupIds.length > 0) {
|
||||||
|
// 设置新的多分组
|
||||||
|
await accountGroupService.setAccountGroups(id, mappedUpdates.groupIds, 'openai')
|
||||||
|
logger.info(
|
||||||
|
`📥 Added OpenAI-Responses account ${id} to groups: ${mappedUpdates.groupIds.join(', ')}`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// groupIds 为空数组,从所有分组中移除
|
||||||
|
await accountGroupService.removeAccountFromAllGroups(id)
|
||||||
|
logger.info(
|
||||||
|
`📤 Removed OpenAI-Responses account ${id} from all groups (empty groupIds)`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (mappedUpdates.groupId) {
|
||||||
|
// 向后兼容:仅当没有 groupIds 但有 groupId 时使用单分组逻辑
|
||||||
|
await accountGroupService.addAccountToGroup(id, mappedUpdates.groupId, 'openai')
|
||||||
|
logger.info(`📥 Added OpenAI-Responses account ${id} to group: ${mappedUpdates.groupId}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const result = await openaiResponsesAccountService.updateAccount(id, mappedUpdates)
|
const result = await openaiResponsesAccountService.updateAccount(id, mappedUpdates)
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
return res.status(400).json(result)
|
return res.status(400).json(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.success(`📝 Admin updated OpenAI-Responses account: ${id}`)
|
||||||
res.json({ success: true, ...result })
|
res.json({ success: true, ...result })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to update OpenAI-Responses account:', error)
|
logger.error('Failed to update OpenAI-Responses account:', error)
|
||||||
@@ -195,13 +278,10 @@ router.delete('/openai-responses-accounts/:id', authenticateAdmin, async (req, r
|
|||||||
// 自动解绑所有绑定的 API Keys
|
// 自动解绑所有绑定的 API Keys
|
||||||
const unboundCount = await apiKeyService.unbindAccountFromAllKeys(id, 'openai-responses')
|
const unboundCount = await apiKeyService.unbindAccountFromAllKeys(id, 'openai-responses')
|
||||||
|
|
||||||
// 检查是否在分组中
|
// 从所有分组中移除此账户
|
||||||
const groups = await accountGroupService.getAllGroups()
|
if (account.accountType === 'group') {
|
||||||
for (const group of groups) {
|
await accountGroupService.removeAccountFromAllGroups(id)
|
||||||
if (group.platform === 'openai' && group.memberIds && group.memberIds.includes(id)) {
|
logger.info(`Removed OpenAI-Responses account ${id} from all groups`)
|
||||||
await accountGroupService.removeMemberFromGroup(group.id, id)
|
|
||||||
logger.info(`Removed OpenAI-Responses account ${id} from group ${group.id}`)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await openaiResponsesAccountService.deleteAccount(id)
|
const result = await openaiResponsesAccountService.deleteAccount(id)
|
||||||
|
|||||||
@@ -834,11 +834,24 @@ class UnifiedOpenAIScheduler {
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取可用的分组成员账户
|
// 获取可用的分组成员账户(支持 OpenAI 和 OpenAI-Responses 两种类型)
|
||||||
const availableAccounts = []
|
const availableAccounts = []
|
||||||
for (const memberId of memberIds) {
|
for (const memberId of memberIds) {
|
||||||
const account = await openaiAccountService.getAccount(memberId)
|
// 首先尝试从 OpenAI 账户服务获取
|
||||||
if (account && account.isActive && account.status !== 'error') {
|
let account = await openaiAccountService.getAccount(memberId)
|
||||||
|
let accountType = 'openai'
|
||||||
|
|
||||||
|
// 如果 OpenAI 账户不存在,尝试从 OpenAI-Responses 账户服务获取
|
||||||
|
if (!account) {
|
||||||
|
account = await openaiResponsesAccountService.getAccount(memberId)
|
||||||
|
accountType = 'openai-responses'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
account &&
|
||||||
|
(account.isActive === true || account.isActive === 'true') &&
|
||||||
|
account.status !== 'error'
|
||||||
|
) {
|
||||||
const readiness = await this._ensureAccountReadyForScheduling(account, account.id, {
|
const readiness = await this._ensureAccountReadyForScheduling(account, account.id, {
|
||||||
sanitized: false
|
sanitized: false
|
||||||
})
|
})
|
||||||
@@ -846,23 +859,25 @@ class UnifiedOpenAIScheduler {
|
|||||||
if (!readiness.canUse) {
|
if (!readiness.canUse) {
|
||||||
if (readiness.reason === 'rate_limited') {
|
if (readiness.reason === 'rate_limited') {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`⏭️ Skipping group member OpenAI account ${account.name} - still rate limited`
|
`⏭️ Skipping group member ${accountType} account ${account.name} - still rate limited`
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`⏭️ Skipping group member OpenAI account ${account.name} - not schedulable`
|
`⏭️ Skipping group member ${accountType} account ${account.name} - not schedulable`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查token是否过期
|
// 检查token是否过期(仅对 OpenAI OAuth 账户检查)
|
||||||
const isExpired = openaiAccountService.isTokenExpired(account)
|
if (accountType === 'openai') {
|
||||||
if (isExpired && !account.refreshToken) {
|
const isExpired = openaiAccountService.isTokenExpired(account)
|
||||||
logger.warn(
|
if (isExpired && !account.refreshToken) {
|
||||||
`⚠️ Group member OpenAI account ${account.name} token expired and no refresh token available`
|
logger.warn(
|
||||||
)
|
`⚠️ Group member OpenAI account ${account.name} token expired and no refresh token available`
|
||||||
continue
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查模型支持(仅在明确设置了supportedModels且不为空时才检查)
|
// 检查模型支持(仅在明确设置了supportedModels且不为空时才检查)
|
||||||
@@ -871,17 +886,17 @@ class UnifiedOpenAIScheduler {
|
|||||||
const modelSupported = account.supportedModels.includes(requestedModel)
|
const modelSupported = account.supportedModels.includes(requestedModel)
|
||||||
if (!modelSupported) {
|
if (!modelSupported) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`⏭️ Skipping group member OpenAI account ${account.name} - doesn't support model ${requestedModel}`
|
`⏭️ Skipping group member ${accountType} account ${account.name} - doesn't support model ${requestedModel}`
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否被限流
|
// 添加到可用账户列表
|
||||||
availableAccounts.push({
|
availableAccounts.push({
|
||||||
...account,
|
...account,
|
||||||
accountId: account.id,
|
accountId: account.id,
|
||||||
accountType: 'openai',
|
accountType,
|
||||||
priority: parseInt(account.priority) || 50,
|
priority: parseInt(account.priority) || 50,
|
||||||
lastUsedAt: account.lastUsedAt || '0'
|
lastUsedAt: account.lastUsedAt || '0'
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user