简介
本技术文档聚焦于 DouPHP 小程序端的本地缓存策略,围绕以下目标展开:
- 缓存架构设计:层级划分、存储介质选择、缓存键设计
- 数据持久化方案:本地存储、内存缓存、文件缓存等场景的使用
- 缓存更新机制:主动更新、被动失效、增量同步
- 过期处理:时间戳管理、LRU 算法、空间清理
- 性能优化:批量操作、懒加载、预加载
- 监控与调试:可观测性与问题定位方法
项目结构
小程序端缓存相关代码集中在 miniprogram/default/stores 目录,其中:
- stores/index.ts:应用启动时 store 的聚合与引导流程(先水合再刷新)
- stores/persist.ts:基于 wx.storage 的持久化封装,包含 schema 版本控制与自动写回
服务端侧提供系统常量配置与会话基础设施,用于理解小程序与服务端交互时的上下文与边界。
graph TB
subgraph "小程序端"
A["stores/index.ts<br/>应用启动引导"] --> B["stores/persist.ts<br/>localStorage 持久化封装"]
A --> C["业务 Storeauth/cart/common"]
B --> D["wx.storage<br/>本地持久化"]
end
subgraph "服务端"
E["config/system.php<br/>系统常量小程序内建模块/页面"]
F["core/infra/session/Session.php<br/>会话基础设施"]
end
C --> |网络请求| E
C --> |鉴权/会话| F
核心组件
- 持久化封装(persist.ts)
- 提供 loadPersisted/savePersisted/clearPersisted/persistAutorun 等方法
- 使用 SCHEMA_VERSION 进行结构升级与旧缓存失效
- 通过 wx.getStorageSync/setStorageSync/removeStorageSync 实现本地持久化
- 应用引导(index.ts)
- bootstrapStores:先 hydrate(从 storage 恢复),再 refresh(网络刷新),最后预热购物车角标
- 将 cartStore.badge 与 tabBar 角标绑定,统一状态驱动 UI
架构总览
小程序端采用“内存 Store + 本地持久化”的双层缓存模型:
- 内存层:MobX Store(auth/cart/common)作为运行时缓存,提供快速读写与响应式更新
- 持久化层:基于 wx.storage 的 JSON 序列化存储,带 schema 版本控制,支持冷启动秒开
- 服务端交互:在 hydrate 之后触发 refresh,优先保证用户体验,失败时保留本地数据
sequenceDiagram
participant App as "小程序应用"
participant StoreIdx as "stores/index.ts"
participant Persist as "stores/persist.ts"
participant Storage as "wx.storage"
participant API as "后端接口"
App->>StoreIdx : 调用 bootstrapStores()
StoreIdx->>StoreIdx : commonStore.hydrate()
StoreIdx->>StoreIdx : authStore.hydrate()
StoreIdx->>StoreIdx : bindCartBadge()
StoreIdx->>API : commonStore.refresh()
API-->>StoreIdx : features/配置
StoreIdx->>StoreIdx : authStore.restore()
StoreIdx->>StoreIdx : cartStore.refresh()
Note over StoreIdx,Storage : 后续 Store 变更通过 autorun 写回 Storage
详细组件分析
持久化封装(persist.ts)
- 设计要点
- 键前缀:store: 避免与其他存储项冲突
- 信封结构:{ __v: SCHEMA_VERSION, data },读取时若版本不匹配则丢弃旧缓存
- 异常安全:读写均 try-catch,写入失败静默忽略,保障稳定性
- 自动写回:persistAutorun 订阅 selector,变化即写回 storage
- 复杂度与行为
- 读/写均为 O(1) 的 storage 操作;JSON 序列化/反序列化成本与数据大小线性相关
- 版本控制使字段不兼容变更时无需手动清理,直接失效旧缓存
flowchart TD
Start(["读取持久化"]) --> Read["wx.getStorageSync('store:' + key)"]
Read --> HasRaw{"存在且非空?"}
HasRaw -- 否 --> ReturnNull["返回 null"]
HasRaw -- 是 --> Parse["解析为对象"]
Parse --> CheckVer{"__v == SCHEMA_VERSION ?"}
CheckVer -- 否 --> ReturnNull
CheckVer -- 是 --> ReturnData["返回 data"]
应用引导与缓存生命周期(index.ts)
- 启动流程
- 先 hydrate:从 storage 恢复内存状态,实现秒开
- 再 refresh:拉取最新数据,失败不影响已恢复的本地数据
- 购物车角标:通过 autorun 监听 cartStore.badge,统一设置/移除 tabBar 角标
- 降级策略
- 当 features.order=false 或未登录时,cartStore 预热短路,减少不必要请求
sequenceDiagram
participant App as "应用"
participant Bootstrap as "bootstrapStores()"
participant Common as "commonStore"
participant Auth as "authStore"
participant Cart as "cartStore"
App->>Bootstrap : 启动
Bootstrap->>Common : hydrate()
Bootstrap->>Auth : hydrate()
Bootstrap->>Bootstrap : bindCartBadge()
Bootstrap->>Common : refresh()
Common-->>Bootstrap : features
Bootstrap->>Auth : restore()
Bootstrap->>Cart : refresh()
服务端系统与会话(system.php / Session.php)
- system.php:定义小程序内建模块与额外页面,影响前端路由与功能开关,间接决定缓存数据的范围与粒度
- Session.php:提供会话基础设施,用于鉴权与会话状态在服务端的管理,配合小程序的登录态与权限控制
依赖关系分析
- 组件耦合
- index.ts 依赖 persist.ts 提供的持久化能力(通过各 Store 内部使用)
- 各 Store 依赖 MobX 的 autorun 实现响应式写回
- 小程序运行环境提供 wx.storage 与 tabBar 能力
- 外部依赖
- 微信小程序 API:getStorageSync/setStorageSync/removeStorageSync、setTabBarBadge/removeTabBarBadge
- 服务端配置与会话:system.php、Session.php
graph LR
Index["stores/index.ts"] --> Persist["stores/persist.ts"]
Index --> Auth["authStore"]
Index --> Cart["cartStore"]
Index --> Common["commonStore"]
Persist --> WxStorage["wx.storage"]
Index --> TabBar["wx.setTabBarBadge / removeTabBarBadge"]
Auth --> ServerCfg["config/system.php"]
Auth --> ServerSess["core/infra/session/Session.php"]
性能考虑
- 启动优化
- 先 hydrate 再 refresh:利用本地缓存实现秒开,网络失败时仍可用
- 购物车角标懒绑定:仅在需要时绑定,避免无效开销
- 存储优化
- 使用 JSON 序列化并控制数据体积,避免大对象频繁写盘
- 通过 SCHEMA_VERSION 实现无感升级,无需全量清理
- 更新策略
- 增量刷新:refresh 仅拉取必要数据,结合 features 控制是否执行
- 自动写回:autorun 仅在数据变化时写回,降低 I/O 频率
- 建议扩展
- 批量操作:合并多次小写为一次写盘(例如队列化 autorun 写回)
- 预加载:对高频访问的数据在空闲时预取并存入本地
- LRU 淘汰:如需多键管理,可在上层实现 LRU 策略以控制存储上限
故障排查指南
- 常见问题
- 存储写入失败:persist.ts 中捕获异常并静默忽略,可能导致数据未持久化
- 版本不匹配:SCHEMA_VERSION 变更后旧缓存被丢弃,表现为首次启动数据为空
- 角标不更新:非 tabBar 页面调用 setTabBarBadge 会失败,已在代码中静默处理
- 定位方法
- 检查 SCHEMA_VERSION 是否与当前代码一致
- 确认 wx.storage 可用性与配额限制
- 观察 refresh 是否成功,必要时降级显示本地缓存数据
结论
DouPHP 小程序端采用“内存 Store + 本地持久化”的分层缓存架构,通过启动时先水合后刷新的策略,兼顾了首屏体验与数据一致性。持久化层以 schema 版本控制为核心,确保结构升级的平滑过渡。结合服务端的系统常量与会话基础设施,整体方案具备高可用性、易维护性与良好的可扩展性。
附录
- 缓存键设计建议
- 命名规范:store:<模块>:<实体>:<标识>,如 store:cart:user:123
- 作用域隔离:按模块或用户维度拆分键,避免污染
- 版本兼容:通过信封结构中的 __v 字段管理兼容性
- 过期与清理策略
- 时间戳管理:可在 data 中附加 lastUpdated 字段,按需判断新鲜度
- LRU 算法:如需多键管理,可在上层实现 LRU 淘汰以控制存储上限
- 空间清理:在写入失败或配额不足时,尝试清理低频缓存项