speed_tester.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. package core
  2. import (
  3. "context"
  4. "crypto/tls"
  5. "fmt"
  6. "io"
  7. "net"
  8. "net/http"
  9. "net/url"
  10. "strconv"
  11. "strings"
  12. "sync"
  13. "time"
  14. "clash-speed-test/internal/config"
  15. "clash-speed-test/internal/database"
  16. applogger "clash-speed-test/internal/logger"
  17. )
  18. type SpeedTester struct {
  19. config *config.Config
  20. testURLs []string
  21. timeout time.Duration
  22. concurrency int
  23. httpClient *http.Client
  24. }
  25. type SpeedTestResult struct {
  26. Latency int `json:"latency"`
  27. DownloadSpeed float64 `json:"download_speed"` // Mbps
  28. UploadSpeed float64 `json:"upload_speed"` // Mbps
  29. IPAddress string `json:"ip_address"`
  30. Location string `json:"location"`
  31. Success bool `json:"success"`
  32. ErrorMessage string `json:"error_message"`
  33. }
  34. func NewSpeedTester(cfg *config.Config) *SpeedTester {
  35. // 创建HTTP客户端
  36. httpClient := &http.Client{
  37. Timeout: cfg.Speed.Timeout,
  38. Transport: &http.Transport{
  39. TLSClientConfig: &tls.Config{
  40. InsecureSkipVerify: false,
  41. },
  42. DisableKeepAlives: true,
  43. MaxIdleConns: 100,
  44. IdleConnTimeout: 30 * time.Second,
  45. },
  46. }
  47. return &SpeedTester{
  48. config: cfg,
  49. testURLs: cfg.Speed.TestURLs,
  50. timeout: cfg.Speed.Timeout,
  51. concurrency: cfg.Speed.Concurrency,
  52. httpClient: httpClient,
  53. }
  54. }
  55. // 测试单个节点
  56. func (st *SpeedTester) TestNode(node database.Node) (*database.TestResult, error) {
  57. startTime := time.Now()
  58. result := &database.TestResult{
  59. NodeID: node.ID,
  60. TestTime: time.Now(),
  61. IsSuccess: false,
  62. }
  63. applogger.Info("开始测试节点", map[string]interface{}{
  64. "name": node.Name,
  65. "type": node.Type,
  66. "server": node.Server,
  67. "port": node.Port,
  68. })
  69. // 测试连接性和速度
  70. speedResult, err := st.testNodeSpeed(node)
  71. if err != nil {
  72. result.ErrorMessage = fmt.Sprintf("测速失败: %v", err)
  73. applogger.Warn("节点测试失败", map[string]interface{}{
  74. "node": node.Name,
  75. "error": err.Error(),
  76. })
  77. } else if speedResult.Success {
  78. result.IsSuccess = true
  79. result.Latency = &speedResult.Latency
  80. result.IPAddress = speedResult.IPAddress
  81. result.Location = speedResult.Location
  82. result.TestURL = st.testURLs[0]
  83. // 如果有速度数据,保存到扩展字段
  84. if speedResult.DownloadSpeed > 0 {
  85. downloadSpeed := int(speedResult.DownloadSpeed)
  86. result.DownloadSpeed = &downloadSpeed
  87. }
  88. if speedResult.UploadSpeed > 0 {
  89. uploadSpeed := int(speedResult.UploadSpeed)
  90. result.UploadSpeed = &uploadSpeed
  91. }
  92. applogger.Info("节点测试成功", map[string]interface{}{
  93. "node": node.Name,
  94. "latency": speedResult.Latency,
  95. "download_speed": speedResult.DownloadSpeed,
  96. "upload_speed": speedResult.UploadSpeed,
  97. "ip": speedResult.IPAddress,
  98. "location": speedResult.Location,
  99. })
  100. } else {
  101. result.ErrorMessage = speedResult.ErrorMessage
  102. applogger.Warn("节点测试失败", map[string]interface{}{
  103. "node": node.Name,
  104. "error": speedResult.ErrorMessage,
  105. })
  106. }
  107. // 计算测试时长
  108. duration := int(time.Since(startTime).Milliseconds())
  109. result.TestDuration = &duration
  110. // 保存测试结果
  111. if err := database.SaveTestResult(result); err != nil {
  112. applogger.Error("保存测试结果失败", map[string]interface{}{
  113. "node": node.Name,
  114. "error": err.Error(),
  115. })
  116. }
  117. return result, nil
  118. }
  119. // 批量测试节点
  120. func (st *SpeedTester) TestNodes(nodes []database.Node) []*database.TestResult {
  121. var results []*database.TestResult
  122. var mu sync.Mutex
  123. var wg sync.WaitGroup
  124. // 创建信号量控制并发数
  125. semaphore := make(chan struct{}, st.concurrency)
  126. for _, node := range nodes {
  127. wg.Add(1)
  128. go func(n database.Node) {
  129. defer wg.Done()
  130. // 获取信号量
  131. semaphore <- struct{}{}
  132. defer func() { <-semaphore }()
  133. result, err := st.TestNode(n)
  134. if err != nil {
  135. applogger.Error("节点测试异常", map[string]interface{}{
  136. "node": n.Name,
  137. "error": err.Error(),
  138. })
  139. return
  140. }
  141. mu.Lock()
  142. results = append(results, result)
  143. mu.Unlock()
  144. }(node)
  145. }
  146. wg.Wait()
  147. return results
  148. }
  149. // 测试节点速度
  150. func (st *SpeedTester) testNodeSpeed(node database.Node) (*SpeedTestResult, error) {
  151. result := &SpeedTestResult{
  152. Success: false,
  153. }
  154. // 根据代理类型选择测试策略
  155. switch node.Type {
  156. case "http", "https":
  157. return st.testHTTPProxy(node)
  158. case "socks5":
  159. return st.testSOCKS5Proxy(node)
  160. case "ss", "ssr", "vmess", "trojan":
  161. // 对于高级代理,尝试多种测试方法
  162. return st.testAdvancedProxy(node)
  163. default:
  164. return result, fmt.Errorf("暂不支持代理类型: %s", node.Type)
  165. }
  166. }
  167. // 测试HTTP代理
  168. func (st *SpeedTester) testHTTPProxy(node database.Node) (*SpeedTestResult, error) {
  169. result := &SpeedTestResult{}
  170. // 构建代理URL
  171. proxyURL := fmt.Sprintf("http://%s:%d", node.Server, node.Port)
  172. if node.Username != "" && node.Password != "" {
  173. proxyURL = fmt.Sprintf("http://%s:%s@%s:%d", node.Username, node.Password, node.Server, node.Port)
  174. }
  175. // 创建代理客户端
  176. proxyURLParsed, err := url.Parse(proxyURL)
  177. if err != nil {
  178. return result, fmt.Errorf("解析代理URL失败: %w", err)
  179. }
  180. proxyClient := &http.Client{
  181. Timeout: st.timeout,
  182. Transport: &http.Transport{
  183. Proxy: http.ProxyURL(proxyURLParsed),
  184. TLSClientConfig: &tls.Config{
  185. InsecureSkipVerify: false,
  186. },
  187. DisableKeepAlives: true,
  188. },
  189. }
  190. // 测试延迟
  191. latency, ipAddress, location, err := st.testLatency(proxyClient)
  192. if err != nil {
  193. return result, fmt.Errorf("延迟测试失败: %w", err)
  194. }
  195. result.Latency = latency
  196. result.IPAddress = ipAddress
  197. result.Location = location
  198. // 测试下载速度
  199. downloadSpeed, err := st.testDownloadSpeed(proxyClient)
  200. if err != nil {
  201. applogger.Warn("下载速度测试失败", map[string]interface{}{
  202. "node": node.Name,
  203. "error": err.Error(),
  204. })
  205. } else {
  206. result.DownloadSpeed = downloadSpeed
  207. }
  208. // 测试上传速度
  209. uploadSpeed, err := st.testUploadSpeed(proxyClient)
  210. if err != nil {
  211. applogger.Warn("上传速度测试失败", map[string]interface{}{
  212. "node": node.Name,
  213. "error": err.Error(),
  214. })
  215. } else {
  216. result.UploadSpeed = uploadSpeed
  217. }
  218. result.Success = true
  219. return result, nil
  220. }
  221. // 测试SOCKS5代理
  222. func (st *SpeedTester) testSOCKS5Proxy(node database.Node) (*SpeedTestResult, error) {
  223. result := &SpeedTestResult{}
  224. // 构建SOCKS5代理URL
  225. proxyURL := fmt.Sprintf("socks5://%s:%d", node.Server, node.Port)
  226. if node.Username != "" && node.Password != "" {
  227. proxyURL = fmt.Sprintf("socks5://%s:%s@%s:%d", node.Username, node.Password, node.Server, node.Port)
  228. }
  229. // 创建代理客户端
  230. proxyURLParsed, err := url.Parse(proxyURL)
  231. if err != nil {
  232. return result, fmt.Errorf("解析SOCKS5代理URL失败: %w", err)
  233. }
  234. proxyClient := &http.Client{
  235. Timeout: st.timeout,
  236. Transport: &http.Transport{
  237. Proxy: http.ProxyURL(proxyURLParsed),
  238. TLSClientConfig: &tls.Config{
  239. InsecureSkipVerify: false,
  240. },
  241. DisableKeepAlives: true,
  242. },
  243. }
  244. // 测试延迟
  245. latency, ipAddress, location, err := st.testLatency(proxyClient)
  246. if err != nil {
  247. return result, fmt.Errorf("延迟测试失败: %w", err)
  248. }
  249. result.Latency = latency
  250. result.IPAddress = ipAddress
  251. result.Location = location
  252. // 测试下载速度
  253. downloadSpeed, err := st.testDownloadSpeed(proxyClient)
  254. if err != nil {
  255. applogger.Warn("下载速度测试失败", map[string]interface{}{
  256. "node": node.Name,
  257. "error": err.Error(),
  258. })
  259. } else {
  260. result.DownloadSpeed = downloadSpeed
  261. }
  262. // 测试上传速度
  263. uploadSpeed, err := st.testUploadSpeed(proxyClient)
  264. if err != nil {
  265. applogger.Warn("上传速度测试失败", map[string]interface{}{
  266. "node": node.Name,
  267. "error": err.Error(),
  268. })
  269. } else {
  270. result.UploadSpeed = uploadSpeed
  271. }
  272. result.Success = true
  273. return result, nil
  274. }
  275. // 测试高级代理(Shadowsocks、Vmess等)
  276. func (st *SpeedTester) testAdvancedProxy(node database.Node) (*SpeedTestResult, error) {
  277. result := &SpeedTestResult{}
  278. // 对于高级代理,我们尝试通过本地代理端口进行测试
  279. // 这需要代理客户端(如Clash)在本地运行并提供HTTP代理服务
  280. // 尝试常见的本地代理端口
  281. localProxyPorts := []int{7890, 7891, 1080, 8080, 8118}
  282. for _, port := range localProxyPorts {
  283. proxyURL := fmt.Sprintf("http://127.0.0.1:%d", port)
  284. proxyURLParsed, err := url.Parse(proxyURL)
  285. if err != nil {
  286. continue
  287. }
  288. proxyClient := &http.Client{
  289. Timeout: st.timeout,
  290. Transport: &http.Transport{
  291. Proxy: http.ProxyURL(proxyURLParsed),
  292. TLSClientConfig: &tls.Config{
  293. InsecureSkipVerify: false,
  294. },
  295. DisableKeepAlives: true,
  296. },
  297. }
  298. // 测试延迟
  299. latency, ipAddress, location, err := st.testLatency(proxyClient)
  300. if err == nil {
  301. result.Latency = latency
  302. result.IPAddress = ipAddress
  303. result.Location = location
  304. result.Success = true
  305. // 测试下载速度
  306. if downloadSpeed, err := st.testDownloadSpeed(proxyClient); err == nil {
  307. result.DownloadSpeed = downloadSpeed
  308. }
  309. // 测试上传速度
  310. if uploadSpeed, err := st.testUploadSpeed(proxyClient); err == nil {
  311. result.UploadSpeed = uploadSpeed
  312. }
  313. logger.Info("通过本地代理测试成功", map[string]interface{}{
  314. "node": node.Name,
  315. "port": port,
  316. })
  317. return result, nil
  318. }
  319. }
  320. return result, fmt.Errorf("无法通过本地代理测试节点: %s", node.Name)
  321. }
  322. // 测试延迟
  323. func (st *SpeedTester) testLatency(client *http.Client) (int, string, string, error) {
  324. startTime := time.Now()
  325. // 尝试多个测试URL
  326. for _, testURL := range st.testURLs {
  327. req, err := http.NewRequest("HEAD", testURL, nil)
  328. if err != nil {
  329. continue
  330. }
  331. req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36")
  332. resp, err := client.Do(req)
  333. if err != nil {
  334. continue
  335. }
  336. defer resp.Body.Close()
  337. latency := int(time.Since(startTime).Milliseconds())
  338. ipAddress := st.extractIPAddress(resp)
  339. location := st.extractLocation(resp)
  340. return latency, ipAddress, location, nil
  341. }
  342. return 0, "", "", fmt.Errorf("所有测试URL都无法访问")
  343. }
  344. // 测试下载速度
  345. func (st *SpeedTester) testDownloadSpeed(client *http.Client) (float64, error) {
  346. // 使用1MB的测试文件
  347. testURL := "https://httpbin.org/bytes/1048576"
  348. startTime := time.Now()
  349. req, err := http.NewRequest("GET", testURL, nil)
  350. if err != nil {
  351. return 0, fmt.Errorf("创建请求失败: %w", err)
  352. }
  353. resp, err := client.Do(req)
  354. if err != nil {
  355. return 0, fmt.Errorf("下载测试失败: %w", err)
  356. }
  357. defer resp.Body.Close()
  358. // 读取响应体
  359. body, err := io.ReadAll(resp.Body)
  360. if err != nil {
  361. return 0, fmt.Errorf("读取响应失败: %w", err)
  362. }
  363. duration := time.Since(startTime).Seconds()
  364. fileSize := len(body)
  365. // 计算下载速度 (Mbps)
  366. speedBps := float64(fileSize) / duration
  367. speedMbps := (speedBps * 8) / (1024 * 1024)
  368. return speedMbps, nil
  369. }
  370. // 测试上传速度
  371. func (st *SpeedTester) testUploadSpeed(client *http.Client) (float64, error) {
  372. // 使用1MB的测试数据
  373. testData := strings.Repeat("A", 1024*1024)
  374. startTime := time.Now()
  375. req, err := http.NewRequest("POST", "https://httpbin.org/post", strings.NewReader(testData))
  376. if err != nil {
  377. return 0, fmt.Errorf("创建请求失败: %w", err)
  378. }
  379. req.Header.Set("Content-Type", "application/octet-stream")
  380. resp, err := client.Do(req)
  381. if err != nil {
  382. return 0, fmt.Errorf("上传测试失败: %w", err)
  383. }
  384. defer resp.Body.Close()
  385. duration := time.Since(startTime).Seconds()
  386. fileSize := len(testData)
  387. // 计算上传速度 (Mbps)
  388. speedBps := float64(fileSize) / duration
  389. speedMbps := (speedBps * 8) / (1024 * 1024)
  390. return speedMbps, nil
  391. }
  392. // 提取IP地址
  393. func (st *SpeedTester) extractIPAddress(resp *http.Response) string {
  394. if ip := resp.Header.Get("X-Forwarded-For"); ip != "" {
  395. return strings.Split(ip, ",")[0]
  396. }
  397. if ip := resp.Header.Get("X-Real-IP"); ip != "" {
  398. return ip
  399. }
  400. if ip := resp.Header.Get("CF-Connecting-IP"); ip != "" {
  401. return ip
  402. }
  403. return "unknown"
  404. }
  405. // 提取位置信息
  406. func (st *SpeedTester) extractLocation(resp *http.Response) string {
  407. if resp.Header.Get("CF-Ray") != "" {
  408. return "Cloudflare"
  409. }
  410. if country := resp.Header.Get("CF-IPCountry"); country != "" {
  411. return country
  412. }
  413. return "unknown"
  414. }