From 9c2f9be6c5b66046fe3e3ea542d496b85b4cb296 Mon Sep 17 00:00:00 2001 From: Rust Date: Wed, 29 Oct 2025 16:07:16 +0800 Subject: [PATCH] =?UTF-8?q?windows=E8=B7=AF=E5=BE=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (cherry picked from commit 9cefb1b9e009575ee7f6a5aef631cb344b6e1df8) --- .../services/singbox_imp/kr_sing_box_imp.dart | 203 +++++++++++++++++- lib/app/utils/kr_secure_storage.dart | 8 + windows/CMakeLists.txt | 6 + 3 files changed, 210 insertions(+), 7 deletions(-) diff --git a/lib/app/services/singbox_imp/kr_sing_box_imp.dart b/lib/app/services/singbox_imp/kr_sing_box_imp.dart index 6dbe79d..271ca71 100755 --- a/lib/app/services/singbox_imp/kr_sing_box_imp.dart +++ b/lib/app/services/singbox_imp/kr_sing_box_imp.dart @@ -177,36 +177,225 @@ class KRSingBoxImp { 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: baseDir, - workingDir: workingDir!, - tempDir: tempDir, + 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() 自己处理 + } + if (!directory.existsSync()) { await directory.create(recursive: true); + KRLogUtil.kr_i('已创建 configs 目录', tag: 'SingBox'); } - KRLogUtil.kr_i('目录创建完成'); + KRLogUtil.kr_i('目录创建完成', tag: 'SingBox'); - KRLogUtil.kr_i('开始设置 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'); + } + } + await kr_singBox.setup(kr_configDics, false).map((r) { - KRLogUtil.kr_i('SingBox 设置成功'); + KRLogUtil.kr_i('✅ SingBox setup() 调用成功', tag: 'SingBox'); + + // setup() 返回成功,但可能数据库初始化还在进行中 + // 在 Windows 上额外等待,确保 libcore 内部的服务初始化完成 + if (Platform.isWindows) { + KRLogUtil.kr_i('⏳ Windows: 等待 libcore 服务初始化完成...', tag: 'SingBox'); + } + return r; }).mapLeft((err) { - KRLogUtil.kr_e('SingBox 设置失败: $err'); + KRLogUtil.kr_e('❌ SingBox setup() 调用失败: $err', tag: 'SingBox'); + KRLogUtil.kr_e('诊断信息:', tag: 'SingBox'); + KRLogUtil.kr_e(' - workingDir: ${kr_configDics.workingDir.path}', tag: 'SingBox'); + KRLogUtil.kr_e(' - workingDir 存在: ${kr_configDics.workingDir.existsSync()}', tag: 'SingBox'); + KRLogUtil.kr_e(' - data 目录绝对路径: ${p.join(kr_configDics.workingDir.path, "data")}', tag: 'SingBox'); + KRLogUtil.kr_e(' - data 目录存在: ${finalDataDir.existsSync()}', tag: 'SingBox'); + if (finalDataDir.existsSync()) { + try { + final files = finalDataDir.listSync(); + KRLogUtil.kr_e(' - data 目录文件数量: ${files.length}', tag: 'SingBox'); + } catch (e) { + KRLogUtil.kr_e(' - 无法列出 data 目录: $e', tag: 'SingBox'); + } + } throw err; }).run(); + + // setup() 成功后的额外等待(Windows 上数据库初始化可能需要时间) + if (Platform.isWindows) { + await Future.delayed(const Duration(milliseconds: 500)); + KRLogUtil.kr_i('⏳ Windows: libcore 初始化等待完成', tag: 'SingBox'); + } KRLogUtil.kr_i('开始更新 SingBox 选项'); KRLogUtil.kr_i('📋 SingBox 配置选项: ${oOption.toJson()}', tag: 'SingBox'); diff --git a/lib/app/utils/kr_secure_storage.dart b/lib/app/utils/kr_secure_storage.dart index acdf879..f16fecd 100755 --- a/lib/app/utils/kr_secure_storage.dart +++ b/lib/app/utils/kr_secure_storage.dart @@ -27,6 +27,13 @@ class KRSecureStorage { if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) { final baseDir = await getApplicationSupportDirectory(); KRLogUtil.kr_i('初始化 Hive,路径: ${baseDir.path}', tag: 'SecureStorage'); + + // 确保目录存在 + if (!baseDir.existsSync()) { + await baseDir.create(recursive: true); + KRLogUtil.kr_i('已创建 Hive 目录: ${baseDir.path}', tag: 'SecureStorage'); + } + await Hive.initFlutter(baseDir.path); } else { // Android 和 iOS 使用默认路径 @@ -39,6 +46,7 @@ class KRSecureStorage { KRLogUtil.kr_i('Hive 初始化成功', tag: 'SecureStorage'); } catch (e, stackTrace) { KRLogUtil.kr_e('初始化 Hive 失败: $e', tag: 'SecureStorage'); + KRLogUtil.kr_e('错误类型: ${e.runtimeType}', tag: 'SecureStorage'); KRLogUtil.kr_e('错误堆栈: $stackTrace', tag: 'SecureStorage'); // 对于 Windows 和 Linux,如果初始化失败,尝试删除旧文件并重试 diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 69e8ac1..5e69f31 100755 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -2,6 +2,12 @@ cmake_minimum_required(VERSION 3.14) project(BearVPN LANGUAGES CXX) +# 设置 CMake 策略以兼容旧版本插件 +# CMP0175: add_custom_command() 拒绝无效参数(用于兼容 flutter_inappwebview_windows 插件) +if(POLICY CMP0175) + cmake_policy(SET CMP0175 OLD) +endif() + # 设置静态链接 set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")