207 lines
14 KiB
Markdown
207 lines
14 KiB
Markdown
# CTMS 桌面端项目计划书
|
||
|
||
## 目的
|
||
|
||
本文档是 CTMS 桌面端工作的长期方向约束。每次开始任何桌面端相关任务前,都必须先阅读本文档,包括 Tauri 初始化、macOS 打包、Windows 适配、桌面端存储、文件集成、系统通知、安全边界和发布流程。
|
||
|
||
桌面端必须服务于现有 CTMS Web 应用和后端架构。目标是为当前 CTMS 服务提供原生桌面入口,而不是创建一个独立的离线产品。
|
||
|
||
## 不可突破的边界
|
||
|
||
- 技术路线固定为 Tauri。
|
||
- 第一开发目标是 macOS 桌面端。
|
||
- Windows 仍只作为第二阶段兼容性验证目标,未获明确批准前不发布正式安装包。
|
||
- 当前桌面端工作只允许在第一、二阶段边界内做修复、稳定化、体验收口和发布准备,不新增第三阶段能力。
|
||
- 不做离线功能。
|
||
- 不在桌面 App 内嵌本地后端服务。
|
||
- 不在桌面 App 内嵌或分发本地数据库来保存 CTMS 业务数据。
|
||
- 不实现离线同步、冲突解决、本地业务数据队列、本地优先工作流。
|
||
- 不把 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/` 后面。
|
||
- 完成 macOS 正式发布前的签名、公证、updater 签名、制品发布、CI 门禁和人工回归。
|
||
- 做 Windows 第二阶段兼容性验证,但不发布正式 Windows 安装包。
|
||
|
||
仍然不允许:
|
||
|
||
- 离线登录、离线浏览、离线队列、离线同步或本地优先工作流。
|
||
- 本地 PostgreSQL、SQLite、IndexedDB 业务数据缓存或本地 API 镜像。
|
||
- 内嵌 Python/FastAPI 后端服务。
|
||
- 绕过后端做本地权限裁决、本地审计缓存或审计回放。
|
||
- 为桌面端复制或重写一套独立业务 UI。
|
||
|
||
## 架构方向
|
||
|
||
使用运行时适配层,不在业务页面里散落平台判断。
|
||
|
||
当前已采用并必须继续保持的适配层边界:
|
||
|
||
- `platform`:识别 Web、macOS 桌面端、Windows 桌面端。
|
||
- `apiBaseUrl`:分别解析 Web 和桌面端的服务端 API 地址。
|
||
- `desktopServerConfig`:管理桌面服务端地址配置和切换事件。
|
||
- `secureSessionStorage`:隔离浏览器 token 存储与桌面系统凭据库。
|
||
- 桌面端允许保存后端签发的最长 30 天在线会话,用于重启 App 后免输入密码;该会话必须存放在系统凭据库中,启动后仍需由后端 token 和 `/me` 校验确认身份,不等同于离线登录。
|
||
- `files`:隔离浏览器上传下载与原生文件能力。
|
||
- `notifications`:隔离 Web 通知与桌面系统通知。
|
||
- `updates`:隔离桌面自动更新检查与安装入口。
|
||
- `appMetadata`:在可用时提供桌面 App 版本、平台、构建通道。
|
||
- `desktopMenu` 和 `desktopUiPreferences`:承接桌面菜单命令、最近访问和收藏等桌面体验状态。
|
||
- `clientRuntime`:作为业务侧获取平台能力的聚合入口。
|
||
|
||
业务模块应调用这些适配层,而不是直接调用 Tauri API。Tauri command 应保持窄职责,不包含 CTMS 业务规则。
|
||
|
||
## 安全与合规方向
|
||
|
||
- 将桌面端视为受监管业务系统的在线客户端。
|
||
- 非本地服务连接优先使用 HTTPS。
|
||
- 认证与授权决策保留在后端。
|
||
- 审计敏感决策保留在后端。
|
||
- 敏感凭据必须继续使用明确批准的安全存储方案。
|
||
- 不向前端暴露宽泛文件系统访问权限。
|
||
- 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 状态一致。
|
||
|
||
如果后续任务试图新增离线登录、本地业务数据存储、内嵌后端、本地业务队列、离线同步或绕过后端权限审计,应先修改并评审本计划书,不能直接实现。
|
||
|
||
## 当前质量门禁
|
||
|
||
前端或桌面端代码变更应按影响范围执行相关检查。发布、桌面端适配层、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 变更可以不执行完整代码门禁,但必须在结果说明中明确未运行。
|
||
|
||
## 每次开发前必须执行的检查
|
||
|
||
开始任何桌面端相关任务前:
|
||
|
||
- 先阅读本文档。
|
||
- 确认任务属于第一阶段或第二阶段。
|
||
- 确认任务不会引入离线能力。
|
||
- 确认实现不会破坏 Web 运行时。
|
||
- 确认 Tauri API 使用被隔离在适配层之后,除非有明确记录的理由。
|
||
- 确认不会重复初始化 Tauri 或绕过既有 `frontend/src/runtime/` 运行时边界。
|
||
- 涉及 Tauri 权限、CSP、updater、凭据、文件或通知能力时,确认桌面发布检查脚本和发布清单是否需要同步更新。
|
||
|
||
如果用户请求与本文档冲突,先停止实现并确认范围,不要直接推进。
|