const axios = require('axios'); const { HttpsProxyAgent } = require('https-proxy-agent'); const { SocksProxyAgent } = require('socks-proxy-agent'); const { HttpProxyAgent } = require('http-proxy-agent'); const logger = require('../utils/logger'); const { TestResult } = require('../models'); const net = require('net'); const { exec } = require('child_process'); class SpeedTester { constructor() { this.testUrls = process.env.SPEED_TEST_URLS?.split(',') || [ 'https://www.google.com', 'https://www.youtube.com', 'https://www.github.com', 'https://httpbin.org/get' ]; this.timeout = parseInt(process.env.SPEED_TEST_TIMEOUT) || 10000; } /** * TCP端口连通性检测 - 使用tcp-ping库改进 */ async testTcpConnectivity(server, port, timeout = 5000) { return new Promise((resolve) => { try { // 尝试使用tcp-ping库,如果不可用则回退到原生实现 const tcpPing = require('tcp-ping'); tcpPing.ping({ address: server, port: port, attempts: 3, timeout: timeout }, (err, data) => { if (err) { logger.warn(`tcp-ping库不可用,使用原生实现: ${err.message}`); this.testTcpConnectivityNative(server, port, timeout).then(resolve); return; } if (data && data.min !== undefined) { // tcp-ping库返回的时间单位是秒,需要转换为毫秒 resolve({ success: true, latency: Math.round(data.min * 1000), avg: Math.round(data.avg * 1000), max: Math.round(data.max * 1000), min: Math.round(data.min * 1000) }); } else { resolve({ success: false, error: '连接失败', latency: -1 }); } }); } catch (error) { logger.warn(`tcp-ping库加载失败,使用原生实现: ${error.message}`); this.testTcpConnectivityNative(server, port, timeout).then(resolve); } }); } /** * 原生TCP连通性检测(备用方案) */ async testTcpConnectivityNative(server, port, timeout = 5000) { return new Promise((resolve) => { const socket = new net.Socket(); let isConnected = false; const startTime = Date.now(); socket.setTimeout(timeout); socket.on('connect', () => { isConnected = true; const latency = Date.now() - startTime; socket.destroy(); resolve({ success: true, latency: latency, avg: latency, max: latency, min: latency }); }); socket.on('timeout', () => { socket.destroy(); if (!isConnected) resolve({ success: false, error: '连接超时', latency: -1 }); }); socket.on('error', (err) => { socket.destroy(); if (!isConnected) resolve({ success: false, error: err.message, latency: -1 }); }); socket.connect(port, server); }); } /** * 使用系统 ping 命令测真实网络延迟(ICMP RTT) * 支持 Windows 和 Linux */ async systemPing(host, attempts = 3, timeout = 2000) { return new Promise((resolve) => { // 判断平台 const isWin = process.platform === 'win32'; // Windows: ping -n 次数 -w 超时 host // Linux/Mac: ping -c 次数 -W 超时 host let cmd; if (isWin) { // -n 次数, -w 单次超时(毫秒) cmd = `ping -n ${attempts} -w ${timeout} ${host}`; } else { // -c 次数, -W 单次超时(秒) cmd = `ping -c ${attempts} -W ${Math.ceil(timeout/1000)} ${host}`; } exec(cmd, (error, stdout) => { if (error) { resolve({ success: false, error: error.message, raw: stdout }); return; } let avg, min, max, results = []; if (isWin) { // 解析每次延迟 const msMatches = [...stdout.matchAll(/时间[=<]([\d]+)ms/g)]; results = msMatches.map((m, i) => ({ seq: i+1, time: parseInt(m[1], 10) })); // 解析统计 const statMatch = stdout.match(/平均 = (\d+)ms/); avg = statMatch ? parseInt(statMatch[1], 10) : null; const minMatch = stdout.match(/最短 = (\d+)ms/); min = minMatch ? parseInt(minMatch[1], 10) : null; const maxMatch = stdout.match(/最长 = (\d+)ms/); max = maxMatch ? parseInt(maxMatch[1], 10) : null; } else { // 解析每次延迟 const msMatches = [...stdout.matchAll(/time=([\d.]+) ms/g)]; results = msMatches.map((m, i) => ({ seq: i+1, time: Math.round(parseFloat(m[1])) })); // 解析统计 const statMatch = stdout.match(/min\/avg\/max\/.* = ([\d.]+)\/([\d.]+)\/([\d.]+)\//); min = statMatch ? Math.round(parseFloat(statMatch[1])) : null; avg = statMatch ? Math.round(parseFloat(statMatch[2])) : null; max = statMatch ? Math.round(parseFloat(statMatch[3])) : null; } if (avg != null) { resolve({ success: true, host, avg, min, max, attempts, results, raw: stdout }); } else { resolve({ success: false, error: '无法解析ping输出', raw: stdout }); } }); }); } /** * 核心ping测速实现 - 参考Electron应用的方式 * 使用tcp-ping库进行精确的延迟测量 */ async pingHosts(host, port, attempts = 3, timeout = 2000) { return new Promise((resolve) => { try { const tcpPing = require('tcp-ping'); tcpPing.ping({ address: host, port: port, attempts: attempts, timeout: timeout }, (err, data) => { if (err) { logger.warn(`tcp-ping测速失败: ${host}:${port} - ${err.message}`); resolve({ success: false, latency: -1, error: err.message }); return; } if (data && data.min !== undefined) { // tcp-ping返回的时间单位是秒,转换为毫秒 const result = { success: true, host: host, port: port, attempts: data.attempts, avg: Math.round(data.avg * 1000), max: Math.round(data.max * 1000), min: Math.round(data.min * 1000), latency: Math.round(data.min * 1000), // 使用最小延迟作为主要延迟值 results: data.results.map(r => ({ seq: r.seq, time: Math.round(r.time * 1000) })) }; logger.debug(`ping测速成功: ${host}:${port} - 延迟: ${result.latency}ms (平均: ${result.avg}ms, 最大: ${result.max}ms)`); resolve(result); } else { logger.warn(`ping测速失败: ${host}:${port} - 连接失败`); resolve({ success: false, latency: -1, error: '连接失败' }); } }); } catch (error) { logger.warn(`tcp-ping库不可用,使用原生实现: ${error.message}`); this.pingHostsNative(host, port, attempts, timeout).then(resolve); } }); } /** * 原生TCP ping实现(备用方案) */ async pingHostsNative(host, port, attempts = 3, timeout = 2000) { const results = []; for (let i = 0; i < attempts; i++) { try { const result = await this.singlePing(host, port, timeout); if (result.success) { results.push(result.latency); } } catch (error) { logger.debug(`单次ping失败: ${host}:${port} - 第${i+1}次尝试`); } } if (results.length > 0) { const min = Math.min(...results); const max = Math.max(...results); const avg = results.reduce((a, b) => a + b, 0) / results.length; return { success: true, host: host, port: port, attempts: attempts, avg: Math.round(avg), max: max, min: min, latency: min, // 使用最小延迟 results: results.map((latency, index) => ({ seq: index + 1, time: latency })) }; } else { return { success: false, host: host, port: port, latency: -1, error: '所有尝试都失败' }; } } /** * 单次ping测试 */ async singlePing(host, port, timeout) { return new Promise((resolve) => { const socket = new net.Socket(); let isConnected = false; const startTime = Date.now(); socket.setTimeout(timeout); socket.on('connect', () => { isConnected = true; const latency = Date.now() - startTime; socket.destroy(); resolve({ success: true, latency: latency }); }); socket.on('timeout', () => { socket.destroy(); if (!isConnected) resolve({ success: false, error: '连接超时' }); }); socket.on('error', (err) => { socket.destroy(); if (!isConnected) resolve({ success: false, error: err.message }); }); socket.connect(port, host); }); } /** * 批量ping测速 - 改进版 * 提供更精细的并发控制和更好的错误处理 */ async batchPingHosts(hosts, options = {}) { const { concurrency = 3, // 降低默认并发数 batchDelay = 2000, // 增加批次间延迟 retryAttempts = 1, // 失败重试次数 timeout = 3000, // 增加超时时间 attempts = 3 // 每次ping的尝试次数 } = options; logger.info(`开始批量ping测速: ${hosts.length}个节点,并发数: ${concurrency}`); const results = []; const startTime = Date.now(); // 分批处理,控制并发数 for (let i = 0; i < hosts.length; i += concurrency) { const batch = hosts.slice(i, i + concurrency); logger.debug(`处理批次 ${Math.floor(i/concurrency) + 1}/${Math.ceil(hosts.length/concurrency)}: ${batch.length}个节点`); const batchPromises = batch.map(async (hostConfig, batchIndex) => { const { host, port } = hostConfig; const nodeName = `${host}:${port}`; // 添加随机延迟,避免同时发起请求 const randomDelay = Math.random() * 500; await new Promise(resolve => setTimeout(resolve, randomDelay)); let lastError = null; // 重试机制 for (let retry = 0; retry <= retryAttempts; retry++) { try { const result = await this.pingHosts(host, port, attempts, timeout); return { host, port, ...result, retryCount: retry, batchIndex: batchIndex }; } catch (error) { lastError = error; logger.debug(`ping测速失败 (重试 ${retry + 1}/${retryAttempts + 1}): ${nodeName} - ${error.message}`); if (retry < retryAttempts) { // 重试前等待 await new Promise(resolve => setTimeout(resolve, 1000 * (retry + 1))); } } } // 所有重试都失败 logger.warn(`ping测速最终失败: ${nodeName} - ${lastError?.message || '未知错误'}`); return { host, port, success: false, error: lastError?.message || '所有重试都失败', latency: -1, retryCount: retryAttempts, batchIndex: batchIndex }; }); const batchResults = await Promise.allSettled(batchPromises); batchResults.forEach((result, index) => { if (result.status === 'fulfilled') { results.push(result.value); } else { logger.error(`ping测速异常: ${batch[index].host}:${batch[index].port} - ${result.reason}`); results.push({ host: batch[index].host, port: batch[index].port, success: false, error: result.reason, latency: -1, retryCount: 0, batchIndex: index }); } }); // 批次间延迟,避免过于频繁的请求 if (i + concurrency < hosts.length) { logger.debug(`批次间延迟: ${batchDelay}ms`); await new Promise(resolve => setTimeout(resolve, batchDelay)); } } // 按延迟排序 const sortedResults = results.sort((a, b) => { if (a.success && b.success) { return (a.latency || a.min || 9999) - (b.latency || b.min || 9999); } return a.success ? -1 : 1; }); const successCount = sortedResults.filter(r => r.success).length; const totalTime = Date.now() - startTime; logger.info(`批量ping测速完成: ${successCount}/${hosts.length}个节点连通,总耗时: ${totalTime}ms`); return { results: sortedResults, summary: { total: results.length, successful: successCount, failed: results.length - successCount, avgLatency: sortedResults .filter(r => r.success && r.latency) .reduce((sum, r) => sum + r.latency, 0) / sortedResults.filter(r => r.success && r.latency).length || 0, totalTime: totalTime, concurrency: concurrency, batchDelay: batchDelay } }; } /** * 单次测速 - 优化版 * 提供更精确的单节点测速 */ async singlePingOptimized(host, port, options = {}) { const { attempts = 5, // 增加尝试次数 timeout = 5000, // 增加超时时间 interval = 500 // 尝试间隔 } = options; logger.info(`开始单次优化测速: ${host}:${port}`); const results = []; const startTime = Date.now(); for (let i = 0; i < attempts; i++) { try { const result = await this.singlePing(host, port, timeout); if (result.success) { results.push(result.latency); logger.debug(`第${i + 1}次尝试成功: ${result.latency}ms`); } else { logger.debug(`第${i + 1}次尝试失败: ${result.error}`); } } catch (error) { logger.debug(`第${i + 1}次尝试异常: ${error.message}`); } // 尝试间隔 if (i < attempts - 1) { await new Promise(resolve => setTimeout(resolve, interval)); } } if (results.length > 0) { const min = Math.min(...results); const max = Math.max(...results); const avg = results.reduce((a, b) => a + b, 0) / results.length; const totalTime = Date.now() - startTime; logger.info(`单次优化测速完成: ${host}:${port} - 延迟: ${min}ms (平均: ${Math.round(avg)}ms, 最大: ${max}ms)`); return { success: true, host: host, port: port, attempts: attempts, avg: Math.round(avg), max: max, min: min, latency: min, results: results.map((latency, index) => ({ seq: index + 1, time: latency })), totalTime: totalTime }; } else { const totalTime = Date.now() - startTime; logger.warn(`单次优化测速失败: ${host}:${port} - 所有尝试都失败`); return { success: false, host: host, port: port, attempts: attempts, latency: -1, error: '所有尝试都失败', totalTime: totalTime }; } } /** * 测试单个节点连通性(改进版) */ async testNode(node) { const startTime = Date.now(); let testResult = { nodeId: node.id, testTime: new Date(), isSuccess: false, latency: null, downloadSpeed: null, // 保留字段但不测试 uploadSpeed: null, // 保留字段但不测试 packetLoss: null, testUrl: null, errorMessage: null, testDuration: null, ipAddress: null, location: null, pingStats: null // 新增ping统计信息 }; try { logger.info(`开始测试节点连通性: ${node.name} (${node.type})`); // 验证节点配置的有效性 const validationResult = this.validateNode(node); if (!validationResult.isValid) { throw new Error(validationResult.error); } // 使用高级ping测试 const pingResult = await this.pingHosts(node.server, node.port, 3, 2000); testResult.isSuccess = pingResult.success; testResult.latency = pingResult.min || pingResult.latency; testResult.errorMessage = pingResult.error || null; testResult.pingStats = pingResult.success ? { attempts: pingResult.attempts, avg: pingResult.avg, max: pingResult.max, min: pingResult.min, results: pingResult.results } : null; testResult.testDuration = Date.now() - startTime; if (testResult.isSuccess) { logger.info(`✅ 节点Ping测试成功: ${node.name} - 延迟: ${testResult.latency}ms (平均: ${pingResult.avg}ms, 最大: ${pingResult.max}ms)`); } else { logger.warn(`❌ 节点Ping测试失败: ${node.name} - ${testResult.errorMessage || '连接超时'}`); } } catch (error) { testResult.errorMessage = error.message; testResult.testDuration = Date.now() - startTime; logger.error(`❌ 节点测试异常: ${node.name} - ${error.message}`); } // 保存测试结果 await this.saveTestResult(testResult); return testResult; } /** * 验证节点配置的有效性 */ validateNode(node) { // 检查必需的字段 if (!node.name) { return { isValid: false, error: '节点名称不能为空' }; } if (!node.type) { return { isValid: false, error: '节点类型不能为空' }; } if (!node.server) { return { isValid: false, error: '服务器地址不能为空' }; } if (!node.port || node.port <= 0 || node.port > 65535) { return { isValid: false, error: '端口号无效' }; } // 验证server字段是否为有效的域名或IP地址 const server = node.server.trim(); // 检查是否为空或只包含空白字符 if (!server || server.trim() === '') { return { isValid: false, error: '服务器地址不能为空' }; } // 检查是否是URL格式(不应该作为server地址) if (server.startsWith('http://') || server.startsWith('https://')) { return { isValid: false, error: `服务器地址不能是URL格式: ${server}` }; } // 检查是否包含明显的无效字符(如空格、特殊符号等) if (/[<>:"\\|?*]/.test(server)) { return { isValid: false, error: `服务器地址包含无效字符: ${server}` }; } // 允许包含中文的服务器地址 if (/[\u4e00-\u9fa5]/.test(server)) { // 包含中文的地址直接通过验证 logger.debug(`检测到包含中文的服务器地址: ${server}`); } else { // 对于不包含中文的域名,进行基本的域名格式验证 if (!this.isValidBasicDomain(server)) { return { isValid: false, error: `服务器地址格式无效: ${server}` }; } } // 对于SS/SSR节点,检查密码 if ((node.type === 'ss' || node.type === 'ssr') && !node.password) { return { isValid: false, error: 'SS/SSR节点必须设置密码' }; } // 对于Vmess节点,检查UUID if (node.type === 'vmess' && !node.uuid) { return { isValid: false, error: 'Vmess节点必须设置UUID' }; } // 对于Trojan节点,检查密码 if (node.type === 'trojan' && !node.password) { return { isValid: false, error: 'Trojan节点必须设置密码' }; } return { isValid: true, error: null }; } /** * 验证是否为合法的中文域名 * 支持以下格式: * - 纯中文域名:中文.域名.com * - 混合域名:中文.example.com * - 包含中文的域名:test.中文.com */ isValidChineseDomain(domain) { // 移除前后空格 domain = domain.trim(); // 检查是否包含中文字符 if (!/[\u4e00-\u9fa5]/.test(domain)) { return false; } // 检查是否包含域名分隔符(点号) if (!domain.includes('.')) { return false; } // 检查是否以点号开头或结尾 if (domain.startsWith('.') || domain.endsWith('.')) { return false; } // 检查是否包含连续的点号 if (domain.includes('..')) { return false; } // 检查每个标签的长度(域名标签不能超过63个字符) const labels = domain.split('.'); for (const label of labels) { if (label.length === 0 || label.length > 63) { return false; } } // 检查顶级域名(最后一部分)是否至少包含一个非中文字符 const tld = labels[labels.length - 1]; if (!/[a-zA-Z0-9]/.test(tld)) { return false; } // 检查是否包含有效的域名字符(中文、英文、数字、连字符) const validDomainRegex = /^[a-zA-Z0-9\u4e00-\u9fa5\-\.]+$/; if (!validDomainRegex.test(domain)) { return false; } return true; } /** * 验证是否为合法的基本域名格式(不包含中文) */ isValidBasicDomain(domain) { // 移除前后空格 domain = domain.trim(); // 检查是否包含域名分隔符(点号) if (!domain.includes('.')) { return false; } // 检查是否以点号开头或结尾 if (domain.startsWith('.') || domain.endsWith('.')) { return false; } // 检查是否包含连续的点号 if (domain.includes('..')) { return false; } // 检查每个标签的长度(域名标签不能超过63个字符) const labels = domain.split('.'); for (const label of labels) { if (label.length === 0 || label.length > 63) { return false; } } // 检查是否包含有效的域名字符(英文、数字、连字符) const validDomainRegex = /^[a-zA-Z0-9\-\.]+$/; if (!validDomainRegex.test(domain)) { return false; } return true; } /** * 创建代理配置 */ createProxyConfig(node) { const baseConfig = { host: node.server, port: node.port, timeout: this.timeout }; switch (node.type) { case 'ss': return this.createShadowsocksConfig(node, baseConfig); case 'ssr': return this.createShadowsocksRConfig(node, baseConfig); case 'vmess': return this.createVmessConfig(node, baseConfig); case 'trojan': return this.createTrojanConfig(node, baseConfig); case 'http': case 'https': return this.createHttpConfig(node, baseConfig); case 'socks5': return this.createSocks5Config(node, baseConfig); default: logger.warn(`不支持的代理类型: ${node.type} - ${node.name}`); return null; } } /** * 创建Shadowsocks代理配置 */ createShadowsocksConfig(node, baseConfig) { return { ...baseConfig, type: 'ss', password: node.password, method: node.cipher || node.method, auth: node.username && node.password ? { username: node.username, password: node.password } : null }; } /** * 创建ShadowsocksR代理配置 */ createShadowsocksRConfig(node, baseConfig) { return { ...baseConfig, type: 'ssr', password: node.password, method: node.cipher || node.method, protocol: node.protocol, obfs: node.obfs, auth: node.username && node.password ? { username: node.username, password: node.password } : null }; } /** * 创建Vmess代理配置 */ createVmessConfig(node, baseConfig) { return { ...baseConfig, type: 'vmess', uuid: node.uuid, alterId: node.alterId, network: node.network, tls: node.tls, sni: node.sni, wsPath: node.wsPath, wsHeaders: node.wsHeaders ? JSON.parse(node.wsHeaders) : {}, auth: node.username && node.password ? { username: node.username, password: node.password } : null }; } /** * 创建Trojan代理配置 */ createTrojanConfig(node, baseConfig) { return { ...baseConfig, type: 'trojan', password: node.password, tls: node.tls, sni: node.sni, auth: node.username && node.password ? { username: node.username, password: node.password } : null }; } /** * 创建HTTP代理配置 */ createHttpConfig(node, baseConfig) { const config = { ...baseConfig, type: 'http' }; if (node.username && node.password) { config.auth = { username: node.username, password: node.password }; } if (node.tls) { config.https = true; config.sni = node.sni; } return config; } /** * 创建SOCKS5代理配置 */ createSocks5Config(node, baseConfig) { const config = { ...baseConfig, type: 'socks5' }; if (node.username && node.password) { config.auth = { username: node.username, password: node.password }; } return config; } /** * 测试基本连通性 */ async testBasicConnectivity(node, proxyConfig) { const startTime = Date.now(); // 如果是直连测试 if (node.type === 'direct') { return this.testDirectConnectivity(); } // 对于高级代理类型,我们仍然尝试通过本地代理测试 // 如果本地代理不可用,会在makeRequest中失败,这是正常的 // 尝试多个测试URL for (const url of this.testUrls) { try { logger.debug(`尝试连接: ${node.name} -> ${url}`); const response = await this.makeRequest(node, proxyConfig, { method: 'HEAD', url: url, timeout: this.timeout, maxRedirects: 3 }); const latency = Date.now() - startTime; logger.debug(`连接成功: ${node.name} -> ${url} (${latency}ms)`); return { success: true, latency: latency, url: url, ipAddress: this.extractIpAddress(response), location: this.extractLocation(response) }; } catch (error) { logger.debug(`连接失败: ${node.name} -> ${url} - ${error.message}`); continue; } } return { success: false, latency: null, url: null, ipAddress: null, location: null }; } /** * 测试直连连通性 */ async testDirectConnectivity() { const startTime = Date.now(); for (const url of this.testUrls) { try { logger.debug(`尝试直连: ${url}`); const response = await axios.get(url, { timeout: this.timeout, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } }); const latency = Date.now() - startTime; logger.debug(`直连成功: ${url} (${latency}ms)`); return { success: true, latency: latency, url: url, ipAddress: this.extractIpAddress(response), location: this.extractLocation(response) }; } catch (error) { logger.debug(`直连失败: ${url} - ${error.message}`); continue; } } return { success: false, latency: null, url: null, ipAddress: null, location: null }; } /** * 发送HTTP请求 */ async makeRequest(node, proxyConfig, requestConfig) { const agent = this.createProxyAgent(node, proxyConfig); if (!agent) { throw new Error(`无法创建代理Agent: ${proxyConfig.type}`); } logger.debug(`使用代理: ${node.name} - ${proxyConfig.type}://${proxyConfig.host}:${proxyConfig.port}`); const config = { ...requestConfig, httpsAgent: agent, httpAgent: agent, headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36', ...requestConfig.headers } }; return axios(config); } /** * 创建代理Agent */ createProxyAgent(node, proxyConfig) { try { logger.debug(`创建代理Agent: ${node.name} - ${proxyConfig.type}`); switch (proxyConfig.type) { case 'http': case 'https': // HTTP/HTTPS代理 let httpProxyUrl = `${proxyConfig.type}://${proxyConfig.host}:${proxyConfig.port}`; // 如果有认证信息,添加到URL中 if (proxyConfig.auth) { const { username, password } = proxyConfig.auth; httpProxyUrl = `${proxyConfig.type}://${username}:${password}@${proxyConfig.host}:${proxyConfig.port}`; } const httpAgent = new HttpsProxyAgent(httpProxyUrl); logger.debug(`HTTP代理创建成功: ${node.name} - ${httpProxyUrl}`); return httpAgent; case 'socks5': // SOCKS5代理 let socksProxyUrl = `socks5://${proxyConfig.host}:${proxyConfig.port}`; // 如果有认证信息,添加到URL中 if (proxyConfig.auth) { const { username, password } = proxyConfig.auth; socksProxyUrl = `socks5://${username}:${password}@${proxyConfig.host}:${proxyConfig.port}`; } const socksAgent = new SocksProxyAgent(socksProxyUrl); logger.debug(`SOCKS5代理创建成功: ${node.name} - ${socksProxyUrl}`); return socksAgent; case 'ss': case 'ssr': case 'vmess': case 'trojan': // 对于这些高级代理类型,我们需要通过本地Clash代理来测试 // 因为Node.js没有直接的vmess/ss客户端库 logger.info(`通过本地Clash代理测试: ${proxyConfig.type} - ${node.name}`); // 使用本地Clash混合端口 const localProxyUrl = 'http://127.0.0.1:7890'; const localAgent = new HttpsProxyAgent(localProxyUrl); logger.debug(`使用本地Clash代理: ${node.name} - ${localProxyUrl}`); return localAgent; default: logger.warn(`不支持的代理类型: ${proxyConfig.type} - ${node.name}`); return null; } } catch (error) { logger.error(`创建代理Agent失败: ${node.name}`, { error: error.message, type: proxyConfig.type }); return null; } } /** * 提取IP地址 */ extractIpAddress(response) { // 从响应头中提取IP地址 return response.headers['x-forwarded-for'] || response.headers['x-real-ip'] || response.headers['cf-connecting-ip'] || 'unknown'; } /** * 提取位置信息 */ extractLocation(response) { // 从响应头中提取位置信息 if (response.headers['cf-ray']) { return 'Cloudflare'; } if (response.headers['cf-ipcountry']) { return response.headers['cf-ipcountry']; } return 'unknown'; } /** * 保存测试结果 */ async saveTestResult(testResult) { try { await TestResult.create(testResult); } catch (error) { logger.error(`保存测试结果失败: ${error.message}`); } } /** * 批量测试节点 */ async testNodes(nodes) { logger.info(`开始批量测试 ${nodes.length} 个节点`); const results = []; const concurrency = parseInt(process.env.CONCURRENCY) || 5; // 分批处理,控制并发数 for (let i = 0; i < nodes.length; i += concurrency) { const batch = nodes.slice(i, i + concurrency); const batchPromises = batch.map(node => this.testNode(node)); const batchResults = await Promise.allSettled(batchPromises); batchResults.forEach((result, index) => { if (result.status === 'fulfilled') { results.push(result.value); } else { logger.error(`节点测试失败: ${batch[index].name} - ${result.reason}`); } }); // 批次间延迟,避免过于频繁的请求 if (i + concurrency < nodes.length) { await new Promise(resolve => setTimeout(resolve, 1000)); } } const successCount = results.filter(r => r.isSuccess).length; logger.info(`批量测试完成: ${successCount}/${nodes.length} 个节点连通`); return results; } /** * 数组分块 */ chunkArray(array, size) { const chunks = []; for (let i = 0; i < array.length; i += size) { chunks.push(array.slice(i, i + size)); } return chunks; } } module.exports = SpeedTester;