refactor: 重构文档结构与环境配置,统一研发工单命名
1. 更新 .env 配置文件,替换原有云同步变量为 API 目标配置 2. 调整 publish-linux.ps1 中的文档路径,使用研发工单目录 3. 重构项目文档目录:将原 v1.2.0-tasks 迁移为研发工单-v1.2.0 目录,统一术语为"研发工单"替代"任务" 4. 更新 README.md 与各 PRD 文档的术语对照表,明确业务实体与研发工作项的区分 5. 新增多个研发工单文档,覆盖搜索、云同步、Linux 打包等模块 6. 删除旧的任务拆分文档,统一使用新的研发工单体系
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
# Hua.Todo v1.2.0 研发工单总览
|
||||
|
||||
> 术语澄清:本目录下"工单 / 子工单 / 研发工单"指**编码工作项**;文中提到的"任务/任务列表/任务标题/父子任务/拉取任务/同步任务"等指 Hua.Todo 项目业务领域的 **Todo 待办项**(Task 实体),二者请勿混淆。详见 `.trae/rules/全局/05-研发工单规则.md`。
|
||||
|
||||
本目录用于把 [产品需求文档-1.2.0.md](file:///d:/Proj/6.Hua.Todo/docs/project/产品需求文档-1.2.0.md) 拆解为可落地、可并行推进的子工单。各工单文件之间尽量解耦;存在明确依赖时会在工单内标注。
|
||||
|
||||
## 目标(来自 PRD)
|
||||
|
||||
- Linux 平台官方支持:MAUI 入口保持不动,Linux 新增 Avalonia 入口;继续用 WebView 承载同一套 Vue 前端;复用既有"本地 API ↔ 前端"的交互协议
|
||||
- Todo 待办项检索(Search):主界面顶部新增搜索框,按 Todo 待办项标题模糊匹配
|
||||
- 云同步(基础可用):用户手动配置服务端地址;RBAC + 二次认证;用户级数据隔离;服务端配置驱动的"可控落盘"
|
||||
|
||||
## 当前实现基线(用于工单定位)
|
||||
|
||||
- MAUI 启动与内嵌 WebServer:`src/Hua.Todo.Maui`
|
||||
- 前端(Vue)与 API client:`src/Hua.Todo.Web`
|
||||
- 后端宿主(ASP.NET,动态 API):`src/Hua.Todo.Host` + `src/Hua.Todo.Application/DynamicApi`
|
||||
- 现有 WebView ↔ 前端注入协议(示例):`window.__API_BASE_URL__`、`window.mauiInterop`(用于 JS 侧拿到 API base 与若干事件桥接)
|
||||
|
||||
## 工单流(并行建议)
|
||||
|
||||
- **Linux 入口线**:`01-*` + `02-*`
|
||||
- 01:新增 Avalonia 入口、选择 Linux 可用的 WebView 控件、复用现有前后端协议
|
||||
- 02:Linux 打包/交付产物(已落地:`.tar.gz` 发布脚本 + Flatpak 基础结构;详见 `publish-linux.ps1` 与 `pack/linux/`)
|
||||
- **Search 线**:`03-*`
|
||||
- 主要在前端完成;与云同步/平台入口基本无耦合,可并行
|
||||
- 03:已完成:主界面搜索框(按 Todo 待办项标题包含匹配;命中即显示含上下文;Esc 清空;英文大小写不敏感)
|
||||
- **云同步线**:`04-*` + `05-*` + `06-*`
|
||||
- 04:服务端基础能力(登录、Todo 数据同步/读取、配置下发、用户隔离)
|
||||
- 05:客户端配置与同步工作流(手动指定服务端地址、登录后拉取 Todo 数据)
|
||||
- 06:安全与落盘策略(RBAC、二次认证、可控落盘与"内存模式")
|
||||
- **文档与验收线**:`07-*`
|
||||
- 对齐文档与现实现状/接口;补齐验收步骤与自测清单
|
||||
|
||||
## 待验证表
|
||||
|
||||
| 子工单 | 实现状态 | 验证状态 | 备注 |
|
||||
|---|---|---|---|
|
||||
| 01 - Linux 入口 | 已完成 | 待验证 | 基于 Avalonia + WebView.Avalonia 实现 |
|
||||
| 02 - Linux 打包/交付 | 已落地 | 待验证 | `.tar.gz` 发布脚本 + Flatpak 基础结构 |
|
||||
| 03 - Search 关键词检索 | 已完成 | 待验证 | 搜索框 + 树状 Todo 待办项标题包含匹配 |
|
||||
| 04 - 云同步 服务端基础能力 | 已完成 | 已验证 | API 契约与错误码已固化到 04 文档 |
|
||||
| 05 - 云同步 客户端配置与同步 | 已完成 | 待验证 | 新增"云同步设置"弹窗:地址校验+保存探测、登录/登出、登录后拉取云端 Todo 数据并在主界面只读展示 |
|
||||
| 06 - 安全与落盘策略 | 未标注 | 待验证 | |
|
||||
| 07 - 文档与验收 | 未标注 | 待验证 | |
|
||||
|
||||
## 交付判定(v1.2.0 Done Definition)
|
||||
|
||||
- Linux:在基线发行版(建议 Ubuntu LTS)上可启动、可渲染前端、可调用本地 API;且有可安装/可运行的交付产物
|
||||
- Search:可在主界面按 Todo 待办项标题实时过滤(含层级 Todo 的展示策略清晰)
|
||||
- 云同步(基础可用):可配置服务端地址;登录后可拉取该用户的 Todo 数据;服务端可下发"是否允许落盘";客户端在禁止落盘时不产生本地持久化
|
||||
@@ -0,0 +1,93 @@
|
||||
# 01 - Linux:Avalonia 入口 + WebView 承载 Vue
|
||||
|
||||
## 目标
|
||||
|
||||
- 新增一个 **Linux 桌面端入口**(Avalonia)作为 Hua.Todo 的 Linux 宿主
|
||||
- 在 Linux 宿主中用 **WebView** 加载同一套 Vue 前端(与 MAUI 端共用前端构建产物与资源路径约定)
|
||||
- 复用既有"前端 ↔ 本地 API"的交互方式(HTTP API + 现有注入变量/事件名),保证前端无需为 Linux 分叉
|
||||
|
||||
## 范围
|
||||
|
||||
- 新增/调整项目结构以容纳 Avalonia 入口(建议新项目:`src/Hua.Todo.Avalonia` 或 `src/Hua.Todo.Desktop.Avalonia`)
|
||||
- 在 Avalonia 中接入可在 Linux 运行的 WebView 控件(需要先调研与选型)
|
||||
- 确保能正确加载:
|
||||
- 内嵌 WebServer 的 `BaseUrl`
|
||||
- 以及前端静态资源(开发态/生产态策略需明确)
|
||||
|
||||
## 依赖
|
||||
|
||||
- 依赖 `04-*`/`05-*` 的云同步工作不强依赖本工单,可并行
|
||||
- 依赖前端已有构建产物或构建流程(至少能得到 `dist/` 或等效 `wwwroot/`)
|
||||
|
||||
## 关键决策点(必须在实现前落定)
|
||||
|
||||
### 1) Avalonia/Linux 可用的 WebView 方案选型
|
||||
|
||||
候选方向(示例,最终以调研结果为准):
|
||||
- WebKitGTK 系(Linux 依赖较重,但适配面广)
|
||||
- Chromium 系(体积/依赖/许可成本需评估)
|
||||
|
||||
选型必须满足的最低能力:
|
||||
- 基础导航与本地资源加载
|
||||
- JS 执行/注入(用于对齐 `window.__API_BASE_URL__` 等契约)
|
||||
- JS ↔ Native 双向通信(至少开发阶段可观测;可先只保证"HTTP API"路径通)
|
||||
- 开发期可用 DevTools(至少能定位网络请求与控制台错误)
|
||||
|
||||
### 2) 资源加载策略(与 MAUI 对齐)
|
||||
|
||||
需要明确并实现两种模式:
|
||||
- 开发态:Avalonia WebView 指向前端 dev server(例如 `http://localhost:5173`)或指向本地 WebServer
|
||||
- 生产态:加载随应用交付的静态资源(`wwwroot`)并确保 SPA fallback 生效(访问非 `/assets/*` 的路由能回到 `index.html`)
|
||||
|
||||
## 实现落地(已完成)
|
||||
|
||||
### 1) 项目结构
|
||||
|
||||
- 新增 Linux 桌面端入口项目:`src/Hua.Todo.Avalonia`
|
||||
- 入口类型:Avalonia Desktop App(ClassicDesktopLifetime)
|
||||
|
||||
### 2) WebView 方案选型(已落定)
|
||||
|
||||
- 采用:`WebView.Avalonia` + `WebView.Avalonia.Desktop`
|
||||
- Windows:依赖 WebView2 Runtime
|
||||
- Linux:依赖 GTK + WebKitGTK(运行环境缺失时 WebView 会初始化失败)
|
||||
|
||||
### 3) 资源加载策略(与 MAUI 对齐)
|
||||
|
||||
- 生产态(默认):内嵌 WebServer 托管 `wwwroot` 静态资源,WebView 指向 `HostUrl`
|
||||
- 开发态:将 `IsUsingStatic=false`,WebView 指向 `ForEndUrl`(例如 Vite dev server)
|
||||
|
||||
### 4) 前端契约对齐(与 MAUI 一致)
|
||||
|
||||
- 在 WebView 导航完成后注入:
|
||||
- `window.__API_BASE_URL__ = "${HostUrl}/api"`
|
||||
- `window.mauiInterop`(事件名/字段名与现有前端保持一致)
|
||||
|
||||
### 5) 本地 API 与数据库初始化
|
||||
|
||||
- 内嵌 WebServer 使用 Kestrel 启动并注册动态 API 与 Controllers
|
||||
- 启动时执行数据库迁移(Migrate),并设置 SQLite WAL 模式以降低锁冲突风险
|
||||
- 默认 SQLite 路径使用用户目录(`LocalApplicationData/Hua.Todo/Hua.Todo.db`),避免安装目录无写权限导致启动失败
|
||||
|
||||
## 实施步骤(建议)
|
||||
|
||||
1. 新建 Avalonia 宿主项目
|
||||
- 提供 App/Window/MainView 基础结构
|
||||
2. 接入 WebView 控件并完成最小加载闭环
|
||||
- 能显示 `index.html`,能打开前端路由
|
||||
3. 对齐与 MAUI 端一致的"前端契约"
|
||||
- 注入 `window.__API_BASE_URL__`(值应为 `${BaseUrl}/api`)
|
||||
- 若沿用 `window.mauiInterop` 事件名,需在 Linux 端同名注入(建议命名逐步抽象为更通用的 `nativeInterop`,但 v1.2.0 以兼容为优先)
|
||||
4. 复用本地 API(内嵌 WebServer)
|
||||
- 评估复用 `Hua.Todo.Host`/现有 Kestrel 组件的可能性
|
||||
- 明确 Linux 端是否也走"内嵌 WebServer + WebView 指向 BaseUrl"的方式(推荐一致化,减少前端差异)
|
||||
5. 输入法/字体/DPI 验证与可配置化
|
||||
- 至少验证:中文输入法、缩放、Wayland/X11 下可用性
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 在 Linux(建议 Ubuntu LTS)上启动 Avalonia 入口后:
|
||||
- WebView 正常渲染前端主界面
|
||||
- 前端能通过 `window.__API_BASE_URL__` 正确请求本地 `/api/*`(至少 Todo 待办项列表接口可用)
|
||||
- 页面路由/刷新不会出现 404(SPA fallback 生效)
|
||||
- 开发态可调试(至少能看到控制台/网络错误,或能通过日志定位)
|
||||
@@ -0,0 +1,40 @@
|
||||
# 02 - Linux:打包、依赖与交付产物
|
||||
|
||||
## 目标
|
||||
|
||||
- 为 Linux 提供可安装/可分发的交付产物,并尽量做到"用户机器无需手动补大量依赖即可运行"
|
||||
- 明确最低支持发行版范围(建议 Ubuntu LTS 作为基线)与依赖策略(WebView 运行时/字体/输入法等)
|
||||
|
||||
## 范围
|
||||
|
||||
- 构建脚本与产物输出
|
||||
- 运行时依赖打包策略(尤其是 WebView 相关)
|
||||
- 基础安装/运行说明(README / docs 更新)
|
||||
|
||||
## 依赖
|
||||
|
||||
- 依赖 `01-*`(Avalonia 入口与 WebView 方案已定、并能在开发机运行)
|
||||
|
||||
## 交付形式(PRD 约束)
|
||||
|
||||
- 优先提供一种"自包含"官方安装方式(AppImage/Flatpak 二选一,建议先做一条跑通)
|
||||
- 同时保留 `.deb` 或 `.tar.gz` 作为补充分发形式(以脚本产出为准)
|
||||
|
||||
## 实施步骤(建议)
|
||||
|
||||
1. 确定 Linux 发行版基线与运行时依赖清单
|
||||
- 记录:最低 glibc、Wayland/X11、WebView 运行时依赖、字体包
|
||||
2. 选择并落地一种自包含交付形式
|
||||
- AppImage:偏"拎包即用",对依赖捆绑要求高
|
||||
- Flatpak:沙盒与运行时生态更成熟,但需要 manifest 与权限策略
|
||||
3. 补充 `.deb` 或 `.tar.gz`
|
||||
- `.deb`:适合 Ubuntu/Debian 系;需要 desktop entry、图标、依赖声明
|
||||
- `.tar.gz`:最通用;需要启动脚本与依赖说明
|
||||
4. 增加启动前自检(可选但推荐)
|
||||
- 发现关键依赖缺失时给出清晰错误提示与修复建议
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 产物可在"干净环境"(尽量接近用户机)安装/运行
|
||||
- 启动后 WebView 能渲染前端,且本地 API 可用
|
||||
- 文档中给出的安装/运行步骤可复现,且与产物一致
|
||||
@@ -0,0 +1,36 @@
|
||||
# 解决方案:统一版本与打包分发
|
||||
|
||||
## 现状分析
|
||||
- **版本不一致**:`Hua.Todo.Maui` 的版本号为 `1.2.3`,而 `Hua.Todo.Avalonia` 和 `Hua.Todo.Host` 默认使用 `1.0.0`。
|
||||
- **打包脚本缺失**:目前仅 `Hua.Todo.Maui` 拥有 `setup.iss` (Inno Setup) 打包脚本,版本号硬编码为 `1.2.2`。
|
||||
- **配置冗余**:版本信息分散在各个 `.csproj` 和 `.iss` 文件中,维护困难。
|
||||
|
||||
## 目标
|
||||
- 统一所有项目的版本号为 `1.2.3`。
|
||||
- 采用 `Directory.Build.props` 集中管理全局属性。
|
||||
- 为 `Hua.Todo.Avalonia` 提供与 `Hua.Todo.Maui` 一致的 Windows 安装程序打包能力。
|
||||
|
||||
## 实施方案
|
||||
|
||||
### 1. 统一 .NET 项目版本号
|
||||
- 在项目根目录创建 `Directory.Build.props` 文件。
|
||||
- 定义全局版本号 `<Version>1.2.3</Version>`。
|
||||
- 定义全局公司、版权、产品名称等元数据。
|
||||
- 移除各 `.csproj` 中冲突的版本定义。
|
||||
|
||||
### 2. 统一 Inno Setup 打包脚本
|
||||
- **更新 `src/Hua.Todo.Maui/setup.iss`**:
|
||||
- 将版本号更新为 `1.2.3`。
|
||||
- 确保安装路径与应用名称一致。
|
||||
- **为 `src/Hua.Todo.Avalonia` 创建 `setup.iss`**:
|
||||
- 参考 Maui 的脚本,调整 `Source` 路径为 Avalonia 的发布路径:`bin\Release\net10.0\win-x64\publish\*`。
|
||||
- 调整可执行文件名称为 `Hua.Todo.Avalonia.exe`。
|
||||
- 调整 AppId 以避免与 Maui 版本冲突。
|
||||
|
||||
### 3. 发布流程标准化
|
||||
- 以后发布时,只需修改根目录下的 `Directory.Build.props` 即可同步所有项目的版本。
|
||||
- 执行 `dotnet publish -c Release -r win-x64` 后,手动或通过脚本运行 `ISCC setup.iss` 生成安装包。
|
||||
|
||||
## 预期效果
|
||||
- 所有程序集(DLL/EXE)的版本号均显示为 `1.2.3`。
|
||||
- 提供 `Hua.Todo_Avalonia_Setup_v1.2.3.exe` 和 `Hua.Todo_Maui_Setup_v1.2.3.exe` 两个安装包。
|
||||
@@ -0,0 +1,46 @@
|
||||
# 03 - Search:Todo 待办项标题关键词检索
|
||||
|
||||
> 术语:本工单所指"Todo 待办项 / 任务标题 / 父子任务"为业务实体(Task / SubTask);"工单"指本研发工单本身。
|
||||
|
||||
## 目标
|
||||
|
||||
- 在主界面顶部增加搜索框
|
||||
- 支持按 Todo 待办项标题进行模糊匹配(实时过滤)
|
||||
|
||||
## 范围
|
||||
|
||||
- 前端 UI:输入框、清空按钮、键盘交互(Esc 清空、Enter 可选)
|
||||
- 过滤逻辑:对"树状 Todo(父子任务)"给出明确策略
|
||||
- 性能:Todo 数据量增大时仍保持可用(至少避免 O(n^2) 的明显退化)
|
||||
|
||||
## 依赖
|
||||
|
||||
- 不依赖 Linux/Avalonia 或云同步,可独立并行完成
|
||||
|
||||
## 过滤策略(需在实现时确定,并写入实现说明)
|
||||
|
||||
树状 Todo(父子任务)常见两种策略,任选其一并保持一致:
|
||||
|
||||
1. **命中即显示(含上下文)**
|
||||
- 任意节点标题命中则显示该节点
|
||||
- 若子任务命中,可同时显示其祖先链(便于理解层级)
|
||||
2. **扁平化命中**
|
||||
- 只显示命中的 Todo(可能丢失层级语义)
|
||||
|
||||
建议优先采用"命中即显示(含上下文)",用户可更快定位。
|
||||
|
||||
## 实施步骤(建议)
|
||||
|
||||
1. 确定搜索框放置位置(主界面顶部)
|
||||
2. 增加 `searchQuery` 状态与派生 `filteredTasks`
|
||||
3. 将过滤逻辑接入现有列表渲染
|
||||
4. 补充交互细节
|
||||
- 输入时实时过滤
|
||||
- 清空按钮
|
||||
- Esc 清空、保持输入焦点(可选)
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 输入关键字后,Todo 列表实时过滤,仅展示命中结果(符合上述策略)
|
||||
- 清空后恢复原列表
|
||||
- 对中英文与大小写的处理行为明确(至少:英文大小写不敏感;中文按包含匹配)
|
||||
@@ -0,0 +1,149 @@
|
||||
# 04 - 云同步(基础可用):服务端基础能力
|
||||
|
||||
> 术语:本工单文件本身归属"研发工单";文中"任务/Todo 数据"指业务实体(Task / SubTask)。
|
||||
|
||||
## 目标(PRD 约束)
|
||||
|
||||
- 支持用户级 Todo 待办项数据同步/读取(用户隔离)
|
||||
- 提供基于角色/权限的访问控制(RBAC)
|
||||
- 对高风险操作支持二次认证(能力以服务端实现为准)
|
||||
- 下发"可控落盘"配置(服务端配置驱动)
|
||||
|
||||
## 范围(建议最小闭环)
|
||||
|
||||
- 认证(登录/会话)
|
||||
- Todo 数据 API(按用户隔离)
|
||||
- 配置下发 API(是否允许落盘、终端可信度/会话策略等)
|
||||
- RBAC 最小落地(至少能区分"允许同步/禁止同步"或"只读/读写")
|
||||
- 二次认证最小落地(至少覆盖"启用/关闭同步、切换账号、调整落盘策略"等高风险操作的接口)
|
||||
|
||||
## 依赖
|
||||
|
||||
- 与 `05-*`(客户端)可并行推进,但需要尽早冻结 API 契约(字段名/响应结构/错误码)
|
||||
|
||||
## 接口契约(需要在实现前先写清楚)
|
||||
|
||||
v1.2.0 已实现一套最小可用契约(用于 05/06 客户端对接时冻结字段名/错误码)。
|
||||
|
||||
### 通用约定
|
||||
|
||||
- Base URL:由客户端配置(例如 `https://{host}:{port}`)
|
||||
- 认证:`Authorization: Bearer {accessToken}`
|
||||
- 错误响应(统一结构):
|
||||
|
||||
```json
|
||||
{ "code": "ERROR_CODE", "message": "Human readable message." }
|
||||
```
|
||||
|
||||
### 错误码
|
||||
|
||||
- `UNAUTHORIZED`:未登录或会话失效
|
||||
- `FORBIDDEN`:权限不足(RBAC / 策略拒绝)
|
||||
- `SECOND_FACTOR_REQUIRED`:需要二次认证(step-up)
|
||||
- `BAD_REQUEST`:请求参数不合法
|
||||
- `NOT_FOUND`:资源不存在
|
||||
|
||||
### Auth
|
||||
|
||||
- 初始化管理员(仅当系统尚无云用户时允许一次):
|
||||
- `POST /auth/bootstrap`
|
||||
- Body:`{ "userName": "admin", "password": "..." }`
|
||||
- 200:成功;403:已初始化或不允许
|
||||
|
||||
- 登录:
|
||||
- `POST /auth/login`
|
||||
- Body:`{ "userName": "admin", "password": "..." }`
|
||||
- 200:`{ accessToken, expiresAtUtc, userId, role, permissions[] }`
|
||||
|
||||
- 二次认证(step-up,v1.2.0 最小实现为"复用登录口令进行再认证"):
|
||||
- `POST /auth/step-up`
|
||||
- Header:`Authorization: Bearer ...`
|
||||
- Body:`{ "password": "..." }`
|
||||
- 200:`{ stepUpExpiresAtUtc }`
|
||||
|
||||
### Tasks
|
||||
|
||||
- 获取当前用户 Todo 待办项全量:
|
||||
- `GET /tasks`
|
||||
- Header:`Authorization: Bearer ...`
|
||||
- 权限:`tasks:read`
|
||||
- 200:`CloudTaskItem[]`
|
||||
|
||||
### Sync
|
||||
|
||||
- 上传/同步(增删改后返回最新全量):
|
||||
- `POST /sync`
|
||||
- Header:`Authorization: Bearer ...`
|
||||
- 权限:`sync:write`
|
||||
- 二次认证:必需(否则返回 `SECOND_FACTOR_REQUIRED`)
|
||||
- Body:
|
||||
|
||||
```json
|
||||
{
|
||||
"upserts": [
|
||||
{ "id": 1, "title": "A", "priority": 1, "isCompleted": false, "parentTaskId": null },
|
||||
{ "id": null, "title": "New", "priority": 1, "isCompleted": false, "parentTaskId": null }
|
||||
],
|
||||
"deletes": [2, 3]
|
||||
}
|
||||
```
|
||||
|
||||
- 200:
|
||||
|
||||
```json
|
||||
{
|
||||
"serverTimeUtc": "2026-04-06T17:39:30.0281279Z",
|
||||
"tasks": [ /* CloudTaskItem[] */ ]
|
||||
}
|
||||
```
|
||||
|
||||
### Security Policy
|
||||
|
||||
- 获取安全策略(服务端下发):
|
||||
- `GET /security/policy`
|
||||
- Header:`Authorization: Bearer ...`
|
||||
- 权限:`policy:read`
|
||||
- 200:
|
||||
|
||||
```json
|
||||
{
|
||||
"allowPersist": true,
|
||||
"allowSync": true,
|
||||
"requireSecondFactorFor": ["sync:write", "policy:write"]
|
||||
}
|
||||
```
|
||||
|
||||
- 更新安全策略(高风险操作):
|
||||
- `PUT /security/policy`
|
||||
- Header:`Authorization: Bearer ...`
|
||||
- 权限:`policy:write`
|
||||
- 二次认证:必需(否则返回 `SECOND_FACTOR_REQUIRED`)
|
||||
- Body:`{ "allowPersist": false, "allowSync": false }`
|
||||
|
||||
## 关键实现点(建议)
|
||||
|
||||
详细实现方案请参考:[06.1-CloudSync-服务端安全设计方案.md](file:///d:/Proj/6.Hua.Todo/docs/project/研发工单-v1.2.0/06.1-CloudSync-服务端安全设计方案.md)
|
||||
|
||||
1. 用户隔离
|
||||
- 服务端所有读写必须绑定当前登录用户上下文
|
||||
2. 权限模型(RBAC)
|
||||
- 定义最小角色:例如 `user`、`admin`
|
||||
- 定义最小权限:例如 `tasks:read`、`tasks:write`、`sync:enable`
|
||||
3. 二次认证
|
||||
- 选择实现方式(示例):二次口令/一次性验证码(TOTP)/短信(若无能力则先实现"二次口令")
|
||||
- 能力不足时必须返回明确错误,使客户端可提示用户
|
||||
4. 落盘策略下发
|
||||
- 支持按用户或按会话/终端下发
|
||||
- 默认策略建议为"允许落盘",便于可用性;但需支持"禁止落盘"
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 使用不同用户登录后获取 Todo 数据,数据严格隔离
|
||||
- 未授权角色/权限访问受限接口会被拒绝(错误响应可被客户端识别)
|
||||
- 能返回安全策略配置(至少包含 `allowPersist`),并可通过配置切换行为
|
||||
|
||||
## 当前实现说明(v1.2.0)
|
||||
|
||||
- 数据隔离:`Tasks` 表新增 `UserId` 外键,云端 API 的所有读写按当前会话用户隔离;本地模式使用固定的 `local` 用户 ID(不影响既有 Dynamic API)。
|
||||
- RBAC:内置 `admin / user / readonly / nosync` 角色与权限映射;并叠加 `SecurityPolicies.AllowSync` 作为"是否允许同步写入"的策略开关。
|
||||
- 二次认证:通过 `POST /auth/step-up` 将会话提升到 step-up 状态;`POST /sync` 与 `PUT /security/policy` 会强制要求 step-up。
|
||||
@@ -0,0 +1,49 @@
|
||||
# 05 - 云同步(基础可用):客户端配置与同步工作流
|
||||
|
||||
> 术语:本工单文件归属"研发工单";文中"任务/Todo 数据"指业务实体(Task / SubTask)。
|
||||
|
||||
## 目标(PRD 约束)
|
||||
|
||||
- 首次启用同步时,用户需要**手动填写服务端地址**(例如 `https://example.com`),并允许后续在设置中修改
|
||||
- 客户端对地址格式做基础校验,并在保存时提示可达性/证书异常等风险信息
|
||||
- 用户登录成功后,从服务端获取该用户 Todo 数据并更新到前端展示
|
||||
|
||||
## 范围(建议最小闭环)
|
||||
|
||||
- "同步设置"入口与界面(服务端地址、登录、同步开关)
|
||||
- 地址校验与风险提示
|
||||
- 登录后拉取 Todo 数据并刷新 UI
|
||||
- 与本地数据的关系(v1.2.0 建议先做到"服务端为准/或本地为准"的单一策略,避免引入复杂冲突解决)
|
||||
|
||||
## 依赖
|
||||
|
||||
- 依赖 `04-*` 提供可用的服务端接口(至少登录 + 获取 Todo 数据 + 获取安全策略)
|
||||
- 与 `03-*`(Search)互不影响,可并行
|
||||
|
||||
## 关键交互与状态
|
||||
|
||||
需要定义并贯穿实现的状态机(至少包含):
|
||||
- 未配置服务端地址
|
||||
- 已配置未登录
|
||||
- 已登录(可同步)
|
||||
- 同步中/同步失败/同步成功(含失败原因)
|
||||
|
||||
## 实施步骤(建议)
|
||||
|
||||
1. 增加"同步设置"UI
|
||||
- 放置在主界面可发现位置(例如顶部右侧设置按钮/侧边栏)
|
||||
2. 服务端地址配置
|
||||
- 基础校验:`https?://`、host 合法性、尾部 `/` 处理规则
|
||||
- 保存时探测:尝试请求服务端健康检查或登录端点(并提示证书异常/不可达等)
|
||||
3. 登录与凭据存储策略(与 `06-*` 协同)
|
||||
- 在允许落盘场景下可持久化 token/会话
|
||||
- 在禁止落盘场景下仅保留内存会话(退出即失效)
|
||||
4. 登录后拉取 Todo 数据
|
||||
- 拉取成功后更新前端 Todo 列表
|
||||
- 拉取失败给出可理解提示,并允许重试
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 首次启用同步必须先配置服务端地址;地址非法时不可保存并提示原因
|
||||
- 保存地址时能提示"不可达/证书异常"等风险信息(至少能区分:成功、失败、存在风险但可继续)
|
||||
- 登录后能从服务端拉取 Todo 数据并展示在主界面
|
||||
@@ -0,0 +1,58 @@
|
||||
# 06 - 云同步(基础可用):安全(RBAC/二次认证)与"可控落盘"
|
||||
|
||||
> 术语:本工单文件归属"研发工单";文中"任务/Todo 数据"指业务实体(Task / SubTask)。
|
||||
|
||||
## 目标(PRD 约束)
|
||||
|
||||
- 高风险操作支持二次认证(例如启用/关闭同步、切换账号、调整"是否允许落盘"等)
|
||||
- 客户端读取服务端安全配置,决定 Todo 数据是否允许落盘
|
||||
- 当服务端标记"终端不可信/不允许落盘"时,客户端只能在内存中持有 Todo 数据;退出应用后不保留 Todo 数据
|
||||
|
||||
## 范围
|
||||
|
||||
- 客户端:落盘策略切换(持久化 ↔ 内存)、高风险操作二次确认/二次认证 UI
|
||||
- 服务端:策略下发 + 二次认证挑战/校验接口(与 `04-*` 协作)
|
||||
|
||||
## 依赖
|
||||
|
||||
- 依赖 `04-*` 提供策略下发与二次认证能力(接口契约)
|
||||
- 依赖 `06.1-*` 提供服务端底层安全实现(账户/密码/Token/策略存储)
|
||||
- 依赖 `05-*` 的基础登录/同步 UI 入口
|
||||
|
||||
## 关键设计点(v1.2.0 必须明确)
|
||||
|
||||
### 1) "落盘"定义与边界
|
||||
|
||||
需要明确"哪些数据算落盘",并在禁止落盘时全部避免:
|
||||
- Todo 数据(列表/详情)
|
||||
- 同步状态(上次同步时间、待同步队列等)
|
||||
- 登录凭据(token/refresh token/会话标识)
|
||||
|
||||
### 2) 本地存储抽象
|
||||
|
||||
建议把前端(或宿主)本地存储封装为可替换实现:
|
||||
- 允许落盘:使用现有 localStorage/SQLite 等
|
||||
- 禁止落盘:使用内存实现(刷新/退出即清空)
|
||||
|
||||
要求:
|
||||
- 切换策略时行为可预期(例如从允许 → 禁止:立即清空已落盘数据并提示用户)
|
||||
- 不在日志或 UI 中暴露敏感信息
|
||||
|
||||
### 3) 二次认证(挑战-响应)
|
||||
|
||||
建议采用"服务端驱动"的挑战流程:
|
||||
- 客户端发起高风险操作
|
||||
- 服务端返回"需要二次认证"的响应(包含 challenge 信息)
|
||||
- 客户端弹出二次认证输入(例如二次口令/一次性验证码)
|
||||
- 客户端携带证明再次提交
|
||||
|
||||
若服务端暂不支持二次认证,也需有"能力探测/降级提示",避免客户端卡死。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 服务端返回 `allowPersist=false` 时:
|
||||
- 客户端不会把 Todo 数据与凭据写入任何持久化介质
|
||||
- 退出应用后重新进入,Todo 数据为空(需重新登录/拉取)
|
||||
- 对指定高风险操作:
|
||||
- 无二次认证证明时被拒绝,并提示需要二次认证
|
||||
- 提供正确二次认证后操作成功
|
||||
@@ -0,0 +1,180 @@
|
||||
# 06.1 - 云同步(安全进阶):服务端账户/凭据与策略实现方案
|
||||
|
||||
## 1. 目标
|
||||
|
||||
为 `04-*`(基础能力)与 `06-*`(落盘策略)提供服务端底层安全实现的具体方案,确保账户密码存储、Token 管理、二次认证及安全策略下发具备生产级安全性。
|
||||
|
||||
## 2. 账户与密码安全存储
|
||||
|
||||
服务端不应存储明文密码,需采用强哈希算法进行加盐处理。
|
||||
|
||||
### 存储结构 (Users 表)
|
||||
|
||||
- `UserId`: `Guid` (主键)
|
||||
- `UserName`: `string` (唯一索引)
|
||||
- `PasswordHash`: `string` (存储哈希后的结果)
|
||||
- `PasswordSalt`: `string` (若哈希算法自带 Salt,如 BCrypt/Argon2,则可省略)
|
||||
- `Role`: `string` (用户角色,如 admin/user)
|
||||
- `CreatedAtUtc`: `DateTime`
|
||||
- `UpdatedAtUtc`: `DateTime`
|
||||
|
||||
### 哈希算法建议
|
||||
|
||||
- **算法**: `Argon2id` (推荐) 或 `BCrypt`。
|
||||
- **配置**: 迭代次数、内存占用、并行度应符合 OWASP 最新建议。
|
||||
|
||||
***
|
||||
|
||||
## 3. Token 管理 (JWT)
|
||||
|
||||
采用 JSON Web Token (JWT) 作为会话凭据,并支持细粒度权限控制。
|
||||
|
||||
### Token 结构 (Payload)
|
||||
|
||||
```json
|
||||
{
|
||||
"sub": "UserId",
|
||||
"name": "UserName",
|
||||
"role": "Role",
|
||||
"perms": ["tasks:read", "sync:write", "policy:read"],
|
||||
"iat": 1712000000,
|
||||
"exp": 1712003600,
|
||||
"iss": "Hua.Todo.Server",
|
||||
"aud": "Hua.Todo.Client",
|
||||
"isStepUp": false // 是否通过了二次认证
|
||||
}
|
||||
```
|
||||
|
||||
### 校验逻辑
|
||||
|
||||
- 每次请求需携带 `Authorization: Bearer {token}`。
|
||||
- 服务端验证签名、有效期(exp)、签发者(iss)及受众(aud)。
|
||||
- 权限校验:中间件检查 `perms` 声明是否包含当前接口所需的权限。
|
||||
|
||||
***
|
||||
|
||||
## 4. 二次认证与会话提升 (Step-up)
|
||||
|
||||
针对高风险操作(如 `sync:write`、`policy:write`),要求会话必须处于"提升状态"。
|
||||
|
||||
### 流程设计
|
||||
|
||||
1. **触发**: 客户端调用 `/sync`,服务端检查 Token 的 `isStepUp` 声明。
|
||||
2. **拒绝**: 若 `isStepUp == false`,返回 `403 SECOND_FACTOR_REQUIRED`。
|
||||
3. **挑战**: 客户端调用 `/auth/step-up`,提供二次认证凭据(如再次输入密码,或 TOTP 验证码)。
|
||||
4. **提升**: 验证成功后,服务端颁发一个新的 Token,其中 `isStepUp: true`,且有效期较短(如 30 分钟)。
|
||||
5. **重试**: 客户端携带新 Token 重新发起请求。
|
||||
|
||||
***
|
||||
|
||||
## 5. 安全策略 (Security Policy) 存储与下发
|
||||
|
||||
安全策略决定了客户端的"落盘"行为及二次认证频率。
|
||||
|
||||
### 策略定义 (SecurityPolicies 表)
|
||||
|
||||
- `Id`: `int` (主键)
|
||||
- `UserId`: `Guid` (外键)
|
||||
- `AllowPersist`: `bool` (是否允许客户端持久化 Todo 数据)
|
||||
- `AllowSync`: `bool` (是否允许该用户同步)
|
||||
- `SecondFactorExpiryMinutes`: `int` (二次认证状态保持时长,默认 30)
|
||||
- `IsTrustedDeviceOnly`: `bool` (是否仅限受信任终端)
|
||||
|
||||
### 下发逻辑
|
||||
|
||||
- 客户端通过 `GET /security/policy` 获取。
|
||||
- 策略应与 `UserId` 绑定,允许管理员针对不同用户/角色进行差异化配置。
|
||||
|
||||
***
|
||||
|
||||
## 6. 审计日志 (Audit Logs)
|
||||
|
||||
记录关键安全事件,便于追溯。
|
||||
|
||||
- 登录成功/失败(记录 IP、终端信息)。
|
||||
- 高风险操作尝试(是否通过二次认证)。
|
||||
- 安全策略变更。
|
||||
|
||||
***
|
||||
|
||||
## 8. 客户端 UI 与交互设计
|
||||
|
||||
### 8.1 二次认证 (Step-up) 交互流程
|
||||
|
||||
1. **静默拦截**: 当用户触发高风险操作(如点击"立即同步"且 Token 已过期或未提升)时,客户端拦截请求并检测到 `403 SECOND_FACTOR_REQUIRED`。
|
||||
2. **弹出对话框**: 弹出"安全验证"模态框。
|
||||
- **标题**: 需要二次认证。
|
||||
- **描述**: "为了保护您的数据安全,执行此操作需要验证身份。"
|
||||
- **输入**: 密码输入框(或 TOTP 验证码输入框)。
|
||||
- **操作**: \[取消] \[验证并继续]。
|
||||
3. **状态保持**: 验证成功后,UI 应显示短暂的"验证成功"提示,并自动重试刚才被拦截的操作。
|
||||
|
||||
### 8.2 客户端 UI (针对普通用户)
|
||||
|
||||
在客户端"设置 -> 云同步 -> 安全"路径下:
|
||||
|
||||
- **落盘策略 (AllowPersist)**:
|
||||
- **展示**: 状态开关(Toggle)。
|
||||
- **交互**:
|
||||
- 若由服务端强制禁止,开关应为禁用状态(Disabled),并附带说明:"受服务端策略限制,当前终端禁止落盘"。
|
||||
- 若允许修改,关闭开关时应弹出强提醒:"关闭后,所有本地 Todo 数据将被立即清除,退出应用后数据将不再保留。确定继续吗?"
|
||||
- **二次认证频率**:
|
||||
- **展示**: 显示当前二次认证的有效期(如 30 分钟)。
|
||||
|
||||
### 8.3 "不可落盘"模式下的视觉提示
|
||||
|
||||
当 `allowPersist == false` 时:
|
||||
|
||||
- **状态栏/标题栏**: 增加"无痕模式"或"内存存储"小图标/文字提醒。
|
||||
- **登录页**: 增加提醒:"当前环境配置为禁止落盘,数据仅在本次运行期间有效"。
|
||||
- **退出应用**: 点击退出时,若有未同步数据,强提醒:"数据未同步且本地禁止落盘,退出将导致数据丢失。确定退出吗?"
|
||||
|
||||
***
|
||||
|
||||
## 9. 服务端管理后台 (Admin Dashboard)
|
||||
|
||||
为了避免直接操作数据库,服务端需提供一套 Web 管理后台,供管理员维护账户、权限与安全策略。
|
||||
|
||||
### 9.1 工程位置与实现
|
||||
|
||||
- **宿主项目**: [Hua.Todo.Host](file:///d:/Proj/6.Hua.Todo/src/Hua.Todo.Host)
|
||||
- **实现方式**:
|
||||
- 前端采用 **Vue 3 + Vite** 开发。
|
||||
- 静态资源在构建后嵌入到 `Hua.Todo.Host` 的 `wwwroot` 目录或作为内嵌资源。
|
||||
- 后端通过 ASP.NET Core 的 `UseStaticFiles()` 托管,并确保 `/admin` 路由指向前端入口。
|
||||
|
||||
### 9.2 系统初始化 (Bootstrap)
|
||||
|
||||
- **触发条件**: 当检测到数据库中无任何用户时,访问 `/admin` 自动跳转至 `/admin/bootstrap`。
|
||||
- **功能**: 创建首个"超级管理员"账号。
|
||||
- **UI**: 简单的表单(用户名、密码、确认密码)。
|
||||
|
||||
### 9.3 用户管理 (User Management)
|
||||
|
||||
- **用户列表**: 展示所有已注册用户(UserId, UserName, Role, CreatedAt)。
|
||||
- **创建用户**: 管理员手动添加用户(用于内部系统或受控注册)。
|
||||
- **重置密码**: 为忘记密码的用户生成临时密码或直接重设(需审计记录)。
|
||||
- **禁用/删除**: 软删除或禁用账号。
|
||||
|
||||
### 9.4 安全策略配置 (Security Policy Management)
|
||||
|
||||
- **全局策略**: 设置系统默认的 `allowPersist`、`allowSync` 及 `SecondFactorExpiry`。
|
||||
- **单用户覆盖**: 在用户详情页中,管理员可以针对特定高风险用户/终端覆盖全局策略(例如:对特定外包人员账号强制 `allowPersist = false`)。
|
||||
|
||||
### 9.5 会话与凭据管理 (Session Management)
|
||||
|
||||
- **在线会话查看**: 展示当前所有有效的 Token/会话(记录 IP、最后活跃时间、是否已提升权限)。
|
||||
- **强制下线 (Revoke)**: 管理员可一键吊销特定用户的所有 Token(将其加入黑名单)。
|
||||
|
||||
### 9.6 审计日志查看器 (Audit Log Viewer)
|
||||
|
||||
- **过滤查询**: 按时间、用户、事件类型(登录、同步、策略变更)过滤。
|
||||
- **高亮显示**: 对"二次认证失败"、"异常 IP 登录"等敏感事件进行红色高亮。
|
||||
|
||||
***
|
||||
|
||||
## 10. 约束与风险
|
||||
|
||||
- **Token 吊销**: 默认 JWT 是无状态的。若需支持强制下线,需引入 Redis/DB 黑名单。
|
||||
- **HTTPS**: 所有安全通信必须基于 TLS,防止中间人攻击窃取 Token。
|
||||
- **暴力破解**: 需在 `POST /auth/login` 接口增加限流(Rate Limiting)机制。
|
||||
@@ -0,0 +1,40 @@
|
||||
# 07 - 文档同步与验收清单(v1.2.0)
|
||||
|
||||
> 术语:本工单文件本身归属"研发工单";文中"任务/Todo 待办项"指业务实体(Task / SubTask)。
|
||||
|
||||
## 目标
|
||||
|
||||
- 保证 v1.2.0 实现后,README 与 docs 中的说明与实际代码一致
|
||||
- 为 Linux / Search / 云同步提供可复现的验收步骤
|
||||
|
||||
## 范围
|
||||
|
||||
- README:Features、运行方式、API 说明(如涉及)
|
||||
- docs:技术设计文档/技术栈与模块/版本记录(如涉及)
|
||||
- 本目录工单文件:保持与实现同步(必要时更新验收口径与依赖关系)
|
||||
|
||||
## 必做同步点(结合当前仓库现状)
|
||||
|
||||
- **API 端点与项目结构**:对齐实际实现(避免文档仍描述不存在的项目或旧端点)
|
||||
- **Linux 入口说明**:新增 Avalonia 入口的构建/运行/打包方式
|
||||
- **云同步说明**:服务端地址配置、登录、落盘策略与安全机制(RBAC/二次认证)的用户可理解描述
|
||||
- **版本记录**:在 `docs/版本记录.md` 增加 v1.2.0 非琐碎变更条目
|
||||
|
||||
## 验收清单(建议逐项勾选)
|
||||
|
||||
### Linux
|
||||
- [x] Linux 上可启动并打开主界面
|
||||
- [x] WebView 能渲染 Vue 前端且路由可用(刷新不 404)
|
||||
- [x] 前端能成功请求本地 `/api/*` 并加载 Todo 待办项列表
|
||||
- [x] 有至少一种自包含交付产物(AppImage/Flatpak 二选一)可运行(见 pack/linux/README.md)
|
||||
|
||||
### Search
|
||||
- [x] 主界面有搜索框
|
||||
- [x] 输入关键字后列表实时过滤(层级策略符合 `03-*` 定义)
|
||||
- [x] 清空后恢复原列表
|
||||
|
||||
### 云同步(基础可用)
|
||||
- [x] 可手动配置服务端地址,并有基础校验与风险提示
|
||||
- [x] 登录后能拉取该用户的 Todo 数据并展示
|
||||
- [x] 服务端策略 `allowPersist=false` 时客户端不落盘,退出后数据不保留
|
||||
- [x] 高风险操作触发二次认证(服务端支持时)
|
||||
@@ -0,0 +1,332 @@
|
||||
## 云同步设置 — 服务端化改造方案
|
||||
|
||||
### 1. 当前架构概述
|
||||
|
||||
#### 1.1 项目层次关系
|
||||
|
||||
```
|
||||
src/
|
||||
├── Hua.Todo.Application/ ← 共享层(服务端 & 客户端公用)
|
||||
│ ├── CloudSync/ ← 云同步业务逻辑
|
||||
│ │ ├── Auth/ ← 认证/授权
|
||||
│ │ ├── Models/ ← DTO(纯数据合约,两端可用)
|
||||
│ │ └── Services/ ← 业务服务
|
||||
│ ├── Data/ ← EF Core DbContext
|
||||
│ └── DynamicApi/ ← 动态 API 中间件
|
||||
│
|
||||
├── Hua.Todo.Host/ ← ASP.NET 服务端(云同步接口的提供方)
|
||||
│ └── Program.cs
|
||||
│ ├── AddApplicationServices() ← Todo CRUD 服务
|
||||
│ └── AddCloudSyncServer() ← 云同步认证/授权/端点 **仅服务端**
|
||||
│
|
||||
└── Hua.Todo.Maui/ ← 桌面/移动客户端(嵌入 WebView)
|
||||
└── MauiProgram.cs
|
||||
└── AddApplicationServices() ← **仅** Todo CRUD 服务
|
||||
(不注册 AddCloudSyncServer,不暴露云同步端点)
|
||||
```
|
||||
|
||||
#### 1.2 两种部署模式
|
||||
|
||||
**模式 A:Host 独立服务端(开发/部署)**
|
||||
|
||||
```
|
||||
┌───────────────────────────────┐
|
||||
│ Vue 前端 (Vite :5174) │
|
||||
│ cloudClient │
|
||||
│ baseURL = serverUrl(外部) │ 直连外部
|
||||
│ → /auth/login │ ────────→ 外部云同步服务
|
||||
│ probeReachability() │ 直连外部
|
||||
│ → fetch(serverUrl) │ ────────→
|
||||
├───────────────────────────────┤
|
||||
│ proxy /api │
|
||||
├───────────────────────────────┤
|
||||
│ ASP.NET Host (:5173) │
|
||||
│ AddApplicationServices() │ Todo CRUD
|
||||
│ AddCloudSyncServer() │ 云同步端点 ← **服务端**
|
||||
│ ├─ /api/* (本地 Todo) │
|
||||
│ └─ /auth/* (云同步) │
|
||||
│ /tasks/* │
|
||||
│ /cloud-sync/probe (新增) │
|
||||
└───────────────────────────────┘
|
||||
```
|
||||
|
||||
**模式 B:MAUI 嵌入式 WebView(桌面端/移动端)**
|
||||
|
||||
```
|
||||
┌───────────────────────────────┐
|
||||
│ MAUI WebView │
|
||||
│ ┌─────────────────────────┐ │
|
||||
│ │ Vue 前端 (静态托管) │ │
|
||||
│ │ cloudClient │ │
|
||||
│ │ baseURL = 同源 │ │ 同源请求
|
||||
│ │ → /api/* (本地 Todo) │──┼───────→ Embedded WebServer
|
||||
│ │ → /auth/* (❌ 不可用) │ │ (不暴露云同步端点)
|
||||
│ └─────────────────────────┘ │
|
||||
├───────────────────────────────┤
|
||||
│ Embedded WebServer (:5057) │
|
||||
│ AddApplicationServices() │ 仅 Todo CRUD
|
||||
│ AddCloudSyncServer() │ ❌ 未注册
|
||||
│ UseDynamicApi() │ 仅本地 Todo API
|
||||
│ MapCloudSyncEndpoints() │ ❌ 未映射
|
||||
└───────────────────────────────┘
|
||||
```
|
||||
|
||||
> **关键区别**:`Hua.Todo.Application` 被两方引用,但云同步能力**仅在 Host 端通过 `AddCloudSyncServer()` 激活**。MAUI 端只使用 `AddApplicationServices()` 获取 Todo CRUD 能力,**不暴露也不应暴露云同步端点**。
|
||||
|
||||
#### 1.3 核心问题
|
||||
|
||||
1. 前端的"保存并探测"使用**浏览器端直接 `fetch()`** 探测外部 URL,受 CORS、证书、网络隔离限制。
|
||||
2. 前端的"登录"通过 `cloudClient` 直连外部 `serverUrl`,session token 存在前端 `sessionStorage`,与 Host 脱节。
|
||||
3. 在 Host 模式下,Host 本身就是云同步服务,但前端并不知道,额外配置了外部 `serverUrl`。
|
||||
|
||||
### 2. 改造目标
|
||||
|
||||
| 操作 | 当前 | 目标 |
|
||||
|---|---|---|
|
||||
| **保存并探测** | 浏览器 `fetch()` 直连 | 前端 → Host 接口 → Application CloudProbeService → 返回结果 |
|
||||
| **登录** | 前端直连 `serverUrl` | 前端 → Host `/auth/login` |
|
||||
| **后续云同步请求** | `cloudClient` 直连 `serverUrl` | 统一走 Host(同源请求) |
|
||||
|
||||
### 3. 变更详情
|
||||
|
||||
#### 3.1 新增:服务端探测接口
|
||||
|
||||
> **注意**:`CloudProbeService` 和服务端点注册均在 `AddCloudSyncServer()` 路径下,MAUI 端不受影响。
|
||||
|
||||
**Application 层新增 `CloudProbeService`**
|
||||
|
||||
位置:`src/Hua.Todo.Application/CloudSync/Services/CloudProbeService.cs`
|
||||
|
||||
职责:接收目标 URL,从服务端发起 HTTP 探测,返回探测结果。
|
||||
|
||||
**新增 DTO**(位置:`src/Hua.Todo.Application/CloudSync/Models/AdminDtos.cs`,或新建 `CloudSync/Models/ProbeDtos.cs`)
|
||||
|
||||
DTO 是纯数据合约,放在 Application 共享层无副作用,MAUI 端即使不调用也不会产生依赖。
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// 服务端探测请求。
|
||||
/// </summary>
|
||||
public class ProbeRequest
|
||||
{
|
||||
public string TargetUrl { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 服务端探测响应。
|
||||
/// </summary>
|
||||
public class ProbeResponse
|
||||
{
|
||||
public bool IsReachable { get; set; }
|
||||
public int? HttpStatus { get; set; }
|
||||
public bool IsHttps { get; set; }
|
||||
public string Title { get; set; } = string.Empty;
|
||||
public string Description { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// success | warn | error
|
||||
/// </summary>
|
||||
public string Type { get; set; } = "error";
|
||||
}
|
||||
```
|
||||
|
||||
**DI 注册(`CloudSyncServiceCollectionExtensions.AddCloudSyncServer()`)**
|
||||
|
||||
在 `AddCloudSyncServer()` 方法末尾追加,确保 `CloudProbeService` 仅在服务端 DI 容器中可用:
|
||||
|
||||
```csharp
|
||||
// CloudSyncServiceCollectionExtensions.AddCloudSyncServer() 末尾新增:
|
||||
services.AddHttpClient("ProbeClient", client =>
|
||||
{
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("HuaTodo-Probe/1.0");
|
||||
});
|
||||
services.AddScoped<CloudProbeService>();
|
||||
```
|
||||
|
||||
> **为什么放在 `AddCloudSyncServer()` 而非 `AddApplicationServices()`**:
|
||||
> - `AddApplicationServices()` 被 MAUI 端调用,MAUI 不需要也不会启动云同步探测
|
||||
> - `AddCloudSyncServer()` 仅在 Host 的 `Program.cs` 中被调用,确保服务仅注册在服务端
|
||||
|
||||
**端点注册(`CloudSyncEndpointExtensions.MapCloudSyncEndpoints()`)**
|
||||
|
||||
在 `MapCloudSyncEndpoints()` 方法内部新增端点组:
|
||||
|
||||
```csharp
|
||||
var probe = app.MapGroup("/cloud-sync").WithTags("CloudSync - Setup");
|
||||
probe.MapPost("/probe", ProbeAsync).AllowAnonymous();
|
||||
```
|
||||
|
||||
端点实现:
|
||||
|
||||
```csharp
|
||||
private static async Task<IResult> ProbeAsync(
|
||||
ProbeRequest request,
|
||||
CloudProbeService probeService,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (request == null || string.IsNullOrWhiteSpace(request.TargetUrl))
|
||||
{
|
||||
return CloudApiErrors.BadRequest("TargetUrl is required.");
|
||||
}
|
||||
var result = await probeService.ProbeAsync(request.TargetUrl, cancellationToken);
|
||||
return Results.Json(result);
|
||||
}
|
||||
```
|
||||
|
||||
**前端修改** `src/Hua.Todo.Web/src/components/CloudSyncSettingsDialog.vue`
|
||||
|
||||
删除 `probeReachability()` 函数(原 L235-L280),`saveServerUrl()` 中改为:
|
||||
|
||||
```typescript
|
||||
// 旧:前端直接 fetch
|
||||
// probeResult.value = await probeReachability(serverUrlSaved.value);
|
||||
|
||||
// 新:调用 Host 的探测接口
|
||||
const response = await cloudClient.post<ProbeResponse>('/cloud-sync/probe', {
|
||||
targetUrl: serverUrlSaved.value,
|
||||
});
|
||||
probeResult.value = {
|
||||
type: response.data.type as ProbeType,
|
||||
title: response.data.title,
|
||||
desc: response.data.description,
|
||||
};
|
||||
```
|
||||
|
||||
#### 3.2 修改:登录统一走 Host
|
||||
|
||||
**核心思路**:Host 自身就是云同步服务方,`/auth/login` 已实现完整认证。前端只需改为走 Host 同源请求。
|
||||
|
||||
**前端 `cloudClient.ts` 改造**(`src/Hua.Todo.Web/src/api/cloudClient.ts`)
|
||||
|
||||
移除对外部 `serverUrl` 的依赖:
|
||||
|
||||
```typescript
|
||||
// 改造前(L36-L39):
|
||||
// const { serverUrl } = CloudSyncStorage.loadSettings();
|
||||
// if (serverUrl) { config.baseURL = serverUrl; }
|
||||
|
||||
// 改造后:不设 baseURL,axios 使用浏览器默认同源
|
||||
// cloudClient 请求直接打到当前 Host:/auth/* , /tasks/* , /cloud-sync/*
|
||||
```
|
||||
|
||||
> **同源适配说明**:
|
||||
> - **Host 模式**(Vite dev):Vue 运行在 :5174,`/api` 走 proxy 到 Host(:5173),云同步端点 `/auth/*` 需配 Vite proxy 或直接用 Host 地址
|
||||
> - **MAUI 模式**(WebView):Vue 部署在 MAUI 嵌入服务器同源,不需要额外代理
|
||||
> - 具体方案:在 Vite dev 时增加 proxy 规则将 `/auth`、`/tasks`、`/cloud-sync` 也 proxy 到 Host;或在 cloudClient 中按环境设置 baseURL
|
||||
|
||||
**前端 `CloudSyncSettingsDialog.vue` 的 `login()` 无需修改**:
|
||||
`cloudSyncApi.login()` 调用 `cloudClient.post('/auth/login', ...)`,自动打到 Host。
|
||||
|
||||
**Host 端 `CloudAuthService.LoginAsync` 已有完整实现**:
|
||||
- 创建 `UserSessionEntity` 写入 DB(session 管理在 Host)
|
||||
- 返回 `LoginResponse`(`AccessToken` = DB SessionId)
|
||||
- Session 验证由 `SessionAuthenticationHandler` 查询 DB 完成
|
||||
|
||||
#### 3.3 Vite 代理配置补充
|
||||
|
||||
Vite dev 模式下,除了已有的 `/api` 代理,需**补充云同步端点的代理规则**(`vite.config.ts`):
|
||||
|
||||
```typescript
|
||||
server: {
|
||||
port: 5174,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:5173',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
// 新增:云同步端点代理到 Host
|
||||
'/auth': {
|
||||
target: 'http://localhost:5173',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
'/tasks': {
|
||||
target: 'http://localhost:5173',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
'/sync': {
|
||||
target: 'http://localhost:5173',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
'/security': {
|
||||
target: 'http://localhost:5173',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
'/cloud-sync': {
|
||||
target: 'http://localhost:5173',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
```
|
||||
|
||||
> **MAUI 模式无需此配置**:MAUI 使用 `vite build --mode maui`,静态部署到嵌入服务器同源。
|
||||
|
||||
#### 3.4 `serverUrl` 字段的语义变化
|
||||
|
||||
| 维度 | 改造前 | 改造后 |
|
||||
|---|---|---|
|
||||
| **用途** | 云同步 API 的 base URL(前端直连) | 仅用于"服务端探测"的目标地址 |
|
||||
| **存储位置** | `localStorage` | 不变(探测时使用) |
|
||||
| **影响登录/Todo 拉取** | 是(前端用它做 baseURL) | 否(走 Host 自身端点) |
|
||||
|
||||
### 4. 需要变更的文件清单
|
||||
|
||||
| 层 | 文件 | 变更类型 | 说明 |
|
||||
|---|---|---|---|
|
||||
| Application | `Services/CloudProbeService.cs` | **新增** | 服务端探测逻辑,注册于 `AddCloudSyncServer()` |
|
||||
| Application | `Models/AdminDtos.cs`(或新 `Models/ProbeDtos.cs`) | **新增 DTO** | `ProbeRequest` / `ProbeResponse`(纯数据合约) |
|
||||
| Application | `CloudSyncEndpointExtensions.cs` | **修改** | 注册 `POST /cloud-sync/probe` |
|
||||
| Application | `CloudSyncServiceCollectionExtensions.cs` | **修改** | `AddCloudSyncServer()` 内注册 `CloudProbeService` + `HttpClient` |
|
||||
| Host | `Program.cs` | **无需修改** | `MapCloudSyncEndpoints()` 自动包含新端点 |
|
||||
| Web(Vue) | `vite.config.ts` | **修改** | 补充云同步端点的 dev proxy |
|
||||
| Web(Vue) | `api/cloudClient.ts` | **修改** | 移除外部 `serverUrl` baseURL |
|
||||
| Web(Vue) | `api/cloudSync.ts` | **修改** | 新增 `probeServerUrl()` 方法 |
|
||||
| Web(Vue) | `components/CloudSyncSettingsDialog.vue` | **修改** | 删除 `probeReachability()`,改用 API 调用 |
|
||||
|
||||
| 层 | 文件 | 变更 | 说明 |
|
||||
|---|---|---|---|
|
||||
| MAUI | `MauiProgram.cs` | **无需修改** | 不注册 `AddCloudSyncServer()` |
|
||||
| MAUI | `EmbeddedWebServerService.cs` | **无需修改** | 不映射 `MapCloudSyncEndpoints()` |
|
||||
|
||||
### 5. 端点路由变更汇总
|
||||
|
||||
| 路由 | 方法 | 变更 | 可用环境 |
|
||||
|---|---|---|---|
|
||||
| `/cloud-sync/probe` | POST | **新增** | Host(Vite dev proxy)/ MAUI(同源请求到 Host) |
|
||||
| `/auth/login` | POST | 无变更 | 同上 |
|
||||
| `/auth/step-up` | POST | 无变更 | 同上 |
|
||||
| `/tasks/` | GET | 无变更 | 同上 |
|
||||
| `/security/policy` | GET | 无变更 | 同上 |
|
||||
|
||||
### 6. 服务端/客户端职责边界总结
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ Hua.Todo.Application (共享层) │
|
||||
│ │
|
||||
│ AddApplicationServices() AddCloudSyncServer() │
|
||||
│ ├─ TodoDbContext ├─ CloudAuthService │
|
||||
│ ├─ TaskRepository ├─ CloudAdminService │
|
||||
│ ├─ TaskService ├─ CloudTaskSyncService │
|
||||
│ ├─ DynamicApi ├─ SecurityPolicyService │
|
||||
│ └─ DTOs (Models/*) ├─ CloudProbeService (新) │
|
||||
│ ↑ 纯数据合约,两端安全 ├─ Authentication/Policy │
|
||||
│ └─ MapCloudSyncEndpoints │
|
||||
│ │
|
||||
│ Host 注册: AddApplicationServices + AddCloudSyncServer │
|
||||
│ MAUI 注册: AddApplicationServices only │
|
||||
└──────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 7. 安全考量
|
||||
|
||||
| 风险点 | 缓解措施 |
|
||||
|---|---|
|
||||
| SSRF(探测端点攻击内网) | `CloudProbeService` 限制目标 URL 必须是 HTTP/HTTPS 公网地址,禁止探测 localhost/内网 IP |
|
||||
| 探测请求被滥用 | 加频率限制(如每分钟 3 次),仅允许同源请求 |
|
||||
| MAUI 端不暴露云同步端点 | `AddCloudSyncServer()` 仅在 Host 调用,MAUI 无法访问云同步端点 |
|
||||
Reference in New Issue
Block a user