简介
本技术文档面向 DouPHP 小程序的“离线数据处理”能力,围绕以下目标展开:
- 离线数据检测机制:网络状态监听、连接质量评估、断网提示。
- 数据冲突解决方案:版本号管理、时间戳比较、用户选择策略。
- 增量同步机制:变更追踪、差异计算、批量提交。
- 断网重试策略:指数退避、失败队列、手动重试。
- 本地存储方案:数据结构设计、存储空间管理、数据清理。
- 离线模式 UI 交互与体验优化。
说明:当前仓库中已实现统一的 HTTP 层(信封解析、缓存/去重、错误拦截)、Store 持久化封装(schema_version 失效)以及环境探测工具;订单页存在“离线支付”流程。基于这些基础能力,可构建完整的离线处理体系。
项目结构
小程序端关键目录与职责:
- services/http.ts:统一 HTTP 请求封装,负责信封解析、缓存/去重、错误拦截、调试增强。
- stores/persist.ts:基于 localStorage 的 Store 持久化封装,带 schema_version 版本控制。
- utils/env.ts:运行环境与调试开关探测。
- pages/order/offlinepay.ts:订单“离线支付”页面,演示上传凭证等异步操作。
graph TB
subgraph "小程序前端"
A["HTTP 层<br/>services/http.ts"]
B["持久化封装<br/>stores/persist.ts"]
C["环境探测<br/>utils/env.ts"]
D["订单离线支付页<br/>pages/order/offlinepay.ts"]
end
A --> |"发起请求/解析信封"| E["后端 API"]
B --> |"读写本地存储"| F["wx.storage"]
C --> |"调试开关/环境判断"| A
D --> |"调用 HTTP/上传文件"| A
核心组件
- 统一 HTTP 层
- 信封解析:code === 'OK' 为成功,否则构造 ApiError。
- 缓存与去重:GET 支持内存缓存与飞行中复用,避免重复请求。
- 错误拦截:统一捕获网络异常与业务码异常,支持调试增强。
- 调试钩子:request_id 注入与展示,便于定位问题。
- 持久化封装
- 写入结构包含 __v(schema_version),读取时不匹配则丢弃旧缓存。
- 提供 load/save/clear 与 autorun 自动写回能力。
- 环境探测
- isDebugEnv() 区分开发/试用/正式版,控制调试 UI 是否显示。
- isServerDebug() 镜像服务端 debug_enable。
- 订单离线支付
- 通过 http.get 获取订单信息,使用 wx.uploadFile 上传支付凭证。
架构总览
下图展示了小程序在在线/离线两种路径下的数据流与控制点:
sequenceDiagram
participant U as "用户界面"
participant H as "HTTP 层<br/>services/http.ts"
participant S as "后端 API"
participant P as "持久化封装<br/>stores/persist.ts"
U->>H : 发起请求(含 cache/revalidate/dedupe)
alt 在线且命中缓存
H-->>U : 返回内存缓存数据
else 在线未命中缓存
H->>S : 发送请求
S-->>H : 返回信封(code/message/data/errors/request_id)
H-->>U : 成功返回 data<T> / 失败抛出 ApiError
else 离线
H-->>U : 抛出 NETWORK_ERROR
U->>P : 读取本地缓存/待提交队列
U-->>U : 降级展示本地数据并提示离线
end
详细组件分析
离线数据检测机制
- 网络状态监听
- 可在应用启动时订阅网络变化事件,结合 http.onError 中的 NETWORK_ERROR 判定,维护全局“是否在线”状态。
- 连接质量评估
- 基于多次请求耗时与成功率估算网络质量,用于决定是否启用强缓存或限制刷新频率。
- 断网提示
- 当检测到 NETWORK_ERROR 时,通过 UI 提示用户当前处于离线状态,并提供“稍后重试”入口。
实践建议
- 将“是否在线”状态放入共享 store,配合 UI 组件进行条件渲染。
- 对关键读接口开启 cache=true,提升离线可用性。
- 对写操作进入失败队列,等待网络恢复后重试。
数据冲突解决方案
- 版本号管理
- 使用 persist.ts 的 SCHEMA_VERSION 作为数据模型版本,升级时旧缓存自动失效,避免脏数据。
- 时间戳比较
- 服务端响应携带更新时间戳,客户端合并时以较新者为准;对于并发修改,采用“最后写入胜出”或“字段级合并”。
- 用户选择
- 当冲突不可自动解决时,弹出对比视图,由用户决定保留本地还是远端数据。
实施要点
- 每次写回持久化前,校验 __v 与当前 SCHEMA_VERSION。
- 对关键字段(如金额、库存)引入乐观锁字段(version/timestamp)。
- 冲突弹窗文案清晰,提供“撤销/确认”操作。
增量同步机制
- 变更追踪
- 对本地可写实体维护 last_sync_time 与 version,仅拉取大于该时间的变更。
- 差异计算
- 服务端返回 diff 列表(新增/更新/删除),客户端按 id 合并到本地。
- 批量提交
- 将离线期间的写操作聚合为批次,在网络可用时一次性提交,减少往返次数。
流程图(差异合并)
flowchart TD
Start(["开始"]) --> LoadLocal["加载本地数据"]
LoadLocal --> FetchDiff["拉取增量差异"]
FetchDiff --> Merge{"合并策略"}
Merge --> |时间戳优先| TS["按时间戳比较"]
Merge --> |版本号优先| Ver["按版本号比较"]
TS --> Apply["应用到本地"]
Ver --> Apply
Apply --> Batch["加入批量提交队列"]
Batch --> End(["结束"])
断网重试策略
- 指数退避
- 首次失败立即重试,随后按 1s、2s、4s、8s… 递增间隔重试,设置最大重试次数与上限间隔。
- 失败队列
- 将失败的写操作入队,网络恢复后按序重试;支持优先级与幂等键。
- 手动重试
- 提供“重试全部/单个”入口,允许用户主动触发。
序列图(指数退避重试)
sequenceDiagram
participant U as "用户界面"
participant H as "HTTP 层"
participant Q as "失败队列"
participant S as "后端 API"
U->>H : 发起写请求
H->>S : 发送请求
alt 失败
H-->>Q : 入队(含幂等键/优先级)
U-->>U : 提示“已加入重试队列”
loop 指数退避
Q->>S : 延迟后重试
alt 成功
S-->>Q : 成功
Q-->>U : 通知完成
else 仍失败
S-->>Q : 失败
end
end
else 成功
S-->>H : 成功
H-->>U : 返回结果
end
本地存储方案
- 数据结构设计
- 每个 key 对应一个 Envelope{v, data},v 用于 schema 版本控制。
- 针对业务实体建立独立 key,便于局部清理与迁移。
- 存储空间管理
- 定期统计各 key 大小,超过阈值时清理过期或低频数据。
- 对大对象(图片/富文本)采用压缩或分片存储。
- 数据清理
- 提供 clearPersisted 接口,支持按 key 或全量清理。
- 结合用户行为(如退出登录)清理敏感数据。
离线模式的 UI 交互设计与用户体验优化
- 状态可见性
- 顶部常驻条显示“离线/弱网”,点击可查看最近一次请求的 request_id。
- 降级展示
- 读接口命中缓存时,标注“缓存数据,可能不是最新”。
- 操作反馈
- 写操作失败时,明确告知“已加入重试队列”,并提供“立即重试”按钮。
- 资源占用
- 弱网下禁用大图/视频预加载,降低带宽消耗。
- 调试友好
- 非正式版环境下,错误消息附加 request_id 片段,便于定位。
订单“离线支付”流程
- 流程说明
- 页面加载后先鉴权,再拉取订单详情;用户选择图片后上传支付凭证,成功后跳转至订单列表。
- 与离线能力的结合
- 若网络不可用,应提示“当前离线,无法上传凭证”,并将“上传凭证”动作加入失败队列,待网络恢复后重试。
序列图(订单离线支付)
sequenceDiagram
participant P as "订单页<br/>offlinepay.ts"
participant H as "HTTP 层"
participant S as "后端 API"
participant W as "微信API"
P->>H : GET 订单详情
H->>S : 请求
S-->>H : 返回订单数据
H-->>P : 渲染订单信息
P->>W : chooseImage 选择凭证
W-->>P : 返回临时路径
P->>W : uploadFile 上传凭证
W-->>P : 返回上传结果
P->>H : 可选:提交支付凭证状态
H->>S : 请求
S-->>H : 返回结果
H-->>P : 成功则跳转
依赖关系分析
- HTTP 层依赖
- 微信原生 wx.request/wx.showModal/wx.setClipboardData。
- 环境探测 utils/env.ts 控制调试行为。
- 持久化封装依赖
- 微信 storage API 与 MobX 的 autorun/toJS。
- 页面依赖
- 订单页依赖 HTTP 层与微信文件上传能力。
graph LR
Env["utils/env.ts"] --> Http["services/http.ts"]
Http --> Storage["stores/persist.ts"]
Page["pages/order/offlinepay.ts"] --> Http
Page --> Storage
性能考量
- 请求去重与缓存
- GET 请求默认去重,避免抖动;合理设置 ttl,平衡新鲜度与性能。
- 批量提交
- 将多次写操作合并为一次提交,降低网络开销。
- 弱网适配
- 弱网下关闭非必要资源加载,降低首屏时间与带宽占用。
- 存储体积
- 定期清理过期数据,避免 storage 膨胀影响性能。
故障排查指南
- 快速定位
- 查看最近一次请求的 request_id,与服务端日志对照。
- 在非正式版环境,错误消息会附带 request_id 片段。
- 常见问题
- 网络异常:检查设备网络与域名配置;必要时提示用户切换网络。
- 业务码非 OK:根据 errors 字段定位具体原因。
- 缓存不一致:调整 ttl 或强制 revalidate。
- 调试技巧
- 利用 onError 拦截器记录上下文(URL、参数、时间戳)。
- 使用 clearCache 清空内存缓存后复现问题。
结论
本项目已具备构建完整离线数据处理体系的基础:
- HTTP 层提供稳定的请求封装与错误处理。
- 持久化封装提供版本化的本地存储能力。
- 环境探测确保调试体验可控。 在此基础上,可通过网络监听、失败队列、增量同步与冲突解决策略,实现高可用的离线模式,并在 UI 层面提供清晰的降级与反馈。
附录
- 术语
- 信封:标准响应体结构 code/message/data/errors/request_id。
- 去重:相同 GET 请求在飞行中复用同一 Promise。
- 幂等:重复提交不会产生副作用。
- 最佳实践
- 所有写操作均标记幂等键,便于重试安全。
- 对关键数据采用“本地优先 + 后台同步”的策略。
- 定期审计本地存储大小与命中率,持续优化。