From 1f87565d5aa8205ed8268a9ad83c6ab5bdfd3122 Mon Sep 17 00:00:00 2001 From: ShaoHua <345265198@qqcom> Date: Mon, 13 Apr 2026 21:17:15 +0800 Subject: [PATCH] =?UTF-8?q?1.MAUI=20Android=E5=8F=AF=E4=BB=A5=E6=AD=A3?= =?UTF-8?q?=E5=B8=B8=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Directory.Build.props | 2 + Hua.Todo.slnx | 2 +- README.md | 17 ++-- docs/manual/代码规范文档.md | 22 +++-- docs/manual/技术栈与模块.md | 22 +++-- docs/manual/版本记录.md | 14 ++- .../Hua.Todo.Application.csproj | 2 +- .../Hua.Todo.Avalonia.csproj | 27 +++++- .../Platforms/Android/AndroidManifest.xml | 7 ++ .../Platforms/Android/MainActivity.cs | 24 +++++ src/Hua.Todo.Maui/Hua.Todo.Maui.csproj | 41 ++++---- src/Hua.Todo.Maui/MauiProgram.cs | 3 +- .../Platforms/Android/AndroidManifest.xml | 10 +- .../Android/MobileEmbeddedWebServerService.cs | 95 +++++++++++++++++-- 14 files changed, 228 insertions(+), 60 deletions(-) create mode 100644 src/Hua.Todo.Avalonia/Platforms/Android/AndroidManifest.xml create mode 100644 src/Hua.Todo.Avalonia/Platforms/Android/MainActivity.cs diff --git a/Directory.Build.props b/Directory.Build.props index fd5e191..b62d5c2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,6 +14,8 @@ true + + true diff --git a/Hua.Todo.slnx b/Hua.Todo.slnx index b3eb81d..5a72ee9 100644 --- a/Hua.Todo.slnx +++ b/Hua.Todo.slnx @@ -11,7 +11,7 @@ - + diff --git a/README.md b/README.md index 55e9c82..68dc3ba 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,16 @@ -# Hua.Todo 跨平台代办管理应用 +# Hua.Todo 跨平台代办管理应用 v1.2.8 -一个基于 WebView 容器(MAUI / Avalonia)+ 嵌入式 ASP.NET Core WebServer 架构开发的跨平台代办管理应用,支持 Windows、macOS、Android、iOS 和 Linux(预览)平台。通过 HTTP API 实现前后端通信,提供轻量、高效的任务管理体验。 +一个基于 WebView 容器(MAUI / Avalonia)+ 嵌入式 ASP.NET Core WebServer 架构开发的跨平台代办管理应用,支持 Windows、macOS、Android、iOS 和 Linux 平台。通过 HTTP API 实现前后端通信,提供轻量、高效的任务管理体验。 ## 🚀 功能特点 ### 核心功能 -- **跨平台支持**:基于 MAUI / Avalonia + WebView 架构,支持 Windows、macOS、Android、iOS 和 Linux(预览) -- **任务管理**:支持创建、编辑、删除、完成状态切换 -- **优先级管理**:支持高、中、低三种优先级设置,通过颜色直观区分 -- **任务状态跟踪**:清晰标记任务完成状态,支持过滤查看(全部/进行中/已完成) -- **本地数据持久化**:使用 SQLite 数据库保存数据,支持完全离线使用 -- **HTTP API 通信**:前后端通过 RESTful API 进行数据交互 -- **云同步(基础)**:支持手动配置服务端地址并登录后拉取云端任务(v1.2.0 为只读展示) +- **跨平台支持**:基于 MAUI / Avalonia + WebView 架构,支持 Windows、macOS、Android、iOS 和 Linux。 +- **任务管理**:支持创建、编辑、删除、完成状态切换、子任务管理。 +- **云同步 (CloudSync)**:支持手动配置服务端地址并登录后拉取云端任务,支持安全策略(SecurityPolicy)配置。 +- **关键词检索**:支持按任务标题实时过滤,支持 Esc 清空,大小写不敏感。 +- **本地数据持久化**:使用 SQLite 数据库保存数据,支持 DateTime 兼容性解析。 +- **动态 API**:后端自动生成 RESTful API 并集成 Swagger UI,便于联调与调试。 ## 📦 安装与使用 diff --git a/docs/manual/代码规范文档.md b/docs/manual/代码规范文档.md index 6d31d48..ca68d24 100644 --- a/docs/manual/代码规范文档.md +++ b/docs/manual/代码规范文档.md @@ -1,4 +1,4 @@ -# Hua.Todo 代码规范文档 v1.1.0 +# Hua.Todo 代码规范文档 v1.2.8 ## 1. 概述 本文档定义 Hua.Todo 项目的代码规范,包括 C#、JavaScript/TypeScript、Vue.js 和其他相关技术的编码标准。遵循这些规范有助于提高代码质量、可读性和可维护性。 @@ -10,21 +10,29 @@ - **避免缩写**: 除非是广泛认知的缩写(如 ID、URL、API) - **一致性**: 在整个项目中保持命名风格一致 -### 2.2 注释规范 -- **公共 API 必须添加 XML 文档注释** +### 2.2 注释规范 (强制) +- **公共 API 必须添加 XML 文档注释** (包括 `public` / `protected` 的类、接口、方法、属性) +- ** summary**:一句话说明用途 +- ** param / returns**:关键参数/返回值说明 +- ** 异常或副作用**:在 summary 中明确说明(例如会注册系统钩子/会启动后台服务) - **复杂逻辑添加行内注释** -- **避免注释显而易见的代码** -- **保持注释与代码同步更新** +- **禁止在日志或注释中输出密钥、Token、用户隐私信息** ### 2.3 代码格式化 -- **使用统一的代码格式化工具** +- **使用统一的代码格式化工具** (VS / IDE 默认格式化) - **保持一致的缩进和空格** - **每行代码不超过 120 字符** - **文件末尾保留一个空行** ## 3. C# 代码规范 -### 3.1 命名规范 +### 3.1 跨平台逻辑规范 +- **禁止混写 `#if`**:禁止在同一文件内混写多个平台的大段 `#if` 实现。 +- **优先使用 partial/接口**:应优先使用 `partial` 类、接口与平台目录分离。 +- **说明平台差异**:平台分离后的公共入口处必须说明“平台差异在哪里、默认实现是什么、为什么这么做”。 + +### 3.2 异步/后台任务 +- **必须说明启动时机、错误处理策略、是否需要 UI 线程、以及是否可并发/可重入**。 #### 类和接口 ```csharp diff --git a/docs/manual/技术栈与模块.md b/docs/manual/技术栈与模块.md index 3e6ee57..24f2bb0 100644 --- a/docs/manual/技术栈与模块.md +++ b/docs/manual/技术栈与模块.md @@ -3,19 +3,19 @@ ## 🛠️ 技术栈 ### 后端技术栈 -- **开发语言**:C# 10 -- **框架**:.NET 10 +- **开发语言**:C# 13 (基于 .NET 10) +- **框架**:.NET 10 (Preview/Early Adopter) - **UI 框架**:MAUI(移动端/部分桌面) + Avalonia(桌面端) - **Web 服务器**:Kestrel (ASP.NET Core 内置) -- **API 框架**:ASP.NET Core Web API -- **数据访问**:Entity Framework Core +- **API 框架**:ASP.NET Core Web API (支持动态 API 生成) +- **数据访问**:Entity Framework Core 10.0 - **数据库**:SQLite (本地存储) - **依赖注入**:Microsoft.Extensions.DependencyInjection ### 前端技术栈 -- **开发语言**:TypeScript +- **开发语言**:TypeScript 5+ - **框架**:Vue.js 3 -- **构建工具**:Vite +- **构建工具**:Vite 5+ - **HTTP 客户端**:Axios - **状态管理**:Pinia - **UI 组件库**:Element Plus / Vant (移动端) @@ -24,13 +24,17 @@ ## 🎯 核心模块说明 ### Hua.Todo.Core -领域实体层,定义核心实体(TaskEntity)、枚举(TaskPriority)以及仓储接口(ITaskRepository)。 +领域实体层,定义核心实体(TaskEntity)、安全策略实体(SecurityPolicyEntity)、枚举(TaskPriority)以及仓储接口(ITaskRepository)。 ### Hua.Todo.Application -应用层实现,包含业务逻辑、动态 API 生成逻辑、EF Core 数据库上下文以及具体的服务实现(TaskService)。 +应用层实现,包含: +- **业务逻辑**:TaskService 实现。 +- **动态 API**:基于 Middleware 的动态 API 生成逻辑。 +- **云同步 (CloudSync)**:包含 Auth 验证、同步服务、安全策略管理等。 +- **数据访问**:EF Core 数据库上下文(TodoDbContext)与迁移。 ### Hua.Todo.Host -后端 API 宿主,提供运行环境和配置,是后端服务的启动入口。 +后端 API 宿主,作为独立 Server 运行时提供运行环境和配置。 ### Hua.Todo.Web 前端 Web 项目,基于 Vue.js 3 + TypeScript + Vite,提供用户界面,通过 HTTP API 与后端通信。 diff --git a/docs/manual/版本记录.md b/docs/manual/版本记录.md index 89ecab3..123f212 100644 --- a/docs/manual/版本记录.md +++ b/docs/manual/版本记录.md @@ -9,13 +9,23 @@ - v1.1.0:MAUI + WebView 跨平台版本 - v1.2.0 (规划中):Linux 支持与增强功能 -### v1.2.0(开发中,2026-04-07) +### v1.2.8 (2026-04-13) + +- **云同步增强**:在 `Hua.Todo.Application` 中深度集成 `CloudSync` 模块,支持权限验证、安全策略(SecurityPolicy)与任务同步 DTO。 +- **动态 API 增强**:完善 `DynamicApi` 逻辑,支持 Swagger 自动过滤与中间件拦截。 +- **代码规范同步**:强制执行 XML 文档注释与跨平台逻辑分离规范,更新 `.trae/rules` 规则库。 +- **多平台构建优化**:优化 `Directory.Build.props` 与 `Directory.Build.targets`,精细化控制各平台(Windows/Android/iOS/Linux)的构建开关与依赖。 +- **版本号统一**:全项目版本号提升至 `v1.2.8`,同步更新各平台安装包与发布脚本。 + +### v1.2.0(2026-04-07) - **Linux 官方支持**:新增 `Hua.Todo.Avalonia` 项目,正式适配 Linux 平台,同时支持 Windows 和 macOS。 - **Avalonia 桌面交互**:增加托盘菜单(显示/退出)、关闭隐藏到托盘、Windows 全局热键唤起主窗口、热键配置本地持久化;并对齐 Avalonia 的 appsettings 默认值。 - **关键词检索**:主界面增加搜索框,按任务标题实时过滤;采用“命中即显示(含上下文)”策略;支持 Esc 清空;英文大小写不敏感。 - **云同步(基础可用)**:新增“云同步设置”弹窗,支持手动配置服务端地址(格式校验 + 保存时可达性/风险提示);登录成功后拉取云端任务并刷新主界面(v1.2.0 为只读展示);401/403 时会自动清会话并弹出登录入口。 - **MAUI(Windows)内嵌 API 文档**:Debug 模式下,内嵌 WebServer 默认提供 Swagger UI(`{HostUrl}/swagger`)与 OpenAPI JSON(`{HostUrl}/swagger/v1/swagger.json`),便于本地接口调试。 +- **Android 启动稳定性修复**:在 AndroidManifest 中移除 `androidx.startup.InitializationProvider` 自动初始化入口,规避 `androidx.lifecycle.ProcessLifecycleInitializer` 缺失导致的启动崩溃(`NoClassDefFoundError`)。 +- **MAUI Android 调试配置修复**:在 `Hua.Todo.Maui.csproj` 中显式启用 `AndroidApplication`,并将调试架构配置从 `AndroidSupportedAbis` 切换为 `RuntimeIdentifiers=android-x64`,减少 Visual Studio 启动 Android 调试时的项目识别与模拟器架构问题。 - **Swagger 输出补齐 Dynamic API**:任务管理等 Dynamic API 端点会出现在 `swagger.json` 中,避免“接口缺失”导致联调困难。 - **SQLite DateTime 兼容修复**:新增 `LenientUtcDateTimeStringConverter`,本地数据库中若存在历史遗留的 DateTime “ticks/时间戳字符串”脏数据,读取时将被兼容解析,避免 `/api/task` 等查询因单条坏数据整体失败。 - **SPA 路由回落行为修复**:当 Release/非 Debug 未启用 Swagger 时,`/swagger` 不再被当作“后端专用路径”排除,访问会按 SPA 路由规则回落到 `/index.html`,避免直接 404。 @@ -26,7 +36,7 @@ - **Windows WebView2 Runtime 误判修复**:当系统已安装 WebView2 Runtime 但发布产物缺少/裁剪 WebView2 托管程序集时,旧检测逻辑会误判为“未安装”;现改为优先从常见安装目录探测 Evergreen 版本,避免阻断主界面加载。 - **Windows 三件套开发体验**:新增 `start-host.ps1` / `start-dev.ps1`,并在 MAUI 中约定 `IsUsingStatic=false` 时不启动内置 WebServer,避免注入覆盖 Vite 的 `/api -> 5173` 代理配置。 - **文档与部署指南**:新增 `docs/manual/部署文档.md`,详细说明开发环境搭建、多平台发布流程(Windows/Linux/Docker)以及关键配置项;并在技术设计文档中建立链接。 -- **用户文档完善**:新增 `docs/manual/新手指南.md` 和 `docs/manual/用户指南.md`,提供详细的使用说明和操作指南。 +- **用户文档完善**:在规划中新增了 `docs/manual/新手指南.md` 和 `docs/manual/用户指南.md`。 27→ 28→### v1.1.1 (2026-04-06) diff --git a/src/Hua.Todo.Application/Hua.Todo.Application.csproj b/src/Hua.Todo.Application/Hua.Todo.Application.csproj index c696ada..7d78d5a 100644 --- a/src/Hua.Todo.Application/Hua.Todo.Application.csproj +++ b/src/Hua.Todo.Application/Hua.Todo.Application.csproj @@ -4,7 +4,7 @@ true net10.0 - net10.0;net10.0-android;net10.0-ios;net10.0-maccatalyst + net10.0;net10.0-android36.0;net10.0-ios;net10.0-maccatalyst enable enable Library diff --git a/src/Hua.Todo.Avalonia/Hua.Todo.Avalonia.csproj b/src/Hua.Todo.Avalonia/Hua.Todo.Avalonia.csproj index d55a0be..2c448aa 100644 --- a/src/Hua.Todo.Avalonia/Hua.Todo.Avalonia.csproj +++ b/src/Hua.Todo.Avalonia/Hua.Todo.Avalonia.csproj @@ -1,10 +1,13 @@ - + WinExe net10.0 net10.0-windows10.0.19041.0 net10.0 net10.0 + + $(TargetFrameworks);net10.0-android + enable app.manifest true @@ -14,6 +17,21 @@ false + + 21.0 + com.hua.todo + 代办 + true + Exe + + + False + False + True + + @@ -21,17 +39,24 @@ + + + + None All + + + diff --git a/src/Hua.Todo.Avalonia/Platforms/Android/AndroidManifest.xml b/src/Hua.Todo.Avalonia/Platforms/Android/AndroidManifest.xml new file mode 100644 index 0000000..1ca80b4 --- /dev/null +++ b/src/Hua.Todo.Avalonia/Platforms/Android/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Hua.Todo.Avalonia/Platforms/Android/MainActivity.cs b/src/Hua.Todo.Avalonia/Platforms/Android/MainActivity.cs new file mode 100644 index 0000000..5a7aab5 --- /dev/null +++ b/src/Hua.Todo.Avalonia/Platforms/Android/MainActivity.cs @@ -0,0 +1,24 @@ +using Android.App; +using Android.Content.PM; +using Avalonia; +using Avalonia.Android; + +namespace Hua.Todo.Avalonia.Platforms.Android; + +/// +/// Avalonia Android 平台入口 Activity。 +/// +[Activity( + Label = "代办", + Theme = "@style/AvaloniaMainActivityTheme", + Icon = "@drawable/icon", + MainLauncher = true, + ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)] +public class MainActivity : AvaloniaMainActivity +{ + protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) + { + return base.CustomizeAppBuilder(builder) + .WithInterFont(); + } +} diff --git a/src/Hua.Todo.Maui/Hua.Todo.Maui.csproj b/src/Hua.Todo.Maui/Hua.Todo.Maui.csproj index dfc3f72..72ff2a5 100644 --- a/src/Hua.Todo.Maui/Hua.Todo.Maui.csproj +++ b/src/Hua.Todo.Maui/Hua.Todo.Maui.csproj @@ -14,6 +14,7 @@ Exe + true Hua.Todo.Maui true true @@ -28,7 +29,7 @@ 代办 - com.companyname.Hua.Todo.maui + com.hua.todo @@ -70,16 +71,12 @@ true apk False - - True - True - Assemblies - False - - False - False - True - x86_64 + + False + False + True + android-x64 false @@ -166,7 +163,7 @@ - + @@ -184,21 +181,33 @@ - wwwroot/%(RecursiveDir)%(Filename)%(Extension) + $([System.String]::Copy('wwwroot/%(RecursiveDir)%(Filename)%(Extension)').Replace('\', '/')) - + - <_MauiWwwrootFiles Include="$(MSBuildProjectDirectory)\wwwroot\**\*" /> + + <_MauiWwwrootFiles Include="wwwroot\**\*" /> + - wwwroot/%(RecursiveDir)%(Filename)%(Extension) + wwwroot\%(RecursiveDir)%(Filename)%(Extension) + $([System.String]::Copy('wwwroot/%(RecursiveDir)%(Filename)%(Extension)').Replace('\', '/')) + + + + + index.html + index.html + + diff --git a/src/Hua.Todo.Maui/MauiProgram.cs b/src/Hua.Todo.Maui/MauiProgram.cs index 4238163..a22d77b 100644 --- a/src/Hua.Todo.Maui/MauiProgram.cs +++ b/src/Hua.Todo.Maui/MauiProgram.cs @@ -100,8 +100,9 @@ public static partial class MauiProgram { try { - InitializeDatabase(app.Services, connectionString); + // 优先启动内嵌 WebServer,确保 WebView 加载时不再 ERR_CONNECTION_REFUSED。 await StartWebServer(app.Services); + InitializeDatabase(app.Services, connectionString); } catch (Exception ex) { diff --git a/src/Hua.Todo.Maui/Platforms/Android/AndroidManifest.xml b/src/Hua.Todo.Maui/Platforms/Android/AndroidManifest.xml index c0ed746..41a64d9 100644 --- a/src/Hua.Todo.Maui/Platforms/Android/AndroidManifest.xml +++ b/src/Hua.Todo.Maui/Platforms/Android/AndroidManifest.xml @@ -1,6 +1,8 @@ - - - + + + + + - \ No newline at end of file + diff --git a/src/Hua.Todo.Maui/Platforms/Android/MobileEmbeddedWebServerService.cs b/src/Hua.Todo.Maui/Platforms/Android/MobileEmbeddedWebServerService.cs index 5d08f2d..8deb806 100644 --- a/src/Hua.Todo.Maui/Platforms/Android/MobileEmbeddedWebServerService.cs +++ b/src/Hua.Todo.Maui/Platforms/Android/MobileEmbeddedWebServerService.cs @@ -55,7 +55,9 @@ public sealed class MobileEmbeddedWebServerService : IEmbeddedWebServerService if (_listener != null) return Task.CompletedTask; _cts = new CancellationTokenSource(); - _listener = new TcpListener(IPAddress.Loopback, _appSettings.WebServer.Port); + // 使用 IPAddress.Any 而非 Loopback,避免 localhost 解析到 IPv6 [::1] 时连接被拒绝。 + // 同时确保在 Android 模拟器/设备上 localhost 能正确连通。 + _listener = new TcpListener(IPAddress.Any, _appSettings.WebServer.Port); _listener.Start(); _acceptLoop = Task.Run(() => AcceptLoopAsync(_cts.Token)); @@ -257,12 +259,42 @@ public sealed class MobileEmbeddedWebServerService : IEmbeddedWebServerService return; } + Console.WriteLine($"[WebServer] Serving static: {path} -> {assetPath}"); + if (!TryOpenAsset(assetPath, out var assetStream)) { + // 如果 wwwroot/path 找不到,尝试直接加载 path(应对路径扁平化) + var fallbackAssetPath = path.TrimStart('/'); + if (!string.IsNullOrEmpty(fallbackAssetPath) && !fallbackAssetPath.StartsWith("wwwroot", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine($"[WebServer] {assetPath} not found, trying fallback path: {fallbackAssetPath}"); + if (TryOpenAsset(fallbackAssetPath, out assetStream)) + { + await using (assetStream) + { + await WriteStreamAsync(stream, 200, assetStream, GetContentType(path), token); + } + return; + } + } + if (!Path.HasExtension(path)) { - assetPath = "wwwroot/index.html"; - if (TryOpenAsset(assetPath, out assetStream)) + var fallbackHtmlPath = "wwwroot/index.html"; + Console.WriteLine($"[WebServer] {assetPath} not found, trying SPA fallback: {fallbackHtmlPath}"); + if (TryOpenAsset(fallbackHtmlPath, out assetStream)) + { + await using (assetStream) + { + await WriteStreamAsync(stream, 200, assetStream, "text/html; charset=utf-8", token); + } + return; + } + + // 再次尝试不带 wwwroot 的 index.html + fallbackHtmlPath = "index.html"; + Console.WriteLine($"[WebServer] {assetPath} not found, trying SPA fallback: {fallbackHtmlPath}"); + if (TryOpenAsset(fallbackHtmlPath, out assetStream)) { await using (assetStream) { @@ -272,6 +304,7 @@ public sealed class MobileEmbeddedWebServerService : IEmbeddedWebServerService } } + Console.WriteLine($"[WebServer] 404 Not Found: {assetPath}"); await WriteTextAsync(stream, 404, "Not Found", "text/plain; charset=utf-8", token); return; } @@ -286,11 +319,43 @@ public sealed class MobileEmbeddedWebServerService : IEmbeddedWebServerService { try { - stream = global::Android.App.Application.Context.Assets.Open(assetPath); + // Android AssetManager.Open() 路径不能以 / 开头,且必须使用正斜杠 + var normalizedPath = assetPath.TrimStart('/').Replace('\\', '/'); + stream = global::Android.App.Application.Context.Assets.Open(normalizedPath); return true; } - catch + catch (Exception ex) { + Console.WriteLine($"[WebServer] Failed to open asset '{assetPath}': {ex.Message}"); + + // 只在 wwwroot/index.html 失败时尝试列出文件,帮助排查打包结构 + if (assetPath.EndsWith("index.html", StringComparison.OrdinalIgnoreCase)) + { + try + { + // 列出 root assets 和 wwwroot 目录,帮助排查打包结构 + var rootAssets = global::Android.App.Application.Context.Assets.List(""); + if (rootAssets != null) + { + Console.WriteLine($"[WebServer] Root Assets: {string.Join(", ", rootAssets)}"); + } + + var assets = global::Android.App.Application.Context.Assets.List("wwwroot"); + if (assets != null && assets.Length > 0) + { + Console.WriteLine($"[WebServer] Assets in 'wwwroot/': {string.Join(", ", assets)}"); + } + else + { + Console.WriteLine("[WebServer] 'wwwroot/' directory is empty or not found in assets."); + } + } + catch (Exception listEx) + { + Console.WriteLine($"[WebServer] Failed to list assets: {listEx.Message}"); + } + } + stream = Stream.Null; return false; } @@ -423,6 +488,8 @@ public sealed class MobileEmbeddedWebServerService : IEmbeddedWebServerService var requestLine = await reader.ReadLineAsync(token); if (string.IsNullOrWhiteSpace(requestLine)) throw new InvalidOperationException("empty request line"); + Console.WriteLine($"[WebServer] Raw Request Line: {requestLine}"); + var parts = requestLine.Split(' '); if (parts.Length < 2) throw new InvalidOperationException("invalid request line"); @@ -430,11 +497,19 @@ public sealed class MobileEmbeddedWebServerService : IEmbeddedWebServerService var target = parts[1].Trim(); string path; - if (Uri.TryCreate(target, UriKind.Absolute, out var absoluteUri) && - (absoluteUri.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) || - absoluteUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase))) + if (target.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || + target.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { - path = absoluteUri.AbsolutePath; + // WebView 有时会发送绝对 URL(例如 GET http://localhost:5057/ HTTP/1.1) + if (Uri.TryCreate(target, UriKind.Absolute, out var absoluteUri)) + { + path = absoluteUri.AbsolutePath; + Console.WriteLine($"[WebServer] Parsed path from absolute URI: {path}"); + } + else + { + path = target; + } } else if (target.StartsWith("//", StringComparison.Ordinal) && Uri.TryCreate("http:" + target, UriKind.Absolute, out var authorityUri)) @@ -451,6 +526,8 @@ public sealed class MobileEmbeddedWebServerService : IEmbeddedWebServerService if (string.IsNullOrEmpty(path)) path = "/"; if (!path.StartsWith("/", StringComparison.Ordinal)) path = "/" + path; + Console.WriteLine($"[WebServer] Final Resolved Path: {path}"); + var headers = new Dictionary(StringComparer.OrdinalIgnoreCase); string? line; while (!string.IsNullOrEmpty(line = await reader.ReadLineAsync(token)))