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

@@ -4,8 +4,7 @@
*/
const crypto = require('crypto')
const { SocksProxyAgent } = require('socks-proxy-agent')
const { HttpsProxyAgent } = require('https-proxy-agent')
const ProxyHelper = require('./proxyHelper')
const axios = require('axios')
const logger = require('./logger')
@@ -125,36 +124,12 @@ function generateSetupTokenParams() {
}
/**
* 创建代理agent
* 创建代理agent(使用统一的代理工具)
* @param {object|null} proxyConfig - 代理配置对象
* @returns {object|null} 代理agent或null
*/
function createProxyAgent(proxyConfig) {
if (!proxyConfig) {
return null
}
try {
if (proxyConfig.type === 'socks5') {
const auth =
proxyConfig.username && proxyConfig.password
? `${proxyConfig.username}:${proxyConfig.password}@`
: ''
const socksUrl = `socks5://${auth}${proxyConfig.host}:${proxyConfig.port}`
return new SocksProxyAgent(socksUrl)
} else if (proxyConfig.type === 'http' || proxyConfig.type === 'https') {
const auth =
proxyConfig.username && proxyConfig.password
? `${proxyConfig.username}:${proxyConfig.password}@`
: ''
const httpUrl = `${proxyConfig.type}://${auth}${proxyConfig.host}:${proxyConfig.port}`
return new HttpsProxyAgent(httpUrl)
}
} catch (error) {
console.warn('⚠️ Invalid proxy configuration:', error)
}
return null
return ProxyHelper.createProxyAgent(proxyConfig)
}
/**

179
src/utils/proxyHelper.js Normal file
View File

@@ -0,0 +1,179 @@
const { SocksProxyAgent } = require('socks-proxy-agent')
const { HttpsProxyAgent } = require('https-proxy-agent')
const logger = require('./logger')
const config = require('../../config/config')
/**
* 统一的代理创建工具
* 支持 SOCKS5 和 HTTP/HTTPS 代理,可配置 IPv4/IPv6
*/
class ProxyHelper {
/**
* 创建代理 Agent
* @param {object|string|null} proxyConfig - 代理配置对象或 JSON 字符串
* @param {object} options - 额外选项
* @param {boolean|number} options.useIPv4 - 是否使用 IPv4 (true=IPv4, false=IPv6, undefined=auto)
* @returns {Agent|null} 代理 Agent 实例或 null
*/
static createProxyAgent(proxyConfig, options = {}) {
if (!proxyConfig) {
return null
}
try {
// 解析代理配置
const proxy = typeof proxyConfig === 'string' ? JSON.parse(proxyConfig) : proxyConfig
// 验证必要字段
if (!proxy.type || !proxy.host || !proxy.port) {
logger.warn('⚠️ Invalid proxy configuration: missing required fields (type, host, port)')
return null
}
// 获取 IPv4/IPv6 配置
const useIPv4 = ProxyHelper._getIPFamilyPreference(options.useIPv4)
// 构建认证信息
const auth = proxy.username && proxy.password ? `${proxy.username}:${proxy.password}@` : ''
// 根据代理类型创建 Agent
if (proxy.type === 'socks5') {
const socksUrl = `socks5://${auth}${proxy.host}:${proxy.port}`
const socksOptions = {}
// 设置 IP 协议族(如果指定)
if (useIPv4 !== null) {
socksOptions.family = useIPv4 ? 4 : 6
}
return new SocksProxyAgent(socksUrl, socksOptions)
} else if (proxy.type === 'http' || proxy.type === 'https') {
const proxyUrl = `${proxy.type}://${auth}${proxy.host}:${proxy.port}`
const httpOptions = {}
// HttpsProxyAgent 支持 family 参数(通过底层的 agent-base
if (useIPv4 !== null) {
httpOptions.family = useIPv4 ? 4 : 6
}
return new HttpsProxyAgent(proxyUrl, httpOptions)
} else {
logger.warn(`⚠️ Unsupported proxy type: ${proxy.type}`)
return null
}
} catch (error) {
logger.warn('⚠️ Failed to create proxy agent:', error.message)
return null
}
}
/**
* 获取 IP 协议族偏好设置
* @param {boolean|number|string} preference - 用户偏好设置
* @returns {boolean|null} true=IPv4, false=IPv6, null=auto
* @private
*/
static _getIPFamilyPreference(preference) {
// 如果没有指定偏好,使用配置文件或默认值
if (preference === undefined) {
// 从配置文件读取默认设置,默认使用 IPv4
const defaultUseIPv4 = config.proxy?.useIPv4
if (defaultUseIPv4 !== undefined) {
return defaultUseIPv4
}
// 默认值IPv4兼容性更好
return true
}
// 处理各种输入格式
if (typeof preference === 'boolean') {
return preference
}
if (typeof preference === 'number') {
return preference === 4 ? true : preference === 6 ? false : null
}
if (typeof preference === 'string') {
const lower = preference.toLowerCase()
if (lower === 'ipv4' || lower === '4') {
return true
}
if (lower === 'ipv6' || lower === '6') {
return false
}
if (lower === 'auto' || lower === 'both') {
return null
}
}
// 无法识别的值返回默认IPv4
return true
}
/**
* 验证代理配置
* @param {object|string} proxyConfig - 代理配置
* @returns {boolean} 是否有效
*/
static validateProxyConfig(proxyConfig) {
if (!proxyConfig) {
return false
}
try {
const proxy = typeof proxyConfig === 'string' ? JSON.parse(proxyConfig) : proxyConfig
// 检查必要字段
if (!proxy.type || !proxy.host || !proxy.port) {
return false
}
// 检查支持的类型
if (!['socks5', 'http', 'https'].includes(proxy.type)) {
return false
}
// 检查端口范围
const port = parseInt(proxy.port)
if (isNaN(port) || port < 1 || port > 65535) {
return false
}
return true
} catch (error) {
return false
}
}
/**
* 获取代理配置的描述信息
* @param {object|string} proxyConfig - 代理配置
* @returns {string} 代理描述
*/
static getProxyDescription(proxyConfig) {
if (!proxyConfig) {
return 'No proxy'
}
try {
const proxy = typeof proxyConfig === 'string' ? JSON.parse(proxyConfig) : proxyConfig
const hasAuth = proxy.username && proxy.password
return `${proxy.type}://${proxy.host}:${proxy.port}${hasAuth ? ' (with auth)' : ''}`
} catch (error) {
return 'Invalid proxy config'
}
}
/**
* 创建代理 Agent兼容旧的函数接口
* @param {object|string|null} proxyConfig - 代理配置
* @param {boolean} useIPv4 - 是否使用 IPv4
* @returns {Agent|null} 代理 Agent 实例或 null
* @deprecated 使用 createProxyAgent 替代
*/
static createProxy(proxyConfig, useIPv4 = true) {
logger.warn('⚠️ ProxyHelper.createProxy is deprecated, use createProxyAgent instead')
return ProxyHelper.createProxyAgent(proxyConfig, { useIPv4 })
}
}
module.exports = ProxyHelper