Files
ctms/docs/desktop-phase-2-design.md
Cheng Zhou d5279b124f
Storage Persistence Guard / storage-persistence-audit (push) Has been cancelled
Client Quality Gates / Shared client and Web (push) Has been cancelled
Client Quality Gates / macOS Desktop (push) Has been cancelled
Client Quality Gates / Shared client and Web (pull_request) Has been cancelled
Client Quality Gates / macOS Desktop (pull_request) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (pull_request) Has been cancelled
release(main): 同步 dev 最新候选改动
2026-07-16 17:15:50 +08:00

6.7 KiB
Raw Permalink Blame History

CTMS 桌面端第二阶段执行设计

范围与边界

第二阶段基于已合入 dev 的 Tauri 基线推进。每个评审单元从最新 dev 创建短期 codex/* 分支,合入后删除,不建立长期桌面主线。

本阶段只增强在线桌面客户端能力:

  • 安全运行时与诊断。
  • 原生文件选择、保存、打开。
  • 服务端持久系统通知。
  • 签名更新、发布流水线与 Windows 打包准备。

本阶段明确不实现离线登录、离线缓存、本地业务数据库、本地业务队列、后台业务同步或本地权限裁决。所有业务数据、权限、审计和认证判定仍以 FastAPI 后端为准。

运行时边界

共享业务模块只能通过 frontend/src/runtime/ 使用平台能力。非 runtime 模块不得直接导入 Tauri API。

ClientRuntime 第二阶段能力包括:

  • secureSessionStorage
  • files
  • notifications
  • updates
  • capabilities.secureSessionStorage/nativeFiles/systemNotifications/automaticUpdates

能力标志只在对应运行时可用或初始化成功后开启。Web 端继续使用浏览器能力,不申请系统通知权限,不启动桌面通知轮询,也不触发 updater。

安全会话存储

桌面端 token 存入系统凭据库:

  • macOSKeychain。
  • WindowsCredential Manager。

桌面端登录使用后端签发的在线会话 token,可在系统凭据库中保存最长 30 天,以支持重启 App 后免输入密码。启动恢复后仍必须使用后端 token 校验和 /auth/me 用户状态校验;服务端不可达或会话被后端拒绝时不得进入离线模式。

Rust 仅暴露固定 service 下的读取、写入、删除命令。凭据 account 使用规范化服务端 origin 的 SHA-256,避免明文服务端地址散落在系统凭据项名称中。

应用挂载前异步初始化 token

  1. Web 端继续读取 localStorage.ctms_token
  2. 桌面端先删除 legacy localStorage.ctms_token
  3. 若 legacy token 仍有效,则迁移为带 30 天本机到期时间的系统凭据库会话记录。
  4. 若迁移或读取凭据失败,则内存 token 置空并要求重新登录,不回退明文存储。

登出、服务器切换、认证失效时必须同步清除内存 token 和当前服务端 origin 对应的系统凭据。

原生文件能力

公共接口固定为:

pickFiles(options): Promise<File[]>
saveFile({ suggestedName, mimeType, data }): Promise<"saved" | "cancelled">
openFile({ suggestedName, mimeType, data }): Promise<void>

桌面端使用 Tauri dialog、fs、opener 插件。权限范围只覆盖用户当次选择的文件路径和 $TEMP/ctms-desktop/**,不启用 persisted-scope,不开放目录遍历或 shell。外部打开文件时写入随机临时目录,并使用净化后的文件名;启动和退出时清理临时目录,用户主动保存的文件不自动删除。

下载与预览统一先通过 Axios Bearer 请求取得 Blob,再交给文件适配器。附件全局下载接口只接受 Authorization header,不再接受 ?token=

系统通知

通知订阅由用户在个人设置中主动开启。开启时才请求 OS 权限并创建服务端订阅;enabled_at 设为当前时间,不补发历史分发记录。关闭后客户端停止领取通知,重新开启时重新设定起点。

后端新增:

  • desktop_notification_subscriptions
  • desktop_notification_deliveries

API

  • GET/PUT /api/v1/desktop-notifications/subscription
  • POST /api/v1/desktop-notifications/claim
  • POST /api/v1/desktop-notifications/ack
  • POST /api/v1/desktop-notifications/{distribution_id}/read

claim 跨有效项目查询当前用户未读且未解决的通用业务提醒,使用五分钟租约、唯一约束和事务防重。客户端显示系统通知后 ack;显示失败则等待租约到期后重试。系统通知只是通用提醒 Feed 的可选投递通道,不单独维护文件分发提醒来源。

系统通知正文只显示通用内容:

  • 标题:CTMS 待办提醒
  • 正文:有新的业务提醒待查看

项目、文件、版本等详细信息仅在应用内列表显示,避免锁屏泄露。

诊断元数据与 CORS

Axios 请求附加:

  • X-CTMS-Client-Type
  • X-CTMS-Client-Version
  • X-CTMS-Client-Platform
  • X-CTMS-Build-Channel
  • X-CTMS-Build-Commit

后端安全访问日志保存这些 nullable 字段,并支持按客户端类型/版本筛选。这些字段只用于诊断与排障,不参与授权。

CORS origin 由环境变量白名单控制,并显式允许桌面 origin、开发 origin 和上述请求头。

Tauri CSP 禁止远程脚本和 shell 入口,仅允许 HTTPS API、本地开发地址、blob 预览与必要资源。

单实例

单实例插件必须最先注册。重复启动时只恢复、显示并聚焦主窗口,不处理命令行参数、深链或业务动作。

自动更新与发布

正式 release 构建启用 updater。客户端从当前 CTMS origin 派生固定清单路径:

/desktop-updates/stable/latest.json

生产只允许 HTTPS;本地测试只允许 localhost HTTP。

更新检查策略:

  • 启动延迟 30 秒检查。
  • 之后每 6 小时检查。
  • 发现更新时展示版本和发布说明。
  • 用户确认后下载、验签、安装并重启。
  • 用户选择“稍后”后,同版本 24 小时内不再提示。
  • 不做静默安装,不中断正在录入的业务流程。

Tauri 配置必须嵌入 updater 公钥并生成 updater artifacts。生产私钥只能存放在组织密钥库或 CI secret,不进仓库。macOS 更新制品为 .app.tar.gz.sig

release tag 流水线应从同一提交构建 Web 与桌面端:

  1. 校验 frontend/package.json、Tauri 配置、Cargo manifest/lock 版本一致。
  2. 构建 macOS Universal。
  3. 完成 Apple 签名和公证。
  4. 使用 updater 私钥签名更新包。
  5. 生成 DMG、更新包、签名、latest.json 与校验清单。
  6. 先上传不可变制品,最后原子替换 latest.json

latest.json 同时提供 darwin-aarch64darwin-x86_64,指向同一个 Universal 更新制品。

Windows 准备

第二阶段只验证 Windows x64 NSIS 构建兼容,不发布正式 Windows 安装包。

验证范围:

  • Windows Credential Manager。
  • 路径净化与临时目录限制。
  • 系统通知编译兼容。
  • updater 编译兼容。
  • WebView2 前置条件。
  • 用户级安装假设。
  • 后续代码签名要求。

验收重点

  • localStorage、URL、日志和系统通知正文不包含 token。
  • 附件下载不再接受 query-token。
  • 业务模块不直接导入 Tauri API。
  • Web 构建、类型检查和单测不回归。
  • macOS .app 构建可重复。
  • Keychain 会话、文件入口、通知权限、重复启动聚焦和签名更新链路完成端到端验证。