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

311 lines
12 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. 主页按钮语义固定为“回主页”,不再变成“返回上一页”。
7. 一级页面唯一为 Home,日历日/月视图、待办、设置均为二级页面;二级页面侧滑只允许返回一级页面,不允许直接退出 App。
8. App 退出入口仅存在于一级页面(Home)。
### 2.2 非目标
1. 本次不改后端协议与接口契约。
2. 本次不引入复杂离线同步冲突解决(如多端 CRDT)。
3. 本次不引入全量本地数据库迁移(先基于 SharedPreferences 持久化层)。
## 3. 复杂度与风险分级
- Complexity: `S3`
- 跨 router、calendar、todo、settings、DI 的架构级调整。
- Risk Tier: `L1`
- 不触及鉴权协议和支付等高风险域,但涉及导航返回栈与数据一致性高回归区。
## 4. 架构总览
### 4.1 导航分层
采用分级导航:
1. 一级页面(唯一):Home
- 仅 Home 允许触发系统退出路径。
2. 二级页面(主业务入口)
- Calendar Day/Month
- Todo ListQuadrants
- Settings
- 规则:二级页面的系统返回/侧滑返回统一回 Home,不允许直接退出 App。
3. 三级页面(细节页)
- Calendar event detail/edit/share
- Todo detail/edit
- Settings 子页面(account/profile 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. 提供稳定手动刷新入口。
### 6.5 日历提醒取消动作的一致性兜底
1. 用户在提醒弹层点击“取消/归档”时,前端必须立即发送归档请求,要求后端立刻将事件归档/过期。
2. “延迟归档(outbox/pending)”仅在 App 进程不可用(被杀/未启动)时生效,作为离线或冷启动兜底。
3. App 冷启动或恢复前台后,必须优先冲刷 pending 归档请求,确保最终一致性。
4. 对用户可见行为要求:点击取消后 UI 立即反映归档状态,网络失败时展示重试提示,并保留 pending 记录。
## 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` 对主页按钮语义的影响。
4. 二级页面(Calendar Day/Month、Todo、Settings)统一拦截系统返回和侧滑返回,目标固定为 Home。
5. App 退出只允许在 Home 页面生效(可采用双击退出或系统默认行为)。
### 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 按钮语义修正。
4. 建立分级返回约束:二级 -> Home,三级 -> 上一级,退出仅 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. 评估参数并收敛默认策略。
4. 验证提醒“点击取消即实时归档”与“App 关闭时延迟归档兜底”双路径。
## 9. 验收标准
### 9.1 体验验收
1. Home/Calendar/Todo 切换无明显重建卡顿。
2. 日/月切换响应明显变快。
3. 首次冷启动可先看到本地缓存内容。
4. Dock Home 始终回主页。
5. 二级页面侧滑返回永远回 Home,不直接退出 App。
### 9.2 网络验收
1. 切换页面时网络请求显著减少。
2. 写操作后关联数据可及时更新。
3. 手动刷新可强制拉取并回写缓存。
4. 提醒取消动作触发实时归档请求,成功率可观测。
### 9.3 一致性验收
1. 不出现旧响应覆盖新数据。
2. 离线后恢复在线可自动静默同步。
3. 软过期/硬过期行为符合策略定义。
4. 提醒归档在在线/离线/冷启动场景下保持最终一致。
## 10. 测试与验证计划
### 10.1 单元测试
1. `hybrid_cache_store`:命中链路、singleflight、软硬过期判定。
2. `cache_invalidator`:写操作触发的 key 精准失效。
3. repository:读缓存、后台刷新、失败兜底、版本保护。
### 10.2 组件/页面测试(高回归)
1. Dock 切换不重建分支主页面。
2. 日/月切换不重复触发全量加载。
3. Home 按钮行为稳定。
4. 二级页面系统返回不会触发 App 退出。
### 10.3 集成回归
1. Calendar -> Todo -> Calendar 多轮切换请求计数。
2. Todo 完成后列表更新与缓存一致性。
3. profile 更新后设置页/其他依赖页可见一致。
4. 提醒取消 -> 立即归档 -> 日历列表刷新链路。
5. App 杀进程后触发提醒,重启后 pending 归档自动冲刷。
## 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 不可用场景兜底。