Files
ctms/docs/desktop-local-cache-plan.md
T
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

9.4 KiB
Raw Blame History

CTMS 桌面端本地缓存执行方案

目标

桌面端新增本地缓存的目标是降低重复网络请求、改善页面回访和 App 重启后的等待时间。当前项目不涉及受试者隐私问题,后端返回的 CTMS 业务 GET 数据均可进入本地缓存候选范围,但缓存仍只是服务端响应副本,不是业务权威数据。

本方案不改变以下边界:

  • 登录、会话恢复和用户身份确认仍必须依赖后端 token 校验和 /me
  • 新增、编辑、删除、审批、导入、导出、通知 ack、权限判断和审计判断仍必须以后端结果为准。
  • 不实现离线登录、离线写入队列、冲突解决、本地优先工作流、本地 API 镜像或内嵌后端服务。
  • token、登录密码、附件下载凭据、临时授权 URL、Authorization/Bearer 文本不得写入本地缓存、URL、日志或系统通知正文。

缓存范围

默认可缓存:

  • 字典、枚举、菜单、权限摘要、用户可见组织/中心、应用配置、系统元数据。
  • 项目、任务、机构、用户、角色、报表、监控页面等业务 GET 响应。
  • 页面查询结果、分页列表、详情页只读数据和图表数据。

默认不缓存:

  • POST、PUT、PATCH、DELETE 等 mutation 请求响应,除非只用于立即失效相关 GET 缓存。
  • 文件下载二进制正文、一次性下载链接、带签名的临时 URL。
  • token、密码、验证码、MFA 信息、Authorization header、Cookie 原文。
  • 系统通知正文和可能包含临时凭据的 release notes 或错误日志。

架构约束

  • 统一入口命名为 desktopDataCache,位于 frontend/src/runtime/,并通过 clientRuntime 暴露。
  • 业务模块不得直接调用 Tauri 存储 API、文件系统、SQLite、IndexedDB 或 Cache Storage 来实现桌面缓存。
  • Web 与 Desktop 共用业务代码,平台差异只在 runtime 适配层之后收敛。
  • Tauri command 只做窄职责存储读写、清理、容量统计或目录定位,不包含 CTMS 业务规则。
  • 如果引入新的 Tauri command、capability、CSP 或存储插件,必须同步评估 frontend/scripts/verify-desktop-release.mjsnpm run runtime:check 和桌面发布检查清单。

缓存命名和数据模型

缓存命名空间必须包含:

  • serverOrigin:规范化后的后端服务地址,不包含用户名、密码、token 或 query 凭据。
  • userId:后端 /me 确认后的用户标识。
  • schemaVersion:缓存结构版本,用于升级时整体失效。
  • requestSignature:请求方法、路径、排序后的 query、稳定序列化后的 body 摘要和必要的业务上下文。

每条缓存记录至少包含:

  • payload:后端响应副本。
  • status:HTTP 状态码,仅缓存成功的可展示响应。
  • headers:只保存白名单响应头,例如 ETagLast-ModifiedCache-Control、业务版本头。
  • createdAtupdatedAtexpiresAtlastAccessedAt
  • sourceEndpointcacheTags,用于诊断和精准失效。

缓存策略

第一优先级是请求减量:

  • 同一会话内相同 GET 请求合并为一个 in-flight promise。
  • 页面返回、tab 切换和重复组件挂载时优先命中短时内存缓存。
  • 页面可使用 stale-while-revalidate:先显示本地副本,再后台刷新,刷新失败时保留旧数据并显示可恢复错误。

第二优先级是持久化缓存:

  • 仅在后端会话恢复和 /me 成功后启用业务缓存读取。
  • 默认缓存 GET 响应;mutation 成功后通过 tags 或 endpoint 规则失效相关 GET 缓存。
  • TTL 先按数据类型配置,缺省值建议 5 分钟;字典和元数据可延长到 24 小时;高频业务列表建议 1 到 5 分钟。
  • 设置总容量上限和 LRU 清理策略,避免缓存无限增长。

第三优先级是 HTTP 条件请求:

  • 后端为稳定数据提供 ETagLast-Modified
  • 客户端对有验证器的缓存请求带 If-None-MatchIf-Modified-Since
  • 收到 304 时刷新缓存元数据,不重复下载 payload。

失效和清理

必须整体清理:

  • 用户登出。
  • 切换服务器。
  • 切换用户。
  • 后端返回 401 或 403。
  • /me 返回的用户标识、角色、权限版本或租户上下文变化。
  • schemaVersion 升级。

必须精准失效:

  • mutation 成功后,清理相关详情、列表、统计和图表缓存。
  • 权限、角色、组织、项目状态变化后,清理依赖这些上下文的页面缓存。
  • 后端返回明确业务版本头或失效事件时,按 tags 清理。

应提供用户入口:

  • 系统偏好或个人中心诊断信息展示缓存大小、记录数、最近清理时间。
  • 提供“清理本地缓存”按钮。
  • 清理提示只描述缓存状态,不包含具体业务内容。

