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

181 lines
9.4 KiB
Markdown
Raw Permalink 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 桌面端本地缓存执行方案
## 目标
桌面端新增本地缓存的目标是降低重复网络请求、改善页面回访和 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.mjs``npm run runtime:check` 和桌面发布检查清单。
## 缓存命名和数据模型
缓存命名空间必须包含:
- `serverOrigin`:规范化后的后端服务地址,不包含用户名、密码、token 或 query 凭据。
- `userId`:后端 `/me` 确认后的用户标识。
- `schemaVersion`:缓存结构版本,用于升级时整体失效。
- `requestSignature`:请求方法、路径、排序后的 query、稳定序列化后的 body 摘要和必要的业务上下文。
每条缓存记录至少包含:
- `payload`:后端响应副本。
- `status`:HTTP 状态码,仅缓存成功的可展示响应。
- `headers`:只保存白名单响应头,例如 `ETag``Last-Modified``Cache-Control`、业务版本头。
- `createdAt``updatedAt``expiresAt``lastAccessedAt`
- `sourceEndpoint``cacheTags`,用于诊断和精准失效。
## 缓存策略
第一优先级是请求减量:
- 同一会话内相同 GET 请求合并为一个 in-flight promise。
- 页面返回、tab 切换和重复组件挂载时优先命中短时内存缓存。
- 页面可使用 stale-while-revalidate:先显示本地副本,再后台刷新,刷新失败时保留旧数据并显示可恢复错误。
第二优先级是持久化缓存:
- 仅在后端会话恢复和 `/me` 成功后启用业务缓存读取。
- 默认缓存 GET 响应;mutation 成功后通过 tags 或 endpoint 规则失效相关 GET 缓存。
- TTL 先按数据类型配置,缺省值建议 5 分钟;字典和元数据可延长到 24 小时;高频业务列表建议 1 到 5 分钟。
- 设置总容量上限和 LRU 清理策略,避免缓存无限增长。
第三优先级是 HTTP 条件请求:
- 后端为稳定数据提供 `ETag``Last-Modified`
- 客户端对有验证器的缓存请求带 `If-None-Match``If-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:check``npm run desktop:release:check` 已增加底层缓存 API 边界检查,业务模块不得直接调用 `indexedDB``caches.open``CacheStorage` 或 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 接口补 `ETag``Last-Modified`
- 前端 API 客户端接入 304 处理。
- 增加后端和前端测试,确认 304 不改变业务数据语义。
### 阶段 5:诊断、验收和发布门禁
- 在个人中心或系统偏好加入缓存诊断和清理入口。
- 验证登出、切换服务器、切换用户、401/403、权限变化和版本升级清理行为。
- 执行相关质量门禁,并把新增缓存边界纳入发布检查清单。
## 验收标准
- App 重启并完成 `/me` 校验后,已访问过的常用页面可以先展示缓存再后台刷新。
- 同一路由重复进入、组件重复挂载、多个组件请求同一资源时,不产生重复并发请求。
- 登出、切换服务器、切换用户、401/403 后不能读取旧命名空间缓存。
- mutation 成功后相关列表、详情、统计和图表缓存被失效或刷新。
- token、密码、Authorization/Bearer、下载凭据不会出现在缓存文件、IndexedDB、日志、URL 或通知正文中。
- 业务模块不直接调用 Tauri API、IndexedDB、SQLite、Cache Storage 或文件系统实现缓存。
## 质量门禁
本地缓存相关实现至少执行:
```bash
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` 中验证缓存清理和诊断入口。