feat: 统一代理配置管理,支持IPv4/IPv6协议族选择

- 新增统一代理工具 ProxyHelper,支持 SOCKS5/HTTP/HTTPS 代理
- 添加 IPv4/IPv6 协议族配置选项,默认使用 IPv4 确保兼容性
- 移除 OpenAI 路由中硬编码的 family: 4 限制
- 统一 8 个服务文件中的代理创建逻辑,避免重复维护
- 支持 OAuth 和 token 交换过程中的代理使用
- 新增配置项:PROXY_USE_IPV4(默认 true)
- 向后兼容:现有配置无需手动更新

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
shaw
2025-08-20 22:36:34 +08:00
parent 4aa562be21
commit a45c832278
11 changed files with 219 additions and 180 deletions

View File

@@ -1,7 +1,6 @@
const { v4: uuidv4 } = require('uuid')
const crypto = require('crypto')
const { SocksProxyAgent } = require('socks-proxy-agent')
const { HttpsProxyAgent } = require('https-proxy-agent')
const ProxyHelper = require('../utils/proxyHelper')
const axios = require('axios')
const redis = require('../models/redis')
const logger = require('../utils/logger')
@@ -861,29 +860,9 @@ class ClaudeAccountService {
}
}
// 🌐 创建代理agent
// 🌐 创建代理agent(使用统一的代理工具)
_createProxyAgent(proxyConfig) {
if (!proxyConfig) {
return null
}
try {
const proxy = JSON.parse(proxyConfig)
if (proxy.type === 'socks5') {
const auth = proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : ''
const socksUrl = `socks5://${auth}${proxy.host}:${proxy.port}`
return new SocksProxyAgent(socksUrl)
} else if (proxy.type === 'http' || proxy.type === 'https') {
const auth = proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : ''
const httpUrl = `${proxy.type}://${auth}${proxy.host}:${proxy.port}`
return new HttpsProxyAgent(httpUrl)
}
} catch (error) {
logger.warn('⚠️ Invalid proxy configuration:', error)
}
return null
return ProxyHelper.createProxyAgent(proxyConfig)
}
// 🔐 加密敏感数据

View File

@@ -1,7 +1,6 @@
const { v4: uuidv4 } = require('uuid')
const crypto = require('crypto')
const { SocksProxyAgent } = require('socks-proxy-agent')
const { HttpsProxyAgent } = require('https-proxy-agent')
const ProxyHelper = require('../utils/proxyHelper')
const redis = require('../models/redis')
const logger = require('../utils/logger')
const config = require('../../config/config')
@@ -480,29 +479,9 @@ class ClaudeConsoleAccountService {
}
}
// 🌐 创建代理agent
// 🌐 创建代理agent(使用统一的代理工具)
_createProxyAgent(proxyConfig) {
if (!proxyConfig) {
return null
}
try {
const proxy = typeof proxyConfig === 'string' ? JSON.parse(proxyConfig) : proxyConfig
if (proxy.type === 'socks5') {
const auth = proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : ''
const socksUrl = `socks5://${auth}${proxy.host}:${proxy.port}`
return new SocksProxyAgent(socksUrl)
} else if (proxy.type === 'http' || proxy.type === 'https') {
const auth = proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : ''
const httpUrl = `${proxy.type}://${auth}${proxy.host}:${proxy.port}`
return new HttpsProxyAgent(httpUrl)
}
} catch (error) {
logger.warn('⚠️ Invalid proxy configuration:', error)
}
return null
return ProxyHelper.createProxyAgent(proxyConfig)
}
// 🔐 加密敏感数据

View File

