import 'dart:convert'; import 'dart:io'; import 'dart:async'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; 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 '../../../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 '../../utils/kr_country_util.dart'; import '../../utils/kr_log_util.dart'; enum KRConnectionType { global, rule, // direct, } class KRSingBoxImp { /// 私有构造函数 KRSingBoxImp._(); /// 单例实例 static final KRSingBoxImp _instance = KRSingBoxImp._(); /// 工厂构造函数 factory KRSingBoxImp() => _instance; /// 获取实例的静态方法 static KRSingBoxImp get instance => _instance; /// 配置文件目录 late Directories kr_configDics; /// 配置文件名称 String kr_configName = "hiFastVPN"; /// 通道方法 final _kr_methodChannel = const MethodChannel("com.hi.app/platform"); final _kr_container = ProviderContainer(); /// 核心服务 late SingboxService kr_singBox; /// more配置 Map kr_configOption = {}; List> kr_outbounds = []; /// 首次启动 RxBool kr_isFristStart = false.obs; /// 状态 final kr_status = SingboxStatus.stopped().obs; /// 拦截广告 final kr_blockAds = true.obs; /// 是否自动自动选择线路 final kr_isAutoOutbound = true.obs; bool _initialized = false; /// 连接类型 final kr_connectionType = KRConnectionType.rule.obs; String _cutPath = ""; /// 端口 int kr_port = 51213; /// 统计 final kr_stats = SingboxStats( connectionsIn: 0, connectionsOut: 0, uplink: 0, downlink: 0, uplinkTotal: 0, downlinkTotal: 0, ).obs; /// 活动的出站分组 RxList kr_activeGroups = [].obs; /// 所有的出站分组 RxList kr_allGroups = [].obs; /// Stream 订阅管理器 final List> _kr_subscriptions = []; /// 初始化标志,防止重复初始化 bool _kr_isInitialized = false; /// 当前混合代理端口是否就绪 bool get kr_isProxyReady => kr_status.value is SingboxStarted; String? _lastProxyRule; /// 构建 Dart HttpClient 可识别的代理规则字符串 /// /// 当 sing-box 尚未启动时返回 `DIRECT`,启动后返回 /// `PROXY 127.0.0.1:; DIRECT`,以便在代理不可用时自动回落。 String kr_buildProxyRule({bool includeDirectFallback = true}) { if (!kr_isProxyReady) { const directRule = 'DIRECT'; if (_lastProxyRule != directRule) { KRLogUtil.kr_i('⏳ sing-box 未就绪,使用 DIRECT 直连', tag: 'SingBox'); _lastProxyRule = directRule; } return directRule; } final proxyRule = StringBuffer('PROXY 127.0.0.1:$kr_port'); if (includeDirectFallback) { proxyRule.write('; DIRECT'); } final ruleString = proxyRule.toString(); if (_lastProxyRule != ruleString) { KRLogUtil.kr_i('🛠️ 使用代理规则: $ruleString', tag: 'SingBox'); _lastProxyRule = ruleString; } return ruleString; } /// 初始化 Future init() async { // 防止重复初始化 if (_kr_isInitialized) { KRLogUtil.kr_i('SingBox 已经初始化,跳过重复初始化', tag: 'SingBox'); return; } try { if (_initialized) { KRLogUtil.kr_i('SingBox 已经初始化,跳过重复初始化'); return; } _initialized = true; KRLogUtil.kr_i('开始初始化 SingBox'); // 在应用启动时初始化 await KRCountryUtil.kr_init(); KRLogUtil.kr_i('国家工具初始化完成'); final oOption = SingboxConfigOption.fromJson(_getConfigOption()); KRLogUtil.kr_i('配置选项初始化完成'); KRLogUtil.kr_i('开始初始化 SingBox 服务'); kr_singBox = await _kr_container.read(singboxServiceProvider); await _kr_container.read(singboxServiceProvider).init(); KRLogUtil.kr_i('SingBox 服务初始化完成'); KRLogUtil.kr_i('开始初始化目录'); /// 初始化目录 if (Platform.isIOS) { final paths = await _kr_methodChannel.invokeMethod("get_paths"); KRLogUtil.kr_i('iOS 路径获取完成: $paths'); kr_configDics = ( baseDir: Directory(paths?["base"]! as String), workingDir: Directory(paths?["working"]! as String), tempDir: Directory(paths?["temp"]! as String), ); } else { final baseDir = await getApplicationSupportDirectory(); final workingDir = Platform.isAndroid ? await getExternalStorageDirectory() : baseDir; final tempDir = await getTemporaryDirectory(); // Windows 路径规范化:确保使用正确的路径分隔符 Directory normalizePath(Directory dir) { if (Platform.isWindows) { final normalized = dir.path.replaceAll('/', '\\'); if (normalized != dir.path) { KRLogUtil.kr_i('路径规范化: ${dir.path} -> $normalized', tag: 'SingBox'); return Directory(normalized); } } return dir; } kr_configDics = ( baseDir: normalizePath(baseDir), workingDir: normalizePath(workingDir!), tempDir: normalizePath(tempDir), ); KRLogUtil.kr_i('其他平台路径初始化完成'); } KRLogUtil.kr_i('开始创建目录'); KRLogUtil.kr_i('baseDir: ${kr_configDics.baseDir.path}', tag: 'SingBox'); KRLogUtil.kr_i('workingDir: ${kr_configDics.workingDir.path}', tag: 'SingBox'); KRLogUtil.kr_i('tempDir: ${kr_configDics.tempDir.path}', tag: 'SingBox'); // 确保所有目录都存在 if (!kr_configDics.baseDir.existsSync()) { await kr_configDics.baseDir.create(recursive: true); KRLogUtil.kr_i('已创建 baseDir', tag: 'SingBox'); } if (!kr_configDics.workingDir.existsSync()) { await kr_configDics.workingDir.create(recursive: true); KRLogUtil.kr_i('已创建 workingDir', tag: 'SingBox'); } if (!kr_configDics.tempDir.existsSync()) { await kr_configDics.tempDir.create(recursive: true); KRLogUtil.kr_i('已创建 tempDir', tag: 'SingBox'); } // 创建 libcore 数据库所需的 data 目录(在 workingDir 下) // 注意:libcore 的 Setup 会调用 os.Chdir(workingPath),所以 data 目录必须在 workingDir 下 final dataDir = Directory(p.join(kr_configDics.workingDir.path, 'data')); // 强制确保 data 目录存在(Windows 可能需要多次尝试) int retryCount = 0; const maxRetries = 5; while (!dataDir.existsSync() && retryCount < maxRetries) { try { await dataDir.create(recursive: true); // 等待文件系统同步(Windows 上可能需要一点时间) await Future.delayed(const Duration(milliseconds: 100)); // 验证目录确实创建成功 if (dataDir.existsSync()) { KRLogUtil.kr_i('✅ 已创建 data 目录: ${dataDir.path}', tag: 'SingBox'); break; } else { retryCount++; KRLogUtil.kr_i('⚠️ data 目录创建后验证失败,重试 $retryCount/$maxRetries', tag: 'SingBox'); await Future.delayed(const Duration(milliseconds: 200)); } } catch (e) { retryCount++; KRLogUtil.kr_e('❌ 创建 data 目录失败 (尝试 $retryCount/$maxRetries): $e', tag: 'SingBox'); if (retryCount >= maxRetries) { throw Exception('无法创建 libcore 数据库目录: ${dataDir.path},错误: $e'); } final delayMs = 200 * retryCount; await Future.delayed(Duration(milliseconds: delayMs)); } } if (!dataDir.existsSync()) { final error = 'data 目录不存在: ${dataDir.path}'; KRLogUtil.kr_e('❌ $error', tag: 'SingBox'); throw Exception(error); } // 验证目录权限(尝试创建一个测试文件) try { final testFile = File(p.join(dataDir.path, '.test_write')); await testFile.writeAsString('test'); await testFile.delete(); KRLogUtil.kr_i('✅ data 目录写入权限验证通过', tag: 'SingBox'); } catch (e) { KRLogUtil.kr_e('⚠️ data 目录写入权限验证失败: $e', tag: 'SingBox'); // 不抛出异常,让 libcore 自己处理 } // 在 Windows 上额外等待,确保文件系统操作完成 if (Platform.isWindows) { await Future.delayed(const Duration(milliseconds: 300)); KRLogUtil.kr_i('⏳ Windows 文件系统同步等待完成', tag: 'SingBox'); } // 最终验证:在 setup() 之前再次确认 workingDir 和 data 目录都存在且可访问 // libcore 的 Setup() 会调用 os.Chdir(workingPath),然后使用相对路径 "./data" // 如果 os.Chdir() 失败(路径不存在或权限问题),后续的相对路径访问会失败 if (!kr_configDics.workingDir.existsSync()) { final error = '❌ workingDir 不存在,无法调用 setup(): ${kr_configDics.workingDir.path}'; KRLogUtil.kr_e(error, tag: 'SingBox'); throw Exception(error); } // 验证 workingDir 可读可写 try { final testWorkingFile = File(p.join(kr_configDics.workingDir.path, '.test_working_dir')); await testWorkingFile.writeAsString('test'); await testWorkingFile.delete(); KRLogUtil.kr_i('✅ workingDir 写入权限验证通过', tag: 'SingBox'); } catch (e) { final error = '❌ workingDir 无写入权限: ${kr_configDics.workingDir.path}, 错误: $e'; KRLogUtil.kr_e(error, tag: 'SingBox'); throw Exception(error); } final finalDataDir = Directory(p.join(kr_configDics.workingDir.path, 'data')); if (!finalDataDir.existsSync()) { KRLogUtil.kr_e('❌ 最终验证失败:data 目录不存在', tag: 'SingBox'); KRLogUtil.kr_e('路径: ${finalDataDir.path}', tag: 'SingBox'); KRLogUtil.kr_e('workingDir 是否存在: ${kr_configDics.workingDir.existsSync()}', tag: 'SingBox'); if (kr_configDics.workingDir.existsSync()) { try { final workingDirContents = kr_configDics.workingDir.listSync(); KRLogUtil.kr_e('workingDir 内容: ${workingDirContents.map((e) => e.path.split(Platform.pathSeparator).last).join(", ")}', tag: 'SingBox'); } catch (e) { KRLogUtil.kr_e('无法列出 workingDir 内容: $e', tag: 'SingBox'); } } throw Exception('data 目录在 setup() 前验证失败: ${finalDataDir.path}'); } // 再次尝试写入测试,确保目录确实可用 try { final verifyFile = File(p.join(finalDataDir.path, '.verify_setup')); await verifyFile.writeAsString('verify'); await verifyFile.delete(); KRLogUtil.kr_i('✅ setup() 前最终验证通过', tag: 'SingBox'); } catch (e) { KRLogUtil.kr_e('❌ setup() 前最终验证失败: $e', tag: 'SingBox'); // 不抛出异常,让 setup() 自己处理 } final configsDir = Directory(p.join(kr_configDics.workingDir.path, "configs")); if (!configsDir.existsSync()) { try { await configsDir.create(recursive: true); KRLogUtil.kr_i('✅ 已创建 configs 目录: ${configsDir.path}', tag: 'SingBox'); } catch (e) { KRLogUtil.kr_e('⚠️ configs 目录创建失败: $e', tag: 'SingBox'); // 不抛出异常,继续初始化 } } else { KRLogUtil.kr_i('✅ configs 目录已存在: ${configsDir.path}', tag: 'SingBox'); } // 特别处理 extensionData.db 文件 (Windows特定) if (Platform.isWindows) { try { final extensionDataDbPath = p.join(finalDataDir.path, 'extensionData.db'); KRLogUtil.kr_i('👉 准备处理 extensionData.db 路径: $extensionDataDbPath', tag: 'SingBox'); // 确保 extensionData.db 的父目录存在 final extensionDataParent = Directory(p.dirname(extensionDataDbPath)); if (!extensionDataParent.existsSync()) { await extensionDataParent.create(recursive: true); KRLogUtil.kr_i('✅ 已创建 extensionData.db 父目录', tag: 'SingBox'); } // 测试文件创建权限 final testFile = File(p.join(extensionDataParent.path, '.test_extension')); await testFile.writeAsString('test'); await testFile.delete(); KRLogUtil.kr_i('✅ extensionData 目录权限验证通过', tag: 'SingBox'); } catch (e) { KRLogUtil.kr_e('⚠️ extensionData 目录处理失败: $e', tag: 'SingBox'); // 不抛出异常,继续初始化 } } KRLogUtil.kr_i('✅ 目录创建完成', tag: 'SingBox'); KRLogUtil.kr_i('开始设置 SingBox', tag: 'SingBox'); KRLogUtil.kr_i(' - baseDir: ${kr_configDics.baseDir.path}', tag: 'SingBox'); KRLogUtil.kr_i(' - workingDir: ${kr_configDics.workingDir.path}', tag: 'SingBox'); KRLogUtil.kr_i(' - tempDir: ${kr_configDics.tempDir.path}', tag: 'SingBox'); KRLogUtil.kr_i(' - data 目录: ${p.join(kr_configDics.workingDir.path, "data")}', tag: 'SingBox'); KRLogUtil.kr_i(' - data 目录存在: ${finalDataDir.existsSync()}', tag: 'SingBox'); // 在 Windows 上,列出 data 目录内容(如果有文件) if (Platform.isWindows && finalDataDir.existsSync()) { try { final dataContents = finalDataDir.listSync(); if (dataContents.isNotEmpty) { KRLogUtil.kr_i(' - data 目录现有文件: ${dataContents.map((e) => e.path.split(Platform.pathSeparator).last).join(", ")}', tag: 'SingBox'); } else { KRLogUtil.kr_i(' - data 目录为空', tag: 'SingBox'); } } catch (e) { KRLogUtil.kr_e(' - 无法列出 data 目录内容: $e', tag: 'SingBox'); } } KRLogUtil.kr_i(' - libcore 将通过 os.Chdir() 切换到: ${kr_configDics.workingDir.path}', tag: 'SingBox'); KRLogUtil.kr_i(' - 然后使用相对路径 "./data" 访问数据库', tag: 'SingBox'); // Windows 特定:验证路径格式是否正确 if (Platform.isWindows) { final workingPath = kr_configDics.workingDir.path; if (workingPath.contains('/')) { KRLogUtil.kr_e('⚠️ 警告:Windows 路径包含正斜杠,可能导致问题: $workingPath', tag: 'SingBox'); } // 确保路径使用反斜杠(Windows 标准) final normalizedPath = workingPath.replaceAll('/', '\\'); if (normalizedPath != workingPath) { KRLogUtil.kr_e('⚠️ 路径格式可能需要规范化: $workingPath -> $normalizedPath', tag: 'SingBox'); } } KRLogUtil.kr_i('✅ SingBox 初始化完成'); _kr_isInitialized = true; } catch (e, stackTrace) { KRLogUtil.kr_e('❌ SingBox 初始化失败: $e'); KRLogUtil.kr_e('📚 错误堆栈: $stackTrace'); // 添加额外的诊断信息 if (Platform.isWindows) { try { final workingDir = kr_configDics.workingDir; final dataDir = Directory(p.join(workingDir.path, 'data')); final configsDir = Directory(p.join(workingDir.path, 'configs')); KRLogUtil.kr_e('🔍 Windows 路径诊断信息:', tag: 'SingBox'); KRLogUtil.kr_e(' - workingDir: ${workingDir.path}', tag: 'SingBox'); KRLogUtil.kr_e(' - workingDir 存在: ${workingDir.existsSync()}', tag: 'SingBox'); KRLogUtil.kr_e(' - data 目录: ${dataDir.path}', tag: 'SingBox'); KRLogUtil.kr_e(' - data 目录存在: ${dataDir.existsSync()}', tag: 'SingBox'); KRLogUtil.kr_e(' - configs 目录: ${configsDir.path}', tag: 'SingBox'); KRLogUtil.kr_e(' - configs 目录存在: ${configsDir.existsSync()}', tag: 'SingBox'); // 检查父目录内容 if (workingDir.existsSync()) { try { final contents = workingDir.listSync(); KRLogUtil.kr_e(' - workingDir 内容: ${contents.map((e) => "${e.path.split(Platform.pathSeparator).last}${e is Directory ? "/" : ""}").join(", ")}', tag: 'SingBox'); } catch (listErr) { KRLogUtil.kr_e(' - 无法列出 workingDir 内容: $listErr', tag: 'SingBox'); } } } catch (diagErr) { KRLogUtil.kr_e(' - 诊断信息收集失败: $diagErr', tag: 'SingBox'); } } // 如果初始化失败,允许下次重试 _kr_isInitialized = false; rethrow; } } Map _getConfigOption() { if (kr_configOption.isNotEmpty) { return kr_configOption; } final op = { "region": KRCountryUtil.kr_getCurrentCountryCode(), "block-ads": kr_blockAds.value, "use-xray-core-when-possible": false, "execute-config-as-is": false, "log-level": "warn", "resolve-destination": false, "ipv6-mode": "ipv4_only", "remote-dns-address": "udp://8.8.8.8", "remote-dns-domain-strategy": "prefer_ipv4", "direct-dns-address": "udp://1.1.1.1", "direct-dns-domain-strategy": "prefer_ipv4", "mixed-port": kr_port, "tproxy-port": kr_port, "local-dns-port": 36450, "tun-implementation": "gvisor", "mtu": 9000, "strict-route": true, // "connection-test-url": "http://www.cloudflare.com", "connection-test-url": "http://www.gstatic.com/generate_204", "url-test-interval": 30, "enable-clash-api": true, "clash-api-port": 36756, "enable-tun": Platform.isIOS || Platform.isAndroid, "enable-tun-service": false, "set-system-proxy": Platform.isWindows || Platform.isLinux || Platform.isMacOS, "bypass-lan": false, "allow-connection-from-lan": false, "enable-fake-dns": false, "enable-dns-routing": true, "independent-dns-cache": true, "rules": [], "mux": { "enable": false, "padding": false, "max-streams": 8, "protocol": "h2mux" }, "tls-tricks": { "enable-fragment": false, "fragment-size": "10-30", "fragment-sleep": "2-8", "mixed-sni-case": false, "enable-padding": false, "padding-size": "1-1500" }, "warp": { "enable": false, "mode": "proxy_over_warp", "wireguard-config": "", "license-key": "", "account-id": "", "access-token": "", "clean-ip": "auto", "clean-port": 0, "noise": "1-3", "noise-size": "10-30", "noise-delay": "10-30", "noise-mode": "m4" }, "warp2": { "enable": false, "mode": "proxy_over_warp", "wireguard-config": "", "license-key": "", "account-id": "", "access-token": "", "clean-ip": "auto", "clean-port": 0, "noise": "1-3", "noise-size": "10-30", "noise-delay": "10-30", "noise-mode": "m4" } }; kr_configOption = op; return op; } /// 订阅统计数据流 void _kr_subscribeToStats() { // 取消之前的统计订阅 for (var sub in _kr_subscriptions) { if (sub.hashCode.toString().contains('Stats')) { sub.cancel(); } } _kr_subscriptions .removeWhere((sub) => sub.hashCode.toString().contains('Stats')); _kr_subscriptions.add( kr_singBox.watchStats().listen( (stats) { kr_stats.value = stats; }, onError: (error) { KRLogUtil.kr_e('统计数据监听错误: $error'); }, cancelOnError: false, ), ); } /// 订阅分组数据流 void _kr_subscribeToGroups() { // 取消之前的分组订阅 for (var sub in _kr_subscriptions) { if (sub.hashCode.toString().contains('Groups')) { sub.cancel(); } } _kr_subscriptions .removeWhere((sub) => sub.hashCode.toString().contains('Groups')); _kr_subscriptions.add( kr_singBox.watchActiveGroups().listen( (groups) { KRLogUtil.kr_i('📡 收到活动组更新,数量: ${groups.length}', tag: 'SingBox'); kr_activeGroups.value = groups; // 详细打印每个组的信息 for (int i = 0; i < groups.length; i++) { final group = groups[i]; KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'SingBox'); for (int j = 0; j < group.items.length; j++) { final item = group.items[j]; KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox'); } } KRLogUtil.kr_i('✅ 活动组处理完成', tag: 'SingBox'); }, onError: (error) { KRLogUtil.kr_e('❌ 活动分组监听错误: $error', tag: 'SingBox'); }, cancelOnError: false, ), ); _kr_subscriptions.add( kr_singBox.watchGroups().listen( (groups) { kr_allGroups.value = groups; }, onError: (error) { KRLogUtil.kr_e('所有分组监听错误: $error'); }, cancelOnError: false, ), ); } /// 监听活动组的详细实现 // Future watchActiveGroups() async { // try { // print("开始监听活动组详情..."); // final status = await kr_singBox.status(); // print("服务状态: ${status.toJson()}"); // final outbounds = await kr_singBox.listOutbounds(); // print("出站列表: ${outbounds.toJson()}"); // for (var outbound in outbounds.outbounds) { // print("出站配置: ${outbound.toJson()}"); // // 检查出站是否活动 // final isActive = await kr_singBox.isOutboundActive(outbound.tag); // print("出站 ${outbound.tag} 活动状态: $isActive"); // } // } catch (e, stack) { // print("监听活动组详情时出错: $e"); // print("错误堆栈: $stack"); // } // } /// 保存配置文件 void kr_saveOutbounds(List> outbounds) async { KRLogUtil.kr_i('💾 开始保存配置文件...', tag: 'SingBox'); KRLogUtil.kr_i('📊 出站节点数量: ${outbounds.length}', tag: 'SingBox'); // 打印每个节点的详细配置 for (int i = 0; i < outbounds.length; i++) { final outbound = outbounds[i]; KRLogUtil.kr_i('📋 节点[$i] 配置:', tag: 'SingBox'); KRLogUtil.kr_i(' - type: ${outbound['type']}', tag: 'SingBox'); KRLogUtil.kr_i(' - tag: ${outbound['tag']}', tag: 'SingBox'); KRLogUtil.kr_i(' - server: ${outbound['server']}', tag: 'SingBox'); KRLogUtil.kr_i(' - server_port: ${outbound['server_port']}', tag: 'SingBox'); if (outbound['method'] != null) { KRLogUtil.kr_i(' - method: ${outbound['method']}', tag: 'SingBox'); } if (outbound['password'] != null) { KRLogUtil.kr_i(' - password: ${outbound['password']?.toString().substring(0, 8)}...', tag: 'SingBox'); } if (outbound['uuid'] != null) { KRLogUtil.kr_i(' - uuid: ${outbound['uuid']?.toString().substring(0, 8)}...', tag: 'SingBox'); } KRLogUtil.kr_i(' - 完整配置: ${jsonEncode(outbound)}', tag: 'SingBox'); } kr_outbounds = outbounds; // 只保存 outbounds,Mobile.buildConfig() 会添加其他配置 final map = { "outbounds": kr_outbounds }; final file = _file(kr_configName); final temp = _tempFile(kr_configName); final mapStr = jsonEncode(map); KRLogUtil.kr_i('📄 配置文件内容长度: ${mapStr.length}', tag: 'SingBox'); KRLogUtil.kr_i('📄 完整配置文件内容: $mapStr', tag: 'SingBox'); await file.writeAsString(mapStr); await temp.writeAsString(mapStr); _cutPath = file.path; KRLogUtil.kr_i('📁 配置文件路径: $_cutPath', tag: 'SingBox'); await kr_singBox .validateConfigByPath(file.path, temp.path, false) .mapLeft((err) { KRLogUtil.kr_e('❌ 保存配置文件失败: $err', tag: 'SingBox'); }).run(); KRLogUtil.kr_i('✅ 配置文件保存完成', tag: 'SingBox'); } Future kr_start() async { kr_status.value = SingboxStarting(); try { KRLogUtil.kr_i('🚀 开始启动 SingBox...', tag: 'SingBox'); KRLogUtil.kr_i('📁 配置文件路径: $_cutPath', tag: 'SingBox'); KRLogUtil.kr_i('📝 配置名称: $kr_configName', tag: 'SingBox'); // 检查配置文件是否存在 final configFile = File(_cutPath); if (await configFile.exists()) { final configContent = await configFile.readAsString(); KRLogUtil.kr_i('📄 配置文件内容长度: ${configContent.length}', tag: 'SingBox'); KRLogUtil.kr_i('📄 配置文件前500字符: ${configContent.substring(0, configContent.length > 500 ? 500 : configContent.length)}', tag: 'SingBox'); } else { KRLogUtil.kr_w('⚠️ 配置文件不存在: $_cutPath', tag: 'SingBox'); } await kr_singBox.start(_cutPath, kr_configName, false).map( (r) { KRLogUtil.kr_i('✅ SingBox 启动成功', tag: 'SingBox'); }, ).mapLeft((err) { KRLogUtil.kr_e('❌ SingBox 启动失败: $err', tag: 'SingBox'); // 确保状态重置为Stopped,触发UI更新 kr_status.value = SingboxStopped(); // 强制刷新状态以触发观察者 kr_status.refresh(); throw err; }).run(); } catch (e, stackTrace) { KRLogUtil.kr_e('💥 SingBox 启动异常: $e', tag: 'SingBox'); KRLogUtil.kr_e('📚 错误堆栈: $stackTrace', tag: 'SingBox'); // 确保状态重置为Stopped,触发UI更新 kr_status.value = SingboxStopped(); // 强制刷新状态以触发观察者 kr_status.refresh(); rethrow; } } /// 停止服务 Future kr_stop() async { try { // 不主动赋值 kr_status await Future.delayed(const Duration(milliseconds: 100)); await kr_singBox.stop().run(); await Future.delayed(const Duration(milliseconds: 1000)); // 取消订阅 final subscriptions = List>.from(_kr_subscriptions); _kr_subscriptions.clear(); for (var subscription in subscriptions) { try { await subscription.cancel(); } catch (e) { KRLogUtil.kr_e('取消订阅时出错: $e'); } } // 不主动赋值 kr_status } catch (e, stackTrace) { KRLogUtil.kr_e('停止服务时出错: $e'); KRLogUtil.kr_e('错误堆栈: $stackTrace'); // 兜底,防止状态卡死 kr_status.value = SingboxStopped(); rethrow; } } /// void kr_updateAdBlockEnabled(bool bl) async { final oOption = _getConfigOption(); oOption["block-ads"] = bl; final op = SingboxConfigOption.fromJson(oOption); await kr_singBox.changeOptions(op) ..map((r) {}).mapLeft((err) { KRLogUtil.kr_e('更新广告拦截失败: $err'); }).run(); if (kr_status.value == SingboxStarted()) { await kr_restart(); } kr_blockAds.value = bl; } Future kr_restart() async { KRLogUtil.kr_i("restart"); kr_singBox.restart(_cutPath, kr_configName, false).mapLeft((err) { KRLogUtil.kr_e('重启失败: $err'); }).run(); } //// 设置出站模式 Future kr_updateConnectionType(KRConnectionType newType) async { try { KRLogUtil.kr_i('🔄 开始更新连接类型...', tag: 'SingBox'); KRLogUtil.kr_i('📊 当前类型: ${kr_connectionType.value}', tag: 'SingBox'); KRLogUtil.kr_i('📊 新类型: $newType', tag: 'SingBox'); if (kr_connectionType.value == newType) { KRLogUtil.kr_i('⚠️ 连接类型相同,无需更新', tag: 'SingBox'); return; } kr_connectionType.value = newType; final oOption = _getConfigOption(); var mode = ""; switch (newType) { case KRConnectionType.global: mode = "other"; KRLogUtil.kr_i('🌍 切换到全局代理模式', tag: 'SingBox'); break; case KRConnectionType.rule: mode = KRCountryUtil.kr_getCurrentCountryCode(); KRLogUtil.kr_i('🎯 切换到规则代理模式: $mode', tag: 'SingBox'); break; // case KRConnectionType.direct: // mode = "direct"; // break; } oOption["region"] = mode; KRLogUtil.kr_i('📝 更新 region 配置: $mode', tag: 'SingBox'); final op = SingboxConfigOption.fromJson(oOption); KRLogUtil.kr_i('📄 配置选项: ${oOption.toString()}', tag: 'SingBox'); await kr_singBox.changeOptions(op) ..map((r) { KRLogUtil.kr_i('✅ 连接类型更新成功', tag: 'SingBox'); }).mapLeft((err) { KRLogUtil.kr_e('❌ 更新连接类型失败: $err', tag: 'SingBox'); throw err; }).run(); if (kr_status.value == SingboxStarted()) { KRLogUtil.kr_i('🔄 VPN已启动,准备重启以应用新配置...', tag: 'SingBox'); await kr_restart(); KRLogUtil.kr_i('✅ VPN重启完成', tag: 'SingBox'); } else { KRLogUtil.kr_i('ℹ️ VPN未启动,配置已更新', tag: 'SingBox'); } } catch (e, stackTrace) { KRLogUtil.kr_e('💥 更新连接类型异常: $e', tag: 'SingBox'); KRLogUtil.kr_e('📚 错误堆栈: $stackTrace', tag: 'SingBox'); rethrow; } } /// 更新国家设置 Future kr_updateCountry(KRCountry kr_country) async { // 如果国家相同,直接返回 if (kr_country.kr_code == KRCountryUtil.kr_getCurrentCountryCode()) { return; } try { // 更新工具类中的当前国家 await KRCountryUtil.kr_setCurrentCountry(kr_country); // 更新配置选项 final oOption = _getConfigOption(); oOption["region"] = kr_country.kr_code; final op = SingboxConfigOption.fromJson(oOption); await kr_singBox.changeOptions(op) ..map((r) {}).mapLeft((err) { KRLogUtil.kr_e('更新国家设置失败: $err'); }).run(); // 如果服务正在运行,重启服务 if (kr_status.value == SingboxStarted()) { await kr_restart(); } } catch (err) { KRLogUtil.kr_e('更新国家失败: $err'); rethrow; } } Stream kr_watchStatus() { return kr_singBox.watchStatus(); } Stream> kr_watchGroups() { return kr_singBox.watchGroups(); } void kr_selectOutbound(String tag) { KRLogUtil.kr_i('🎯 开始选择出站节点: $tag', tag: 'SingBox'); KRLogUtil.kr_i('📊 当前活动组数量: ${kr_activeGroups.length}', tag: 'SingBox'); // 打印所有活动组信息 for (int i = 0; i < kr_activeGroups.length; i++) { final group = kr_activeGroups[i]; KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'SingBox'); for (int j = 0; j < group.items.length; j++) { final item = group.items[j]; KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox'); } } kr_singBox.selectOutbound("select", tag).run(); } /// 配合文件地址 Directory get directory => Directory(p.join(kr_configDics.workingDir.path, "configs")); File _file(String fileName) { return File(p.join(directory.path, "$fileName.json")); } File _tempFile(String fileName) => _file("$fileName.tmp"); // File tempFile(String fileName) => file("$fileName.tmp"); Future kr_urlTest(String groupTag) async { KRLogUtil.kr_i('🧪 开始 URL 测试: $groupTag', tag: 'SingBox'); KRLogUtil.kr_i('📊 当前活动组数量: ${kr_activeGroups.length}', tag: 'SingBox'); // 打印所有活动组信息 for (int i = 0; i < kr_activeGroups.length; i++) { final group = kr_activeGroups[i]; KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'SingBox'); for (int j = 0; j < group.items.length; j++) { final item = group.items[j]; KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox'); } } try { KRLogUtil.kr_i('🚀 调用 SingBox URL 测试 API...', tag: 'SingBox'); final result = await kr_singBox.urlTest(groupTag).run(); KRLogUtil.kr_i('✅ URL 测试完成: $groupTag, 结果: $result', tag: 'SingBox'); // 等待一段时间让 SingBox 完成测试 await Future.delayed(const Duration(seconds: 2)); // 再次检查活动组状态 KRLogUtil.kr_i('🔄 测试后活动组状态检查:', tag: 'SingBox'); for (int i = 0; i < kr_activeGroups.length; i++) { final group = kr_activeGroups[i]; KRLogUtil.kr_i('📋 活动组[$i]: tag=${group.tag}, type=${group.type}, selected=${group.selected}', tag: 'SingBox'); for (int j = 0; j < group.items.length; j++) { final item = group.items[j]; KRLogUtil.kr_i(' └─ 节点[$j]: tag=${item.tag}, type=${item.type}, delay=${item.urlTestDelay}', tag: 'SingBox'); } } } catch (e) { KRLogUtil.kr_e('❌ URL 测试失败: $groupTag, 错误: $e', tag: 'SingBox'); KRLogUtil.kr_e('📚 错误详情: ${e.toString()}', tag: 'SingBox'); } } }