mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 09:21:24 +00:00
fix: 代理ip使用重构为统一方法
This commit is contained in:
@@ -168,7 +168,9 @@ router.post('/responses', authenticateApiKey, async (req, res) => {
|
|||||||
// 如果有代理,添加代理配置
|
// 如果有代理,添加代理配置
|
||||||
if (proxyAgent) {
|
if (proxyAgent) {
|
||||||
axiosConfig.httpsAgent = proxyAgent
|
axiosConfig.httpsAgent = proxyAgent
|
||||||
logger.info('Using proxy for OpenAI request')
|
logger.info(`🌐 Using proxy for OpenAI request: ${ProxyHelper.getProxyDescription(proxy)}`)
|
||||||
|
} else {
|
||||||
|
logger.debug('🌐 No proxy configured for OpenAI request')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据 stream 参数决定请求类型
|
// 根据 stream 参数决定请求类型
|
||||||
|
|||||||
@@ -862,7 +862,17 @@ class ClaudeAccountService {
|
|||||||
|
|
||||||
// 🌐 创建代理agent(使用统一的代理工具)
|
// 🌐 创建代理agent(使用统一的代理工具)
|
||||||
_createProxyAgent(proxyConfig) {
|
_createProxyAgent(proxyConfig) {
|
||||||
return ProxyHelper.createProxyAgent(proxyConfig)
|
const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig)
|
||||||
|
if (proxyAgent) {
|
||||||
|
logger.info(
|
||||||
|
`🌐 Using proxy for Claude request: ${ProxyHelper.getProxyDescription(proxyConfig)}`
|
||||||
|
)
|
||||||
|
} else if (proxyConfig) {
|
||||||
|
logger.debug('🌐 Failed to create proxy agent for Claude')
|
||||||
|
} else {
|
||||||
|
logger.debug('🌐 No proxy configured for Claude request')
|
||||||
|
}
|
||||||
|
return proxyAgent
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔐 加密敏感数据
|
// 🔐 加密敏感数据
|
||||||
|
|||||||
@@ -481,7 +481,17 @@ class ClaudeConsoleAccountService {
|
|||||||
|
|
||||||
// 🌐 创建代理agent(使用统一的代理工具)
|
// 🌐 创建代理agent(使用统一的代理工具)
|
||||||
_createProxyAgent(proxyConfig) {
|
_createProxyAgent(proxyConfig) {
|
||||||
return ProxyHelper.createProxyAgent(proxyConfig)
|
const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig)
|
||||||
|
if (proxyAgent) {
|
||||||
|
logger.info(
|
||||||
|
`🌐 Using proxy for Claude Console request: ${ProxyHelper.getProxyDescription(proxyConfig)}`
|
||||||
|
)
|
||||||
|
} else if (proxyConfig) {
|
||||||
|
logger.debug('🌐 Failed to create proxy agent for Claude Console')
|
||||||
|
} else {
|
||||||
|
logger.debug('🌐 No proxy configured for Claude Console request')
|
||||||
|
}
|
||||||
|
return proxyAgent
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🔐 加密敏感数据
|
// 🔐 加密敏感数据
|
||||||
|
|||||||
@@ -502,10 +502,17 @@ class ClaudeRelayService {
|
|||||||
const account = accountData.find((acc) => acc.id === accountId)
|
const account = accountData.find((acc) => acc.id === accountId)
|
||||||
|
|
||||||
if (!account || !account.proxy) {
|
if (!account || !account.proxy) {
|
||||||
|
logger.debug('🌐 No proxy configured for Claude account')
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return ProxyHelper.createProxyAgent(account.proxy)
|
const proxyAgent = ProxyHelper.createProxyAgent(account.proxy)
|
||||||
|
if (proxyAgent) {
|
||||||
|
logger.info(
|
||||||
|
`🌐 Using proxy for Claude request: ${ProxyHelper.getProxyDescription(account.proxy)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return proxyAgent
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn('⚠️ Failed to create proxy agent:', error)
|
logger.warn('⚠️ Failed to create proxy agent:', error)
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const config = require('../../config/config')
|
|||||||
const logger = require('../utils/logger')
|
const logger = require('../utils/logger')
|
||||||
const { OAuth2Client } = require('google-auth-library')
|
const { OAuth2Client } = require('google-auth-library')
|
||||||
const { maskToken } = require('../utils/tokenMask')
|
const { maskToken } = require('../utils/tokenMask')
|
||||||
|
const ProxyHelper = require('../utils/proxyHelper')
|
||||||
const {
|
const {
|
||||||
logRefreshStart,
|
logRefreshStart,
|
||||||
logRefreshSuccess,
|
logRefreshSuccess,
|
||||||
@@ -228,7 +229,7 @@ async function exchangeCodeForTokens(code, redirectUri = null, codeVerifier = nu
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 刷新访问令牌
|
// 刷新访问令牌
|
||||||
async function refreshAccessToken(refreshToken) {
|
async function refreshAccessToken(refreshToken, proxyConfig = null) {
|
||||||
const oAuth2Client = createOAuth2Client()
|
const oAuth2Client = createOAuth2Client()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -237,6 +238,20 @@ async function refreshAccessToken(refreshToken) {
|
|||||||
refresh_token: refreshToken
|
refresh_token: refreshToken
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 配置代理(如果提供)
|
||||||
|
const proxyAgent = ProxyHelper.createProxyAgent(proxyConfig)
|
||||||
|
if (proxyAgent) {
|
||||||
|
// Google Auth Library 使用 Axios,可以通过 transportOptions 配置代理
|
||||||
|
oAuth2Client.transportOptions = {
|
||||||
|
httpsAgent: proxyAgent
|
||||||
|
}
|
||||||
|
logger.info(
|
||||||
|
`🔄 Using proxy for Gemini token refresh: ${ProxyHelper.maskProxyInfo(proxyConfig)}`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
logger.debug('🔄 No proxy configured for Gemini token refresh')
|
||||||
|
}
|
||||||
|
|
||||||
// 调用 refreshAccessToken 获取新的 tokens
|
// 调用 refreshAccessToken 获取新的 tokens
|
||||||
const response = await oAuth2Client.refreshAccessToken()
|
const response = await oAuth2Client.refreshAccessToken()
|
||||||
const { credentials } = response
|
const { credentials } = response
|
||||||
@@ -261,7 +276,9 @@ async function refreshAccessToken(refreshToken) {
|
|||||||
logger.error('Error refreshing access token:', {
|
logger.error('Error refreshing access token:', {
|
||||||
message: error.message,
|
message: error.message,
|
||||||
code: error.code,
|
code: error.code,
|
||||||
response: error.response?.data
|
response: error.response?.data,
|
||||||
|
hasProxy: !!proxyConfig,
|
||||||
|
proxy: proxyConfig ? ProxyHelper.maskProxyInfo(proxyConfig) : 'No proxy'
|
||||||
})
|
})
|
||||||
throw new Error(`Failed to refresh access token: ${error.message}`)
|
throw new Error(`Failed to refresh access token: ${error.message}`)
|
||||||
}
|
}
|
||||||
@@ -786,7 +803,8 @@ async function refreshAccountToken(accountId) {
|
|||||||
logger.info(`🔄 Starting token refresh for Gemini account: ${account.name} (${accountId})`)
|
logger.info(`🔄 Starting token refresh for Gemini account: ${account.name} (${accountId})`)
|
||||||
|
|
||||||
// account.refreshToken 已经是解密后的值(从 getAccount 返回)
|
// account.refreshToken 已经是解密后的值(从 getAccount 返回)
|
||||||
const newTokens = await refreshAccessToken(account.refreshToken)
|
// 传入账户的代理配置
|
||||||
|
const newTokens = await refreshAccessToken(account.refreshToken, account.proxy)
|
||||||
|
|
||||||
// 更新账户信息
|
// 更新账户信息
|
||||||
const updates = {
|
const updates = {
|
||||||
|
|||||||
@@ -280,7 +280,9 @@ async function sendGeminiRequest({
|
|||||||
const proxyAgent = createProxyAgent(proxy)
|
const proxyAgent = createProxyAgent(proxy)
|
||||||
if (proxyAgent) {
|
if (proxyAgent) {
|
||||||
axiosConfig.httpsAgent = proxyAgent
|
axiosConfig.httpsAgent = proxyAgent
|
||||||
logger.debug('Using proxy for Gemini request')
|
logger.info(`🌐 Using proxy for Gemini API request: ${ProxyHelper.getProxyDescription(proxy)}`)
|
||||||
|
} else {
|
||||||
|
logger.debug('🌐 No proxy configured for Gemini API request')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加 AbortController 信号支持
|
// 添加 AbortController 信号支持
|
||||||
@@ -386,6 +388,11 @@ async function getAvailableModels(accessToken, proxy, projectId, location = 'us-
|
|||||||
const proxyAgent = createProxyAgent(proxy)
|
const proxyAgent = createProxyAgent(proxy)
|
||||||
if (proxyAgent) {
|
if (proxyAgent) {
|
||||||
axiosConfig.httpsAgent = proxyAgent
|
axiosConfig.httpsAgent = proxyAgent
|
||||||
|
logger.info(
|
||||||
|
`🌐 Using proxy for Gemini models request: ${ProxyHelper.getProxyDescription(proxy)}`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
logger.debug('🌐 No proxy configured for Gemini models request')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -482,7 +489,11 @@ async function countTokens({
|
|||||||
const proxyAgent = createProxyAgent(proxy)
|
const proxyAgent = createProxyAgent(proxy)
|
||||||
if (proxyAgent) {
|
if (proxyAgent) {
|
||||||
axiosConfig.httpsAgent = proxyAgent
|
axiosConfig.httpsAgent = proxyAgent
|
||||||
logger.debug('Using proxy for Gemini countTokens request')
|
logger.info(
|
||||||
|
`🌐 Using proxy for Gemini countTokens request: ${ProxyHelper.getProxyDescription(proxy)}`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
logger.debug('🌐 No proxy configured for Gemini countTokens request')
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -135,6 +135,11 @@ async function refreshAccessToken(refreshToken, proxy = null) {
|
|||||||
const proxyAgent = ProxyHelper.createProxyAgent(proxy)
|
const proxyAgent = ProxyHelper.createProxyAgent(proxy)
|
||||||
if (proxyAgent) {
|
if (proxyAgent) {
|
||||||
requestOptions.httpsAgent = proxyAgent
|
requestOptions.httpsAgent = proxyAgent
|
||||||
|
logger.info(
|
||||||
|
`🌐 Using proxy for OpenAI token refresh: ${ProxyHelper.getProxyDescription(proxy)}`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
logger.debug('🌐 No proxy configured for OpenAI token refresh')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送请求
|
// 发送请求
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ const path = require('path')
|
|||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const os = require('os')
|
const os = require('os')
|
||||||
|
|
||||||
// 安全的 JSON 序列化函数,处理循环引用
|
// 安全的 JSON 序列化函数,处理循环引用和特殊字符
|
||||||
const safeStringify = (obj, maxDepth = 3, fullDepth = false) => {
|
const safeStringify = (obj, maxDepth = 3, fullDepth = false) => {
|
||||||
const seen = new WeakSet()
|
const seen = new WeakSet()
|
||||||
// 如果是fullDepth模式,增加深度限制
|
// 如果是fullDepth模式,增加深度限制
|
||||||
@@ -16,6 +16,28 @@ const safeStringify = (obj, maxDepth = 3, fullDepth = false) => {
|
|||||||
return '[Max Depth Reached]'
|
return '[Max Depth Reached]'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理字符串值,清理可能导致JSON解析错误的特殊字符
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
// 移除或转义可能导致JSON解析错误的字符
|
||||||
|
let cleanValue = value
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '') // 移除控制字符
|
||||||
|
.replace(/[\uD800-\uDFFF]/g, '') // 移除孤立的代理对字符
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
.replace(/\u0000/g, '') // 移除NUL字节
|
||||||
|
|
||||||
|
// 如果字符串过长,截断并添加省略号
|
||||||
|
if (cleanValue.length > 1000) {
|
||||||
|
cleanValue = `${cleanValue.substring(0, 997)}...`
|
||||||
|
}
|
||||||
|
|
||||||
|
return cleanValue
|
||||||
|
} catch (error) {
|
||||||
|
return '[Invalid String Data]'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (value !== null && typeof value === 'object') {
|
if (value !== null && typeof value === 'object') {
|
||||||
if (seen.has(value)) {
|
if (seen.has(value)) {
|
||||||
return '[Circular Reference]'
|
return '[Circular Reference]'
|
||||||
@@ -40,7 +62,10 @@ const safeStringify = (obj, maxDepth = 3, fullDepth = false) => {
|
|||||||
} else {
|
} else {
|
||||||
const result = {}
|
const result = {}
|
||||||
for (const [k, v] of Object.entries(value)) {
|
for (const [k, v] of Object.entries(value)) {
|
||||||
result[k] = replacer(k, v, depth + 1)
|
// 确保键名也是安全的
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
const safeKey = typeof k === 'string' ? k.replace(/[\u0000-\u001F\u007F]/g, '') : k
|
||||||
|
result[safeKey] = replacer(safeKey, v, depth + 1)
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
@@ -50,9 +75,20 @@ const safeStringify = (obj, maxDepth = 3, fullDepth = false) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return JSON.stringify(replacer('', obj))
|
const processed = replacer('', obj)
|
||||||
|
return JSON.stringify(processed)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return JSON.stringify({ error: 'Failed to serialize object', message: error.message })
|
// 如果JSON.stringify仍然失败,使用更保守的方法
|
||||||
|
try {
|
||||||
|
return JSON.stringify({
|
||||||
|
error: 'Failed to serialize object',
|
||||||
|
message: error.message,
|
||||||
|
type: typeof obj,
|
||||||
|
keys: obj && typeof obj === 'object' ? Object.keys(obj) : undefined
|
||||||
|
})
|
||||||
|
} catch (finalError) {
|
||||||
|
return '{"error":"Critical serialization failure","message":"Unable to serialize any data"}'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,14 @@ async function exchangeCodeForTokens(authorizationCode, codeVerifier, state, pro
|
|||||||
const agent = createProxyAgent(proxyConfig)
|
const agent = createProxyAgent(proxyConfig)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (agent) {
|
||||||
|
logger.info(
|
||||||
|
`🌐 Using proxy for OAuth token exchange: ${ProxyHelper.maskProxyInfo(proxyConfig)}`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
logger.debug('🌐 No proxy configured for OAuth token exchange')
|
||||||
|
}
|
||||||
|
|
||||||
logger.debug('🔄 Attempting OAuth token exchange', {
|
logger.debug('🔄 Attempting OAuth token exchange', {
|
||||||
url: OAUTH_CONFIG.TOKEN_URL,
|
url: OAUTH_CONFIG.TOKEN_URL,
|
||||||
codeLength: cleanedCode.length,
|
codeLength: cleanedCode.length,
|
||||||
@@ -354,6 +362,14 @@ async function exchangeSetupTokenCode(authorizationCode, codeVerifier, state, pr
|
|||||||
const agent = createProxyAgent(proxyConfig)
|
const agent = createProxyAgent(proxyConfig)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (agent) {
|
||||||
|
logger.info(
|
||||||
|
`🌐 Using proxy for Setup Token exchange: ${ProxyHelper.maskProxyInfo(proxyConfig)}`
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
logger.debug('🌐 No proxy configured for Setup Token exchange')
|
||||||
|
}
|
||||||
|
|
||||||
logger.debug('🔄 Attempting Setup Token exchange', {
|
logger.debug('🔄 Attempting Setup Token exchange', {
|
||||||
url: OAUTH_CONFIG.TOKEN_URL,
|
url: OAUTH_CONFIG.TOKEN_URL,
|
||||||
codeLength: cleanedCode.length,
|
codeLength: cleanedCode.length,
|
||||||
|
|||||||
@@ -163,6 +163,39 @@ class ProxyHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 脱敏代理配置信息用于日志记录
|
||||||
|
* @param {object|string} proxyConfig - 代理配置
|
||||||
|
* @returns {string} 脱敏后的代理信息
|
||||||
|
*/
|
||||||
|
static maskProxyInfo(proxyConfig) {
|
||||||
|
if (!proxyConfig) {
|
||||||
|
return 'No proxy'
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const proxy = typeof proxyConfig === 'string' ? JSON.parse(proxyConfig) : proxyConfig
|
||||||
|
|
||||||
|
let proxyDesc = `${proxy.type}://${proxy.host}:${proxy.port}`
|
||||||
|
|
||||||
|
// 如果有认证信息,进行脱敏处理
|
||||||
|
if (proxy.username && proxy.password) {
|
||||||
|
const maskedUsername =
|
||||||
|
proxy.username.length <= 2
|
||||||
|
? proxy.username
|
||||||
|
: proxy.username[0] +
|
||||||
|
'*'.repeat(Math.max(1, proxy.username.length - 2)) +
|
||||||
|
proxy.username.slice(-1)
|
||||||
|
const maskedPassword = '*'.repeat(Math.min(8, proxy.password.length))
|
||||||
|
proxyDesc += ` (auth: ${maskedUsername}:${maskedPassword})`
|
||||||
|
}
|
||||||
|
|
||||||
|
return proxyDesc
|
||||||
|
} catch (error) {
|
||||||
|
return 'Invalid proxy config'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建代理 Agent(兼容旧的函数接口)
|
* 创建代理 Agent(兼容旧的函数接口)
|
||||||
* @param {object|string|null} proxyConfig - 代理配置
|
* @param {object|string|null} proxyConfig - 代理配置
|
||||||
|
|||||||
Reference in New Issue
Block a user