feat: 节点测速调整
This commit is contained in:
parent
1809c11473
commit
909020654f
@ -8,7 +8,7 @@ import 'package:flutter/foundation.dart';
|
||||
/// 表示出站项列表的模型类
|
||||
class KrOutboundsList {
|
||||
|
||||
|
||||
|
||||
|
||||
/// 服务器分组
|
||||
final List<KRGroupOutboundList> groupOutboundList = []; // 存储服务器分组的列表
|
||||
@ -18,20 +18,20 @@ class KrOutboundsList {
|
||||
|
||||
/// 全部列表
|
||||
final List<KROutboundItem> allList = []; // 存储国家分组的列表
|
||||
|
||||
|
||||
// 配置json
|
||||
final List<Map<String,dynamic>> configJsonList = [];
|
||||
final List<Map<String,dynamic>> configJsonList = [];
|
||||
|
||||
/// 标签列表
|
||||
final Map<String,KROutboundItem> keyList = {}; // 存储国家分组的列表
|
||||
|
||||
|
||||
|
||||
/// 处理出站项并将其分组
|
||||
/// [list] 是要处理的出站项列表
|
||||
void processOutboundItems(List<KrNodeListItem> list,List<KRNodeGroupListItem> groupList) {
|
||||
final Map<String, List<KROutboundItem>> tagGroups = {};
|
||||
final Map<String, List<KROutboundItem>> countryGroups = {};
|
||||
|
||||
|
||||
// 用于追踪已使用的标签
|
||||
final Map<String, int> tagCounter = {};
|
||||
|
||||
@ -88,14 +88,11 @@ class KrOutboundsList {
|
||||
}
|
||||
}
|
||||
|
||||
// 生成国家自动选择虚拟节点
|
||||
_generateCountryAutoNodes(countryGroups);
|
||||
|
||||
// 将标签分组转换为 KRGroupOutboundList 并添加到 groupOutboundList
|
||||
for (var tag in tagGroups.keys) {
|
||||
final item = KRGroupOutboundList(
|
||||
final item = KRGroupOutboundList(
|
||||
tag: tag, outboundList: tagGroups[tag]!);
|
||||
|
||||
|
||||
for (var group in groupList) {
|
||||
if (item.tag == group.name) {
|
||||
item.icon = group.icon;
|
||||
@ -111,41 +108,7 @@ class KrOutboundsList {
|
||||
country: country,
|
||||
outboundList: countryGroups[country]!)); // 添加国家分组到列表
|
||||
}
|
||||
}
|
||||
|
||||
/// 生成国家自动选择虚拟节点
|
||||
void _generateCountryAutoNodes(Map<String, List<KROutboundItem>> countryGroups) {
|
||||
for (var entry in countryGroups.entries) {
|
||||
final country = entry.key;
|
||||
final nodes = entry.value;
|
||||
final autoTag = '${country}-auto';
|
||||
|
||||
if (kDebugMode) {
|
||||
print('🤖 生成国家自动选择节点: $autoTag, 包含 ${nodes.length} 个节点');
|
||||
}
|
||||
|
||||
// 构建 urltest 配置
|
||||
final urltestConfig = {
|
||||
'type': 'urltest',
|
||||
'tag': autoTag,
|
||||
'outbounds': nodes.map((node) => node.tag).toList(),
|
||||
'url': 'https://www.google.com/generate_204',
|
||||
'interval': '10m',
|
||||
'tolerance': 50,
|
||||
"interrupt_exist_connections": true,
|
||||
};
|
||||
|
||||
// 创建虚拟节点
|
||||
final virtualNode = KROutboundItem.fromVirtual(autoTag, country, urltestConfig);
|
||||
|
||||
// 添加到各个列表
|
||||
allList.add(virtualNode);
|
||||
keyList[autoTag] = virtualNode;
|
||||
// configJsonList.add(urltestConfig);
|
||||
|
||||
if (kDebugMode) {
|
||||
print('✅ 生成虚拟节点: $autoTag, 配置: ${urltestConfig.toString()}');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -95,6 +95,13 @@ class HINodeListController extends GetxController with WidgetsBindingObserver {
|
||||
KRLogUtil.kr_i('调试模式已重置', tag: 'HINodeListController');
|
||||
}
|
||||
|
||||
Future<void> kr_handleRefresh() async {
|
||||
await kr_subscribeService.kr_refreshAll();
|
||||
if (!homeController.kr_isLatency.value) {
|
||||
homeController.kr_urlTest();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
@ -23,22 +23,6 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
static const Color krModernGreen = Color(0xFF4CAF50);
|
||||
static const Color krModernGreenLight = Color(0xFF81C784);
|
||||
|
||||
// 存储随机延迟值,用于UI展示
|
||||
static final Map<String, int> _fakeDelays = {};
|
||||
|
||||
/// 获取用于显示的延迟值
|
||||
int _getDisplayDelay(HINodeListController controller, KROutboundItem item) {
|
||||
return item.urlTestDelay.value;
|
||||
// if (controller.homeController.kr_isConnected.value) {
|
||||
//
|
||||
// }
|
||||
// if (!_fakeDelays.containsKey(item.tag)) {
|
||||
// final random = Random();
|
||||
// _fakeDelays[item.tag] = 30 + random.nextInt(71); // 30-100ms
|
||||
// }
|
||||
// return _fakeDelays[item.tag] ?? 0;
|
||||
}
|
||||
|
||||
/// 获取分组内最快节点的延迟值(单位:ms)
|
||||
/// 如果列表为空,返回 0
|
||||
int getFastestNodeDelay(
|
||||
@ -77,14 +61,18 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
Widget build(BuildContext context) {
|
||||
// 1. 使用 Material 作为根组件,确保 InkWell 的水波纹效果正常
|
||||
// 并设置透明背景,让父组件的背景可以透出来
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: _buildSubscribeList(context)
|
||||
// child: _kr_buildRegionList(context)
|
||||
);
|
||||
return Obx(() {
|
||||
// Material 保持透明背景,让 InkWell 水波纹正常
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: controller.isDebugMode.value
|
||||
? _buildSubscribeList(context)
|
||||
: _kr_buildRegionList(context),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/// 构建国家/地区分组列表(废弃不使用)
|
||||
/// 构建国家/地区分组列表
|
||||
Widget _kr_buildRegionList(BuildContext context) {
|
||||
return Obx(() {
|
||||
return _kr_buildListContainer(
|
||||
@ -185,10 +173,8 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
try {
|
||||
|
||||
final success =
|
||||
await controller.homeController.kr_performNodeSwitch('${country.country}-auto');
|
||||
print('node 点击 ${country.country} 节点数量${country.outboundList.length} 节点详情 ${country.outboundList}');
|
||||
await controller.homeController.kr_performNodeSwitch('auto');
|
||||
if (success) {
|
||||
controller.homeController.kr_currentListStatus.value =
|
||||
KRHomeViewsListStatus.kr_none;
|
||||
@ -216,10 +202,7 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
padding: EdgeInsets.symmetric(vertical: 8.w),
|
||||
// 2. 使用 children 属性,并一次性构建所有列表项
|
||||
children: [
|
||||
if (controller.kr_getFilteredNodeList().isEmpty)
|
||||
_buildEmptyListPlaceholder(context,
|
||||
controller.isDebugMode.value ? '调试模式:无节点数据' : AppTranslations.kr_home.noNodes)
|
||||
else ...[
|
||||
...[
|
||||
InkWell(
|
||||
// 🔧 修复:改为 async,等待节点切换完成后再关闭列表
|
||||
onTap: () async {
|
||||
@ -327,7 +310,7 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
),
|
||||
),
|
||||
),
|
||||
...controller.kr_getFilteredNodeList().map((item) {
|
||||
...controller.kr_subscribeService.allList().map((item) {
|
||||
return InkWell(
|
||||
// 🔧 修复:改为 async,等待节点切换完成后再关闭列表
|
||||
onTap: () async {
|
||||
@ -423,18 +406,9 @@ class HINodeListView extends GetView<HINodeListController> {
|
||||
// 极简逻辑:只根据节点类型决定显示方式
|
||||
print('🔄 _kr_buildNodeListItem节点: item.tag=${item.tag}, country=${item.country}');
|
||||
|
||||
// 如果节点本身是auto组节点,显示组内最快节点的速度
|
||||
if (item.tag.endsWith('-auto')) {
|
||||
print('🎯 检测到auto组节点,获取最快节点信息');
|
||||
final autoNodeInfo = controller.homeController.kr_getCountryAutoSelectedNode(item.country);
|
||||
print('📊 _kr_buildNodeListItem获取auto最快节点: $autoNodeInfo');
|
||||
displayDelay = autoNodeInfo?['delay'] ?? 0;
|
||||
print('✅ _kr_buildNodeListItem使用auto最快节点延迟: $displayDelay');
|
||||
} else {
|
||||
// 普通节点,直接显示节点自身的速度
|
||||
displayDelay = item.urlTestDelay.value;
|
||||
print('🔄 _kr_buildNodeListItem使用普通节点延迟: $displayDelay');
|
||||
}
|
||||
// 普通节点,直接显示节点自身的速度
|
||||
displayDelay = item.urlTestDelay.value;
|
||||
print('🔄 _kr_buildNodeListItem使用普通节点延迟: $displayDelay');
|
||||
|
||||
// 2. 声明文本和颜色变量
|
||||
String delayText;
|
||||
|
||||
@ -159,8 +159,7 @@ class HINodePageView extends GetView<HINodeListController> {
|
||||
padding: EdgeInsets.only(left: 60.w, right: 60.w, bottom: 90.w), // 为HIHelpEntrance预留空间
|
||||
// 2. 在 Padding 内部使用 EasyRefresh
|
||||
child: EasyRefresh(
|
||||
// 3. 绑定 onRefresh 回调
|
||||
onRefresh: controller.kr_subscribeService.kr_refreshAll,
|
||||
onRefresh: controller.kr_handleRefresh,
|
||||
// 4. 自定义 Header 样式
|
||||
header: ClassicHeader(
|
||||
dragText: '下拉刷新',
|
||||
@ -228,4 +227,4 @@ class HINodePageView extends GetView<HINodeListController> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,7 +309,6 @@ class KRSubscribeService {
|
||||
|
||||
// 保存配置
|
||||
KRSingBoxImp.instance.kr_saveOutbounds(listModel.configJsonList);
|
||||
KRSingBoxImp.instance.kr_saveAllOutbounds(listModel.countryOutboundList);
|
||||
|
||||
// 更新试用和订阅状态
|
||||
_kr_updateSubscribeStatus();
|
||||
@ -662,7 +661,6 @@ class KRSubscribeService {
|
||||
|
||||
// 保存配置
|
||||
KRSingBoxImp.instance.kr_saveOutbounds(listModel.configJsonList);
|
||||
KRSingBoxImp.instance.kr_saveAllOutbounds(listModel.countryOutboundList);
|
||||
|
||||
// 更新试用和订阅状态
|
||||
_kr_updateSubscribeStatus();
|
||||
@ -725,7 +723,6 @@ class KRSubscribeService {
|
||||
|
||||
// 保存配置
|
||||
KRSingBoxImp.instance.kr_saveOutbounds([]);
|
||||
KRSingBoxImp.instance.kr_saveAllOutbounds([]);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -12,15 +12,12 @@ import 'package:kaer_with_panels/singbox/service/singbox_service.dart';
|
||||
import 'package:kaer_with_panels/singbox/service/singbox_service_provider.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
|
||||
import '../../../core/model/directories.dart';
|
||||
import '../../../singbox/model/singbox_config_option.dart';
|
||||
import '../../../singbox/model/singbox_outbound.dart';
|
||||
import '../../../singbox/model/singbox_stats.dart';
|
||||
import '../../../singbox/model/singbox_status.dart';
|
||||
import '../../model/business/kr_group_outbound_list.dart';
|
||||
import '../../model/business/kr_outbound_item.dart';
|
||||
import '../../utils/kr_country_util.dart';
|
||||
import '../../utils/kr_log_util.dart';
|
||||
import '../../utils/kr_secure_storage.dart';
|
||||
@ -68,7 +65,6 @@ class KRSingBoxImp {
|
||||
Map<String, dynamic> kr_configOption = {};
|
||||
|
||||
List<Map<String, dynamic>> kr_outbounds = [];
|
||||
List<KRCountryOutboundList> Kr_allOutbounds = [];
|
||||
|
||||
/// 首次启动
|
||||
RxBool kr_isFristStart = false.obs;
|
||||
@ -103,6 +99,9 @@ class KRSingBoxImp {
|
||||
/// 活动的出站分组
|
||||
RxList<SingboxOutboundGroup> kr_activeGroups = <SingboxOutboundGroup>[].obs;
|
||||
|
||||
// 🔒 Windows DNS 优化:标志位,记录 DNS 是否已备份
|
||||
bool _dnsBackedUp = false;
|
||||
|
||||
/// 所有的出站分组
|
||||
RxList<SingboxOutboundGroup> kr_allGroups = <SingboxOutboundGroup>[].obs;
|
||||
|
||||
@ -115,62 +114,6 @@ class KRSingBoxImp {
|
||||
/// 初始化进行中共享 Future(single-flight 防并发)
|
||||
Future<void>? _kr_initFuture;
|
||||
|
||||
/// 是否为Android模拟器
|
||||
bool _kr_isAndroidEmulator = false;
|
||||
|
||||
/// 检测并缓存模拟器标识
|
||||
///
|
||||
/// - 仅在 Android 上进行模拟器特征识别
|
||||
/// - 通过 brand/model/product/manufacturer 的常见模拟器特征判断
|
||||
/// 返回:无(结果写入 `_kr_isAndroidEmulator`)
|
||||
Future<void> _kr_detectEmulator() async {
|
||||
try {
|
||||
if (!Platform.isAndroid) {
|
||||
_kr_isAndroidEmulator = false;
|
||||
return;
|
||||
}
|
||||
|
||||
final info = await DeviceInfoPlugin().androidInfo;
|
||||
final brand = (info.brand ?? '').toLowerCase();
|
||||
final model = (info.model ?? '').toLowerCase();
|
||||
final product = (info.product ?? '').toLowerCase();
|
||||
final manufacturer = (info.manufacturer ?? '').toLowerCase();
|
||||
final device = (info.device ?? '').toLowerCase();
|
||||
|
||||
bool containsAny(String s, List<String> keys) => keys.any((k) => s.contains(k));
|
||||
|
||||
final indicators = <bool>[
|
||||
containsAny(brand, [
|
||||
'generic', 'unknown', 'google', 'vbox', 'virtualbox',
|
||||
'bluestacks', 'nox', 'ldplayer', 'genymotion', 'mumu', 'netease', 'leidian'
|
||||
]),
|
||||
containsAny(model, [
|
||||
'emulator', 'sdk', 'sdk_gphone', 'google_sdk', 'android sdk built for x86',
|
||||
'bluestacks', 'genymotion', 'nox', 'ldplayer', 'mumu'
|
||||
]),
|
||||
containsAny(product, [
|
||||
'sdk', 'google_sdk', 'sdk_gphone', 'emulator', 'vbox', 'virtualbox'
|
||||
]),
|
||||
containsAny(manufacturer, [
|
||||
'genymotion', 'unknown', 'bluestacks', 'nox', 'ld', 'netease', 'mumu'
|
||||
]),
|
||||
containsAny(device, [
|
||||
'emulator', 'generic', 'vbox', 'virtualbox', 'sdk'
|
||||
]),
|
||||
];
|
||||
|
||||
_kr_isAndroidEmulator = indicators.any((v) => v);
|
||||
KRLogUtil.kr_i(
|
||||
'🔍 Android 模拟器检测: ${_kr_isAndroidEmulator ? '是模拟器' : '非模拟器'}'
|
||||
' (brand=$brand, model=$model, product=$product, manufacturer=$manufacturer, device=$device)',
|
||||
tag: 'SingBox',
|
||||
);
|
||||
} catch (e) {
|
||||
_kr_isAndroidEmulator = false;
|
||||
KRLogUtil.kr_w('⚠️ 模拟器检测失败,按真机处理: $e', tag: 'SingBox');
|
||||
}
|
||||
}
|
||||
|
||||
/// 当前混合代理端口是否就绪
|
||||
bool get kr_isProxyReady => kr_status.value is SingboxStarted;
|
||||
|
||||
@ -226,9 +169,6 @@ class KRSingBoxImp {
|
||||
await KRCountryUtil.kr_init();
|
||||
KRLogUtil.kr_i('国家工具初始化完成');
|
||||
|
||||
// 设备环境检测(Android模拟器)
|
||||
await _kr_detectEmulator();
|
||||
|
||||
final oOption = SingboxConfigOption.fromJson(_getConfigOption());
|
||||
KRLogUtil.kr_i('配置选项初始化完成');
|
||||
|
||||
@ -617,11 +557,9 @@ class KRSingBoxImp {
|
||||
"log-level": "info", // 调试阶段使用 info,生产环境改为 warn
|
||||
"resolve-destination": false,
|
||||
"ipv6-mode": "ipv4_only", // 参考 hiddify-app: 仅使用 IPv4 (有效值: ipv4_only, prefer_ipv4, prefer_ipv6, ipv6_only)
|
||||
"remote-dns-address": _kr_isAndroidEmulator
|
||||
? "local"
|
||||
: "https://dns.google/dns-query",
|
||||
"remote-dns-address": "https://dns.google/dns-query", // 使用 Google DoH,避免中转节点 DNS 死锁
|
||||
"remote-dns-domain-strategy": "prefer_ipv4",
|
||||
"direct-dns-address": "local",
|
||||
"direct-dns-address": "local", // 使用系统 DNS,确保中转服务器域名能被解析
|
||||
"direct-dns-domain-strategy": "prefer_ipv4",
|
||||
"mixed-port": kr_port,
|
||||
"tproxy-port": kr_port,
|
||||
@ -743,56 +681,9 @@ class KRSingBoxImp {
|
||||
addFromOutbound(g);
|
||||
}
|
||||
|
||||
for (final g in Kr_allOutbounds) {
|
||||
for (final it in g.outboundList) {
|
||||
addFromOutbound(it.config);
|
||||
}
|
||||
}
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
/// 动态构建 DNS 配置
|
||||
///
|
||||
/// - 真机:使用远程 DoH + 本地系统 DNS,final 指向远程
|
||||
/// - 模拟器:仅使用系统 DNS,final 指向直连,避免覆盖系统 DNS
|
||||
/// 返回:Sing-box `dns` 段配置 Map
|
||||
Map<String, dynamic> _kr_buildDnsConfig() {
|
||||
if (_kr_isAndroidEmulator) {
|
||||
KRLogUtil.kr_i('🛡️ 模拟器兼容模式:强制使用系统 DNS', tag: 'SingBox');
|
||||
return {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "dns-direct",
|
||||
"address": "local",
|
||||
"detour": "direct"
|
||||
}
|
||||
],
|
||||
"rules": [],
|
||||
"final": "dns-direct",
|
||||
"strategy": "prefer_ipv4"
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "dns-remote",
|
||||
"address": "https://1.1.1.1/dns-query",
|
||||
"address_resolver": "dns-direct"
|
||||
},
|
||||
{
|
||||
"tag": "dns-direct",
|
||||
"address": "local",
|
||||
"detour": "direct"
|
||||
}
|
||||
],
|
||||
"rules": _kr_buildDnsRules(),
|
||||
"final": "dns-remote",
|
||||
"strategy": "prefer_ipv4"
|
||||
};
|
||||
}
|
||||
|
||||
/// 订阅状态变化流
|
||||
/// 参考 hiddify-app: 监听 libcore 发送的状态事件来自动更新 UI
|
||||
void _kr_subscribeToStatus() {
|
||||
@ -1206,7 +1097,7 @@ class KRSingBoxImp {
|
||||
await Future.delayed(const Duration(milliseconds: 2000));
|
||||
|
||||
final savedNode = await KRSecureStorage().kr_readData(key: _keySelectedNode);
|
||||
if (savedNode != null && savedNode.isNotEmpty && savedNode != 'auto' && !savedNode.endsWith('-auto')) {
|
||||
if (savedNode != null && savedNode.isNotEmpty && savedNode != 'auto') {
|
||||
KRLogUtil.kr_i('🔄 恢复用户选择的节点: $savedNode', tag: 'SingBox');
|
||||
|
||||
try {
|
||||
@ -1247,14 +1138,7 @@ class KRSingBoxImp {
|
||||
// print("错误堆栈: $stack");
|
||||
// }
|
||||
// }
|
||||
void kr_saveAllOutbounds(List<KRCountryOutboundList> outbounds) {
|
||||
Kr_allOutbounds = outbounds;
|
||||
KRLogUtil.kr_i('📊 保存国家分组: ${Kr_allOutbounds.length}', tag: 'SingBox');
|
||||
for (int i = 0; i < Kr_allOutbounds.length; i++) {
|
||||
final c = Kr_allOutbounds[i];
|
||||
KRLogUtil.kr_i(' group[$i] country="${c.country}" outbounds=${c.outboundList.length}', tag: 'SingBox');
|
||||
}
|
||||
}
|
||||
|
||||
/// 保存配置文件
|
||||
void kr_saveOutbounds(List<Map<String, dynamic>> outbounds) async {
|
||||
KRLogUtil.kr_i('💾 开始保存配置文件...', tag: 'SingBox');
|
||||
@ -1303,7 +1187,23 @@ class KRSingBoxImp {
|
||||
"level": "debug",
|
||||
"timestamp": true
|
||||
},
|
||||
"dns": _kr_buildDnsConfig(),
|
||||
"dns": {
|
||||
"servers": [
|
||||
{
|
||||
"tag": "dns-remote",
|
||||
"address": "https://1.1.1.1/dns-query",
|
||||
"address_resolver": "dns-direct"
|
||||
},
|
||||
{
|
||||
"tag": "dns-direct",
|
||||
"address": "local",
|
||||
"detour": "direct"
|
||||
}
|
||||
],
|
||||
"rules": _kr_buildDnsRules(), // ✅ 使用动态构建的 DNS 规则
|
||||
"final": "dns-remote",
|
||||
"strategy": "prefer_ipv4"
|
||||
},
|
||||
"inbounds": [
|
||||
{
|
||||
"type": "tun",
|
||||
@ -1506,55 +1406,21 @@ class KRSingBoxImp {
|
||||
// 🔧 强制重新生成配置文件(确保最新的路由规则生效)
|
||||
if (kr_outbounds.isNotEmpty) {
|
||||
KRLogUtil.kr_i('🔄 启动前强制重新生成配置文件...', tag: 'SingBox');
|
||||
final selectedNode = await KRSecureStorage().kr_readData(key: _keySelectedNode);
|
||||
KRLogUtil.kr_i('📊 国家分组数量: ${Kr_allOutbounds.length}', tag: 'SingBox');
|
||||
for (int i = 0; i < Kr_allOutbounds.length; i++) {
|
||||
final g = Kr_allOutbounds[i];
|
||||
KRLogUtil.kr_i('Kr_allOutbounds[$i] country="${g.country}" outbounds=${g.outboundList.length}', tag: 'SingBox');
|
||||
}
|
||||
List<Map<String, dynamic>> toSave = [];
|
||||
if (selectedNode != null && selectedNode.endsWith('-auto')) {
|
||||
KRLogUtil.kr_i('🤖 检测到自动国家节点: $selectedNode', tag: 'SingBox');
|
||||
String selectedCountry = selectedNode.replaceAll(RegExp(r'-auto$'), '');
|
||||
final selectedCountryLower = selectedCountry.toLowerCase();
|
||||
final matchedGroup = Kr_allOutbounds.firstWhere(
|
||||
(g) => g.country.toLowerCase() == selectedCountryLower,
|
||||
orElse: () => KRCountryOutboundList(country: '', outboundList: []),
|
||||
);
|
||||
if (matchedGroup.country.isNotEmpty) {
|
||||
KRLogUtil.kr_i('🎯 命中分组: ${matchedGroup.country}, 节点数: ${matchedGroup.outboundList.length}', tag: 'SingBox');
|
||||
toSave = matchedGroup.outboundList.map((it) => it.config).toList();
|
||||
} else {
|
||||
for (final g in Kr_allOutbounds) {
|
||||
for (final it in g.outboundList) {
|
||||
final tagLower = it.tag.toLowerCase();
|
||||
if (tagLower == selectedNode.toLowerCase() || tagLower.contains(selectedCountryLower)) {
|
||||
toSave.add(it.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
KRLogUtil.kr_i('✅ 自动国家过滤: ${toSave.length}', tag: 'SingBox');
|
||||
} else {
|
||||
for (final g in Kr_allOutbounds) {
|
||||
for (final it in g.outboundList) {
|
||||
toSave.add(it.config);
|
||||
}
|
||||
}
|
||||
}
|
||||
kr_saveOutbounds(toSave);
|
||||
kr_saveOutbounds(kr_outbounds);
|
||||
// 等待配置文件写入完成
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
|
||||
KRLogUtil.kr_i('📁 配置文件路径: $_cutPath', tag: 'SingBox');
|
||||
KRLogUtil.kr_i('📝 配置名称: $kr_configName', tag: 'SingBox');
|
||||
|
||||
// 🔑 Windows 平台:在启动前备份 DNS 设置
|
||||
if (Platform.isWindows) {
|
||||
KRLogUtil.kr_i('🪟 Windows 平台,备份 DNS 设置...', tag: 'SingBox');
|
||||
// 🔑 Windows 平台:仅在首次启动时备份 DNS 设置(优化:避免重复备份)
|
||||
if (Platform.isWindows && !_dnsBackedUp) {
|
||||
KRLogUtil.kr_i('🪟 Windows 平台,首次启动备份 DNS 设置...', tag: 'SingBox');
|
||||
try {
|
||||
final backupSuccess = await KRWindowsDnsUtil.instance.kr_backupDnsSettings();
|
||||
if (backupSuccess) {
|
||||
_dnsBackedUp = true; // 标记已备份
|
||||
KRLogUtil.kr_i('✅ Windows DNS 备份成功', tag: 'SingBox');
|
||||
} else {
|
||||
KRLogUtil.kr_w('⚠️ Windows DNS 备份失败,将在停止时使用兜底恢复', tag: 'SingBox');
|
||||
@ -1562,6 +1428,8 @@ class KRSingBoxImp {
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_w('⚠️ Windows DNS 备份异常: $e,将在停止时使用兜底恢复', tag: 'SingBox');
|
||||
}
|
||||
} else if (Platform.isWindows && _dnsBackedUp) {
|
||||
KRLogUtil.kr_i('⏭️ Windows 平台,DNS 已备份,跳过重复备份(节点切换优化)', tag: 'SingBox');
|
||||
}
|
||||
|
||||
// 🔑 先尝试停止旧实例,避免 command.sock 冲突
|
||||
@ -1731,12 +1599,42 @@ class KRSingBoxImp {
|
||||
// 继续执行清理操作
|
||||
}
|
||||
|
||||
// 🔑 Windows 平台:停止后立即恢复 DNS 设置
|
||||
if (Platform.isWindows) {
|
||||
KRLogUtil.kr_i('🪟 Windows 平台,开始恢复 DNS 设置...', tag: 'SingBox');
|
||||
// 🔑 Windows 平台:仅在有备份时恢复 DNS 设置
|
||||
if (Platform.isWindows && _dnsBackedUp) {
|
||||
KRLogUtil.kr_i('🪟 Windows 平台,等待 sing-box 完全停止...', tag: 'SingBox');
|
||||
|
||||
// 等待 sing-box 完全停止
|
||||
await Future.delayed(const Duration(milliseconds: 1000));
|
||||
// 🔧 P3优化: 监听状态而非固定延迟,确保 sing-box 真正停止后再恢复 DNS
|
||||
try {
|
||||
// 如果当前已经是停止状态,直接继续
|
||||
if (kr_status.value is SingboxStopped) {
|
||||
KRLogUtil.kr_i('✅ sing-box 已经是停止状态,立即恢复 DNS', tag: 'SingBox');
|
||||
} else {
|
||||
// 等待状态变为停止,最多等待3秒
|
||||
final completer = Completer<void>();
|
||||
late final Worker worker;
|
||||
|
||||
worker = ever(kr_status, (status) {
|
||||
if (status is SingboxStopped) {
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete();
|
||||
worker.dispose();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await completer.future.timeout(
|
||||
const Duration(seconds: 3),
|
||||
onTimeout: () {
|
||||
KRLogUtil.kr_w('⏱️ 等待停止状态超时,继续执行 DNS 恢复', tag: 'SingBox');
|
||||
worker.dispose();
|
||||
},
|
||||
);
|
||||
|
||||
KRLogUtil.kr_i('✅ sing-box 已完全停止,开始恢复 DNS...', tag: 'SingBox');
|
||||
}
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_w('⚠️ 状态监听异常: $e,继续执行 DNS 恢复', tag: 'SingBox');
|
||||
}
|
||||
|
||||
try {
|
||||
// 尝试恢复 DNS
|
||||
@ -1749,7 +1647,14 @@ class KRSingBoxImp {
|
||||
} catch (e) {
|
||||
KRLogUtil.kr_e('❌ Windows DNS 恢复异常: $e', tag: 'SingBox');
|
||||
// 异常时也会在工具类内部执行兜底恢复
|
||||
} finally {
|
||||
// 🔧 P0修复2: 使用 finally 确保标志位始终被重置,避免下次启动跳过备份
|
||||
_dnsBackedUp = false;
|
||||
KRLogUtil.kr_i('🔄 重置 DNS 备份标志位', tag: 'SingBox');
|
||||
}
|
||||
} else if (Platform.isWindows && !_dnsBackedUp) {
|
||||
KRLogUtil.kr_i('⏭️ Windows 平台,DNS 未备份,跳过恢复', tag: 'SingBox');
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
} else {
|
||||
// 非 Windows 平台正常等待
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
@ -1777,12 +1682,16 @@ class KRSingBoxImp {
|
||||
KRLogUtil.kr_e('错误堆栈: $stackTrace');
|
||||
|
||||
// 🔑 即使出错,也要尝试恢复 Windows DNS
|
||||
if (Platform.isWindows) {
|
||||
if (Platform.isWindows && _dnsBackedUp) {
|
||||
KRLogUtil.kr_w('⚠️ 停止异常,强制执行 DNS 恢复', tag: 'SingBox');
|
||||
try {
|
||||
await KRWindowsDnsUtil.instance.kr_restoreDnsSettings();
|
||||
} catch (dnsError) {
|
||||
KRLogUtil.kr_e('❌ 强制 DNS 恢复失败: $dnsError', tag: 'SingBox');
|
||||
} finally {
|
||||
// 🔧 P0修复2: 异常路径也要重置标志位
|
||||
_dnsBackedUp = false;
|
||||
KRLogUtil.kr_i('🔄 异常后重置 DNS 备份标志位', tag: 'SingBox');
|
||||
}
|
||||
}
|
||||
|
||||
@ -1989,7 +1898,7 @@ class KRSingBoxImp {
|
||||
// 🔄 如果用户选择了具体节点(不是 auto),启动定期检查和重新选择
|
||||
// 这是为了防止 urltest 自动覆盖用户的手动选择
|
||||
_nodeSelectionTimer?.cancel();
|
||||
if (tag != 'auto' && !tag.endsWith('-auto')) {
|
||||
if (tag != 'auto') {
|
||||
KRLogUtil.kr_i('🔁 启动节点选择监控,防止被 auto 覆盖', tag: 'SingBox');
|
||||
_nodeSelectionTimer = Timer.periodic(const Duration(seconds: 20), (timer) {
|
||||
// 每 20 秒重新选择一次,确保用户选择不被覆盖
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user