Files
social-app/docs/plans/2026-03-20-navigation-cache-decoupling-design.md
T

286 lines
9.8 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.
# 前端导航解耦与统一缓存重构设计
## 1. 背景与问题定义
当前 `apps` 端在日历(日/月)与待办页面中存在以下系统性问题:
1. 页面切换语义错误:将业务 tab 切换实现为 `push/go` 混用,导致页面重建与路由栈膨胀。
2. 数据刷新触发错误:页面通过路由监听触发 `load`,频繁重复请求后端。
3. 状态职责耦合:导航状态、页面状态、数据状态边界不清,导致“切换逻辑改动会牵出数据 bug”。
4. 回主页语义不一致:Dock 首页按钮被 `canPop -> pop` 策略污染,行为变成“返回上页”。
5. 缓存能力分散:仅存在局部的个人信息缓存(`SettingsUserCache`),缺少统一可复用缓存模块。
目标是完成一次结构化重构,建立「解耦的导航切换 + 统一缓存 + 可控一致性」体系。
## 2. 目标与非目标
### 2.1 目标
1. Home/Calendar/Todo 切换不重建主页面(保持页面实例与滚动状态)。
2. 日/月视图切换不触发整页重建和无必要网络请求。
3. 建立统一缓存模块,合并个人信息缓存并覆盖 Calendar/Todo 数据读取。
4. 启动体验采用「本地优先 + 后台静默刷新」策略,减少进入 App 的重复请求。
5. 数据只在必要时刷新:手动下拉、写操作失效、生命周期关键点、缓存策略命中。
6. 主页按钮语义固定为“回主页”,不再变成“返回上一页”。
### 2.2 非目标
1. 本次不改后端协议与接口契约。
2. 本次不引入复杂离线同步冲突解决(如多端 CRDT)。
3. 本次不引入全量本地数据库迁移(先基于 SharedPreferences 持久化层)。
## 3. 复杂度与风险分级
- Complexity: `S3`
- 跨 router、calendar、todo、settings、DI 的架构级调整。
- Risk Tier: `L1`
- 不触及鉴权协议和支付等高风险域,但涉及导航返回栈与数据一致性高回归区。
## 4. 架构总览
### 4.1 导航分层
采用两级导航:
1. 一级(主容器):`StatefulShellRoute.indexedStack`
- 分支:Home / Calendar / Todo
- 作用:保活分支页面,避免 tab 切换重建。
2. 二级(分支内部)
- Calendar 分支:管理 month/day 主视图切换 + event detail/edit/share 子路由。
- Todo 分支:管理 list/detail/edit 子路由。
### 4.2 状态与数据边界
1. 导航状态:Shell 当前分支 index、Calendar 内部视图类型。
2. UI 状态:选中日期、滚动位置、拖拽态、loading/error。
3. 数据状态:统一缓存模块管理(内存 + 持久化 + 网络回写)。
结论:页面只发“意图”,不直接承担缓存与路由策略。
## 5. 统一缓存模块设计
## 5.1 模块结构
新增 `apps/lib/core/cache/`
1. `cache_key.dart`
- 统一 key 命名规范。
2. `cache_policy.dart`
- TTL、软/硬过期、最小刷新间隔、刷新原因枚举。
3. `cache_entry.dart`
- 标准缓存实体(data/fetchedAt/expiresAt/version/dirty)。
4. `cache_store.dart`
- 抽象接口(get/set/remove/invalidateNamespace)。
5. `memory_cache_store.dart`
- 会话级热缓存。
6. `persistent_cache_store.dart`
- 本地冷缓存(SharedPreferences JSON)。
7. `hybrid_cache_store.dart`
- 两级缓存协调与 singleflight 去重。
8. `cache_invalidator.dart`
- 统一精准失效入口。
### 5.2 key 设计(首版)
1. 用户信息
- `user:profile:me`
2. 日历
- `calendar:day:YYYY-MM-DD`
- `calendar:month:YYYY-MM`
3. 待办
- `todo:list:pending`
- `todo:list:priority:<n>`(按需)
- `todo:detail:<id>`(按需)
### 5.3 策略设计(平衡型)
读取顺序:`memory -> persistent -> network`
刷新策略:
1. 软过期(stale-while-revalidate
- 先展示缓存,后台静默刷新。
2. 硬过期
- 超过硬过期后必须请求网络或提示数据过旧。
3. 最小刷新间隔
- 避免频繁切换/回前台引发抖动请求。
建议默认值:
1. `user:profile`:软过期 30min,硬过期 24h。
2. `calendar:day`:软过期 2min,硬过期 30min。
3. `calendar:month`:软过期 5min,硬过期 60min。
4. `todo:list:pending`:软过期 2min,硬过期 30min。
### 5.4 个人信息缓存合并方案
现有 `SettingsUserCache` 并入统一缓存模块:
1. 新建 `UserProfileRepository`(或在现有 settings service 中引入统一缓存)。
2. `getProfile()` 通过 hybrid cache 获取 `user:profile:me`
3. 更新 profile 成功后立即写回缓存并同步持久化。
4. 登出/会话失效时统一调用 `invalidateNamespace('user')`
## 6. 一致性风险与解决方案
平衡型缓存会存在“短暂陈旧窗口”。本设计通过以下机制将体验风险降到可接受范围。
### 6.1 触发刷新矩阵
1. 手动下拉刷新:强制网络刷新。
2. 写操作成功:精准失效受影响 key 并触发回填。
3. App 回前台:若超过最小刷新间隔,触发静默刷新。
4. 网络离线 -> 在线:触发静默刷新。
5. 进入关键详情页:按策略进行 freshness check。
### 6.2 写后一致性
1. 乐观更新:本地先更新 UI 与缓存,避免“我刚改完却没变”。
2. 失败回滚:API 失败时恢复旧值并 Toast 提示。
3. 精准失效:不做全局清空,只失效关联 key,兼顾一致性与性能。
### 6.3 并发安全
1. singleflight:同 key 同时只允许一个网络请求。
2. 版本保护:缓存写入比较 `updatedAt/version`,拒绝旧响应覆盖新状态。
3. 失败兜底:请求失败不清空旧缓存,保持可读并允许重试。
### 6.4 可见性保障
1. 页面可显示“上次同步时间”(轻提示)。
2. 硬过期数据需可见提醒(弱提示,不阻断基础浏览)。
3. 提供稳定手动刷新入口。
## 7. 导航与页面职责重构
### 7.1 路由重构
1. `app_router` 引入 shell 分支,不再平铺所有主页面。
2. Dock 切换改为 branch index 切换,不再 `push` 主页面。
3. Calendar 内部 month/day 切换改为视图切换,不新增栈层。
4. 事件详情/编辑等保留 `push`(细节页合理叠栈)。
### 7.2 回主页逻辑修正
1. Dock Home 统一执行“切到 Home 分支/`go('/home')`”。
2. `returnToHomePreserveState` 仅用于非 Dock 的返回策略场景。
3. 消除 `canPop -> pop` 对主页按钮语义的影响。
### 7.3 页面职责收敛
1. Calendar/Todo 页面移除路由监听触发 `load`
2. 页面只调用 repository
- `get(policy)`
- `refresh(force: true)`
- `mutate(...) + invalidate(...)`
3. 页面不直接感知“缓存在哪一层”。
## 8. 分阶段实施计划(里程碑)
### M1 导航壳层与切换语义
1. 引入 shell + 分支保活。
2. Dock 接口改造与主 tab 切换实现。
3. Home 按钮语义修正。
### M2 统一缓存骨架
1. 新增 core cache 模块。
2. 接入 user profile(替换 `SettingsUserCache`)。
3. DI 注入 cache store 与 invalidator。
### M3 Calendar 接入
1. 引入 `CalendarRepository` 与 day/month key。
2. 移除 route listener 自动刷新。
3. 切换 month/day 时默认走缓存,不触发无必要请求。
### M4 Todo 接入
1. 引入 `TodoRepository` 与 list/detail key。
2. 拖拽、完成、编辑后的精准失效。
3. 下拉刷新走强制网络。
### M5 清理与验证
1. 清理旧缓存与重复加载逻辑。
2. 补齐测试与性能观测。
3. 评估参数并收敛默认策略。
## 9. 验收标准
### 9.1 体验验收
1. Home/Calendar/Todo 切换无明显重建卡顿。
2. 日/月切换响应明显变快。
3. 首次冷启动可先看到本地缓存内容。
4. Dock Home 始终回主页。
### 9.2 网络验收
1. 切换页面时网络请求显著减少。
2. 写操作后关联数据可及时更新。
3. 手动刷新可强制拉取并回写缓存。
### 9.3 一致性验收
1. 不出现旧响应覆盖新数据。
2. 离线后恢复在线可自动静默同步。
3. 软过期/硬过期行为符合策略定义。
## 10. 测试与验证计划
### 10.1 单元测试
1. `hybrid_cache_store`:命中链路、singleflight、软硬过期判定。
2. `cache_invalidator`:写操作触发的 key 精准失效。
3. repository:读缓存、后台刷新、失败兜底、版本保护。
### 10.2 组件/页面测试(高回归)
1. Dock 切换不重建分支主页面。
2. 日/月切换不重复触发全量加载。
3. Home 按钮行为稳定。
### 10.3 集成回归
1. Calendar -> Todo -> Calendar 多轮切换请求计数。
2. Todo 完成后列表更新与缓存一致性。
3. profile 更新后设置页/其他依赖页可见一致。
## 11. 风险与回滚
### 11.1 主要风险
1. 导航壳层改造可能引发深链与返回栈回归。
2. 缓存策略参数不当可能造成陈旧感。
3. 早期失效 key 设计不完整可能出现局部不刷新。
### 11.2 控制策略
1. 按里程碑逐步落地,每个里程碑可单独回滚。
2. 默认保留手动刷新兜底。
3. 增加请求计数与缓存命中日志(开发态)。
### 11.3 回滚策略
1. 若 M1 不稳定,可先回退 shell 改造并保留缓存模块。
2. 若缓存接入问题集中,可按域回退(user/calendar/todo 分域开关)。
## 12. 最终落地参数(2026-03-20
1. 导航分级
- 一级页面唯一为 `Home`
- 二级页面(日/月、待办、设置)侧滑返回统一回 `Home`,不允许直接退出 App。
- App 退出入口仅保留在 `Home`
2. 缓存默认策略
- `user:profile`:软过期 30min,硬过期 24h。
- `calendar:day`:软过期 2min,硬过期 30min。
- `calendar:month`:软过期 5min,硬过期 60min。
- `todo:list:pending`:软过期 2min,硬过期 30min。
3. 生命周期刷新
- App 回前台时启用最小间隔 5min 的静默刷新协调器。
4. 提醒归档策略
- App 活跃态点击取消:立即请求后端归档。
- 延迟归档(pending/outbox)仅用于 App 不可用场景兜底。