diff --git a/src/routes/admin/geminiAccounts.js b/src/routes/admin/geminiAccounts.js index 68649730..35419ef8 100644 --- a/src/routes/admin/geminiAccounts.js +++ b/src/routes/admin/geminiAccounts.js @@ -248,16 +248,29 @@ router.post('/', authenticateAdmin, async (req, res) => { .json({ error: 'Invalid account type. Must be "shared", "dedicated" or "group"' }) } - // 如果是分组类型,验证groupId - if (accountData.accountType === 'group' && !accountData.groupId) { + // 如果是分组类型,验证groupId或groupIds + if ( + accountData.accountType === 'group' && + !accountData.groupId && + (!accountData.groupIds || accountData.groupIds.length === 0) + ) { return res.status(400).json({ error: 'Group ID is required for group type accounts' }) } const newAccount = await geminiAccountService.createAccount(accountData) - // 如果是分组类型,将账户添加到分组 - if (accountData.accountType === 'group' && accountData.groupId) { - await accountGroupService.addAccountToGroup(newAccount.id, accountData.groupId, 'gemini') + // 如果是分组类型,处理分组绑定 + if (accountData.accountType === 'group') { + if (accountData.groupIds && accountData.groupIds.length > 0) { + // 多分组模式 + await accountGroupService.setAccountGroups(newAccount.id, accountData.groupIds, 'gemini') + logger.info( + `🏢 Added Gemini account ${newAccount.id} to groups: ${accountData.groupIds.join(', ')}` + ) + } else if (accountData.groupId) { + // 单分组模式(向后兼容) + await accountGroupService.addAccountToGroup(newAccount.id, accountData.groupId, 'gemini') + } } logger.success(`🏢 Admin created new Gemini account: ${accountData.name}`) @@ -282,8 +295,12 @@ router.put('/:accountId', authenticateAdmin, async (req, res) => { .json({ error: 'Invalid account type. Must be "shared", "dedicated" or "group"' }) } - // 如果更新为分组类型,验证groupId - if (updates.accountType === 'group' && !updates.groupId) { + // 如果更新为分组类型,验证groupId或groupIds + if ( + updates.accountType === 'group' && + !updates.groupId && + (!updates.groupIds || updates.groupIds.length === 0) + ) { return res.status(400).json({ error: 'Group ID is required for group type accounts' }) } diff --git a/src/routes/admin/geminiApiAccounts.js b/src/routes/admin/geminiApiAccounts.js index 72d9304b..df49efa0 100644 --- a/src/routes/admin/geminiApiAccounts.js +++ b/src/routes/admin/geminiApiAccounts.js @@ -23,8 +23,9 @@ router.get('/gemini-api-accounts', authenticateAdmin, async (req, res) => { // 根据分组ID筛选 if (groupId) { const group = await accountGroupService.getGroup(groupId) - if (group && group.platform === 'gemini' && group.memberIds && group.memberIds.length > 0) { - accounts = accounts.filter((account) => group.memberIds.includes(account.id)) + if (group && group.platform === 'gemini') { + const groupMembers = await accountGroupService.getGroupMembers(groupId) + accounts = accounts.filter((account) => groupMembers.includes(account.id)) } else { accounts = [] } @@ -62,8 +63,12 @@ router.get('/gemini-api-accounts', authenticateAdmin, async (req, res) => { } } + // 获取分组信息 + const groupInfos = await accountGroupService.getAccountGroups(account.id) + return { ...account, + groupInfos, usage: { daily: usageStats.daily, total: usageStats.total, @@ -257,13 +262,10 @@ router.delete('/gemini-api-accounts/:id', authenticateAdmin, async (req, res) => // 自动解绑所有绑定的 API Keys(支持 api: 前缀) const unboundCount = await apiKeyService.unbindAccountFromAllKeys(id, 'gemini-api') - // 检查是否在分组中 - const groups = await accountGroupService.getAllGroups() - for (const group of groups) { - if (group.platform === 'gemini' && group.memberIds && group.memberIds.includes(id)) { - await accountGroupService.removeMemberFromGroup(group.id, id) - logger.info(`Removed Gemini-API account ${id} from group ${group.id}`) - } + // 从所有分组中移除此账户 + if (account.accountType === 'group') { + await accountGroupService.removeAccountFromAllGroups(id) + logger.info(`Removed Gemini-API account ${id} from all groups`) } const result = await geminiApiAccountService.deleteAccount(id) diff --git a/web/admin-spa/src/components/accounts/AccountForm.vue b/web/admin-spa/src/components/accounts/AccountForm.vue index f5606787..4e5c53df 100644 --- a/web/admin-spa/src/components/accounts/AccountForm.vue +++ b/web/admin-spa/src/components/accounts/AccountForm.vue @@ -3716,6 +3716,8 @@ const form = ref({ endpointType: props.account?.endpointType || 'anthropic', // OpenAI-Responses 特定字段 baseApi: props.account?.baseApi || '', + // Gemini-API 特定字段 + baseUrl: props.account?.baseUrl || 'https://generativelanguage.googleapis.com', rateLimitDuration: props.account?.rateLimitDuration || 60, supportedModels: (() => { const models = props.account?.supportedModels @@ -5567,6 +5569,8 @@ watch( deploymentName: newAccount.deploymentName || '', // OpenAI-Responses 特定字段 baseApi: newAccount.baseApi || '', + // Gemini-API 特定字段 + baseUrl: newAccount.baseUrl || 'https://generativelanguage.googleapis.com', // 额度管理字段 dailyQuota: newAccount.dailyQuota || 0, dailyUsage: newAccount.dailyUsage || 0, @@ -5586,12 +5590,27 @@ watch( loadGroups().then(async () => { const foundGroupIds = [] - // 如果账户有 groupInfo,直接使用它的 groupId - if (newAccount.groupInfo && newAccount.groupInfo.id) { + // 优先使用 groupInfos 数组(后端返回的标准格式) + if ( + newAccount.groupInfos && + Array.isArray(newAccount.groupInfos) && + newAccount.groupInfos.length > 0 + ) { + // 从 groupInfos 数组中提取所有分组 ID + newAccount.groupInfos.forEach((group) => { + if (group && group.id) { + foundGroupIds.push(group.id) + } + }) + if (foundGroupIds.length > 0) { + form.value.groupId = foundGroupIds[0] + } + } else if (newAccount.groupInfo && newAccount.groupInfo.id) { + // 兼容旧的 groupInfo 单对象格式 form.value.groupId = newAccount.groupInfo.id foundGroupIds.push(newAccount.groupInfo.id) } else if (newAccount.groupId) { - // 如果账户有 groupId 字段,直接使用(OpenAI-Responses 等账户) + // 如果账户有 groupId 字段,直接使用 form.value.groupId = newAccount.groupId foundGroupIds.push(newAccount.groupId) } else if (