feat: 支持Gemini-Api接入

This commit is contained in:
shaw
2025-11-23 22:00:13 +08:00
parent b197cba325
commit bae39d5468
13 changed files with 2355 additions and 287 deletions

View File

@@ -1,4 +1,5 @@
const geminiAccountService = require('./geminiAccountService')
const geminiApiAccountService = require('./geminiApiAccountService')
const accountGroupService = require('./accountGroupService')
const redis = require('../models/redis')
const logger = require('../utils/logger')
@@ -19,35 +20,69 @@ class UnifiedGeminiScheduler {
}
// 🎯 统一调度Gemini账号
async selectAccountForApiKey(apiKeyData, sessionHash = null, requestedModel = null) {
async selectAccountForApiKey(
apiKeyData,
sessionHash = null,
requestedModel = null,
options = {}
) {
const { allowApiAccounts = false } = options
try {
// 如果API Key绑定了专属账户或分组优先使用
if (apiKeyData.geminiAccountId) {
// 检查是否是 Gemini API 账户api: 前缀)
if (apiKeyData.geminiAccountId.startsWith('api:')) {
const accountId = apiKeyData.geminiAccountId.replace('api:', '')
const boundAccount = await geminiApiAccountService.getAccount(accountId)
if (boundAccount && boundAccount.isActive === 'true' && boundAccount.status !== 'error') {
logger.info(
`🎯 Using bound Gemini-API account: ${boundAccount.name} (${accountId}) for API key ${apiKeyData.name}`
)
// 更新账户的最后使用时间
await geminiApiAccountService.markAccountUsed(accountId)
return {
accountId,
accountType: 'gemini-api'
}
} else {
// 提供详细的不可用原因
const reason = !boundAccount
? 'account not found'
: boundAccount.isActive !== 'true'
? `isActive=${boundAccount.isActive}`
: `status=${boundAccount.status}`
logger.warn(
`⚠️ Bound Gemini-API account ${accountId} is not available (${reason}), falling back to pool`
)
}
}
// 检查是否是分组
if (apiKeyData.geminiAccountId.startsWith('group:')) {
else if (apiKeyData.geminiAccountId.startsWith('group:')) {
const groupId = apiKeyData.geminiAccountId.replace('group:', '')
logger.info(
`🎯 API key ${apiKeyData.name} is bound to group ${groupId}, selecting from group`
)
return await this.selectAccountFromGroup(groupId, sessionHash, requestedModel, apiKeyData)
}
// 普通专属账户
const boundAccount = await geminiAccountService.getAccount(apiKeyData.geminiAccountId)
if (boundAccount && boundAccount.isActive === 'true' && boundAccount.status !== 'error') {
logger.info(
`🎯 Using bound dedicated Gemini account: ${boundAccount.name} (${apiKeyData.geminiAccountId}) for API key ${apiKeyData.name}`
)
// 更新账户的最后使用时间
await geminiAccountService.markAccountUsed(apiKeyData.geminiAccountId)
return {
accountId: apiKeyData.geminiAccountId,
accountType: 'gemini'
// 普通 Gemini OAuth 专属账户
else {
const boundAccount = await geminiAccountService.getAccount(apiKeyData.geminiAccountId)
if (boundAccount && boundAccount.isActive === 'true' && boundAccount.status !== 'error') {
logger.info(
`🎯 Using bound dedicated Gemini account: ${boundAccount.name} (${apiKeyData.geminiAccountId}) for API key ${apiKeyData.name}`
)
// 更新账户的最后使用时间
await geminiAccountService.markAccountUsed(apiKeyData.geminiAccountId)
return {
accountId: apiKeyData.geminiAccountId,
accountType: 'gemini'
}
} else {
logger.warn(
`⚠️ Bound Gemini account ${apiKeyData.geminiAccountId} is not available, falling back to pool`
)
}
} else {
logger.warn(
`⚠️ Bound Gemini account ${apiKeyData.geminiAccountId} is not available, falling back to pool`
)
}
}
@@ -66,8 +101,12 @@ class UnifiedGeminiScheduler {
logger.info(
`🎯 Using sticky session account: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}`
)
// 更新账户的最后使用时间
await geminiAccountService.markAccountUsed(mappedAccount.accountId)
// 更新账户的最后使用时间(根据账户类型调用正确的服务)
if (mappedAccount.accountType === 'gemini-api') {
await geminiApiAccountService.markAccountUsed(mappedAccount.accountId)
} else {
await geminiAccountService.markAccountUsed(mappedAccount.accountId)
}
return mappedAccount
} else {
logger.warn(
@@ -79,7 +118,11 @@ class UnifiedGeminiScheduler {
}
// 获取所有可用账户
const availableAccounts = await this._getAllAvailableAccounts(apiKeyData, requestedModel)
const availableAccounts = await this._getAllAvailableAccounts(
apiKeyData,
requestedModel,
allowApiAccounts
)
if (availableAccounts.length === 0) {
// 提供更详细的错误信息
@@ -114,8 +157,12 @@ class UnifiedGeminiScheduler {
`🎯 Selected account: ${selectedAccount.name} (${selectedAccount.accountId}, ${selectedAccount.accountType}) with priority ${selectedAccount.priority} for API key ${apiKeyData.name}`
)
// 更新账户的最后使用时间
await geminiAccountService.markAccountUsed(selectedAccount.accountId)
// 更新账户的最后使用时间(根据账户类型调用正确的服务)
if (selectedAccount.accountType === 'gemini-api') {
await geminiApiAccountService.markAccountUsed(selectedAccount.accountId)
} else {
await geminiAccountService.markAccountUsed(selectedAccount.accountId)
}
return {
accountId: selectedAccount.accountId,
@@ -128,53 +175,104 @@ class UnifiedGeminiScheduler {
}
// 📋 获取所有可用账户
async _getAllAvailableAccounts(apiKeyData, requestedModel = null) {
async _getAllAvailableAccounts(apiKeyData, requestedModel = null, allowApiAccounts = false) {
const availableAccounts = []
// 如果API Key绑定了专属账户优先返回
if (apiKeyData.geminiAccountId) {
const boundAccount = await geminiAccountService.getAccount(apiKeyData.geminiAccountId)
if (boundAccount && boundAccount.isActive === 'true' && boundAccount.status !== 'error') {
const isRateLimited = await this.isAccountRateLimited(boundAccount.id)
if (!isRateLimited) {
// 检查模型支持
if (
requestedModel &&
boundAccount.supportedModels &&
boundAccount.supportedModels.length > 0
) {
// 处理可能带有 models/ 前缀的模型名
const normalizedModel = requestedModel.replace('models/', '')
const modelSupported = boundAccount.supportedModels.some(
(model) => model.replace('models/', '') === normalizedModel
)
if (!modelSupported) {
logger.warn(
`⚠️ Bound Gemini account ${boundAccount.name} does not support model ${requestedModel}`
// 检查是否是 Gemini API 账户api: 前缀)
if (apiKeyData.geminiAccountId.startsWith('api:')) {
const accountId = apiKeyData.geminiAccountId.replace('api:', '')
const boundAccount = await geminiApiAccountService.getAccount(accountId)
if (boundAccount && boundAccount.isActive === 'true' && boundAccount.status !== 'error') {
const isRateLimited = await this.isAccountRateLimited(accountId)
if (!isRateLimited) {
// 检查模型支持
if (
requestedModel &&
boundAccount.supportedModels &&
boundAccount.supportedModels.length > 0
) {
const normalizedModel = requestedModel.replace('models/', '')
const modelSupported = boundAccount.supportedModels.some(
(model) => model.replace('models/', '') === normalizedModel
)
return availableAccounts
if (!modelSupported) {
logger.warn(
`⚠️ Bound Gemini-API account ${boundAccount.name} does not support model ${requestedModel}`
)
return availableAccounts
}
}
}
logger.info(
`🎯 Using bound dedicated Gemini account: ${boundAccount.name} (${apiKeyData.geminiAccountId})`
logger.info(`🎯 Using bound Gemini-API account: ${boundAccount.name} (${accountId})`)
return [
{
...boundAccount,
accountId,
accountType: 'gemini-api',
priority: parseInt(boundAccount.priority) || 50,
lastUsedAt: boundAccount.lastUsedAt || '0'
}
]
}
} else {
// 提供详细的不可用原因
const reason = !boundAccount
? 'account not found'
: boundAccount.isActive !== 'true'
? `isActive=${boundAccount.isActive}`
: `status=${boundAccount.status}`
logger.warn(
`⚠️ Bound Gemini-API account ${accountId} is not available in _getAllAvailableAccounts (${reason})`
)
return [
{
...boundAccount,
accountId: boundAccount.id,
accountType: 'gemini',
priority: parseInt(boundAccount.priority) || 50,
lastUsedAt: boundAccount.lastUsedAt || '0'
}
]
}
} else {
logger.warn(`⚠️ Bound Gemini account ${apiKeyData.geminiAccountId} is not available`)
}
// 普通 Gemini OAuth 账户
else if (!apiKeyData.geminiAccountId.startsWith('group:')) {
const boundAccount = await geminiAccountService.getAccount(apiKeyData.geminiAccountId)
if (boundAccount && boundAccount.isActive === 'true' && boundAccount.status !== 'error') {
const isRateLimited = await this.isAccountRateLimited(boundAccount.id)
if (!isRateLimited) {
// 检查模型支持
if (
requestedModel &&
boundAccount.supportedModels &&
boundAccount.supportedModels.length > 0
) {
// 处理可能带有 models/ 前缀的模型名
const normalizedModel = requestedModel.replace('models/', '')
const modelSupported = boundAccount.supportedModels.some(
(model) => model.replace('models/', '') === normalizedModel
)
if (!modelSupported) {
logger.warn(
`⚠️ Bound Gemini account ${boundAccount.name} does not support model ${requestedModel}`
)
return availableAccounts
}
}
logger.info(
`🎯 Using bound dedicated Gemini account: ${boundAccount.name} (${apiKeyData.geminiAccountId})`
)
return [
{
...boundAccount,
accountId: boundAccount.id,
accountType: 'gemini',
priority: parseInt(boundAccount.priority) || 50,
lastUsedAt: boundAccount.lastUsedAt || '0'
}
]
}
} else {
logger.warn(`⚠️ Bound Gemini account ${apiKeyData.geminiAccountId} is not available`)
}
}
}
// 获取所有Gemini账户共享池
// 获取所有Gemini OAuth账户(共享池)
const geminiAccounts = await geminiAccountService.getAllAccounts()
for (const account of geminiAccounts) {
if (
@@ -223,7 +321,48 @@ class UnifiedGeminiScheduler {
}
}
logger.info(`📊 Total available Gemini accounts: ${availableAccounts.length}`)
// 如果允许调度 Gemini API 账户,则添加到可用列表
if (allowApiAccounts) {
const geminiApiAccounts = await geminiApiAccountService.getAllAccounts()
for (const account of geminiApiAccounts) {
if (
account.isActive === 'true' &&
account.status !== 'error' &&
(account.accountType === 'shared' || !account.accountType) &&
this._isSchedulable(account.schedulable)
) {
// 检查模型支持
if (requestedModel && account.supportedModels && account.supportedModels.length > 0) {
const normalizedModel = requestedModel.replace('models/', '')
const modelSupported = account.supportedModels.some(
(model) => model.replace('models/', '') === normalizedModel
)
if (!modelSupported) {
logger.debug(
`⏭️ Skipping Gemini-API account ${account.name} - doesn't support model ${requestedModel}`
)
continue
}
}
// 检查是否被限流
const isRateLimited = await this.isAccountRateLimited(account.id)
if (!isRateLimited) {
availableAccounts.push({
...account,
accountId: account.id,
accountType: 'gemini-api',
priority: parseInt(account.priority) || 50,
lastUsedAt: account.lastUsedAt || '0'
})
}
}
}
}
logger.info(
`📊 Total available accounts: ${availableAccounts.length} (Gemini OAuth + ${allowApiAccounts ? 'Gemini API' : 'no API accounts'})`
)
return availableAccounts
}
@@ -256,6 +395,17 @@ class UnifiedGeminiScheduler {
return false
}
return !(await this.isAccountRateLimited(accountId))
} else if (accountType === 'gemini-api') {
const account = await geminiApiAccountService.getAccount(accountId)
if (!account || account.isActive !== 'true' || account.status === 'error') {
return false
}
// 检查是否可调度
if (!this._isSchedulable(account.schedulable)) {
logger.info(`🚫 Gemini-API account ${accountId} is not schedulable`)
return false
}
return !(await this.isAccountRateLimited(accountId))
}
return false
} catch (error) {
@@ -344,6 +494,8 @@ class UnifiedGeminiScheduler {
try {
if (accountType === 'gemini') {
await geminiAccountService.setAccountRateLimited(accountId, true)
} else if (accountType === 'gemini-api') {
await geminiApiAccountService.setAccountRateLimited(accountId, true)
}
// 删除会话映射
@@ -366,6 +518,8 @@ class UnifiedGeminiScheduler {
try {
if (accountType === 'gemini') {
await geminiAccountService.setAccountRateLimited(accountId, false)
} else if (accountType === 'gemini-api') {
await geminiApiAccountService.setAccountRateLimited(accountId, false)
}
return { success: true }
@@ -379,9 +533,23 @@ class UnifiedGeminiScheduler {
}
// 🔍 检查账户是否处于限流状态
async isAccountRateLimited(accountId) {
async isAccountRateLimited(accountId, accountType = null) {
try {
const account = await geminiAccountService.getAccount(accountId)
let account = null
// 如果指定了账户类型,直接使用对应服务
if (accountType === 'gemini-api') {
account = await geminiApiAccountService.getAccount(accountId)
} else if (accountType === 'gemini') {
account = await geminiAccountService.getAccount(accountId)
} else {
// 未指定类型,先尝试 gemini再尝试 gemini-api
account = await geminiAccountService.getAccount(accountId)
if (!account) {
account = await geminiApiAccountService.getAccount(accountId)
}
}
if (!account) {
return false
}
@@ -389,7 +557,9 @@ class UnifiedGeminiScheduler {
if (account.rateLimitStatus === 'limited' && account.rateLimitedAt) {
const limitedAt = new Date(account.rateLimitedAt).getTime()
const now = Date.now()
const limitDuration = 60 * 60 * 1000 // 1小时
// 使用账户配置的限流时长,默认1小时
const rateLimitDuration = parseInt(account.rateLimitDuration) || 60
const limitDuration = rateLimitDuration * 60 * 1000
return now < limitedAt + limitDuration
}
@@ -400,7 +570,7 @@ class UnifiedGeminiScheduler {
}
}
// 👥 从分组中选择账户
// 👥 从分组中选择账户(支持 Gemini OAuth 和 Gemini API 两种账户类型)
async selectAccountFromGroup(groupId, sessionHash = null, requestedModel = null) {
try {
// 获取分组信息
@@ -432,8 +602,12 @@ class UnifiedGeminiScheduler {
logger.info(
`🎯 Using sticky session account from group: ${mappedAccount.accountId} (${mappedAccount.accountType}) for session ${sessionHash}`
)
// 更新账户的最后使用时间
await geminiAccountService.markAccountUsed(mappedAccount.accountId)
// 更新账户的最后使用时间(根据账户类型调用正确的服务)
if (mappedAccount.accountType === 'gemini-api') {
await geminiApiAccountService.markAccountUsed(mappedAccount.accountId)
} else {
await geminiAccountService.markAccountUsed(mappedAccount.accountId)
}
return mappedAccount
}
}
@@ -450,9 +624,17 @@ class UnifiedGeminiScheduler {
const availableAccounts = []
// 获取所有成员账户的详细信息
// 获取所有成员账户的详细信息(支持 Gemini OAuth 和 Gemini API 两种类型)
for (const memberId of memberIds) {
const account = await geminiAccountService.getAccount(memberId)
// 首先尝试从 Gemini OAuth 账户服务获取
let account = await geminiAccountService.getAccount(memberId)
let accountType = 'gemini'
// 如果 Gemini OAuth 账户不存在,尝试从 Gemini API 账户服务获取
if (!account) {
account = await geminiApiAccountService.getAccount(memberId)
accountType = 'gemini-api'
}
if (!account) {
logger.warn(`⚠️ Gemini account ${memberId} not found in group ${group.name}`)
@@ -465,13 +647,15 @@ class UnifiedGeminiScheduler {
account.status !== 'error' &&
this._isSchedulable(account.schedulable)
) {
// 检查token是否过期
const isExpired = geminiAccountService.isTokenExpired(account)
if (isExpired && !account.refreshToken) {
logger.warn(
`⚠️ Gemini account ${account.name} in group token expired and no refresh token available`
)
continue
// 对于 Gemini OAuth 账户,检查 token 是否过期
if (accountType === 'gemini') {
const isExpired = geminiAccountService.isTokenExpired(account)
if (isExpired && !account.refreshToken) {
logger.warn(
`⚠️ Gemini account ${account.name} in group token expired and no refresh token available`
)
continue
}
}
// 检查模型支持
@@ -483,19 +667,19 @@ class UnifiedGeminiScheduler {
)
if (!modelSupported) {
logger.debug(
`⏭️ Skipping Gemini account ${account.name} in group - doesn't support model ${requestedModel}`
`⏭️ Skipping ${accountType} account ${account.name} in group - doesn't support model ${requestedModel}`
)
continue
}
}
// 检查是否被限流
const isRateLimited = await this.isAccountRateLimited(account.id)
const isRateLimited = await this.isAccountRateLimited(account.id, accountType)
if (!isRateLimited) {
availableAccounts.push({
...account,
accountId: account.id,
accountType: 'gemini',
accountType,
priority: parseInt(account.priority) || 50,
lastUsedAt: account.lastUsedAt || '0'
})
@@ -529,8 +713,12 @@ class UnifiedGeminiScheduler {
`🎯 Selected account from Gemini group ${group.name}: ${selectedAccount.name} (${selectedAccount.accountId}, ${selectedAccount.accountType}) with priority ${selectedAccount.priority}`
)
// 更新账户的最后使用时间
await geminiAccountService.markAccountUsed(selectedAccount.accountId)
// 更新账户的最后使用时间(根据账户类型调用正确的服务)
if (selectedAccount.accountType === 'gemini-api') {
await geminiApiAccountService.markAccountUsed(selectedAccount.accountId)
} else {
await geminiAccountService.markAccountUsed(selectedAccount.accountId)
}
return {
accountId: selectedAccount.accountId,