mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 20:41:03 +00:00
fix: 修复gemini转发代理ip未使用的问题
This commit is contained in:
@@ -2152,7 +2152,7 @@ router.post('/bedrock-accounts/:accountId/test', authenticateAdmin, async (req,
|
|||||||
// 生成 Gemini OAuth 授权 URL
|
// 生成 Gemini OAuth 授权 URL
|
||||||
router.post('/gemini-accounts/generate-auth-url', authenticateAdmin, async (req, res) => {
|
router.post('/gemini-accounts/generate-auth-url', authenticateAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { state } = req.body
|
const { state, proxy } = req.body // 接收代理配置
|
||||||
|
|
||||||
// 使用新的 codeassist.google.com 回调地址
|
// 使用新的 codeassist.google.com 回调地址
|
||||||
const redirectUri = 'https://codeassist.google.com/authcode'
|
const redirectUri = 'https://codeassist.google.com/authcode'
|
||||||
@@ -2166,13 +2166,14 @@ router.post('/gemini-accounts/generate-auth-url', authenticateAdmin, async (req,
|
|||||||
redirectUri: finalRedirectUri
|
redirectUri: finalRedirectUri
|
||||||
} = await geminiAccountService.generateAuthUrl(state, redirectUri)
|
} = await geminiAccountService.generateAuthUrl(state, redirectUri)
|
||||||
|
|
||||||
// 创建 OAuth 会话,包含 codeVerifier
|
// 创建 OAuth 会话,包含 codeVerifier 和代理配置
|
||||||
const sessionId = authState
|
const sessionId = authState
|
||||||
await redis.setOAuthSession(sessionId, {
|
await redis.setOAuthSession(sessionId, {
|
||||||
state: authState,
|
state: authState,
|
||||||
type: 'gemini',
|
type: 'gemini',
|
||||||
redirectUri: finalRedirectUri,
|
redirectUri: finalRedirectUri,
|
||||||
codeVerifier, // 保存 PKCE code verifier
|
codeVerifier, // 保存 PKCE code verifier
|
||||||
|
proxy: proxy || null, // 保存代理配置
|
||||||
createdAt: new Date().toISOString()
|
createdAt: new Date().toISOString()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -2216,7 +2217,7 @@ router.post('/gemini-accounts/poll-auth-status', authenticateAdmin, async (req,
|
|||||||
// 交换 Gemini 授权码
|
// 交换 Gemini 授权码
|
||||||
router.post('/gemini-accounts/exchange-code', authenticateAdmin, async (req, res) => {
|
router.post('/gemini-accounts/exchange-code', authenticateAdmin, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { code, sessionId } = req.body
|
const { code, sessionId, proxy: requestProxy } = req.body
|
||||||
|
|
||||||
if (!code) {
|
if (!code) {
|
||||||
return res.status(400).json({ error: 'Authorization code is required' })
|
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 redirectUri = 'https://codeassist.google.com/authcode'
|
||||||
let codeVerifier = null
|
let codeVerifier = null
|
||||||
|
let proxyConfig = null
|
||||||
|
|
||||||
// 如果提供了 sessionId,从 OAuth 会话中获取信息
|
// 如果提供了 sessionId,从 OAuth 会话中获取信息
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
const sessionData = await redis.getOAuthSession(sessionId)
|
const sessionData = await redis.getOAuthSession(sessionId)
|
||||||
if (sessionData) {
|
if (sessionData) {
|
||||||
const { redirectUri: sessionRedirectUri, codeVerifier: sessionCodeVerifier } = sessionData
|
const {
|
||||||
|
redirectUri: sessionRedirectUri,
|
||||||
|
codeVerifier: sessionCodeVerifier,
|
||||||
|
proxy
|
||||||
|
} = sessionData
|
||||||
redirectUri = sessionRedirectUri || redirectUri
|
redirectUri = sessionRedirectUri || redirectUri
|
||||||
codeVerifier = sessionCodeVerifier
|
codeVerifier = sessionCodeVerifier
|
||||||
|
proxyConfig = proxy // 获取代理配置
|
||||||
logger.info(
|
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 会话
|
// 清理 OAuth 会话
|
||||||
if (sessionId) {
|
if (sessionId) {
|
||||||
|
|||||||
@@ -541,12 +541,24 @@ async function handleGenerateContent(req, res) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const client = await geminiAccountService.getOauthClient(accessToken, refreshToken)
|
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(
|
const response = await geminiAccountService.generateContent(
|
||||||
client,
|
client,
|
||||||
{ model, request: actualRequestData },
|
{ model, request: actualRequestData },
|
||||||
user_prompt_id,
|
user_prompt_id,
|
||||||
account.projectId, // 始终使用账户配置的项目ID,忽略请求中的project
|
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)
|
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(
|
const streamResponse = await geminiAccountService.generateContentStream(
|
||||||
client,
|
client,
|
||||||
{ model, request: actualRequestData },
|
{ model, request: actualRequestData },
|
||||||
user_prompt_id,
|
user_prompt_id,
|
||||||
account.projectId, // 始终使用账户配置的项目ID,忽略请求中的project
|
account.projectId, // 始终使用账户配置的项目ID,忽略请求中的project
|
||||||
req.apiKey?.id, // 使用 API Key ID 作为 session ID
|
req.apiKey?.id, // 使用 API Key ID 作为 session ID
|
||||||
abortController.signal // 传递中止信号
|
abortController.signal, // 传递中止信号
|
||||||
|
proxyConfig // 传递代理配置
|
||||||
)
|
)
|
||||||
|
|
||||||
// 设置 SSE 响应头
|
// 设置 SSE 响应头
|
||||||
|
|||||||
@@ -110,11 +110,32 @@ setInterval(
|
|||||||
10 * 60 * 1000
|
10 * 60 * 1000
|
||||||
)
|
)
|
||||||
|
|
||||||
// 创建 OAuth2 客户端
|
// 创建 OAuth2 客户端(支持代理配置)
|
||||||
function createOAuth2Client(redirectUri = null) {
|
function createOAuth2Client(redirectUri = null, proxyConfig = null) {
|
||||||
// 如果没有提供 redirectUri,使用默认值
|
// 如果没有提供 redirectUri,使用默认值
|
||||||
const uri = redirectUri || 'http://localhost:45462'
|
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)
|
// 生成授权 URL (支持 PKCE)
|
||||||
@@ -197,11 +218,25 @@ async function pollAuthorizationStatus(sessionId, maxAttempts = 60, interval = 2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 交换授权码获取 tokens (支持 PKCE)
|
// 交换授权码获取 tokens (支持 PKCE 和代理)
|
||||||
async function exchangeCodeForTokens(code, redirectUri = null, codeVerifier = null) {
|
async function exchangeCodeForTokens(
|
||||||
const oAuth2Client = createOAuth2Client(redirectUri)
|
code,
|
||||||
|
redirectUri = null,
|
||||||
|
codeVerifier = null,
|
||||||
|
proxyConfig = null
|
||||||
|
) {
|
||||||
try {
|
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 = {
|
const tokenParams = {
|
||||||
code,
|
code,
|
||||||
redirect_uri: redirectUri
|
redirect_uri: redirectUri
|
||||||
@@ -230,7 +265,8 @@ async function exchangeCodeForTokens(code, redirectUri = null, codeVerifier = nu
|
|||||||
|
|
||||||
// 刷新访问令牌
|
// 刷新访问令牌
|
||||||
async function refreshAccessToken(refreshToken, proxyConfig = null) {
|
async function refreshAccessToken(refreshToken, proxyConfig = null) {
|
||||||
const oAuth2Client = createOAuth2Client()
|
// 创建带代理配置的 OAuth2Client
|
||||||
|
const oAuth2Client = createOAuth2Client(null, proxyConfig)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 设置 refresh_token
|
// 设置 refresh_token
|
||||||
@@ -238,13 +274,7 @@ async function refreshAccessToken(refreshToken, proxyConfig = null) {
|
|||||||
refresh_token: refreshToken
|
refresh_token: refreshToken
|
||||||
})
|
})
|
||||||
|
|
||||||
// 配置代理(如果提供)
|
if (proxyConfig) {
|
||||||
const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig)
|
|
||||||
if (proxyAgent) {
|
|
||||||
// Google Auth Library 使用 Axios,可以通过 transportOptions 配置代理
|
|
||||||
oAuth2Client.transportOptions = {
|
|
||||||
httpsAgent: proxyAgent
|
|
||||||
}
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`🔄 Using proxy for Gemini token refresh: ${ProxyHelper.maskProxyInfo(proxyConfig)}`
|
`🔄 Using proxy for Gemini token refresh: ${ProxyHelper.maskProxyInfo(proxyConfig)}`
|
||||||
)
|
)
|
||||||
@@ -1187,7 +1217,8 @@ async function generateContent(
|
|||||||
requestData,
|
requestData,
|
||||||
userPromptId,
|
userPromptId,
|
||||||
projectId = null,
|
projectId = null,
|
||||||
sessionId = null
|
sessionId = null,
|
||||||
|
proxyConfig = null
|
||||||
) {
|
) {
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const CODE_ASSIST_ENDPOINT = 'https://cloudcode-pa.googleapis.com'
|
const CODE_ASSIST_ENDPOINT = 'https://cloudcode-pa.googleapis.com'
|
||||||
@@ -1224,6 +1255,17 @@ async function generateContent(
|
|||||||
timeout: 60000 // 生成内容可能需要更长时间
|
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)
|
const response = await axios(axiosConfig)
|
||||||
|
|
||||||
logger.info('✅ generateContent API调用成功')
|
logger.info('✅ generateContent API调用成功')
|
||||||
@@ -1237,7 +1279,8 @@ async function generateContentStream(
|
|||||||
userPromptId,
|
userPromptId,
|
||||||
projectId = null,
|
projectId = null,
|
||||||
sessionId = null,
|
sessionId = null,
|
||||||
signal = null
|
signal = null,
|
||||||
|
proxyConfig = null
|
||||||
) {
|
) {
|
||||||
const axios = require('axios')
|
const axios = require('axios')
|
||||||
const CODE_ASSIST_ENDPOINT = 'https://cloudcode-pa.googleapis.com'
|
const CODE_ASSIST_ENDPOINT = 'https://cloudcode-pa.googleapis.com'
|
||||||
@@ -1278,6 +1321,17 @@ async function generateContentStream(
|
|||||||
timeout: 60000
|
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) {
|
if (signal) {
|
||||||
axiosConfig.signal = signal
|
axiosConfig.signal = signal
|
||||||
|
|||||||
Reference in New Issue
Block a user