@@ -2,8 +2,7 @@ const https = require('https')
const zlib = require('zlib')
const fs = require('fs')
const path = require('path')
const { SocksProxyAgent } = require('socks-proxy-agent')
const { HttpsProxyAgent } = require('https-proxy-agent')
const ProxyHelper = require('../utils/proxyHelper')
const claudeAccountService = require('./claudeAccountService')
const unifiedClaudeScheduler = require('./unifiedClaudeScheduler')
const sessionHelper = require('../utils/sessionHelper')
@@ -496,7 +495,7 @@ class ClaudeRelayService {
}
}
// 🌐 获取代理Agent
// 🌐 获取代理Agent(使用统一的代理工具)
async _getProxyAgent(accountId) {
try {
const accountData = await claudeAccountService.getAllAccounts()
@@ -506,22 +505,11 @@ class ClaudeRelayService {
return null
}
const { proxy } = account
if (proxy.type === 'socks5') {
const auth = proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : ''
const socksUrl = `socks5://${auth}${proxy.host}:${proxy.port}`
return new SocksProxyAgent(socksUrl)
} else if (proxy.type === 'http' || proxy.type === 'https') {
const auth = proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : ''
const httpUrl = `${proxy.type}://${auth}${proxy.host}:${proxy.port}`
return new HttpsProxyAgent(httpUrl)
}
return ProxyHelper.createProxyAgent(account.proxy)
} catch (error) {
logger.warn('⚠️ Failed to create proxy agent:', error)
return null
}
return null
}
// 🔧 过滤客户端请求头

View File

@@ -1,6 +1,5 @@
const axios = require('axios')
const { HttpsProxyAgent } = require('https-proxy-agent')
const { SocksProxyAgent } = require('socks-proxy-agent')
const ProxyHelper = require('../utils/proxyHelper')
const logger = require('../utils/logger')
const config = require('../../config/config')
const apiKeyService = require('./apiKeyService')
@@ -9,34 +8,9 @@ const apiKeyService = require('./apiKeyService')
const GEMINI_API_BASE = 'https://cloudcode.googleapis.com/v1'
const DEFAULT_MODEL = 'models/gemini-2.0-flash-exp'
// 创建代理 agent
// 创建代理 agent(使用统一的代理工具)
function createProxyAgent(proxyConfig) {
if (!proxyConfig) {
return null
}
try {
const proxy = typeof proxyConfig === 'string' ? JSON.parse(proxyConfig) : proxyConfig
if (!proxy.type || !proxy.host || !proxy.port) {
return null
}
const proxyUrl =
proxy.username && proxy.password
? `${proxy.type}://${proxy.username}:${proxy.password}@${proxy.host}:${proxy.port}`
: `${proxy.type}://${proxy.host}:${proxy.port}`
if (proxy.type === 'socks5') {
return new SocksProxyAgent(proxyUrl)
} else if (proxy.type === 'http' || proxy.type === 'https') {
return new HttpsProxyAgent(proxyUrl)
}
} catch (error) {
logger.error('Error creating proxy agent:', error)
}
return null
return ProxyHelper.createProxyAgent(proxyConfig)
}
// 转换 OpenAI 消息格式到 Gemini 格式

View File

@@ -2,8 +2,7 @@ const redisClient = require('../models/redis')
const { v4: uuidv4 } = require('uuid')
const crypto = require('crypto')
const axios = require('axios')
const { SocksProxyAgent } = require('socks-proxy-agent')
const { HttpsProxyAgent } = require('https-proxy-agent')
const ProxyHelper = require('../utils/proxyHelper')
const config = require('../../config/config')
const logger = require('../utils/logger')
// const { maskToken } = require('../utils/tokenMask')
@@ -133,18 +132,9 @@ async function refreshAccessToken(refreshToken, proxy = null) {
}
// 配置代理(如果有)
if (proxy && proxy.host && proxy.port) {
if (proxy.type === 'socks5') {
const proxyAuth =
proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : ''
const socksProxy = `socks5://${proxyAuth}${proxy.host}:${proxy.port}`
requestOptions.httpsAgent = new SocksProxyAgent(socksProxy)
} else if (proxy.type === 'http' || proxy.type === 'https') {
const proxyAuth =
proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : ''
const httpProxy = `http://${proxyAuth}${proxy.host}:${proxy.port}`
requestOptions.httpsAgent = new HttpsProxyAgent(httpProxy)
}
const proxyAgent = ProxyHelper.createProxyAgent(proxy)
if (proxyAgent) {
requestOptions.httpsAgent = proxyAgent
}
// 发送请求