Files
ctms/docs/desktop-project-plan.md
T
2026-07-09 09:30:44 +08:00

246 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# CTMS 桌面端项目计划书
## 目的
本文档是 CTMS 桌面端工作的长期方向约束。每次开始任何桌面端相关任务前,都必须先阅读本文档,包括 Tauri 初始化、macOS 打包、Windows 适配、桌面端存储、文件集成、系统通知、安全边界和发布流程。
桌面端必须服务于现有 CTMS Web 应用和后端架构。目标是为当前 CTMS 服务提供原生桌面入口,而不是创建一个独立的离线产品。
## 不可突破的边界
- 技术路线固定为 Tauri。
- 第一开发目标是 macOS 桌面端。
- Windows 仍只作为第二阶段兼容性验证目标,未获明确批准前不发布正式安装包。
- 当前桌面端工作只允许在第一、二阶段边界内做修复、稳定化、体验收口、发布准备,以及本文档明确批准的在线辅助本地缓存;不新增独立第三阶段桌面产品线。
- 不做离线登录、离线写入、离线同步或本地优先工作流;本地缓存只作为已认证在线客户端的体验加速能力。
- 不在桌面 App 内嵌本地后端服务。
- 允许桌面端保存可清除的服务端响应缓存;缓存不是业务权威数据,不得替代后端持久化、权限裁决或审计判断。
- 不实现离线同步、冲突解决、本地业务数据队列、本地优先工作流。
- 不把 CTMS 业务 UI 拆成一套独立的桌面端产品;除非桌面能力确实需要小范围适配层。
- FastAPI 后端仍是业务权威来源。权限裁决、审计判断、认证、业务数据持久化都保持在服务端。
## 当前技术基线
桌面端工作基于当前 CTMS Web 技术栈:
- 前端:Vue 3、Vite、TypeScript、Element Plus、Pinia、Vue Router、Axios、ECharts。
- 后端:FastAPI、Uvicorn、SQLAlchemy、Alembic。
- 数据库:PostgreSQL。
- 当前部署:Docker Compose、nginx 托管前端静态资源、nginx 反代 `/api`
桌面端应复用现有前端代码和 API 契约。任何共享适配层都应保持小而明确,并可测试。
## 已完成阶段边界
第一阶段 macOS 在线桌面壳和第二阶段原生能力主体改造已经形成。详细历史方案见 [`desktop-phase-1-design.md`](desktop-phase-1-design.md) 和 [`desktop-phase-2-design.md`](desktop-phase-2-design.md)。
后续不再按第一阶段空白项目初始化 Tauri,也不再扩展第二阶段以外的新桌面产品能力。当前允许推进的工作仅包括:
- 修复既有 Tauri、运行时适配层、文件、通知、凭据、更新、菜单/快捷键和打包问题。
- 稳定 Web 与 Desktop 共用业务代码,确保平台差异继续收敛在 `frontend/src/runtime/` 后面。
- 按 [`desktop-local-cache-plan.md`](desktop-local-cache-plan.md) 推进在线辅助本地缓存、请求去重、条件请求和缓存失效能力。
- 完成 macOS 正式发布前的签名、公证、updater 签名、制品发布、CI 门禁和人工回归。
- 做 Windows 第二阶段兼容性验证,但不发布正式 Windows 安装包。
仍然不允许:
- 离线登录、离线写入、离线队列、离线同步或本地优先工作流。
- 未完成后端会话恢复、token 校验和 `/me` 身份确认前展示业务缓存。
- 本地 PostgreSQL、本地 API 镜像、内嵌业务后端,或把 SQLite/IndexedDB/Cache Storage/Tauri app data 目录作为业务数据库使用。
- 内嵌 Python/FastAPI 后端服务。
- 绕过后端做本地权限裁决、本地审计判定、审计回放或写入补偿。
- 为桌面端复制或重写一套独立业务 UI。
## 架构方向
使用运行时适配层,不在业务页面里散落平台判断。
当前已采用并必须继续保持的适配层边界:
- `platform`:识别 Web、macOS 桌面端、Windows 桌面端。
- `apiBaseUrl`:分别解析 Web 和桌面端的服务端 API 地址。
- `desktopServerConfig`:管理桌面服务端地址配置和切换事件。
- `secureSessionStorage`:隔离浏览器 token 存储与桌面系统凭据库。
- 桌面端允许保存后端签发的最长 30 天在线会话,用于重启 App 后免输入密码;该会话必须存放在系统凭据库中,启动后仍需由后端 token 和 `/me` 校验确认身份,不等同于离线登录。
- `savedLoginCredentials`:隔离网页端浏览器凭据管理与桌面端系统凭据库中的登录表单密码保存;不得把密码写入 `localStorage``sessionStorage`、URL、日志或通知正文,且不等同于离线登录。
- `files`:隔离浏览器上传下载与原生文件能力。
- `notifications`:隔离 Web 通知与桌面系统通知。
- `updates`:隔离桌面自动更新检查与安装入口。
- `appMetadata`:在可用时提供桌面 App 版本、平台、构建通道。
- `desktopMenu``desktopUiPreferences`:承接桌面菜单命令、收藏模块等桌面体验状态。
- `desktopDataCache`:承接桌面端在线辅助本地缓存、缓存命名空间、失效、清理和诊断;业务模块不得直接使用 Tauri 存储 API、IndexedDB、SQLite 或文件系统实现桌面缓存。
- `clientRuntime`:作为业务侧获取平台能力的聚合入口。
业务模块应调用这些适配层,而不是直接调用 Tauri API。Tauri command 应保持窄职责,不包含 CTMS 业务规则。
## 安全与合规方向
- 将桌面端视为受监管业务系统的在线客户端。
- 非本地服务连接优先使用 HTTPS。
- 认证与授权决策保留在后端。
- 审计敏感决策保留在后端。
- 敏感凭据必须继续使用明确批准的安全存储方案;网页端密码只能交给浏览器凭据管理能力,桌面端密码只能交给系统凭据库。
- token、登录密码、附件下载凭据和临时授权信息不得进入本地业务缓存、URL、日志或系统通知正文。
- 本地业务缓存必须按 `server origin + user id + cache schema version` 隔离;登出、切换服务器、切换用户、后端返回 401/403、权限版本变化或缓存结构升级时必须清理或失效。
- 本地缓存命中只能用于展示后端曾返回的数据副本;新增、编辑、删除、审批、权限判断和审计判断仍必须请求后端并以后端结果为准。
- 不向前端暴露宽泛文件系统访问权限。
- Tauri 权限保持最小化,并按功能精确授权。
- 每个新增 Tauri command 都需要被视为桌面端安全边界的一部分进行审查。
## 分支与工作区
桌面端工作在以下位置开发:
- Worktree`/Users/zcc/MyCTMS/ctms-dev/worktrees/ctms-desktop`
- 短期分支:Agent 默认使用 `codex/<任务名称>`,也可按任务类型使用 `feature/*``fix/*``docs/*``release-prep/*`;所有普通桌面端工作都必须从最新 `dev` 创建
当前代码状态:Tauri 基线和第二阶段原生能力已经形成,后续桌面端工作默认是第二阶段范围内的修复、稳定化、体验收口和发布准备。`codex/ctms-desktop` 是历史临时集成分支,不再作为当前工作线;不得继续向该分支提交、变基或推送新的桌面端工作,除非用户明确要求做收尾或删除分支。
## 2026-07-01 审查结论与后续方向
本轮审查结论:桌面端已经完成从 Tauri 接入基线到第二阶段原生能力的主体改造,不再按空白桌面项目推进。当前重点不是扩展离线或本地业务能力,而是围绕既有在线桌面客户端做准发布稳定化。
已形成的能力边界:
- Tauri 工程、macOS App/DMG/updater artifacts 配置已经存在。
- `frontend/src/runtime/` 已作为平台能力统一入口,业务代码不得绕过该适配层直接使用 Tauri API。
- 桌面服务器地址、API baseURL、客户端元数据请求头、安全 session 存储、原生文件能力、系统通知、单实例、菜单/快捷键和自动更新入口已经形成。
- 后端已经包含桌面通知订阅/投递状态、相关 API 和客户端诊断请求头支持。
- 已有 `npm run version:check``npm run runtime:check``npm run desktop:release:check` 等门禁用于约束版本、运行时边界和桌面发布安全边界。
后续优化优先级:
1. 发布稳定化:补齐 macOS 签名、公证、组织 updater 私钥签名、release tag 构建变量注入、不可变制品上传和 `latest.json` 原子替换。
2. 端到端回归:按 `docs/audits/desktop-release-stabilization-checklist.md` 覆盖服务器配置、服务器切换清会话、Keychain/凭据库、附件上传下载、系统通知、单实例和自动更新失败恢复。
3. 安全复审:持续确认 token 不进入 URL、日志、系统通知正文、下载链接或明文持久化;Tauri command、capability、CSP 和 updater 改动必须同步评估发布门禁。
4. 桌面体验收口:重点检查登录、服务器设置、个人中心诊断信息、通知开关、更新弹窗和最小窗口 `1180x760` 下的布局稳定性。
5. CI 与发布流程:Web 与 Desktop 必须从同一提交、同一语义化版本号和同一正式标签构建;发布候选应执行本文档列出的相关质量门禁。
6. Windows 兼容验证:仅作为第二阶段兼容性目标,验证 Credential Manager、路径处理、通知/updater 编译、WebView2 和安装器假设;未获明确批准前不发布正式 Windows 安装包。
## 2026-07-02 发布稳定化推进记录
本次推进仍保持第一、二阶段边界,不引入离线、本地业务存储、内嵌后端或独立桌面业务 UI。
已补齐的发布稳定化自动化:
- 新增 `npm run desktop:build:macos-release`,用于 macOS Universal `app`/`dmg` 正式候选构建。
- 新增 `npm run desktop:update-feed:create`,从签名 updater artifact 和 `.sig` 生成 `latest.json``SHA256SUMS.txt`,并要求 artifact URL 使用包含当前版本号的 HTTPS 不可变路径。
- `npm run desktop:update-feed:check` 在传入 `--artifacts-dir` 时同步校验 `SHA256SUMS.txt`、updater artifact、`.sig``latest.json`
- 新增 `npm run desktop:release-readiness:check`,用于在进入正式签名候选构建前确认当前提交精确匹配 `vX.Y.Z` tag、构建元数据、签名/公证变量、updater 私钥和生产 artifact HTTPS 基址已经齐备。
- 新增 `.github/workflows/desktop-release-candidate.yml`,在 release tag 上执行签名 macOS 候选构建、feed 生成、feed 校验和 GitHub artifact 上传;它不替代人工发布审批和生产下载源原子替换。
- `npm run desktop:release:check` 已纳入上述发布候选工作流和脚本存在性检查,防止发布链路门禁被误删。
仍需正式发布负责人在真实发布环境完成:
- Apple Developer 签名、公证凭据和组织 updater 私钥配置。
- 从正式 `vX.Y.Z` tag 运行 signed macOS release candidate workflow。
- 将已校验的不可变制品上传到生产下载源,最后原子替换线上 `latest.json`
- 执行桌面端人工端到端回归、最小窗口体验验收和真实系统通知/自动更新验证。
进入端到端人工回归前,应先确认 `npm run desktop:release-readiness:check` 在正式 release tag 和签名环境中通过;否则只能进行普通 smoke 验证,不能判定 macOS 发布稳定化已经完成。
## 2026-07-02 安全边界复审推进记录
本次安全边界复审在端到端自动化收口之后继续推进,仍不引入离线能力、本地业务数据存储、内嵌后端或独立桌面业务 UI。
已补齐的安全边界自动化:
- Tauri 通知 capability 已从 `notification:default` 收敛为权限查询、权限请求和发送通知三项显式权限。
- `npm run desktop:release:check` 已拒绝 `notification:default`、opener URL/reveal 权限和 WebView 直连 updater 权限,继续要求文件与 opener scope 仅限 `$TEMP/ctms-desktop/**`
- `npm run desktop:release:check` 已扩展 token URL 和日志静态检查,覆盖 `token``access_token``Authorization``Bearer` 形态。
- 自动更新弹窗 release notes 已过滤 URL、token 查询参数和 Authorization/Bearer 形态文本,避免从 feed 将下载链接或凭据样式文本带入用户界面。
- Rust 凭据命令新增单测,确认带凭据 server origin 被拒绝,系统凭据库 account 使用 origin 哈希且不暴露原始服务器地址。
仍需正式发布负责人在真实发布环境确认:
- 生产 release notes 内容保持通用,不包含项目、文件、下载链接或敏感业务详情。
- 签名、公证、updater feed 和 artifact 上传日志不泄露 Apple 凭据、updater 私钥或下载源内部凭据。
## 2026-07-02 桌面体验收口推进记录
本次体验收口继续保持在线桌面客户端边界,不新增离线、本地业务存储或独立桌面业务 UI。
已补齐的桌面体验收口:
- 个人中心新增客户端诊断信息展示与复制能力,内容仅包含客户端类型、版本、平台、构建通道、提交、服务器和能力状态,不包含 token 或业务敏感数据。
- 桌面侧栏已移除被动“最近访问”入口和对应本地记录,只保留用户主动维护的收藏入口;收藏仅保存路由元数据,不保存业务数据或敏感凭据。
- 登录页、服务器设置页、个人中心和系统偏好增加最小窗口布局约束,长服务器地址、健康检查 URL、诊断值和更新/通知状态说明均可在容器内换行。
- 服务器设置页和个人中心在内容高度超过窗口时使用内部滚动,避免 `1180x760` 下对话框或面板溢出。
- 通知权限运行时已区分 WebView `Notification.permission` 的授权、拒绝和待授权状态;取消 macOS 权限请求时继续保持“待授权”,避免错误显示为“已拒绝”。
- 系统偏好通知页已移除授权成功态的固定标签文案,开启状态只由开关表达;待授权、已拒绝和不可用状态继续显示提示标签。
- 系统偏好通知页新增“测试通知”命令,完整走 `frontend/src/runtime/notifications.ts` -> `@tauri-apps/plugin-notification` 的系统通知发送链路;测试通知文案为固定通用内容,不包含项目、文件、token 或其他敏感业务信息。
- `showSystemNotification` 和测试通知调用会在发送前确认系统通知权限,只有真正发起系统通知时才返回成功;桌面通知轮询据此只 ack 已实际发起系统通知的后端通知。
- 真实 macOS `.app` 已验证系统偏好通知开关可调用系统通知权限并生效。验证截图位于 `/Users/zcc/Library/Application Support/CleanShot/media/media_boKznpjOwt/CleanShot 2026-07-02 at 11.07.52@2x.png`
- `Layout.desktop.test.ts` 已覆盖个人中心诊断、最小窗口断点、服务器设置滚动和偏好页控制区换行契约。
- `notifications.test.ts` 已覆盖通知权限状态映射和测试通知发送链路。
仍需真实桌面环境人工确认:
- macOS `.app``1180x760` 下的登录页、服务器设置、个人中心、系统偏好和更新弹窗实际布局。
- 系统通知权限拒绝后的开关状态、权限提示和错误提示是否与 OS 状态一致。
## 2026-07-08 本地缓存边界调整与执行方案
经当前项目边界复核,桌面端允许新增在线辅助本地缓存,用于减少重复请求、缩短页面回访和 App 重启后的数据展示等待时间。该能力不改变 CTMS 的业务权威边界:FastAPI 后端仍是认证、授权、审计和业务持久化的唯一权威来源。
执行方案以 [`desktop-local-cache-plan.md`](desktop-local-cache-plan.md) 为准,实施顺序为:
1. 建立请求观测和缓存清单,确认高频 GET 接口、页面冷启动接口、基础数据接口和 mutation 后需要失效的资源。
2. 先实现请求去重、短时内存缓存和页面级 stale-while-revalidate,减少同一会话内重复拉取。
3.`frontend/src/runtime/` 增加 `desktopDataCache` 适配层,统一提供缓存读写、命名空间、TTL、容量上限、失效、清理和诊断能力。
4. 增加持久化缓存,默认只缓存后端 GET 响应和明确标记可缓存的数据;缓存 key 必须包含 server origin、user id、请求签名和 cache schema version。
5. 推进后端 HTTP 条件请求支持,优先为字典、菜单、权限摘要、组织/中心、项目摘要等稳定数据提供 `ETag``Last-Modified`,客户端使用 `If-None-Match``If-Modified-Since` 降低 304 场景的数据传输。
6. 建立统一失效机制:登出、切换服务器、切换用户、401/403、权限版本变化、mutation 成功、应用版本或 cache schema 变化时清理或精准失效。
7. 在系统偏好或诊断信息中提供缓存状态和手动清理入口,清理文案不得包含业务详情。
8. 补充单元测试、运行时边界检查和桌面发布检查,确保 token、密码、下载凭据不会写入缓存,业务模块不会绕过 `frontend/src/runtime/` 使用 Tauri 或底层存储 API。
仍禁止把缓存扩展为离线产品能力:不得实现离线登录、离线写入队列、冲突解决、本地审批、本地权限裁决、本地审计回放或本地 API 镜像。服务端不可达时,桌面端不得把缓存解释为已完成身份认证;是否展示已过期缓存必须以后续明确的产品验收口径为准,默认只在在线身份校验通过后使用缓存。
如果后续任务试图新增离线登录、离线写入、本地业务队列、离线同步、内嵌后端、本地 API 镜像或绕过后端权限审计,应先修改并评审本计划书,不能直接实现。
## 2026-07-09 Windows 内测构建流水线记录
本次补充 Windows 第二阶段兼容性验证的手动构建入口,仍不改变“未获明确批准前不发布正式 Windows 安装包”的边界。
已补齐的内部验证自动化:
- 新增 `.github/workflows/desktop-windows-internal.yml`,通过 `workflow_dispatch` 手动触发,在 GitHub `windows-latest` 环境构建 Windows NSIS 安装器。
- 工作流从用户在 GitHub Actions 页面选择的分支上下文注入 `VITE_BUILD_CHANNEL``VITE_BUILD_COMMIT`,并拒绝从 release tag 触发,避免和正式发布链路混用。
- 工作流执行 `release:env:check``version:check``runtime:check``desktop:release:check``ui:contract``type-check``test:unit``build` 后,再构建 Windows NSIS 内测安装器。
- Windows 内测构建显式关闭 updater artifacts,只上传 `.exe``SHA256SUMS.txt` GitHub artifact;不生成生产 `latest.json`,不创建 update feed,不替代 Windows 代码签名或正式发布审批。
- `npm run desktop:release:check` 已纳入 Windows 内测 workflow 约束,防止该入口误加入 updater feed、签名 release 或生产发布逻辑。
该 workflow 只用于内部测试和 Windows 兼容性验证。正式 Windows 发布仍需先完成 Windows 代码签名、安装器验收、更新链路和发布治理审批。
## 当前质量门禁
前端或桌面端代码变更应按影响范围执行相关检查。发布、桌面端适配层、Tauri 配置或安全边界相关变更至少考虑:
```bash
cd frontend
npm run version:check
npm run runtime:check
npm run desktop:release:check
npm run ui:contract
npm run type-check
npm run test:unit
npm run build
npm run desktop:build:app
```
正式桌面发布构建仍必须使用组织批准的 updater 签名私钥和 Apple 签名/公证流程;未签名或 ad-hoc 构建只能作为内部验证构建描述。文档-only 变更可以不执行完整代码门禁,但必须在结果说明中明确未运行。
## 每次开发前必须执行的检查
开始任何桌面端相关任务前:
- 先阅读本文档。
- 确认任务属于第一阶段或第二阶段。
- 确认任务不会引入未批准的离线能力;本地缓存任务必须符合 2026-07-08 缓存边界和 `desktop-local-cache-plan.md`
- 确认实现不会破坏 Web 运行时。
- 确认 Tauri API 使用被隔离在适配层之后,除非有明确记录的理由。
- 确认不会重复初始化 Tauri 或绕过既有 `frontend/src/runtime/` 运行时边界。
- 涉及本地缓存、Tauri 权限、CSP、updater、凭据、文件或通知能力时,确认桌面发布检查脚本和发布清单是否需要同步更新。
如果用户请求与本文档冲突,先停止实现并确认范围,不要直接推进。