diff --git a/src/routes/admin.js b/src/routes/admin.js index ba822f21..10c1757c 100644 --- a/src/routes/admin.js +++ b/src/routes/admin.js @@ -2152,7 +2152,7 @@ router.post('/bedrock-accounts/:accountId/test', authenticateAdmin, async (req, // 生成 Gemini OAuth 授权 URL router.post('/gemini-accounts/generate-auth-url', authenticateAdmin, async (req, res) => { try { - const { state } = req.body + const { state, proxy } = req.body // 接收代理配置 // 使用新的 codeassist.google.com 回调地址 const redirectUri = 'https://codeassist.google.com/authcode' @@ -2166,13 +2166,14 @@ router.post('/gemini-accounts/generate-auth-url', authenticateAdmin, async (req, redirectUri: finalRedirectUri } = await geminiAccountService.generateAuthUrl(state, redirectUri) - // 创建 OAuth 会话,包含 codeVerifier + // 创建 OAuth 会话,包含 codeVerifier 和代理配置 const sessionId = authState await redis.setOAuthSession(sessionId, { state: authState, type: 'gemini', redirectUri: finalRedirectUri, codeVerifier, // 保存 PKCE code verifier + proxy: proxy || null, // 保存代理配置 createdAt: new Date().toISOString() }) @@ -2216,7 +2217,7 @@ router.post('/gemini-accounts/poll-auth-status', authenticateAdmin, async (req, // 交换 Gemini 授权码 router.post('/gemini-accounts/exchange-code', authenticateAdmin, async (req, res) => { try { - const { code, sessionId } = req.body + const { code, sessionId, proxy: requestProxy } = req.body if (!code) { return res.status(400).json({ error: 'Authorization code is required' }) @@ -2224,21 +2225,40 @@ router.post('/gemini-accounts/exchange-code', authenticateAdmin, async (req, res let redirectUri = 'https://codeassist.google.com/authcode' let codeVerifier = null + let proxyConfig = null // 如果提供了 sessionId,从 OAuth 会话中获取信息 if (sessionId) { const sessionData = await redis.getOAuthSession(sessionId) if (sessionData) { - const { redirectUri: sessionRedirectUri, codeVerifier: sessionCodeVerifier } = sessionData + const { + redirectUri: sessionRedirectUri, + codeVerifier: sessionCodeVerifier, + proxy + } = sessionData redirectUri = sessionRedirectUri || redirectUri codeVerifier = sessionCodeVerifier + proxyConfig = proxy // 获取代理配置 logger.info( - `Using session redirect_uri: ${redirectUri}, has codeVerifier: ${!!codeVerifier}` + `Using session redirect_uri: ${redirectUri}, has codeVerifier: ${!!codeVerifier}, has proxy from session: ${!!proxyConfig}` ) } } - const tokens = await geminiAccountService.exchangeCodeForTokens(code, redirectUri, codeVerifier) + // 如果请求体中直接提供了代理配置,优先使用它 + if (requestProxy) { + proxyConfig = requestProxy + logger.info( + `Using proxy from request body: ${proxyConfig ? JSON.stringify(proxyConfig) : 'none'}` + ) + } + + const tokens = await geminiAccountService.exchangeCodeForTokens( + code, + redirectUri, + codeVerifier, + proxyConfig // 传递代理配置 + ) // 清理 OAuth 会话 if (sessionId) { diff --git a/src/routes/geminiRoutes.js b/src/routes/geminiRoutes.js index 88255b35..c5d706a3 100644 --- a/src/routes/geminiRoutes.js +++ b/src/routes/geminiRoutes.js @@ -541,12 +541,24 @@ async function handleGenerateContent(req, res) { }) const client = await geminiAccountService.getOauthClient(accessToken, refreshToken) + + // 解析账户的代理配置 + let proxyConfig = null + if (account.proxy) { + try { + proxyConfig = typeof account.proxy === 'string' ? JSON.parse(account.proxy) : account.proxy + } catch (e) { + logger.warn('Failed to parse proxy configuration:', e) + } + } + const response = await geminiAccountService.generateContent( client, { model, request: actualRequestData }, user_prompt_id, account.projectId, // 始终使用账户配置的项目ID,忽略请求中的project - req.apiKey?.id // 使用 API Key ID 作为 session ID + req.apiKey?.id, // 使用 API Key ID 作为 session ID + proxyConfig // 传递代理配置 ) // 记录使用统计 @@ -663,13 +675,25 @@ async function handleStreamGenerateContent(req, res) { }) const client = await geminiAccountService.getOauthClient(accessToken, refreshToken) + + // 解析账户的代理配置 + let proxyConfig = null + if (account.proxy) { + try { + proxyConfig = typeof account.proxy === 'string' ? JSON.parse(account.proxy) : account.proxy + } catch (e) { + logger.warn('Failed to parse proxy configuration:', e) + } + } + const streamResponse = await geminiAccountService.generateContentStream( client, { model, request: actualRequestData }, user_prompt_id, account.projectId, // 始终使用账户配置的项目ID,忽略请求中的project req.apiKey?.id, // 使用 API Key ID 作为 session ID - abortController.signal // 传递中止信号 + abortController.signal, // 传递中止信号 + proxyConfig // 传递代理配置 ) // 设置 SSE 响应头 diff --git a/src/services/geminiAccountService.js b/src/services/geminiAccountService.js index 58d0b76d..78e1d5a1 100644 --- a/src/services/geminiAccountService.js +++ b/src/services/geminiAccountService.js @@ -110,11 +110,32 @@ setInterval( 10 * 60 * 1000 ) -// 创建 OAuth2 客户端 -function createOAuth2Client(redirectUri = null) { +// 创建 OAuth2 客户端(支持代理配置) +function createOAuth2Client(redirectUri = null, proxyConfig = null) { // 如果没有提供 redirectUri,使用默认值 const uri = redirectUri || 'http://localhost:45462' - return new OAuth2Client(OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, uri) + + // 准备客户端选项 + const clientOptions = { + clientId: OAUTH_CLIENT_ID, + clientSecret: OAUTH_CLIENT_SECRET, + redirectUri: uri + } + + // 如果有代理配置,设置 transporterOptions + if (proxyConfig) { + const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig) + if (proxyAgent) { + // 通过 transporterOptions 传递代理配置给底层的 Gaxios + clientOptions.transporterOptions = { + agent: proxyAgent, + httpsAgent: proxyAgent + } + logger.debug('Created OAuth2Client with proxy configuration') + } + } + + return new OAuth2Client(clientOptions) } // 生成授权 URL (支持 PKCE) @@ -197,11 +218,25 @@ async function pollAuthorizationStatus(sessionId, maxAttempts = 60, interval = 2 } } -// 交换授权码获取 tokens (支持 PKCE) -async function exchangeCodeForTokens(code, redirectUri = null, codeVerifier = null) { - const oAuth2Client = createOAuth2Client(redirectUri) - +// 交换授权码获取 tokens (支持 PKCE 和代理) +async function exchangeCodeForTokens( + code, + redirectUri = null, + codeVerifier = null, + proxyConfig = null +) { try { + // 创建带代理配置的 OAuth2Client + const oAuth2Client = createOAuth2Client(redirectUri, proxyConfig) + + if (proxyConfig) { + logger.info( + `🌐 Using proxy for Gemini token exchange: ${ProxyHelper.getProxyDescription(proxyConfig)}` + ) + } else { + logger.debug('🌐 No proxy configured for Gemini token exchange') + } + const tokenParams = { code, redirect_uri: redirectUri @@ -230,7 +265,8 @@ async function exchangeCodeForTokens(code, redirectUri = null, codeVerifier = nu // 刷新访问令牌 async function refreshAccessToken(refreshToken, proxyConfig = null) { - const oAuth2Client = createOAuth2Client() + // 创建带代理配置的 OAuth2Client + const oAuth2Client = createOAuth2Client(null, proxyConfig) try { // 设置 refresh_token @@ -238,13 +274,7 @@ async function refreshAccessToken(refreshToken, proxyConfig = null) { refresh_token: refreshToken }) - // 配置代理(如果提供) - const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig) - if (proxyAgent) { - // Google Auth Library 使用 Axios,可以通过 transportOptions 配置代理 - oAuth2Client.transportOptions = { - httpsAgent: proxyAgent - } + if (proxyConfig) { logger.info( `🔄 Using proxy for Gemini token refresh: ${ProxyHelper.maskProxyInfo(proxyConfig)}` ) @@ -1187,7 +1217,8 @@ async function generateContent( requestData, userPromptId, projectId = null, - sessionId = null + sessionId = null, + proxyConfig = null ) { const axios = require('axios') const CODE_ASSIST_ENDPOINT = 'https://cloudcode-pa.googleapis.com' @@ -1224,6 +1255,17 @@ async function generateContent( timeout: 60000 // 生成内容可能需要更长时间 } + // 添加代理配置 + const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig) + if (proxyAgent) { + axiosConfig.httpsAgent = proxyAgent + logger.info( + `🌐 Using proxy for Gemini generateContent: ${ProxyHelper.getProxyDescription(proxyConfig)}` + ) + } else { + logger.debug('🌐 No proxy configured for Gemini generateContent') + } + const response = await axios(axiosConfig) logger.info('✅ generateContent API调用成功') @@ -1237,7 +1279,8 @@ async function generateContentStream( userPromptId, projectId = null, sessionId = null, - signal = null + signal = null, + proxyConfig = null ) { const axios = require('axios') const CODE_ASSIST_ENDPOINT = 'https://cloudcode-pa.googleapis.com' @@ -1278,6 +1321,17 @@ async function generateContentStream( timeout: 60000 } + // 添加代理配置 + const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig) + if (proxyAgent) { + axiosConfig.httpsAgent = proxyAgent + logger.info( + `🌐 Using proxy for Gemini streamGenerateContent: ${ProxyHelper.getProxyDescription(proxyConfig)}` + ) + } else { + logger.debug('🌐 No proxy configured for Gemini streamGenerateContent') + } + // 如果提供了中止信号,添加到配置中 if (signal) { axiosConfig.signal = signal