Procházet zdrojové kódy

批量ping测速优化

Taio_O před 1 týdnem
rodič
revize
6bcf567bc0

+ 136 - 18
go-speed-test/internal/core/speed_tester.go

@@ -5,10 +5,12 @@ import (
 	"crypto/tls"
 	"fmt"
 	"io"
+	"math/rand"
 	"net"
 	"net/http"
 	"net/url"
 	"regexp"
+	"sort"
 	"strconv"
 	"strings"
 	"sync"
@@ -165,12 +167,9 @@ func (st *SpeedTester) validateNode(node database.Node) error {
 	// 验证server字段是否为有效的域名或IP地址
 	server := strings.TrimSpace(node.Server)
 	
-	// 只检查明显无效的情况,而不是过于严格的验证
-	// 检查是否包含中文字符(通常表示这是描述而不是有效地址)
-	for _, r := range server {
-		if r >= 0x4e00 && r <= 0x9fff {
-			return fmt.Errorf("服务器地址包含中文字符,无效: %s", server)
-		}
+	// 检查是否为空或只包含空白字符
+	if server == "" {
+		return fmt.Errorf("服务器地址不能为空")
 	}
 	
 	// 检查是否是URL格式(不应该作为server地址)
@@ -184,9 +183,12 @@ func (st *SpeedTester) validateNode(node database.Node) error {
 		return fmt.Errorf("服务器地址包含无效字符: %s", server)
 	}
 	
-	// 检查是否为空或只包含空白字符
-	if server == "" {
-		return fmt.Errorf("服务器地址不能为空")
+	// 允许包含中文的服务器地址
+	if st.containsChinese(server) {
+		// 包含中文的地址直接通过验证
+		applogger.Debug("检测到包含中文的服务器地址", map[string]interface{}{
+			"domain": server,
+		})
 	}
 
 	// 对于SS/SSR节点,检查密码
@@ -207,40 +209,156 @@ func (st *SpeedTester) validateNode(node database.Node) error {
 	return nil
 }
 
-// 批量测试节点
+// 检查字符串是否包含中文字符
+func (st *SpeedTester) containsChinese(s string) bool {
+	for _, r := range s {
+		if r >= 0x4e00 && r <= 0x9fff {
+			return true
+		}
+	}
+	return false
+}
+
+// 验证是否为合法的中文域名
+// 支持以下格式:
+// - 纯中文域名:中文.域名.com
+// - 混合域名:中文.example.com
+// - 包含中文的域名:test.中文.com
+func (st *SpeedTester) isValidChineseDomain(domain string) bool {
+	// 移除前后空格
+	domain = strings.TrimSpace(domain)
+	
+	// 检查是否包含中文字符
+	if !st.containsChinese(domain) {
+		return false
+	}
+	
+	// 检查是否包含域名分隔符(点号)
+	if !strings.Contains(domain, ".") {
+		return false
+	}
+	
+	// 检查是否以点号开头或结尾
+	if strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") {
+		return false
+	}
+	
+	// 检查是否包含连续的点号
+	if strings.Contains(domain, "..") {
+		return false
+	}
+	
+	// 检查每个标签的长度(域名标签不能超过63个字符)
+	labels := strings.Split(domain, ".")
+	for _, label := range labels {
+		if len(label) == 0 || len(label) > 63 {
+			return false
+		}
+	}
+	
+	// 检查顶级域名(最后一部分)是否至少包含一个非中文字符
+	tld := labels[len(labels)-1]
+	if !regexp.MustCompile(`[a-zA-Z0-9]`).MatchString(tld) {
+		return false
+	}
+	
+	// 检查是否包含有效的域名字符(中文、英文、数字、连字符)
+	validDomainRegex := regexp.MustCompile(`^[a-zA-Z0-9\u4e00-\u9fa5\-\.]+$`)
+	if !validDomainRegex.MatchString(domain) {
+		return false
+	}
+	
+	return true
+}
+
+// 批量测试节点 - 改进版
 func (st *SpeedTester) TestNodes(nodes []database.Node) []*database.TestResult {
 	var results []*database.TestResult
 	var mu sync.Mutex
 	var wg sync.WaitGroup
 
+	// 降低默认并发数,提高稳定性
+	concurrency := st.concurrency
+	if concurrency > 3 {
+		concurrency = 3
+	}
+
 	// 创建信号量控制并发数
-	semaphore := make(chan struct{}, st.concurrency)
+	semaphore := make(chan struct{}, concurrency)
+
+	applogger.Info("开始批量测试节点", map[string]interface{}{
+		"total":       len(nodes),
+		"concurrency": concurrency,
+	})
+
+	startTime := time.Now()
 
-	for _, node := range nodes {
+	for i, node := range nodes {
 		wg.Add(1)
-		go func(n database.Node) {
+		go func(n database.Node, index int) {
 			defer wg.Done()
 			
+			// 添加随机延迟,避免同时发起请求
+			randomDelay := time.Duration(rand.Intn(500)) * time.Millisecond
+			time.Sleep(randomDelay)
+			
 			// 获取信号量
 			semaphore <- struct{}{}
 			defer func() { <-semaphore }()
 
-			result, err := st.TestNode(n)
-			if err != nil {
-							applogger.Error("节点测试异常", map[string]interface{}{
+			applogger.Debug("开始测试节点", map[string]interface{}{
 				"node":  n.Name,
-				"error": err.Error(),
+				"index": index + 1,
+				"total": len(nodes),
 			})
+
+			result, err := st.TestNode(n)
+			if err != nil {
+				applogger.Error("节点测试异常", map[string]interface{}{
+					"node":  n.Name,
+					"error": err.Error(),
+				})
 				return
 			}
 
 			mu.Lock()
 			results = append(results, result)
 			mu.Unlock()
-		}(node)
+
+			applogger.Debug("节点测试完成", map[string]interface{}{
+				"node":     n.Name,
+				"success":  result.IsSuccess,
+				"latency":  result.Latency,
+				"duration": time.Since(startTime),
+			})
+		}(node, i)
 	}
 
 	wg.Wait()
+
+	// 按延迟排序
+	sort.Slice(results, func(i, j int) bool {
+		if results[i].IsSuccess && results[j].IsSuccess {
+			return results[i].Latency < results[j].Latency
+		}
+		return results[i].IsSuccess
+	})
+
+	successCount := 0
+	for _, result := range results {
+		if result.IsSuccess {
+			successCount++
+		}
+	}
+
+	totalTime := time.Since(startTime)
+	applogger.Info("批量测试完成", map[string]interface{}{
+		"total":      len(results),
+		"successful": successCount,
+		"failed":     len(results) - successCount,
+		"duration":   totalTime,
+	})
+
 	return results
 }
 

+ 0 - 87
go-speed-test/test_speed.go

@@ -1,87 +0,0 @@
-package main
-
-import (
-	"fmt"
-	"log"
-	"time"
-
-	"clash-speed-test/internal/config"
-	"clash-speed-test/internal/core"
-	"clash-speed-test/internal/database"
-)
-
-func main() {
-	fmt.Println("=== Clash 测速工具测试 ===")
-
-	// 加载配置
-	cfg, err := config.Load()
-	if err != nil {
-		log.Fatalf("加载配置失败: %v", err)
-	}
-
-	// 初始化测速器
-	speedTester := core.NewSpeedTester(cfg)
-
-	// 创建测试节点
-	testNodes := []database.Node{
-		{
-			Name:   "测试HTTP代理",
-			Type:   "http",
-			Server: "127.0.0.1",
-			Port:   7890,
-		},
-		{
-			Name:   "测试SOCKS5代理",
-			Type:   "socks5",
-			Server: "127.0.0.1",
-			Port:   7891,
-		},
-	}
-
-	fmt.Printf("开始测试 %d 个节点...\n", len(testNodes))
-
-	// 测试节点
-	results := speedTester.TestNodes(testNodes)
-
-	// 显示结果
-	fmt.Println("\n=== 测试结果 ===")
-	for i, result := range results {
-		fmt.Printf("\n节点 %d: %s\n", i+1, testNodes[i].Name)
-		fmt.Printf("  类型: %s\n", testNodes[i].Type)
-		fmt.Printf("  地址: %s:%d\n", testNodes[i].Server, testNodes[i].Port)
-		
-		if result.IsSuccess {
-			fmt.Printf("  状态: ✅ 成功\n")
-			if result.Latency != nil {
-				fmt.Printf("  延迟: %d ms\n", *result.Latency)
-			}
-			if result.DownloadSpeed != nil {
-				fmt.Printf("  下载速度: %.2f Mbps\n", float64(*result.DownloadSpeed))
-			}
-			if result.UploadSpeed != nil {
-				fmt.Printf("  上传速度: %.2f Mbps\n", float64(*result.UploadSpeed))
-			}
-			if result.IPAddress != "" {
-				fmt.Printf("  IP地址: %s\n", result.IPAddress)
-			}
-			if result.Location != "" {
-				fmt.Printf("  位置: %s\n", result.Location)
-			}
-		} else {
-			fmt.Printf("  状态: ❌ 失败\n")
-			if result.ErrorMessage != "" {
-				fmt.Printf("  错误: %s\n", result.ErrorMessage)
-			}
-		}
-		
-		if result.TestDuration != nil {
-			fmt.Printf("  测试耗时: %d ms\n", *result.TestDuration)
-		}
-	}
-
-	fmt.Println("\n=== 测试完成 ===")
-	fmt.Println("提示: 如果测试失败,请确保:")
-	fmt.Println("1. Clash客户端正在运行")
-	fmt.Println("2. 本地代理端口 (7890/7891) 可访问")
-	fmt.Println("3. 网络连接正常")
-} 

+ 253 - 30
src/core/speedTester.js

@@ -286,32 +286,71 @@ class SpeedTester {
   }
 
   /**
-   * 批量ping测速 - 参考前端调用方式
+   * 批量ping测速 - 改进版
+   * 提供更精细的并发控制和更好的错误处理
    */
-  async batchPingHosts(hosts) {
-    logger.info(`开始批量ping测速: ${hosts.length}个节点`);
+  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 concurrency = parseInt(process.env.CONCURRENCY) || 5;
+    const startTime = Date.now();
     
     // 分批处理,控制并发数
     for (let i = 0; i < hosts.length; i += concurrency) {
       const batch = hosts.slice(i, i + concurrency);
-      const batchPromises = batch.map(async (hostConfig) => {
-        const { host, port, attempts = 3, timeout = 2000 } = hostConfig;
-        try {
-          const result = await this.pingHosts(host, port, attempts, timeout);
-          return { host, port, ...result };
-        } catch (error) {
-          logger.error(`ping测速异常: ${host}:${port} - ${error.message}`);
-          return { 
-            host, 
-            port, 
-            success: false, 
-            error: error.message,
-            latency: -1
-          };
+      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);
@@ -320,26 +359,38 @@ class SpeedTester {
         if (result.status === 'fulfilled') {
           results.push(result.value);
         } else {
-          logger.error(`ping测速失败: ${batch[index].host}:${batch[index].port} - ${result.reason}`);
+          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) {
-        await new Promise(resolve => setTimeout(resolve, 1000));
+        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) - (b.latency || b.min);
+        return (a.latency || a.min || 9999) - (b.latency || b.min || 9999);
       }
       return a.success ? -1 : 1;
     });
     
     const successCount = sortedResults.filter(r => r.success).length;
-    logger.info(`批量ping测速完成: ${successCount}/${hosts.length}个节点连通`);
+    const totalTime = Date.now() - startTime;
+    
+    logger.info(`批量ping测速完成: ${successCount}/${hosts.length}个节点连通,总耗时: ${totalTime}ms`);
     
     return {
       results: sortedResults,
@@ -349,11 +400,85 @@ class SpeedTester {
         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
+          .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
+      };
+    }
+  }
+
   /**
    * 测试单个节点连通性(改进版)
    */
@@ -440,10 +565,9 @@ class SpeedTester {
     // 验证server字段是否为有效的域名或IP地址
     const server = node.server.trim();
     
-    // 只检查明显无效的情况,而不是过于严格的验证
-    // 检查是否包含中文字符(通常表示这是描述而不是有效地址)
-    if (/[\u4e00-\u9fa5]/.test(server)) {
-      return { isValid: false, error: `服务器地址包含中文字符,无效: ${server}` };
+    // 检查是否为空或只包含空白字符
+    if (!server || server.trim() === '') {
+      return { isValid: false, error: '服务器地址不能为空' };
     }
     
     // 检查是否是URL格式(不应该作为server地址)
@@ -456,9 +580,15 @@ class SpeedTester {
       return { isValid: false, error: `服务器地址包含无效字符: ${server}` };
     }
     
-    // 检查是否为空或只包含空白字符
-    if (!server || server.trim() === '') {
-      return { isValid: false, error: '服务器地址不能为空' };
+    // 允许包含中文的服务器地址
+    if (/[\u4e00-\u9fa5]/.test(server)) {
+      // 包含中文的地址直接通过验证
+      logger.debug(`检测到包含中文的服务器地址: ${server}`);
+    } else {
+      // 对于不包含中文的域名,进行基本的域名格式验证
+      if (!this.isValidBasicDomain(server)) {
+        return { isValid: false, error: `服务器地址格式无效: ${server}` };
+      }
     }
 
     // 对于SS/SSR节点,检查密码
@@ -479,6 +609,99 @@ class SpeedTester {
     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;
+  }
+
   /**
    * 创建代理配置
    */

+ 0 - 69
test-ping-new.js

@@ -1,69 +0,0 @@
-const SpeedTester = require('./src/core/speedTester');
-
-async function testNewPing() {
-  console.log('🚀 开始测试重写后的Ping功能...\n');
-  
-  const speedTester = new SpeedTester();
-  
-  // 测试单个ping
-  console.log('📡 测试单个Ping...');
-  const singleResult = await speedTester.pingHosts('www.google.com', 443, 3, 2000);
-  console.log('单个Ping结果:', JSON.stringify(singleResult, null, 2));
-  
-  // 测试批量ping
-  console.log('\n📡 测试批量Ping...');
-  const hosts = [
-    { host: 'www.google.com', port: 443 },
-    { host: 'www.github.com', port: 443 },
-    { host: 'www.youtube.com', port: 443 },
-    { host: 'www.cloudflare.com', port: 443 },
-    { host: 'www.baidu.com', port: 443 }
-  ];
-  
-  const batchResult = await speedTester.batchPingHosts(hosts);
-  
-  console.log('批量Ping结果:');
-  batchResult.results.forEach((result, index) => {
-    const status = result.success ? '✅' : '❌';
-    const latency = result.success ? `${result.latency}ms (平均: ${result.avg}ms)` : '失败';
-    console.log(`${index + 1}. ${status} ${result.host}:${result.port} - ${latency}`);
-  });
-  
-  // 统计信息
-  console.log('\n📊 统计信息:');
-  console.log(`总测试数: ${batchResult.summary.total}`);
-  console.log(`成功数: ${batchResult.summary.successful}`);
-  console.log(`失败数: ${batchResult.summary.failed}`);
-  console.log(`平均延迟: ${Math.round(batchResult.summary.avgLatency)}ms`);
-  
-  // 测试API兼容性
-  console.log('\n🔧 测试API兼容性...');
-  const testHosts = [
-    { host: 'www.google.com', port: 443 },
-    { host: 'www.github.com', port: 443 }
-  ];
-  
-  try {
-    const response = await fetch('http://localhost:3000/api/test/ping/batch', {
-      method: 'POST',
-      headers: {
-        'Content-Type': 'application/json'
-      },
-      body: JSON.stringify({
-        hosts: testHosts,
-        attempts: 3,
-        timeout: 2000
-      })
-    });
-    
-    const apiResult = await response.json();
-    console.log('API测试结果:', JSON.stringify(apiResult, null, 2));
-  } catch (error) {
-    console.log('API测试失败 (服务器可能未启动):', error.message);
-  }
-  
-  console.log('\n✅ 测试完成!');
-}
-
-// 运行测试
-testNewPing().catch(console.error);