简介
本技术文档围绕 DouPHP 小程序与主站的数据同步机制,系统阐述以下方面:
- API 接口设计与信封格式、请求/响应处理流程
- 本地缓存策略(内存缓存、持久化、过期与失效)
- 状态管理模式(全局状态、局部状态同步、状态持久化)
- 离线数据处理方案(冲突解决、增量同步、断网重试)
- 性能优化技巧(批量操作、懒加载、预加载)
- 错误处理与日志记录方案
该机制以“统一 HTTP 层 + Store 状态管理 + 持久化”为核心,结合 SWR(Stale-While-Revalidate)刷新策略,确保首屏秒开与数据一致性。
项目结构
小程序端关键目录与职责:
- services/http.ts:统一 HTTP 封装,负责信封解析、拦截器、去重、内存缓存、调试增强
- services/bootstrap.ts:应用引导数据拉取(site/param/features/nav_list/lang_v/version)
- stores/*:状态管理(commonStore、authStore、cartStore),提供 hydrate/refresh/restore 等能力
- stores/persist.ts:基于 storage 的持久化封装(带 schema_version 版本控制)
- app.ts:应用启动入口,注册全局错误处理、自动更新、推广参数解析、store 引导
- api/controller/BaseController.php:API 控制器基类,承载 API 端通用逻辑
graph TB
subgraph "小程序"
A["app.ts<br/>应用启动"] --> B["stores/index.ts<br/>Store 聚合与引导"]
B --> C["stores/common.ts<br/>公共数据 Store"]
B --> D["stores/auth.ts<br/>登录态 Store"]
C --> E["services/bootstrap.ts<br/>引导数据服务"]
E --> F["services/http.ts<br/>统一 HTTP 层"]
D --> F
end
subgraph "后端"
G["api/controller/BaseController.php<br/>API 控制器基类"]
end
F --> G
核心组件
- 统一 HTTP 层(services/http.ts)
- 标准信封解析:code/message/data/errors/request_id
- 默认头注入:Content-Type、Authorization: Bearer <api_token>
- 拦截器:onRequest/success/error 钩子
- 去重:相同 GET 在飞行中复用 Promise
- 内存缓存:cache/ttl/revalidate 支持
- 调试:request_id 追踪、异常弹窗(仅调试环境)
- 应用引导服务(services/bootstrap.ts)
- 拉取 bootstrap/index 数据,驱动 commonStore.applyData
- Store 体系
- commonStore:站点信息、语言包、功能开关 features、导航等;SWR 刷新并持久化
- authStore:登录态、鉴权标志、模块降级(features.user=false 时短路)
- cartStore:购物车角标绑定(由 index.ts 驱动)
- 持久化(stores/persist.ts)
- 读写 storage,带 schema_version 版本控制,变更即失效旧缓存
- autorun 订阅 selector,变化写回 storage
架构总览
小程序通过统一 HTTP 层访问后端 API,Store 作为单一真相源,配合持久化实现“种子数据 → 本地缓存 → 网络刷新”的 SWR 模式。应用启动时先 hydrate 再 refresh,保证首屏秒开与数据最新性。
sequenceDiagram
participant App as "app.ts"
participant Stores as "stores/index.ts"
participant Common as "stores/common.ts"
participant Auth as "stores/auth.ts"
participant Boot as "services/bootstrap.ts"
participant Http as "services/http.ts"
participant API as "后端 API"
App->>Stores : 调用 bootstrapStores()
Stores->>Common : hydrate()
Stores->>Auth : hydrate()
Stores->>Common : refresh()
Common->>Boot : fetchBootstrap()
Boot->>Http : http.get(route('bootstrap'))
Http->>API : 发起请求
API-->>Http : 返回信封 {code,message,data,...}
Http-->>Boot : 解析后返回 data
Boot-->>Common : BootstrapData
Common->>Common : applyData(合并 lang/site/features...)
Common->>Common : savePersisted(PERSIST_KEY, merged)
Stores->>Auth : restore()
Auth->>Http : http.get(user/index)
Http->>API : 发起请求
API-->>Http : 返回认证信息
Http-->>Auth : 解析并设置 is_login 等标志
详细组件分析
统一 HTTP 层(services/http.ts)
- 信封解析:严格读取 code/message/data/errors/request_id,非 OK 则抛出 ApiError
- 默认头:自动注入 Authorization: Bearer <api_token>
- 拦截器:onRequest/success/error 扩展点,便于全局处理(如 UNAUTHORIZED 登出)
- 去重:GET 请求在飞行中复用同一 Promise,避免重复网络开销
- 内存缓存:支持 cache/ttl/revalidate,命中且未过期直接返回
- 调试:request_id 追踪,调试环境可弹出服务端异常堆栈
flowchart TD
Start(["进入 request(config, opts)"]) --> BuildCfg["构建配置<br/>注入默认头"]
BuildCfg --> Interceptors["执行请求拦截器"]
Interceptors --> CacheCheck{"启用缓存且未强制刷新?"}
CacheCheck --> |是| HitCache{"命中内存缓存且未过期?"}
HitCache --> |是| ReturnCache["直接返回缓存数据"]
HitCache --> |否| DedupeCheck{"是否允许去重?"}
CacheCheck --> |否| DedupeCheck
DedupeCheck --> |是且有进行中| Reuse["复用进行中 Promise"]
DedupeCheck --> |否| DoRequest["发起 wx.request"]
Reuse --> End(["返回 Promise"])
DoRequest --> OnSuccess{"HTTP 状态码 2xx?"}
OnSuccess --> |否| ParseErr["解析信封并构造 ApiError"]
ParseErr --> RunErrInterceptors["运行错误拦截器"]
RunErrInterceptors --> Reject["reject(ApiError)"]
OnSuccess --> |是| ParseOk["解析信封"]
ParseOk --> Ok{"code === 'OK'?"}
Ok --> |否| BizErr["构造业务 ApiError"]
BizErr --> RunErrInterceptors
Ok --> |是| AttachMeta["附加 __message/__request_id"]
AttachMeta --> SaveCache{"是否启用缓存?"}
SaveCache --> |是| PutCache["写入内存缓存"]
SaveCache --> |否| SuccessInterceptors["运行成功拦截器"]
PutCache --> SuccessInterceptors
SuccessInterceptors --> Resolve["resolve(data)"]
Reject --> End
Resolve --> End
应用引导与 Store 聚合(stores/index.ts)
- 启动顺序:hydrate → refresh → restore
- 购物车角标:autorun 监听 cartStore.badge,统一设置 tabBar 角标
- 模块降级:根据 commonStore.features 决定是否继续 auth/cart 刷新
公共数据 Store(stores/common.ts)
- 数据来源:bootstrap/index + lang/fetchLang
- 数据合并:优先使用网络返回的语言包,否则回退到已有或种子语言包
- 持久化:refresh 成功后将合并后的数据持久化,schema_version 控制失效
- 功能开关:features 用于模块级降级(如 user/product/article)
登录态 Store(stores/auth.ts)
- 状态字段:api_token/user_id/loginEd/is_login/is_vip/is_work/is_distribution
- 恢复流程:restore 调用 user/index 获取认证标志,UNAUTHORIZED 时清空登录态
- 模块降级:当 features.user=false 时,is_login 恒 false,不发起任何请求
- 登录/登出:login 写入 storage 并更新状态;logout 清理 storage 与状态
持久化(stores/persist.ts)
- 读写 storage:loadPersisted/savePersisted/clearPersisted
- 版本控制:SCHEMA_VERSION 变更导致旧缓存自动失效
- 自动写回:persistAutorun 用 autorun 订阅 selector,变化即写回 storage
应用入口(app.ts)
- 全局错误处理:onError/onUnhandledRejection/onPageNotFound
- 推广参数解析:从 launch options 解析 user_sn 并落本地缓存
- 自动更新:检查新版本并提示用户重启
- Store 引导:bootstrapStores 触发 hydrate/refresh/restore
后端 API 控制器基类(api/controller/BaseController.php)
- API 控制器继承基础类,复用前台会员中心导航构建器
- 在 API 端无模板相关字段,聚焦数据输出与业务逻辑
依赖关系分析
- 小程序侧依赖链:
- app.ts → stores/index.ts → stores/common.ts → services/bootstrap.ts → services/http.ts
- stores/index.ts → stores/auth.ts → services/http.ts
- stores/common.ts → stores/persist.ts
- 后端侧:
- BaseController.php 为 API 控制器基类,承载通用逻辑
graph LR
App["app.ts"] --> Index["stores/index.ts"]
Index --> Common["stores/common.ts"]
Index --> Auth["stores/auth.ts"]
Common --> Persist["stores/persist.ts"]
Common --> Boot["services/bootstrap.ts"]
Boot --> Http["services/http.ts"]
Auth --> Http
Http --> API["api/controller/BaseController.php"]
性能考虑
- 首屏秒开:
- 种子数据 + hydrate:commonStore/hydrate 从 storage 立即渲染
- SWR 刷新:refresh 后台异步拉取最新数据并写回
- 网络优化:
- 去重:相同 GET 请求复用 Promise,减少并发压力
- 内存缓存:cache/ttl/revalidate 控制缓存命中与刷新策略
- 懒加载与预加载:
- 按需刷新:authStore.restore 仅在必要时拉取认证信息
- 模块降级:features.user=false 时跳过不必要请求
- 建议:
- 对列表页采用分页与懒加载
- 对热点数据使用较长 ttl 与 revalidate 组合
- 对写操作进行批量化与幂等设计
故障排查指南
- 统一错误类型:ApiError 包含 code/statusCode/errors/request_id,便于定位
- 调试增强:
- 调试环境打印请求信息与错误详情
- 服务端异常堆栈弹窗(errors.exception/file/line/trace)
- 最近一次 request_id 可通过 getLastRequestId 获取
- 常见错误分类:
- NETWORK_ERROR:网络异常
- HTTP_XXX:HTTP 状态码异常
- UNAUTHORIZED:会话过期,触发登出
- 业务错误:code !== 'OK',携带 errors 字段
- 排查步骤:
- 查看控制台日志与 vConsole(调试环境开启)
- 通过 request_id 在后端日志中定位请求链路
- 检查 store 状态与缓存是否一致(storage 中的 store:* 键)
结论
DouPHP 小程序数据同步机制以“统一 HTTP 层 + Store 状态管理 + 持久化”为核心,结合 SWR 刷新策略,实现了首屏秒开、数据一致性与良好的用户体验。通过拦截器、去重、内存缓存与版本化持久化,系统在弱网与离线场景下具备较强鲁棒性。建议在业务层进一步引入批量化与幂等设计,以提升整体性能与可靠性。
附录
- API 信封规范(小程序侧解析):
- code:字符串,'OK' 表示成功
- message:字符串,提示信息
- data:对象,业务数据
- errors:对象,字段级错误
- request_id:字符串,请求追踪号
- 请求头:
- Content-Type: application/x-www-form-urlencoded
- Authorization: Bearer <api_token>
- Store 持久化键:
- store:<key>,包含 __v 与 data
- 功能开关:
- features.*:用于模块级降级(如 user/product/article)