分阶段实施

当前落地状态

2026-07-08 已完成阶段 1 的首批实现:

  • 新增 frontend/src/runtime/desktopDataCache.ts,提供内存级缓存 scope、TTL、tag 失效、清理和统计能力,并通过 clientRuntime.dataCache 暴露。
  • frontend/src/api/axios.ts 已支持 GET 并发去重、显式 GET 内存缓存、mutation 成功后默认清理缓存、401/403 清理缓存、服务器切换清理缓存。
  • 缓存清理、scope 切换和 tag 失效会递增 generationGET 响应返回时若 generation 已变化,不再写入缓存,避免旧用户、旧服务器或 mutation 前响应回填到新缓存空间。
  • /api/v1/auth/login-key 明确禁用 GET 去重,避免复用登录 challenge。
  • GET 去重和缓存 key 已包含 schema version、server baseURL、请求参数、非敏感请求头、responseType、paramsSerializer 和 withCredentials;包含敏感 header 或 axios auth 的请求不会进入去重或缓存。
  • 登录态已在 /me 成功后绑定缓存 scope,登出时清空 scope 和缓存。
  • 首批缓存接入范围包括项目列表/详情、项目配置、权限摘要、系统权限、权限模板、机构、成员、项目概览和仪表盘摘要。
  • npm run runtime:checknpm run desktop:release:check 已增加底层缓存 API 边界检查,业务模块不得直接调用 indexedDBcaches.openCacheStorage 或 SQLite 入口。

仍未完成:

  • 持久化缓存尚未落地,当前缓存只存在于本次前端运行内存中。
  • 后端 ETag / Last-Modified 和客户端 304 处理尚未落地。
  • 系统偏好或个人中心缓存诊断与手动清理入口尚未落地。
  • mutation 失效当前以默认全量清理为主,后续再按 tags 收敛为精准失效。

阶段 0:请求观测和缓存清单

  • 梳理 frontend/src/api/、Pinia store 和高频页面的 GET 请求。
  • 标记每个接口的缓存 tags、TTL、mutation 失效关系和是否需要后端 ETag。
  • 记录启动、登录后首页、常用列表和详情页的请求数量作为基线。

阶段 1:请求去重和内存缓存

  • 在 API 客户端层增加 GET 请求 in-flight 去重。
  • 增加短 TTL 内存缓存,先覆盖字典、菜单、权限摘要、组织/中心和常用列表。
  • 增加单元测试覆盖请求合并、TTL 过期、mutation 后失效。

阶段 2runtime 持久化缓存

  • frontend/src/runtime/desktopDataCache.ts 定义平台无关接口。
  • Web 端提供内存或 IndexedDB 实现;Desktop 端通过 runtime 封装 IndexedDB、Cache Storage 或 Tauri app data 存储。
  • 增加命名空间、容量限制、LRU、schema version、手动清理和诊断 API。
  • 更新 runtime:check 和桌面发布检查,禁止业务页面直接使用底层缓存存储。

阶段 3:页面接入

  • 先接入基础数据和全局布局依赖接口。
  • 再接入项目列表、任务列表、报表图表、详情页只读数据。
  • 为 mutation 建立集中失效规则,避免页面各自手写清理逻辑。

阶段 4:后端条件请求

  • 为稳定 GET 接口补 ETagLast-Modified
  • 前端 API 客户端接入 304 处理。
  • 增加后端和前端测试,确认 304 不改变业务数据语义。

阶段 5:诊断、验收和发布门禁

  • 在个人中心或系统偏好加入缓存诊断和清理入口。
  • 验证登出、切换服务器、切换用户、401/403、权限变化和版本升级清理行为。
  • 执行相关质量门禁,并把新增缓存边界纳入发布检查清单。

验收标准

  • App 重启并完成 /me 校验后,已访问过的常用页面可以先展示缓存再后台刷新。
  • 同一路由重复进入、组件重复挂载、多个组件请求同一资源时,不产生重复并发请求。
  • 登出、切换服务器、切换用户、401/403 后不能读取旧命名空间缓存。
  • mutation 成功后相关列表、详情、统计和图表缓存被失效或刷新。
  • token、密码、Authorization/Bearer、下载凭据不会出现在缓存文件、IndexedDB、日志、URL 或通知正文中。
  • 业务模块不直接调用 Tauri API、IndexedDB、SQLite、Cache Storage 或文件系统实现缓存。

质量门禁

本地缓存相关实现至少执行:

cd frontend
npm run runtime:check
npm run desktop:release:check
npm run ui:contract
npm run type-check
npm run test:unit
npm run build

如果新增或修改后端条件请求、缓存头、权限版本或失效事件,还需执行对应后端测试。若新增 Tauri command、capability、CSP 或存储插件,还需执行 npm run desktop:build:app 并在真实 macOS .app 中验证缓存清理和诊断入口。