mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-22 16:43:35 +00:00
feat: 完善账户多分组功能和Azure OpenAI支持
主要功能: - 实现账户多分组调度功能完整支持 - 修复Azure OpenAI账户优先级显示问题(前端条件判断缺失) - 修复未分组筛选功能失效(API参数处理) - 修复Azure OpenAI账户创建错误调用Gemini API的问题 - 完善各平台分组信息支持和使用统计显示 - 统一删除账户时的分组清理逻辑 - 添加前端请求参数处理支持 技术改进: - 前端支持多平台账户请求构造 - 后端统一groupInfos返回格式 - API客户端完善查询参数处理 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,379 +0,0 @@
|
||||
/**
|
||||
* 多分组功能测试脚本
|
||||
* 测试一个账户可以属于多个分组的功能
|
||||
*/
|
||||
|
||||
require('dotenv').config()
|
||||
const redis = require('../src/models/redis')
|
||||
const accountGroupService = require('../src/services/accountGroupService')
|
||||
const claudeAccountService = require('../src/services/claudeAccountService')
|
||||
|
||||
// 测试配置
|
||||
const TEST_PREFIX = 'multi_group_test_'
|
||||
const CLEANUP_ON_FINISH = true
|
||||
|
||||
// 测试数据存储
|
||||
const testData = {
|
||||
groups: [],
|
||||
accounts: []
|
||||
}
|
||||
|
||||
// 颜色输出
|
||||
const colors = {
|
||||
green: '\x1b[32m',
|
||||
red: '\x1b[31m',
|
||||
yellow: '\x1b[33m',
|
||||
blue: '\x1b[34m',
|
||||
reset: '\x1b[0m'
|
||||
}
|
||||
|
||||
function log(message, type = 'info') {
|
||||
const color =
|
||||
{
|
||||
success: colors.green,
|
||||
error: colors.red,
|
||||
warning: colors.yellow,
|
||||
info: colors.blue
|
||||
}[type] || colors.reset
|
||||
|
||||
console.log(`${color}${message}${colors.reset}`)
|
||||
}
|
||||
|
||||
async function sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
// 清理测试数据
|
||||
async function cleanup() {
|
||||
log('\n🧹 清理测试数据...', 'info')
|
||||
|
||||
// 删除测试账户
|
||||
for (const account of testData.accounts) {
|
||||
try {
|
||||
await claudeAccountService.deleteAccount(account.id)
|
||||
log(`✅ 删除测试账户: ${account.name}`, 'success')
|
||||
} catch (error) {
|
||||
log(`❌ 删除账户失败: ${error.message}`, 'error')
|
||||
}
|
||||
}
|
||||
|
||||
// 删除测试分组
|
||||
for (const group of testData.groups) {
|
||||
try {
|
||||
// 先移除所有成员
|
||||
const members = await accountGroupService.getGroupMembers(group.id)
|
||||
for (const memberId of members) {
|
||||
await accountGroupService.removeAccountFromGroup(memberId, group.id)
|
||||
}
|
||||
|
||||
await accountGroupService.deleteGroup(group.id)
|
||||
log(`✅ 删除测试分组: ${group.name}`, 'success')
|
||||
} catch (error) {
|
||||
log(`❌ 删除分组失败: ${error.message}`, 'error')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 测试1: 创建测试数据
|
||||
async function test1_createTestData() {
|
||||
log('\n📝 测试1: 创建测试数据', 'info')
|
||||
|
||||
try {
|
||||
// 创建3个测试分组
|
||||
const group1 = await accountGroupService.createGroup({
|
||||
name: `${TEST_PREFIX}高优先级组`,
|
||||
platform: 'claude',
|
||||
description: '高优先级账户分组'
|
||||
})
|
||||
testData.groups.push(group1)
|
||||
log(`✅ 创建分组1: ${group1.name}`, 'success')
|
||||
|
||||
const group2 = await accountGroupService.createGroup({
|
||||
name: `${TEST_PREFIX}备用组`,
|
||||
platform: 'claude',
|
||||
description: '备用账户分组'
|
||||
})
|
||||
testData.groups.push(group2)
|
||||
log(`✅ 创建分组2: ${group2.name}`, 'success')
|
||||
|
||||
const group3 = await accountGroupService.createGroup({
|
||||
name: `${TEST_PREFIX}专用组`,
|
||||
platform: 'claude',
|
||||
description: '专用账户分组'
|
||||
})
|
||||
testData.groups.push(group3)
|
||||
log(`✅ 创建分组3: ${group3.name}`, 'success')
|
||||
|
||||
// 创建测试账户
|
||||
const account1 = await claudeAccountService.createAccount({
|
||||
name: `${TEST_PREFIX}测试账户1`,
|
||||
email: 'test1@example.com',
|
||||
refreshToken: 'test_refresh_token_1',
|
||||
accountType: 'group'
|
||||
})
|
||||
testData.accounts.push(account1)
|
||||
log(`✅ 创建测试账户1: ${account1.name}`, 'success')
|
||||
|
||||
const account2 = await claudeAccountService.createAccount({
|
||||
name: `${TEST_PREFIX}测试账户2`,
|
||||
email: 'test2@example.com',
|
||||
refreshToken: 'test_refresh_token_2',
|
||||
accountType: 'group'
|
||||
})
|
||||
testData.accounts.push(account2)
|
||||
log(`✅ 创建测试账户2: ${account2.name}`, 'success')
|
||||
|
||||
log(`✅ 测试数据创建完成: 3个分组, 2个账户`, 'success')
|
||||
} catch (error) {
|
||||
log(`❌ 测试1失败: ${error.message}`, 'error')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 测试2: 账户加入多个分组
|
||||
async function test2_addAccountToMultipleGroups() {
|
||||
log('\n📝 测试2: 账户加入多个分组', 'info')
|
||||
|
||||
try {
|
||||
const [group1, group2, group3] = testData.groups
|
||||
const [account1, account2] = testData.accounts
|
||||
|
||||
// 账户1加入分组1和分组2
|
||||
await accountGroupService.addAccountToGroup(account1.id, group1.id, 'claude')
|
||||
log(`✅ 账户1加入分组1: ${group1.name}`, 'success')
|
||||
|
||||
await accountGroupService.addAccountToGroup(account1.id, group2.id, 'claude')
|
||||
log(`✅ 账户1加入分组2: ${group2.name}`, 'success')
|
||||
|
||||
// 账户2加入分组2和分组3
|
||||
await accountGroupService.addAccountToGroup(account2.id, group2.id, 'claude')
|
||||
log(`✅ 账户2加入分组2: ${group2.name}`, 'success')
|
||||
|
||||
await accountGroupService.addAccountToGroup(account2.id, group3.id, 'claude')
|
||||
log(`✅ 账户2加入分组3: ${group3.name}`, 'success')
|
||||
|
||||
log(`✅ 多分组关系建立完成`, 'success')
|
||||
} catch (error) {
|
||||
log(`❌ 测试2失败: ${error.message}`, 'error')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 测试3: 验证多分组关系
|
||||
async function test3_verifyMultiGroupRelationships() {
|
||||
log('\n📝 测试3: 验证多分组关系', 'info')
|
||||
|
||||
try {
|
||||
const [group1, group2, group3] = testData.groups
|
||||
const [account1, account2] = testData.accounts
|
||||
|
||||
// 验证账户1的分组关系
|
||||
const account1Groups = await accountGroupService.getAccountGroup(account1.id)
|
||||
log(`📊 账户1所属分组数量: ${account1Groups.length}`, 'info')
|
||||
|
||||
const account1GroupNames = account1Groups.map((g) => g.name).sort()
|
||||
const expectedAccount1Groups = [group1.name, group2.name].sort()
|
||||
|
||||
if (JSON.stringify(account1GroupNames) === JSON.stringify(expectedAccount1Groups)) {
|
||||
log(`✅ 账户1分组关系正确: [${account1GroupNames.join(', ')}]`, 'success')
|
||||
} else {
|
||||
throw new Error(
|
||||
`账户1分组关系错误,期望: [${expectedAccount1Groups.join(', ')}], 实际: [${account1GroupNames.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// 验证账户2的分组关系
|
||||
const account2Groups = await accountGroupService.getAccountGroup(account2.id)
|
||||
log(`📊 账户2所属分组数量: ${account2Groups.length}`, 'info')
|
||||
|
||||
const account2GroupNames = account2Groups.map((g) => g.name).sort()
|
||||
const expectedAccount2Groups = [group2.name, group3.name].sort()
|
||||
|
||||
if (JSON.stringify(account2GroupNames) === JSON.stringify(expectedAccount2Groups)) {
|
||||
log(`✅ 账户2分组关系正确: [${account2GroupNames.join(', ')}]`, 'success')
|
||||
} else {
|
||||
throw new Error(
|
||||
`账户2分组关系错误,期望: [${expectedAccount2Groups.join(', ')}], 实际: [${account2GroupNames.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
log(`✅ 多分组关系验证通过`, 'success')
|
||||
} catch (error) {
|
||||
log(`❌ 测试3失败: ${error.message}`, 'error')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 测试4: 验证分组成员关系
|
||||
async function test4_verifyGroupMemberships() {
|
||||
log('\n📝 测试4: 验证分组成员关系', 'info')
|
||||
|
||||
try {
|
||||
const [group1, group2, group3] = testData.groups
|
||||
const [account1, account2] = testData.accounts
|
||||
|
||||
// 验证分组1的成员
|
||||
const group1Members = await accountGroupService.getGroupMembers(group1.id)
|
||||
if (group1Members.includes(account1.id) && group1Members.length === 1) {
|
||||
log(`✅ 分组1成员正确: [${account1.name}]`, 'success')
|
||||
} else {
|
||||
throw new Error(`分组1成员错误,期望: [${account1.id}], 实际: [${group1Members.join(', ')}]`)
|
||||
}
|
||||
|
||||
// 验证分组2的成员(应该包含两个账户)
|
||||
const group2Members = await accountGroupService.getGroupMembers(group2.id)
|
||||
const expectedGroup2Members = [account1.id, account2.id].sort()
|
||||
const actualGroup2Members = group2Members.sort()
|
||||
|
||||
if (JSON.stringify(actualGroup2Members) === JSON.stringify(expectedGroup2Members)) {
|
||||
log(`✅ 分组2成员正确: [${account1.name}, ${account2.name}]`, 'success')
|
||||
} else {
|
||||
throw new Error(
|
||||
`分组2成员错误,期望: [${expectedGroup2Members.join(', ')}], 实际: [${actualGroup2Members.join(', ')}]`
|
||||
)
|
||||
}
|
||||
|
||||
// 验证分组3的成员
|
||||
const group3Members = await accountGroupService.getGroupMembers(group3.id)
|
||||
if (group3Members.includes(account2.id) && group3Members.length === 1) {
|
||||
log(`✅ 分组3成员正确: [${account2.name}]`, 'success')
|
||||
} else {
|
||||
throw new Error(`分组3成员错误,期望: [${account2.id}], 实际: [${group3Members.join(', ')}]`)
|
||||
}
|
||||
|
||||
log(`✅ 分组成员关系验证通过`, 'success')
|
||||
} catch (error) {
|
||||
log(`❌ 测试4失败: ${error.message}`, 'error')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 测试5: 从部分分组中移除账户
|
||||
async function test5_removeFromPartialGroups() {
|
||||
log('\n📝 测试5: 从部分分组中移除账户', 'info')
|
||||
|
||||
try {
|
||||
const [group1, group2] = testData.groups
|
||||
const [account1] = testData.accounts
|
||||
|
||||
// 将账户1从分组1中移除(但仍在分组2中)
|
||||
await accountGroupService.removeAccountFromGroup(account1.id, group1.id)
|
||||
log(`✅ 从分组1中移除账户1`, 'success')
|
||||
|
||||
// 验证账户1现在只属于分组2
|
||||
const account1Groups = await accountGroupService.getAccountGroup(account1.id)
|
||||
if (account1Groups.length === 1 && account1Groups[0].id === group2.id) {
|
||||
log(`✅ 账户1现在只属于分组2: ${account1Groups[0].name}`, 'success')
|
||||
} else {
|
||||
const groupNames = account1Groups.map((g) => g.name)
|
||||
throw new Error(`账户1分组状态错误,期望只在分组2中,实际: [${groupNames.join(', ')}]`)
|
||||
}
|
||||
|
||||
// 验证分组1现在为空
|
||||
const group1Members = await accountGroupService.getGroupMembers(group1.id)
|
||||
if (group1Members.length === 0) {
|
||||
log(`✅ 分组1现在为空`, 'success')
|
||||
} else {
|
||||
throw new Error(`分组1应该为空,但还有成员: [${group1Members.join(', ')}]`)
|
||||
}
|
||||
|
||||
// 验证分组2仍有两个成员
|
||||
const group2Members = await accountGroupService.getGroupMembers(group2.id)
|
||||
if (group2Members.length === 2) {
|
||||
log(`✅ 分组2仍有两个成员`, 'success')
|
||||
} else {
|
||||
throw new Error(`分组2应该有2个成员,实际: ${group2Members.length}个`)
|
||||
}
|
||||
|
||||
log(`✅ 部分移除测试通过`, 'success')
|
||||
} catch (error) {
|
||||
log(`❌ 测试5失败: ${error.message}`, 'error')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 测试6: 账户完全移除时的分组清理
|
||||
async function test6_accountDeletionGroupCleanup() {
|
||||
log('\n📝 测试6: 账户删除时的分组清理', 'info')
|
||||
|
||||
try {
|
||||
const [, group2, group3] = testData.groups // 跳过第一个元素
|
||||
const [account1, account2] = testData.accounts
|
||||
|
||||
// 记录删除前的状态
|
||||
const beforeGroup2Members = await accountGroupService.getGroupMembers(group2.id)
|
||||
const beforeGroup3Members = await accountGroupService.getGroupMembers(group3.id)
|
||||
|
||||
log(`📊 删除前分组2成员数: ${beforeGroup2Members.length}`, 'info')
|
||||
log(`📊 删除前分组3成员数: ${beforeGroup3Members.length}`, 'info')
|
||||
|
||||
// 删除账户2(这应该会触发从所有分组中移除的逻辑)
|
||||
await claudeAccountService.deleteAccount(account2.id)
|
||||
log(`✅ 删除账户2: ${account2.name}`, 'success')
|
||||
|
||||
// 从测试数据中移除,避免cleanup时重复删除
|
||||
testData.accounts = testData.accounts.filter((acc) => acc.id !== account2.id)
|
||||
|
||||
// 等待一下确保删除操作完成
|
||||
await sleep(500)
|
||||
|
||||
// 验证分组2现在只有账户1
|
||||
const afterGroup2Members = await accountGroupService.getGroupMembers(group2.id)
|
||||
if (afterGroup2Members.length === 1 && afterGroup2Members[0] === account1.id) {
|
||||
log(`✅ 分组2现在只有账户1`, 'success')
|
||||
} else {
|
||||
throw new Error(`分组2成员状态错误,期望只有账户1,实际: [${afterGroup2Members.join(', ')}]`)
|
||||
}
|
||||
|
||||
// 验证分组3现在为空
|
||||
const afterGroup3Members = await accountGroupService.getGroupMembers(group3.id)
|
||||
if (afterGroup3Members.length === 0) {
|
||||
log(`✅ 分组3现在为空`, 'success')
|
||||
} else {
|
||||
throw new Error(`分组3应该为空,但还有成员: [${afterGroup3Members.join(', ')}]`)
|
||||
}
|
||||
|
||||
log(`✅ 账户删除的分组清理测试通过`, 'success')
|
||||
} catch (error) {
|
||||
log(`❌ 测试6失败: ${error.message}`, 'error')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 主测试函数
|
||||
async function runTests() {
|
||||
log('\n🚀 开始多分组功能测试\n', 'info')
|
||||
|
||||
try {
|
||||
// 连接Redis
|
||||
await redis.connect()
|
||||
log('✅ Redis连接成功', 'success')
|
||||
|
||||
// 执行测试
|
||||
await test1_createTestData()
|
||||
await test2_addAccountToMultipleGroups()
|
||||
await test3_verifyMultiGroupRelationships()
|
||||
await test4_verifyGroupMemberships()
|
||||
await test5_removeFromPartialGroups()
|
||||
await test6_accountDeletionGroupCleanup()
|
||||
|
||||
log('\n🎉 所有测试通过!多分组功能工作正常', 'success')
|
||||
} catch (error) {
|
||||
log(`\n❌ 测试失败: ${error.message}`, 'error')
|
||||
console.error(error)
|
||||
} finally {
|
||||
// 清理测试数据
|
||||
if (CLEANUP_ON_FINISH) {
|
||||
await cleanup()
|
||||
} else {
|
||||
log('\n⚠️ 测试数据未清理,请手动清理', 'warning')
|
||||
}
|
||||
|
||||
// 关闭Redis连接
|
||||
await redis.disconnect()
|
||||
process.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// 运行测试
|
||||
runTests()
|
||||
@@ -1183,7 +1183,34 @@ router.get('/account-groups/:groupId/members', authenticateAdmin, async (req, re
|
||||
}
|
||||
|
||||
if (account) {
|
||||
members.push(account)
|
||||
try {
|
||||
// 添加使用统计信息
|
||||
const usageStats = await redis.getAccountUsageStats(account.id)
|
||||
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
||||
|
||||
members.push({
|
||||
...account,
|
||||
groupInfos,
|
||||
usage: {
|
||||
daily: usageStats.daily,
|
||||
total: usageStats.total,
|
||||
averages: usageStats.averages
|
||||
}
|
||||
})
|
||||
} catch (statsError) {
|
||||
logger.warn(`⚠️ Failed to get usage stats for account ${account.id}:`, statsError.message)
|
||||
// 如果获取统计失败,返回空统计
|
||||
const groupInfos = await accountGroupService.getAccountGroups(account.id).catch(() => [])
|
||||
members.push({
|
||||
...account,
|
||||
groupInfos,
|
||||
usage: {
|
||||
daily: { tokens: 0, requests: 0 },
|
||||
total: { tokens: 0, requests: 0 },
|
||||
averages: { tokensPerRequest: 0 }
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1449,15 +1476,18 @@ router.get('/claude-accounts', authenticateAdmin, async (req, res) => {
|
||||
if (groupId && groupId !== 'all') {
|
||||
if (groupId === 'ungrouped') {
|
||||
// 筛选未分组账户
|
||||
accounts = accounts.filter(
|
||||
(account) => !account.groupInfos || account.groupInfos.length === 0
|
||||
)
|
||||
const filteredAccounts = []
|
||||
for (const account of accounts) {
|
||||
const groups = await accountGroupService.getAccountGroups(account.id)
|
||||
if (!groups || groups.length === 0) {
|
||||
filteredAccounts.push(account)
|
||||
}
|
||||
}
|
||||
accounts = filteredAccounts
|
||||
} else {
|
||||
// 筛选特定分组的账户
|
||||
accounts = accounts.filter(
|
||||
(account) =>
|
||||
account.groupInfos && account.groupInfos.some((group) => group.id === groupId)
|
||||
)
|
||||
const groupMembers = await accountGroupService.getGroupMembers(groupId)
|
||||
accounts = accounts.filter((account) => groupMembers.includes(account.id))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1466,7 +1496,7 @@ router.get('/claude-accounts', authenticateAdmin, async (req, res) => {
|
||||
accounts.map(async (account) => {
|
||||
try {
|
||||
const usageStats = await redis.getAccountUsageStats(account.id)
|
||||
const groupInfos = await accountGroupService.getAccountGroup(account.id)
|
||||
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
||||
|
||||
return {
|
||||
...account,
|
||||
@@ -1481,7 +1511,7 @@ router.get('/claude-accounts', authenticateAdmin, async (req, res) => {
|
||||
logger.warn(`⚠️ Failed to get usage stats for account ${account.id}:`, statsError.message)
|
||||
// 如果获取统计失败,返回空统计
|
||||
try {
|
||||
const groupInfos = await accountGroupService.getAccountGroup(account.id)
|
||||
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
||||
return {
|
||||
...account,
|
||||
groupInfos,
|
||||
@@ -1531,7 +1561,8 @@ router.post('/claude-accounts', authenticateAdmin, async (req, res) => {
|
||||
accountType,
|
||||
platform = 'claude',
|
||||
priority,
|
||||
groupId
|
||||
groupId,
|
||||
groupIds
|
||||
} = req.body
|
||||
|
||||
if (!name) {
|
||||
@@ -1545,9 +1576,11 @@ router.post('/claude-accounts', authenticateAdmin, async (req, res) => {
|
||||
.json({ error: 'Invalid account type. Must be "shared", "dedicated" or "group"' })
|
||||
}
|
||||
|
||||
// 如果是分组类型,验证groupId
|
||||
if (accountType === 'group' && !groupId) {
|
||||
return res.status(400).json({ error: 'Group ID is required for group type accounts' })
|
||||
// 如果是分组类型,验证groupId或groupIds
|
||||
if (accountType === 'group' && !groupId && (!groupIds || groupIds.length === 0)) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: 'Group ID or Group IDs are required for group type accounts' })
|
||||
}
|
||||
|
||||
// 验证priority的有效性
|
||||
@@ -1572,9 +1605,15 @@ router.post('/claude-accounts', authenticateAdmin, async (req, res) => {
|
||||
})
|
||||
|
||||
// 如果是分组类型,将账户添加到分组
|
||||
if (accountType === 'group' && groupId) {
|
||||
if (accountType === 'group') {
|
||||
if (groupIds && groupIds.length > 0) {
|
||||
// 使用多分组设置
|
||||
await accountGroupService.setAccountGroups(newAccount.id, groupIds, newAccount.platform)
|
||||
} else if (groupId) {
|
||||
// 兼容单分组模式
|
||||
await accountGroupService.addAccountToGroup(newAccount.id, groupId, newAccount.platform)
|
||||
}
|
||||
}
|
||||
|
||||
logger.success(`🏢 Admin created new Claude account: ${name} (${accountType || 'shared'})`)
|
||||
return res.json({ success: true, data: newAccount })
|
||||
@@ -1607,9 +1646,15 @@ router.put('/claude-accounts/:accountId', authenticateAdmin, async (req, res) =>
|
||||
.json({ error: 'Invalid account type. Must be "shared", "dedicated" or "group"' })
|
||||
}
|
||||
|
||||
// 如果更新为分组类型,验证groupId
|
||||
if (updates.accountType === 'group' && !updates.groupId) {
|
||||
return res.status(400).json({ error: 'Group ID is required for group type accounts' })
|
||||
// 如果更新为分组类型,验证groupId或groupIds
|
||||
if (
|
||||
updates.accountType === 'group' &&
|
||||
!updates.groupId &&
|
||||
(!updates.groupIds || updates.groupIds.length === 0)
|
||||
) {
|
||||
return res
|
||||
.status(400)
|
||||
.json({ error: 'Group ID or Group IDs are required for group type accounts' })
|
||||
}
|
||||
|
||||
// 获取账户当前信息以处理分组变更
|
||||
@@ -1622,18 +1667,26 @@ router.put('/claude-accounts/:accountId', authenticateAdmin, async (req, res) =>
|
||||
if (updates.accountType !== undefined) {
|
||||
// 如果之前是分组类型,需要从所有分组中移除
|
||||
if (currentAccount.accountType === 'group') {
|
||||
const oldGroups = await accountGroupService.getAccountGroup(accountId)
|
||||
for (const oldGroup of oldGroups) {
|
||||
await accountGroupService.removeAccountFromGroup(accountId, oldGroup.id)
|
||||
}
|
||||
await accountGroupService.removeAccountFromAllGroups(accountId)
|
||||
}
|
||||
|
||||
// 如果新类型是分组,添加到新分组
|
||||
if (updates.accountType === 'group' && updates.groupId) {
|
||||
// 从路由知道这是 Claude OAuth 账户,平台为 'claude'
|
||||
if (updates.accountType === 'group') {
|
||||
// 处理多分组/单分组的兼容性
|
||||
if (Object.prototype.hasOwnProperty.call(updates, 'groupIds')) {
|
||||
if (updates.groupIds && updates.groupIds.length > 0) {
|
||||
// 使用多分组设置
|
||||
await accountGroupService.setAccountGroups(accountId, updates.groupIds, 'claude')
|
||||
} else {
|
||||
// groupIds 为空数组,从所有分组中移除
|
||||
await accountGroupService.removeAccountFromAllGroups(accountId)
|
||||
}
|
||||
} else if (updates.groupId) {
|
||||
// 兼容单分组模式
|
||||
await accountGroupService.addAccountToGroup(accountId, updates.groupId, 'claude')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await claudeAccountService.updateAccount(accountId, updates)
|
||||
|
||||
@@ -1805,15 +1858,18 @@ router.get('/claude-console-accounts', authenticateAdmin, async (req, res) => {
|
||||
if (groupId && groupId !== 'all') {
|
||||
if (groupId === 'ungrouped') {
|
||||
// 筛选未分组账户
|
||||
accounts = accounts.filter(
|
||||
(account) => !account.groupInfos || account.groupInfos.length === 0
|
||||
)
|
||||
const filteredAccounts = []
|
||||
for (const account of accounts) {
|
||||
const groups = await accountGroupService.getAccountGroups(account.id)
|
||||
if (!groups || groups.length === 0) {
|
||||
filteredAccounts.push(account)
|
||||
}
|
||||
}
|
||||
accounts = filteredAccounts
|
||||
} else {
|
||||
// 筛选特定分组的账户
|
||||
accounts = accounts.filter(
|
||||
(account) =>
|
||||
account.groupInfos && account.groupInfos.some((group) => group.id === groupId)
|
||||
)
|
||||
const groupMembers = await accountGroupService.getGroupMembers(groupId)
|
||||
accounts = accounts.filter((account) => groupMembers.includes(account.id))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1822,7 +1878,7 @@ router.get('/claude-console-accounts', authenticateAdmin, async (req, res) => {
|
||||
accounts.map(async (account) => {
|
||||
try {
|
||||
const usageStats = await redis.getAccountUsageStats(account.id)
|
||||
const groupInfos = await accountGroupService.getAccountGroup(account.id)
|
||||
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
||||
|
||||
return {
|
||||
...account,
|
||||
@@ -1839,7 +1895,7 @@ router.get('/claude-console-accounts', authenticateAdmin, async (req, res) => {
|
||||
statsError.message
|
||||
)
|
||||
try {
|
||||
const groupInfos = await accountGroupService.getAccountGroup(account.id)
|
||||
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
||||
return {
|
||||
...account,
|
||||
groupInfos,
|
||||
@@ -1931,7 +1987,7 @@ router.post('/claude-console-accounts', authenticateAdmin, async (req, res) => {
|
||||
|
||||
// 如果是分组类型,将账户添加到分组
|
||||
if (accountType === 'group' && groupId) {
|
||||
await accountGroupService.addAccountToGroup(newAccount.id, groupId, 'claude')
|
||||
await accountGroupService.addAccountToGroup(newAccount.id, groupId)
|
||||
}
|
||||
|
||||
logger.success(`🎮 Admin created Claude Console account: ${name}`)
|
||||
@@ -1977,17 +2033,28 @@ router.put('/claude-console-accounts/:accountId', authenticateAdmin, async (req,
|
||||
if (updates.accountType !== undefined) {
|
||||
// 如果之前是分组类型,需要从所有分组中移除
|
||||
if (currentAccount.accountType === 'group') {
|
||||
const oldGroups = await accountGroupService.getAccountGroup(accountId)
|
||||
const oldGroups = await accountGroupService.getAccountGroups(accountId)
|
||||
for (const oldGroup of oldGroups) {
|
||||
await accountGroupService.removeAccountFromGroup(accountId, oldGroup.id)
|
||||
}
|
||||
}
|
||||
// 如果新类型是分组,添加到新分组
|
||||
if (updates.accountType === 'group' && updates.groupId) {
|
||||
// Claude Console 账户在分组中被视为 'claude' 平台
|
||||
// 如果新类型是分组,处理多分组支持
|
||||
if (updates.accountType === 'group') {
|
||||
if (Object.prototype.hasOwnProperty.call(updates, 'groupIds')) {
|
||||
// 如果明确提供了 groupIds 参数(包括空数组)
|
||||
if (updates.groupIds && updates.groupIds.length > 0) {
|
||||
// 设置新的多分组
|
||||
await accountGroupService.setAccountGroups(accountId, updates.groupIds, 'claude')
|
||||
} else {
|
||||
// groupIds 为空数组,从所有分组中移除
|
||||
await accountGroupService.removeAccountFromAllGroups(accountId)
|
||||
}
|
||||
} else if (updates.groupId) {
|
||||
// 向后兼容:仅当没有 groupIds 但有 groupId 时使用单分组逻辑
|
||||
await accountGroupService.addAccountToGroup(accountId, updates.groupId, 'claude')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await claudeConsoleAccountService.updateAccount(accountId, updates)
|
||||
|
||||
@@ -2119,15 +2186,18 @@ router.get('/bedrock-accounts', authenticateAdmin, async (req, res) => {
|
||||
if (groupId && groupId !== 'all') {
|
||||
if (groupId === 'ungrouped') {
|
||||
// 筛选未分组账户
|
||||
accounts = accounts.filter(
|
||||
(account) => !account.groupInfos || account.groupInfos.length === 0
|
||||
)
|
||||
const filteredAccounts = []
|
||||
for (const account of accounts) {
|
||||
const groups = await accountGroupService.getAccountGroups(account.id)
|
||||
if (!groups || groups.length === 0) {
|
||||
filteredAccounts.push(account)
|
||||
}
|
||||
}
|
||||
accounts = filteredAccounts
|
||||
} else {
|
||||
// 筛选特定分组的账户
|
||||
accounts = accounts.filter(
|
||||
(account) =>
|
||||
account.groupInfos && account.groupInfos.some((group) => group.id === groupId)
|
||||
)
|
||||
const groupMembers = await accountGroupService.getGroupMembers(groupId)
|
||||
accounts = accounts.filter((account) => groupMembers.includes(account.id))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2136,7 +2206,7 @@ router.get('/bedrock-accounts', authenticateAdmin, async (req, res) => {
|
||||
accounts.map(async (account) => {
|
||||
try {
|
||||
const usageStats = await redis.getAccountUsageStats(account.id)
|
||||
const groupInfos = await accountGroupService.getAccountGroup(account.id)
|
||||
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
||||
|
||||
return {
|
||||
...account,
|
||||
@@ -2153,7 +2223,7 @@ router.get('/bedrock-accounts', authenticateAdmin, async (req, res) => {
|
||||
statsError.message
|
||||
)
|
||||
try {
|
||||
const groupInfos = await accountGroupService.getAccountGroup(account.id)
|
||||
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
||||
return {
|
||||
...account,
|
||||
groupInfos,
|
||||
@@ -2566,15 +2636,18 @@ router.get('/gemini-accounts', authenticateAdmin, async (req, res) => {
|
||||
if (groupId && groupId !== 'all') {
|
||||
if (groupId === 'ungrouped') {
|
||||
// 筛选未分组账户
|
||||
accounts = accounts.filter(
|
||||
(account) => !account.groupInfos || account.groupInfos.length === 0
|
||||
)
|
||||
const filteredAccounts = []
|
||||
for (const account of accounts) {
|
||||
const groups = await accountGroupService.getAccountGroups(account.id)
|
||||
if (!groups || groups.length === 0) {
|
||||
filteredAccounts.push(account)
|
||||
}
|
||||
}
|
||||
accounts = filteredAccounts
|
||||
} else {
|
||||
// 筛选特定分组的账户
|
||||
accounts = accounts.filter(
|
||||
(account) =>
|
||||
account.groupInfos && account.groupInfos.some((group) => group.id === groupId)
|
||||
)
|
||||
const groupMembers = await accountGroupService.getGroupMembers(groupId)
|
||||
accounts = accounts.filter((account) => groupMembers.includes(account.id))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2583,7 +2656,7 @@ router.get('/gemini-accounts', authenticateAdmin, async (req, res) => {
|
||||
accounts.map(async (account) => {
|
||||
try {
|
||||
const usageStats = await redis.getAccountUsageStats(account.id)
|
||||
const groupInfos = await accountGroupService.getAccountGroup(account.id)
|
||||
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
||||
|
||||
return {
|
||||
...account,
|
||||
@@ -2601,7 +2674,7 @@ router.get('/gemini-accounts', authenticateAdmin, async (req, res) => {
|
||||
)
|
||||
// 如果获取统计失败,返回空统计
|
||||
try {
|
||||
const groupInfos = await accountGroupService.getAccountGroup(account.id)
|
||||
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
||||
return {
|
||||
...account,
|
||||
groupInfos,
|
||||
@@ -2705,16 +2778,28 @@ router.put('/gemini-accounts/:accountId', authenticateAdmin, async (req, res) =>
|
||||
if (updates.accountType !== undefined) {
|
||||
// 如果之前是分组类型,需要从所有分组中移除
|
||||
if (currentAccount.accountType === 'group') {
|
||||
const oldGroups = await accountGroupService.getAccountGroup(accountId)
|
||||
const oldGroups = await accountGroupService.getAccountGroups(accountId)
|
||||
for (const oldGroup of oldGroups) {
|
||||
await accountGroupService.removeAccountFromGroup(accountId, oldGroup.id)
|
||||
}
|
||||
}
|
||||
// 如果新类型是分组,添加到新分组
|
||||
if (updates.accountType === 'group' && updates.groupId) {
|
||||
// 如果新类型是分组,处理多分组支持
|
||||
if (updates.accountType === 'group') {
|
||||
if (Object.prototype.hasOwnProperty.call(updates, 'groupIds')) {
|
||||
// 如果明确提供了 groupIds 参数(包括空数组)
|
||||
if (updates.groupIds && updates.groupIds.length > 0) {
|
||||
// 设置新的多分组
|
||||
await accountGroupService.setAccountGroups(accountId, updates.groupIds, 'gemini')
|
||||
} else {
|
||||
// groupIds 为空数组,从所有分组中移除
|
||||
await accountGroupService.removeAccountFromAllGroups(accountId)
|
||||
}
|
||||
} else if (updates.groupId) {
|
||||
// 向后兼容:仅当没有 groupIds 但有 groupId 时使用单分组逻辑
|
||||
await accountGroupService.addAccountToGroup(accountId, updates.groupId, 'gemini')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updatedAccount = await geminiAccountService.updateAccount(accountId, updates)
|
||||
|
||||
@@ -5073,25 +5158,30 @@ router.get('/openai-accounts', authenticateAdmin, async (req, res) => {
|
||||
if (groupId && groupId !== 'all') {
|
||||
if (groupId === 'ungrouped') {
|
||||
// 筛选未分组账户
|
||||
accounts = accounts.filter(
|
||||
(account) => !account.groupInfos || account.groupInfos.length === 0
|
||||
)
|
||||
const filteredAccounts = []
|
||||
for (const account of accounts) {
|
||||
const groups = await accountGroupService.getAccountGroups(account.id)
|
||||
if (!groups || groups.length === 0) {
|
||||
filteredAccounts.push(account)
|
||||
}
|
||||
}
|
||||
accounts = filteredAccounts
|
||||
} else {
|
||||
// 筛选特定分组的账户
|
||||
accounts = accounts.filter(
|
||||
(account) =>
|
||||
account.groupInfos && account.groupInfos.some((group) => group.id === groupId)
|
||||
)
|
||||
const groupMembers = await accountGroupService.getGroupMembers(groupId)
|
||||
accounts = accounts.filter((account) => groupMembers.includes(account.id))
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个账户添加使用统计信息
|
||||
// 为每个账户添加使用统计信息和分组信息
|
||||
const accountsWithStats = await Promise.all(
|
||||
accounts.map(async (account) => {
|
||||
try {
|
||||
const usageStats = await redis.getAccountUsageStats(account.id)
|
||||
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
||||
return {
|
||||
...account,
|
||||
groupInfos,
|
||||
usage: {
|
||||
daily: usageStats.daily,
|
||||
total: usageStats.total,
|
||||
@@ -5100,12 +5190,27 @@ router.get('/openai-accounts', authenticateAdmin, async (req, res) => {
|
||||
}
|
||||
} catch (error) {
|
||||
logger.debug(`Failed to get usage stats for OpenAI account ${account.id}:`, error)
|
||||
try {
|
||||
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
||||
return {
|
||||
...account,
|
||||
groupInfos,
|
||||
usage: {
|
||||
daily: { requests: 0, tokens: 0, allTokens: 0 },
|
||||
total: { requests: 0, tokens: 0, allTokens: 0 },
|
||||
monthly: { requests: 0, tokens: 0, allTokens: 0 }
|
||||
averages: { rpm: 0, tpm: 0 }
|
||||
}
|
||||
}
|
||||
} catch (groupError) {
|
||||
logger.debug(`Failed to get group info for account ${account.id}:`, groupError)
|
||||
return {
|
||||
...account,
|
||||
groupInfos: [],
|
||||
usage: {
|
||||
daily: { requests: 0, tokens: 0, allTokens: 0 },
|
||||
total: { requests: 0, tokens: 0, allTokens: 0 },
|
||||
averages: { rpm: 0, tpm: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5402,10 +5507,81 @@ router.put(
|
||||
// 获取所有 Azure OpenAI 账户
|
||||
router.get('/azure-openai-accounts', authenticateAdmin, async (req, res) => {
|
||||
try {
|
||||
const accounts = await azureOpenaiAccountService.getAllAccounts()
|
||||
const { platform, groupId } = req.query
|
||||
let accounts = await azureOpenaiAccountService.getAllAccounts()
|
||||
|
||||
// 根据查询参数进行筛选
|
||||
if (platform && platform !== 'all' && platform !== 'azure_openai') {
|
||||
// 如果指定了其他平台,返回空数组
|
||||
accounts = []
|
||||
}
|
||||
|
||||
// 如果指定了分组筛选
|
||||
if (groupId && groupId !== 'all') {
|
||||
if (groupId === 'ungrouped') {
|
||||
// 筛选未分组账户
|
||||
const filteredAccounts = []
|
||||
for (const account of accounts) {
|
||||
const groups = await accountGroupService.getAccountGroups(account.id)
|
||||
if (!groups || groups.length === 0) {
|
||||
filteredAccounts.push(account)
|
||||
}
|
||||
}
|
||||
accounts = filteredAccounts
|
||||
} else {
|
||||
// 筛选特定分组的账户
|
||||
const groupMembers = await accountGroupService.getGroupMembers(groupId)
|
||||
accounts = accounts.filter((account) => groupMembers.includes(account.id))
|
||||
}
|
||||
}
|
||||
|
||||
// 为每个账户添加使用统计信息和分组信息
|
||||
const accountsWithStats = await Promise.all(
|
||||
accounts.map(async (account) => {
|
||||
try {
|
||||
const usageStats = await redis.getAccountUsageStats(account.id)
|
||||
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
||||
return {
|
||||
...account,
|
||||
groupInfos,
|
||||
usage: {
|
||||
daily: usageStats.daily,
|
||||
total: usageStats.total,
|
||||
averages: usageStats.averages
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.debug(`Failed to get usage stats for Azure OpenAI account ${account.id}:`, error)
|
||||
try {
|
||||
const groupInfos = await accountGroupService.getAccountGroups(account.id)
|
||||
return {
|
||||
...account,
|
||||
groupInfos,
|
||||
usage: {
|
||||
daily: { requests: 0, tokens: 0, allTokens: 0 },
|
||||
total: { requests: 0, tokens: 0, allTokens: 0 },
|
||||
averages: { rpm: 0, tpm: 0 }
|
||||
}
|
||||
}
|
||||
} catch (groupError) {
|
||||
logger.debug(`Failed to get group info for account ${account.id}:`, groupError)
|
||||
return {
|
||||
...account,
|
||||
groupInfos: [],
|
||||
usage: {
|
||||
daily: { requests: 0, tokens: 0, allTokens: 0 },
|
||||
total: { requests: 0, tokens: 0, allTokens: 0 },
|
||||
averages: { rpm: 0, tpm: 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: accounts
|
||||
data: accountsWithStats
|
||||
})
|
||||
} catch (error) {
|
||||
logger.error('Failed to fetch Azure OpenAI accounts:', error)
|
||||
@@ -5431,6 +5607,7 @@ router.post('/azure-openai-accounts', authenticateAdmin, async (req, res) => {
|
||||
supportedModels,
|
||||
proxy,
|
||||
groupId,
|
||||
groupIds,
|
||||
priority,
|
||||
isActive,
|
||||
schedulable
|
||||
@@ -5510,6 +5687,17 @@ router.post('/azure-openai-accounts', authenticateAdmin, async (req, res) => {
|
||||
schedulable: schedulable !== false
|
||||
})
|
||||
|
||||
// 如果是分组类型,将账户添加到分组
|
||||
if (accountType === 'group') {
|
||||
if (groupIds && groupIds.length > 0) {
|
||||
// 使用多分组设置
|
||||
await accountGroupService.setAccountGroups(account.id, groupIds, 'azure_openai')
|
||||
} else if (groupId) {
|
||||
// 兼容单分组模式
|
||||
await accountGroupService.addAccountToGroup(account.id, groupId, 'azure_openai')
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: account,
|
||||
|
||||
@@ -13,7 +13,7 @@ class AccountGroupService {
|
||||
* 创建账户分组
|
||||
* @param {Object} groupData - 分组数据
|
||||
* @param {string} groupData.name - 分组名称
|
||||
* @param {string} groupData.platform - 平台类型 (claude/gemini)
|
||||
* @param {string} groupData.platform - 平台类型 (claude/gemini/openai)
|
||||
* @param {string} groupData.description - 分组描述
|
||||
* @returns {Object} 创建的分组
|
||||
*/
|
||||
@@ -327,12 +327,36 @@ class AccountGroupService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据账户ID获取其所属的分组(兼容性方法,返回单个分组)
|
||||
* @param {string} accountId - 账户ID
|
||||
* @returns {Object|null} 分组信息
|
||||
*/
|
||||
async getAccountGroup(accountId) {
|
||||
try {
|
||||
const client = redis.getClientSafe()
|
||||
const allGroupIds = await client.smembers(this.GROUPS_KEY)
|
||||
|
||||
for (const groupId of allGroupIds) {
|
||||
const isMember = await client.sismember(`${this.GROUP_MEMBERS_PREFIX}${groupId}`, accountId)
|
||||
if (isMember) {
|
||||
return await this.getGroup(groupId)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
} catch (error) {
|
||||
logger.error('❌ 获取账户所属分组失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据账户ID获取其所属的所有分组
|
||||
* @param {string} accountId - 账户ID
|
||||
* @returns {Array} 分组信息数组
|
||||
*/
|
||||
async getAccountGroup(accountId) {
|
||||
async getAccountGroups(accountId) {
|
||||
try {
|
||||
const client = redis.getClientSafe()
|
||||
const allGroupIds = await client.smembers(this.GROUPS_KEY)
|
||||
@@ -357,6 +381,49 @@ class AccountGroupService {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量设置账户的分组
|
||||
* @param {string} accountId - 账户ID
|
||||
* @param {Array} groupIds - 分组ID数组
|
||||
* @param {string} accountPlatform - 账户平台
|
||||
*/
|
||||
async setAccountGroups(accountId, groupIds, accountPlatform) {
|
||||
try {
|
||||
// 首先移除账户的所有现有分组
|
||||
await this.removeAccountFromAllGroups(accountId)
|
||||
|
||||
// 然后添加到新的分组中
|
||||
for (const groupId of groupIds) {
|
||||
await this.addAccountToGroup(accountId, groupId, accountPlatform)
|
||||
}
|
||||
|
||||
logger.success(`✅ 批量设置账户分组成功: ${accountId} -> [${groupIds.join(', ')}]`)
|
||||
} catch (error) {
|
||||
logger.error('❌ 批量设置账户分组失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从所有分组中移除账户
|
||||
* @param {string} accountId - 账户ID
|
||||
*/
|
||||
async removeAccountFromAllGroups(accountId) {
|
||||
try {
|
||||
const client = redis.getClientSafe()
|
||||
const allGroupIds = await client.smembers(this.GROUPS_KEY)
|
||||
|
||||
for (const groupId of allGroupIds) {
|
||||
await client.srem(`${this.GROUP_MEMBERS_PREFIX}${groupId}`, accountId)
|
||||
}
|
||||
|
||||
logger.success(`✅ 从所有分组移除账户成功: ${accountId}`)
|
||||
} catch (error) {
|
||||
logger.error('❌ 从所有分组移除账户失败:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AccountGroupService()
|
||||
|
||||
@@ -249,6 +249,10 @@ async function updateAccount(accountId, updates) {
|
||||
|
||||
// 删除账户
|
||||
async function deleteAccount(accountId) {
|
||||
// 首先从所有分组中移除此账户
|
||||
const accountGroupService = require('./accountGroupService')
|
||||
await accountGroupService.removeAccountFromAllGroups(accountId)
|
||||
|
||||
const client = redisClient.getClientSafe()
|
||||
const accountKey = `${AZURE_OPENAI_ACCOUNT_KEY_PREFIX}${accountId}`
|
||||
|
||||
|
||||
@@ -611,10 +611,7 @@ class ClaudeAccountService {
|
||||
try {
|
||||
// 首先从所有分组中移除此账户
|
||||
const accountGroupService = require('./accountGroupService')
|
||||
const groups = await accountGroupService.getAccountGroup(accountId)
|
||||
for (const group of groups) {
|
||||
await accountGroupService.removeAccountFromGroup(accountId, group.id)
|
||||
}
|
||||
await accountGroupService.removeAccountFromAllGroups(accountId)
|
||||
|
||||
const result = await redis.deleteClaudeAccount(accountId)
|
||||
|
||||
|
||||
@@ -585,6 +585,10 @@ async function deleteAccount(accountId) {
|
||||
throw new Error('Account not found')
|
||||
}
|
||||
|
||||
// 首先从所有分组中移除此账户
|
||||
const accountGroupService = require('./accountGroupService')
|
||||
await accountGroupService.removeAccountFromAllGroups(accountId)
|
||||
|
||||
// 从 Redis 删除
|
||||
const client = redisClient.getClientSafe()
|
||||
await client.del(`${GEMINI_ACCOUNT_KEY_PREFIX}${accountId}`)
|
||||
|
||||
@@ -440,6 +440,10 @@ async function deleteAccount(accountId) {
|
||||
throw new Error('Account not found')
|
||||
}
|
||||
|
||||
// 首先从所有分组中移除此账户
|
||||
const accountGroupService = require('./accountGroupService')
|
||||
await accountGroupService.removeAccountFromAllGroups(accountId)
|
||||
|
||||
// 从 Redis 删除
|
||||
const client = redisClient.getClientSafe()
|
||||
await client.del(`${OPENAI_ACCOUNT_KEY_PREFIX}${accountId}`)
|
||||
|
||||
@@ -244,19 +244,47 @@
|
||||
>选择分组 *</label
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
<select
|
||||
v-model="form.groupId"
|
||||
class="form-input flex-1 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
required
|
||||
<div class="flex-1">
|
||||
<!-- 多选分组界面 -->
|
||||
<div
|
||||
class="max-h-48 space-y-2 overflow-y-auto rounded-md border p-3 dark:border-gray-600 dark:bg-gray-700"
|
||||
>
|
||||
<option value="">请选择分组</option>
|
||||
<option v-for="group in filteredGroups" :key="group.id" :value="group.id">
|
||||
<div
|
||||
v-if="filteredGroups.length === 0"
|
||||
class="text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
暂无可用分组
|
||||
</div>
|
||||
<label
|
||||
v-for="group in filteredGroups"
|
||||
:key="group.id"
|
||||
class="flex cursor-pointer items-center gap-2 rounded-md p-2 hover:bg-gray-50 dark:hover:bg-gray-600"
|
||||
>
|
||||
<input
|
||||
v-model="form.groupIds"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
|
||||
type="checkbox"
|
||||
:value="group.id"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-200">
|
||||
{{ group.name }} ({{ group.memberCount || 0 }} 个成员)
|
||||
</option>
|
||||
<option value="__new__">+ 新建分组</option>
|
||||
</select>
|
||||
</span>
|
||||
</label>
|
||||
<!-- 新建分组选项 -->
|
||||
<div class="border-t pt-2 dark:border-gray-600">
|
||||
<button
|
||||
class="rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
|
||||
class="flex items-center gap-2 text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
||||
type="button"
|
||||
@click="handleNewGroup"
|
||||
>
|
||||
<i class="fas fa-plus" />
|
||||
新建分组
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600"
|
||||
type="button"
|
||||
@click="refreshGroups"
|
||||
>
|
||||
@@ -1240,19 +1268,47 @@
|
||||
>选择分组 *</label
|
||||
>
|
||||
<div class="flex gap-2">
|
||||
<select
|
||||
v-model="form.groupId"
|
||||
class="form-input flex-1 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200"
|
||||
required
|
||||
<div class="flex-1">
|
||||
<!-- 多选分组界面 -->
|
||||
<div
|
||||
class="max-h-48 space-y-2 overflow-y-auto rounded-md border p-3 dark:border-gray-600 dark:bg-gray-700"
|
||||
>
|
||||
<option value="">请选择分组</option>
|
||||
<option v-for="group in filteredGroups" :key="group.id" :value="group.id">
|
||||
<div
|
||||
v-if="filteredGroups.length === 0"
|
||||
class="text-sm text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
暂无可用分组
|
||||
</div>
|
||||
<label
|
||||
v-for="group in filteredGroups"
|
||||
:key="group.id"
|
||||
class="flex cursor-pointer items-center gap-2 rounded-md p-2 hover:bg-gray-50 dark:hover:bg-gray-600"
|
||||
>
|
||||
<input
|
||||
v-model="form.groupIds"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 dark:border-gray-600 dark:bg-gray-700"
|
||||
type="checkbox"
|
||||
:value="group.id"
|
||||
/>
|
||||
<span class="text-sm text-gray-700 dark:text-gray-200">
|
||||
{{ group.name }} ({{ group.memberCount || 0 }} 个成员)
|
||||
</option>
|
||||
<option value="__new__">+ 新建分组</option>
|
||||
</select>
|
||||
</span>
|
||||
</label>
|
||||
<!-- 新建分组选项 -->
|
||||
<div class="border-t pt-2 dark:border-gray-600">
|
||||
<button
|
||||
class="rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-300 dark:hover:bg-gray-700"
|
||||
class="flex items-center gap-2 text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
|
||||
type="button"
|
||||
@click="handleNewGroup"
|
||||
>
|
||||
<i class="fas fa-plus" />
|
||||
新建分组
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="rounded-md border border-gray-300 bg-white px-3 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-200 dark:hover:bg-gray-600"
|
||||
type="button"
|
||||
@click="refreshGroups"
|
||||
>
|
||||
@@ -1771,6 +1827,7 @@ const form = ref({
|
||||
accountType: props.account?.accountType || 'shared',
|
||||
subscriptionType: 'claude_max', // 默认为 Claude Max,兼容旧数据
|
||||
groupId: '',
|
||||
groupIds: [],
|
||||
projectId: props.account?.projectId || '',
|
||||
idToken: '',
|
||||
accessToken: '',
|
||||
@@ -1872,15 +1929,24 @@ const nextStep = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 分组类型验证
|
||||
// 分组类型验证 - OAuth流程修复
|
||||
if (
|
||||
form.value.accountType === 'group' &&
|
||||
(!form.value.groupId || form.value.groupId.trim() === '')
|
||||
(!form.value.groupIds || form.value.groupIds.length === 0)
|
||||
) {
|
||||
showToast('请选择一个分组', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
// 数据同步:确保 groupId 和 groupIds 保持一致 - OAuth流程
|
||||
if (form.value.accountType === 'group') {
|
||||
if (form.value.groupIds && form.value.groupIds.length > 0) {
|
||||
form.value.groupId = form.value.groupIds[0]
|
||||
} else {
|
||||
form.value.groupId = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 对于Gemini账户,检查项目 ID
|
||||
if (form.value.platform === 'gemini' && oauthStep.value === 1 && form.value.addType === 'oauth') {
|
||||
if (!form.value.projectId || form.value.projectId.trim() === '') {
|
||||
@@ -2014,6 +2080,7 @@ const handleOAuthSuccess = async (tokenInfo) => {
|
||||
description: form.value.description,
|
||||
accountType: form.value.accountType,
|
||||
groupId: form.value.accountType === 'group' ? form.value.groupId : undefined,
|
||||
groupIds: form.value.accountType === 'group' ? form.value.groupIds : undefined,
|
||||
proxy: form.value.proxy.enabled
|
||||
? {
|
||||
type: form.value.proxy.type,
|
||||
@@ -2106,6 +2173,20 @@ const createAccount = async () => {
|
||||
errors.value.region = '请选择 AWS 区域'
|
||||
hasError = true
|
||||
}
|
||||
} else if (form.value.platform === 'azure_openai') {
|
||||
// Azure OpenAI 验证
|
||||
if (!form.value.azureEndpoint || form.value.azureEndpoint.trim() === '') {
|
||||
errors.value.azureEndpoint = 'Azure Endpoint 是必填项'
|
||||
hasError = true
|
||||
}
|
||||
if (!form.value.deploymentName || form.value.deploymentName.trim() === '') {
|
||||
errors.value.deploymentName = 'Deployment Name 是必填项'
|
||||
hasError = true
|
||||
}
|
||||
if (!form.value.apiKey || form.value.apiKey.trim() === '') {
|
||||
errors.value.apiKey = 'API Key 是必填项'
|
||||
hasError = true
|
||||
}
|
||||
} else if (form.value.addType === 'manual') {
|
||||
// 手动模式验证
|
||||
if (!form.value.accessToken || form.value.accessToken.trim() === '') {
|
||||
@@ -2122,15 +2203,24 @@ const createAccount = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 分组类型验证
|
||||
// 分组类型验证 - 创建账户流程修复
|
||||
if (
|
||||
form.value.accountType === 'group' &&
|
||||
(!form.value.groupId || form.value.groupId.trim() === '')
|
||||
(!form.value.groupIds || form.value.groupIds.length === 0)
|
||||
) {
|
||||
showToast('请选择一个分组', 'error')
|
||||
hasError = true
|
||||
}
|
||||
|
||||
// 数据同步:确保 groupId 和 groupIds 保持一致 - 创建流程
|
||||
if (form.value.accountType === 'group') {
|
||||
if (form.value.groupIds && form.value.groupIds.length > 0) {
|
||||
form.value.groupId = form.value.groupIds[0]
|
||||
} else {
|
||||
form.value.groupId = ''
|
||||
}
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
return
|
||||
}
|
||||
@@ -2142,6 +2232,7 @@ const createAccount = async () => {
|
||||
description: form.value.description,
|
||||
accountType: form.value.accountType,
|
||||
groupId: form.value.accountType === 'group' ? form.value.groupId : undefined,
|
||||
groupIds: form.value.accountType === 'group' ? form.value.groupIds : undefined,
|
||||
proxy: form.value.proxy.enabled
|
||||
? {
|
||||
type: form.value.proxy.type,
|
||||
@@ -2266,6 +2357,16 @@ const createAccount = async () => {
|
||||
data.priority = form.value.priority || 50
|
||||
// 如果不启用限流,传递 0 表示不限流
|
||||
data.rateLimitDuration = form.value.enableRateLimit ? form.value.rateLimitDuration || 60 : 0
|
||||
} else if (form.value.platform === 'azure_openai') {
|
||||
// Azure OpenAI 账户特定数据
|
||||
data.azureEndpoint = form.value.azureEndpoint
|
||||
data.apiVersion = form.value.apiVersion || '2024-02-01'
|
||||
data.deploymentName = form.value.deploymentName
|
||||
data.apiKey = form.value.apiKey
|
||||
data.supportedModels = form.value.supportedModels || []
|
||||
data.priority = form.value.priority || 50
|
||||
data.isActive = form.value.isActive !== false
|
||||
data.schedulable = form.value.schedulable !== false
|
||||
}
|
||||
|
||||
let result
|
||||
@@ -2277,8 +2378,12 @@ const createAccount = async () => {
|
||||
result = await accountsStore.createBedrockAccount(data)
|
||||
} else if (form.value.platform === 'openai') {
|
||||
result = await accountsStore.createOpenAIAccount(data)
|
||||
} else {
|
||||
} else if (form.value.platform === 'azure_openai') {
|
||||
result = await accountsStore.createAzureOpenAIAccount(data)
|
||||
} else if (form.value.platform === 'gemini') {
|
||||
result = await accountsStore.createGeminiAccount(data)
|
||||
} else {
|
||||
throw new Error(`不支持的平台: ${form.value.platform}`)
|
||||
}
|
||||
|
||||
emit('success', result)
|
||||
@@ -2300,15 +2405,24 @@ const updateAccount = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// 分组类型验证
|
||||
// 分组类型验证 - 更新账户流程修复
|
||||
if (
|
||||
form.value.accountType === 'group' &&
|
||||
(!form.value.groupId || form.value.groupId.trim() === '')
|
||||
(!form.value.groupIds || form.value.groupIds.length === 0)
|
||||
) {
|
||||
showToast('请选择一个分组', 'error')
|
||||
return
|
||||
}
|
||||
|
||||
// 数据同步:确保 groupId 和 groupIds 保持一致 - 更新流程
|
||||
if (form.value.accountType === 'group') {
|
||||
if (form.value.groupIds && form.value.groupIds.length > 0) {
|
||||
form.value.groupId = form.value.groupIds[0]
|
||||
} else {
|
||||
form.value.groupId = ''
|
||||
}
|
||||
}
|
||||
|
||||
// 对于Gemini账户,检查项目 ID
|
||||
if (form.value.platform === 'gemini') {
|
||||
if (!form.value.projectId || form.value.projectId.trim() === '') {
|
||||
@@ -2332,6 +2446,7 @@ const updateAccount = async () => {
|
||||
description: form.value.description,
|
||||
accountType: form.value.accountType,
|
||||
groupId: form.value.accountType === 'group' ? form.value.groupId : undefined,
|
||||
groupIds: form.value.accountType === 'group' ? form.value.groupIds : undefined,
|
||||
proxy: form.value.proxy.enabled
|
||||
? {
|
||||
type: form.value.proxy.type,
|
||||
@@ -2458,8 +2573,12 @@ const updateAccount = async () => {
|
||||
await accountsStore.updateBedrockAccount(props.account.id, data)
|
||||
} else if (props.account.platform === 'openai') {
|
||||
await accountsStore.updateOpenAIAccount(props.account.id, data)
|
||||
} else {
|
||||
} else if (props.account.platform === 'azure_openai') {
|
||||
await accountsStore.updateAzureOpenAIAccount(props.account.id, data)
|
||||
} else if (props.account.platform === 'gemini') {
|
||||
await accountsStore.updateGeminiAccount(props.account.id, data)
|
||||
} else {
|
||||
throw new Error(`不支持的平台: ${props.account.platform}`)
|
||||
}
|
||||
|
||||
emit('success')
|
||||
@@ -2541,6 +2660,11 @@ const refreshGroups = async () => {
|
||||
showToast('分组列表已刷新', 'success')
|
||||
}
|
||||
|
||||
// 处理新建分组
|
||||
const handleNewGroup = () => {
|
||||
showGroupManagement.value = true
|
||||
}
|
||||
|
||||
// 处理分组管理模态框刷新
|
||||
const handleGroupRefresh = async () => {
|
||||
await loadGroups()
|
||||
@@ -2567,10 +2691,28 @@ watch(
|
||||
// 平台变化时,清空分组选择
|
||||
if (form.value.accountType === 'group') {
|
||||
form.value.groupId = ''
|
||||
form.value.groupIds = []
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// 监听分组选择变化,保持 groupId 和 groupIds 同步
|
||||
watch(
|
||||
() => form.value.groupIds,
|
||||
(newGroupIds) => {
|
||||
if (form.value.accountType === 'group') {
|
||||
if (newGroupIds && newGroupIds.length > 0) {
|
||||
// 如果有选中的分组,使用第一个作为主分组
|
||||
form.value.groupId = newGroupIds[0]
|
||||
} else {
|
||||
// 如果没有选中分组,清空主分组
|
||||
form.value.groupId = ''
|
||||
}
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 监听Setup Token授权码输入,自动提取URL中的code参数
|
||||
watch(setupTokenAuthCode, (newValue) => {
|
||||
if (!newValue || typeof newValue !== 'string') return
|
||||
@@ -2731,6 +2873,7 @@ watch(
|
||||
accountType: newAccount.accountType || 'shared',
|
||||
subscriptionType: subscriptionType,
|
||||
groupId: groupId,
|
||||
groupIds: [],
|
||||
projectId: newAccount.projectId || '',
|
||||
accessToken: '',
|
||||
refreshToken: '',
|
||||
@@ -2768,24 +2911,35 @@ watch(
|
||||
// 如果是分组类型,加载分组ID
|
||||
if (newAccount.accountType === 'group') {
|
||||
// 先加载分组列表
|
||||
loadGroups().then(() => {
|
||||
loadGroups().then(async () => {
|
||||
const foundGroupIds = []
|
||||
|
||||
// 如果账户有 groupInfo,直接使用它的 groupId
|
||||
if (newAccount.groupInfo && newAccount.groupInfo.id) {
|
||||
form.value.groupId = newAccount.groupInfo.id
|
||||
foundGroupIds.push(newAccount.groupInfo.id)
|
||||
} else {
|
||||
// 否则查找账户所属的分组
|
||||
groups.value.forEach((group) => {
|
||||
apiClient
|
||||
.get(`/admin/account-groups/${group.id}/members`)
|
||||
.then((response) => {
|
||||
const checkPromises = groups.value.map(async (group) => {
|
||||
try {
|
||||
const response = await apiClient.get(`/admin/account-groups/${group.id}/members`)
|
||||
const members = response.data || []
|
||||
if (members.some((m) => m.id === newAccount.id)) {
|
||||
form.value.groupId = group.id
|
||||
foundGroupIds.push(group.id)
|
||||
if (!form.value.groupId) {
|
||||
form.value.groupId = group.id // 设置第一个找到的分组作为主分组
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// 忽略错误
|
||||
}
|
||||
})
|
||||
.catch(() => {})
|
||||
})
|
||||
|
||||
await Promise.all(checkPromises)
|
||||
}
|
||||
|
||||
// 设置多选分组
|
||||
form.value.groupIds = foundGroupIds
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,9 +98,18 @@ class ApiClient {
|
||||
|
||||
// GET 请求
|
||||
async get(url, options = {}) {
|
||||
const fullUrl = createApiUrl(url)
|
||||
// 处理查询参数
|
||||
let fullUrl = createApiUrl(url)
|
||||
if (options.params) {
|
||||
const params = new URLSearchParams(options.params)
|
||||
fullUrl += '?' + params.toString()
|
||||
}
|
||||
|
||||
// 移除 params 避免传递给 fetch
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const { params, ...configOptions } = options
|
||||
const config = this.buildConfig({
|
||||
...options,
|
||||
...configOptions,
|
||||
method: 'GET'
|
||||
})
|
||||
|
||||
|
||||
@@ -418,7 +418,8 @@
|
||||
account.platform === 'claude-console' ||
|
||||
account.platform === 'bedrock' ||
|
||||
account.platform === 'gemini' ||
|
||||
account.platform === 'openai'
|
||||
account.platform === 'openai' ||
|
||||
account.platform === 'azure_openai'
|
||||
"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
@@ -917,12 +918,24 @@ const sortedAccounts = computed(() => {
|
||||
const loadAccounts = async (forceReload = false) => {
|
||||
accountsLoading.value = true
|
||||
try {
|
||||
// 构建查询参数
|
||||
// 检查是否选择了特定分组
|
||||
if (groupFilter.value && groupFilter.value !== 'all' && groupFilter.value !== 'ungrouped') {
|
||||
// 直接调用分组成员接口
|
||||
const response = await apiClient.get(`/admin/account-groups/${groupFilter.value}/members`)
|
||||
if (response.success) {
|
||||
// 分组成员接口已经包含了完整的账户信息,直接使用
|
||||
accounts.value = response.data
|
||||
accountsLoading.value = false
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 构建查询参数(用于其他筛选情况)
|
||||
const params = {}
|
||||
if (platformFilter.value !== 'all') {
|
||||
params.platform = platformFilter.value
|
||||
}
|
||||
if (groupFilter.value !== 'all') {
|
||||
if (groupFilter.value === 'ungrouped') {
|
||||
params.groupId = groupFilter.value
|
||||
}
|
||||
|
||||
@@ -947,7 +960,9 @@ const loadAccounts = async (forceReload = false) => {
|
||||
apiClient.get('/admin/claude-accounts', { params }),
|
||||
Promise.resolve({ success: true, data: [] }), // claude-console 占位
|
||||
Promise.resolve({ success: true, data: [] }), // bedrock 占位
|
||||
Promise.resolve({ success: true, data: [] }) // gemini 占位
|
||||
Promise.resolve({ success: true, data: [] }), // gemini 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai 占位
|
||||
Promise.resolve({ success: true, data: [] }) // azure-openai 占位
|
||||
)
|
||||
break
|
||||
case 'claude-console':
|
||||
@@ -955,7 +970,9 @@ const loadAccounts = async (forceReload = false) => {
|
||||
Promise.resolve({ success: true, data: [] }), // claude 占位
|
||||
apiClient.get('/admin/claude-console-accounts', { params }),
|
||||
Promise.resolve({ success: true, data: [] }), // bedrock 占位
|
||||
Promise.resolve({ success: true, data: [] }) // gemini 占位
|
||||
Promise.resolve({ success: true, data: [] }), // gemini 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai 占位
|
||||
Promise.resolve({ success: true, data: [] }) // azure-openai 占位
|
||||
)
|
||||
break
|
||||
case 'bedrock':
|
||||
@@ -963,7 +980,9 @@ const loadAccounts = async (forceReload = false) => {
|
||||
Promise.resolve({ success: true, data: [] }), // claude 占位
|
||||
Promise.resolve({ success: true, data: [] }), // claude-console 占位
|
||||
apiClient.get('/admin/bedrock-accounts', { params }),
|
||||
Promise.resolve({ success: true, data: [] }) // gemini 占位
|
||||
Promise.resolve({ success: true, data: [] }), // gemini 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai 占位
|
||||
Promise.resolve({ success: true, data: [] }) // azure-openai 占位
|
||||
)
|
||||
break
|
||||
case 'gemini':
|
||||
@@ -971,7 +990,40 @@ const loadAccounts = async (forceReload = false) => {
|
||||
Promise.resolve({ success: true, data: [] }), // claude 占位
|
||||
Promise.resolve({ success: true, data: [] }), // claude-console 占位
|
||||
Promise.resolve({ success: true, data: [] }), // bedrock 占位
|
||||
apiClient.get('/admin/gemini-accounts', { params })
|
||||
apiClient.get('/admin/gemini-accounts', { params }),
|
||||
Promise.resolve({ success: true, data: [] }), // openai 占位
|
||||
Promise.resolve({ success: true, data: [] }) // azure-openai 占位
|
||||
)
|
||||
break
|
||||
case 'openai':
|
||||
requests.push(
|
||||
Promise.resolve({ success: true, data: [] }), // claude 占位
|
||||
Promise.resolve({ success: true, data: [] }), // claude-console 占位
|
||||
Promise.resolve({ success: true, data: [] }), // bedrock 占位
|
||||
Promise.resolve({ success: true, data: [] }), // gemini 占位
|
||||
apiClient.get('/admin/openai-accounts', { params }),
|
||||
Promise.resolve({ success: true, data: [] }) // azure-openai 占位
|
||||
)
|
||||
break
|
||||
case 'azure_openai':
|
||||
requests.push(
|
||||
Promise.resolve({ success: true, data: [] }), // claude 占位
|
||||
Promise.resolve({ success: true, data: [] }), // claude-console 占位
|
||||
Promise.resolve({ success: true, data: [] }), // bedrock 占位
|
||||
Promise.resolve({ success: true, data: [] }), // gemini 占位
|
||||
Promise.resolve({ success: true, data: [] }), // openai 占位
|
||||
apiClient.get('/admin/azure-openai-accounts', { params })
|
||||
)
|
||||
break
|
||||
default:
|
||||
// 默认情况下返回空数组
|
||||
requests.push(
|
||||
Promise.resolve({ success: true, data: [] }),
|
||||
Promise.resolve({ success: true, data: [] }),
|
||||
Promise.resolve({ success: true, data: [] }),
|
||||
Promise.resolve({ success: true, data: [] }),
|
||||
Promise.resolve({ success: true, data: [] }),
|
||||
Promise.resolve({ success: true, data: [] })
|
||||
)
|
||||
break
|
||||
}
|
||||
@@ -1046,8 +1098,8 @@ const loadAccounts = async (forceReload = false) => {
|
||||
const boundApiKeysCount = apiKeys.value.filter(
|
||||
(key) => key.azureOpenaiAccountId === acc.id
|
||||
).length
|
||||
const groupInfo = accountGroupMap.value.get(acc.id) || null
|
||||
return { ...acc, platform: 'azure_openai', boundApiKeysCount, groupInfo }
|
||||
// 后端已经包含了groupInfos,直接使用
|
||||
return { ...acc, platform: 'azure_openai', boundApiKeysCount }
|
||||
})
|
||||
allAccounts.push(...azureOpenaiAccounts)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user