fix: 修复gemini转发代理ip未使用的问题

This commit is contained in:
shaw
2025-08-21 10:55:05 +08:00
parent bd10032857
commit 102fc54e8e
3 changed files with 123 additions and 25 deletions

View File

@@ -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) {

View File

@@ -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 响应头

View File

@@ -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