// ignore_for_file: use_build_context_synchronously import 'dart:async'; import 'package:dio/dio.dart'; import 'package:jyfans/config/config.dart'; import 'package:jyfans/hj_utils/text_util.dart'; import 'package:jyfans/tools_base/debug_log.dart'; import 'package:jyfans/tools_base/net/http_manager.dart'; //选线管理 class DetectLineManager { // 在途请求的取消列表,超时时统一取消 final _cancelList = []; Future detectLineOnce() async { // 硬兜底:整个选线流程最多 20s,超时按"无可用线路"处理,避免 UI 永久卡在"选线中..." String line; try { line = await _detectLineOnce().timeout(const Duration(seconds: 20)); } catch (e) { debugLog("selectLine", "detectLineOnce()...timeout or error:$e"); line = ""; } _cancelInflight(); _cancelList.clear(); return line; } // 取消在途请求;except 为本次胜出的线路,不取消自己 void _cancelInflight({CancelToken? except}) { for (final t in _cancelList) { if (t != except) t.cancel(); } } /// 一次完整的批量选线过程 Future _detectLineOnce() async { //线路只来自 Config.lineList:DNS 解析兜底和本地线路缓存都已去掉 return await _pingCheckBatch(Config.lineList); } ///检查一批线路:并发 ping,谁先成功用谁;全失败返回 "" Future _pingCheckBatch(List lines) async { final validLines = lines.where(TextUtil.isNotEmpty).toList(); if (validLines.isEmpty) return ""; final completer = Completer(); final tasks = validLines.map((line) async { debugLog("selectLine", "_pingCheckBatch()...开始选线:$line"); final cancelToken = CancelToken(); _cancelList.add(cancelToken); try { if (await _pingCheck(line, cancelToken)) { if (!completer.isCompleted) { debugLog("selectLine", "_pingCheckBatch()...选线成功:$line"); completer.complete(line); // 竞速:已选到最快线路,立即取消其余在途请求 _cancelInflight(except: cancelToken); } } else { debugLog("selectLine", "_pingCheckBatch()...线路不可用:$line"); } } catch (e) { debugLog("selectLine", "_pingCheckBatch()...线路异常:$line $e"); } finally { _cancelList.remove(cancelToken); } }).toList(); // 全部结束仍无成功 → 返回空 Future.wait(tasks).whenComplete(() { if (!completer.isCompleted) completer.complete(""); }); return completer.future; } /// 单次网络请求,业务码 200 才算成功 Future _pingCheck(String line, [CancelToken? cancelToken]) async { final startTime = DateTime.now(); final resp = await httpManager.fetchDetectLineResponse( "$line/api/app/ping/check", options: Options( method: "GET", sendTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 10), ), cancelToken: cancelToken, ); debugLog("ping", "pingCheck()...line:$line cost ${DateTime.now().difference(startTime).inMilliseconds} milSeconds"); if (TextUtil.isNotEmpty(resp.time)) { httpManager.setServerTime(resp.time); } return resp.isSuccess; } }