简介
本技术文档面向 DouPHP 小程序的数据流架构,围绕“单向数据流、状态管理、数据同步”三大主题展开。重点说明:
- stores 状态管理机制:状态定义、变更操作、订阅更新与持久化
- services 服务层设计:API 调用封装、数据处理逻辑、错误处理机制
- 本地存储策略:缓存机制、数据持久化、离线支持
- 调试方法与性能优化建议
项目结构
小程序端采用“页面/组件 -> Store(MobX)-> Service(HTTP)-> 后端 API”的单向数据流组织方式。应用启动时通过统一引导流程完成状态水合与网络刷新,确保首屏秒开与数据一致性。
graph TB
subgraph "小程序入口"
A["app.ts"]
end
subgraph "状态层 Stores"
B["stores/index.ts"]
C["stores/common.ts"]
D["stores/auth.ts"]
E["stores/cart.ts"]
F["stores/persist.ts"]
end
subgraph "服务层 Services"
G["services/http.ts"]
H["services/bootstrap.ts"]
I["services/lang.ts"]
end
subgraph "后端"
J["Bootstrap/Lang/API"]
end
A --> B
B --> C
B --> D
B --> E
C --> F
C --> H
C --> I
D --> G
E --> G
H --> G
I --> G
G --> J
核心组件
- 应用入口 app.ts:注册 HTTP 全局错误拦截器(UNAUTHORIZED 登出)、推广参数解析、全局 store 引导、自动更新与调试开关
- 状态聚合 index.ts:统一导出 store、绑定购物车角标、启动顺序 hydrate -> refresh -> restore
- 公共状态 commonStore:bootstrap 信封的唯一真相源,负责 features 下发、语言包合并、版本与就绪标记
- 登录态 authStore:api_token/user_id/loginEd 持久化,restore 拉取用户模块状态,ensureLogin 强制登录跳转
- 购物车 cartStore:数量与角标计算,按 features.order 与登录态短路请求
- HTTP 服务 http.ts:标准信封解析、鉴权头注入、缓存/去重、拦截器链、ApiError 统一错误模型
- 引导服务 bootstrap.ts / lang.ts:分别拉取站点引导与语言包,供 commonStore 合并
- 持久化 persist.ts:带 schema_version 的 localStorage 读写与 autorun 自动写回
架构总览
小程序采用 MobX 驱动的单向数据流:
- 视图层通过 createStoreBindings 订阅 store 字段变化
- Store 仅通过 action 修改自身状态,并通过 autorun/computed 驱动副作用(如 tabBar 角标)
- Service 层封装 HTTP 请求,统一处理信封、鉴权、缓存、去重与错误
- 启动阶段先 hydrate 从 storage 恢复状态,再 refresh 网络刷新,保证“零白屏 + 最新数据”
sequenceDiagram
participant App as "app.ts"
participant Bootstrap as "stores/index.ts"
participant Common as "stores/common.ts"
participant Auth as "stores/auth.ts"
participant Cart as "stores/cart.ts"
participant Http as "services/http.ts"
participant API as "后端API"
App->>Bootstrap : onLaunch() 调用 bootstrapStores()
Bootstrap->>Common : hydrate() 从 storage 恢复
Bootstrap->>Auth : hydrate() 从 storage 恢复
Bootstrap->>Cart : bindCartBadge() 监听 badge 变化
Bootstrap->>Common : refresh() 拉取 bootstrap/index
Common->>Http : GET bootstrap/index
Http->>API : 发送请求
API-->>Http : 返回信封 {code,data,...}
Http-->>Common : data(BootstrapData)
Common->>Common : applyData() 合并 features/lang/nav...
Common-->>Bootstrap : 完成
Bootstrap->>Auth : restore() 拉取 user/index
Auth->>Http : GET user/index
Http->>API : 发送请求
API-->>Http : 返回信封
Http-->>Auth : data(dou.auth)
Auth->>Auth : applyAuthFlags() 更新 is_login 等
Bootstrap->>Cart : refresh() 拉取 order/cart_number
Cart->>Http : GET order/cart_number
Http->>API : 发送请求
API-->>Http : 返回信封
Http-->>Cart : data(cart_number)
Cart->>Cart : number = n, badge 计算
Note over Cart,App : autorun 触发 wx.setTabBarBadge
详细组件分析
状态层 Stores
- 状态定义:使用 MobX observable 声明状态字段;类型由 types/store.d.ts 约束
- 变更操作:action 包裹的状态变更,保证可追踪与批量更新
- 订阅更新:autorun/computed 驱动副作用(如购物车角标),避免在业务中散落 setTabBarBadge
- 持久化:persist.ts 提供带 schema_version 的读写与 autorun 自动写回
classDiagram
class CommonState {
+site
+lang
+param
+nav_list
+features
+data
+user_level_has_data
+version
+ready
+applyData(payload)
+hydrate()
+refresh() Promise<void>
}
class AuthState {
+api_token
+user_id
+loginEd
+is_login
+is_vip
+is_work
+is_distribution
+hydrate()
+applyAuthFlags(flags)
+restore() Promise<void>
+ensureLogin(redirectUrl?) Promise<boolean>
+login(payload)
+logout()
}
class CartState {
+number
+badge string
+refresh() Promise<void>
+reset()
}
CommonState <.. AuthState : "features 控制降级"
CommonState <.. CartState : "features 控制降级"
服务层 Services
- HTTP 统一封装:
- 信封解析:code === 'OK' 为成功,否则 reject(ApiError)
- 默认头注入:Content-Type 与 Authorization: Bearer api_token
- 拦截器链:onRequest/onSuccess/onError,便于扩展(如全局登出)
- 缓存与去重:内存 cache + ttl,GET 飞行中复用 Promise
- 调试增强:request_id 装饰、vConsole、服务端异常弹窗(仅调试环境)
- 引导与语言:
- bootstrap.ts 拉取站点引导数据
- lang.ts 拉取语言包全表,供 commonStore 合并
flowchart TD
Start(["发起请求"]) --> BuildCfg["构建请求配置<br/>注入默认头"]
BuildCfg --> CacheCheck{"启用缓存且未过期?"}
CacheCheck --> |是| ReturnCache["直接返回缓存数据"]
CacheCheck --> |否| DedupeCheck{"GET 且飞行中?"}
DedupeCheck --> |是| ReturnInflight["复用同一 Promise"]
DedupeCheck --> |否| DoRequest["wx.request 发送请求"]
DoRequest --> ParseEnvelope["解析信封 code/message/data/errors/request_id"]
ParseEnvelope --> Ok{"code === 'OK' ?"}
Ok --> |是| SuccessHook["执行 onSuccess 拦截器"]
SuccessHook --> Resolve["resolve(data)"]
Ok --> |否| ErrorHook["执行 onError 拦截器"]
ErrorHook --> Reject["reject(ApiError)"]
Resolve --> End(["结束"])
Reject --> End
数据流时序与关键路径
- 启动引导:app.onLaunch -> bootstrapStores -> common.hydrate -> auth.hydrate -> bindCartBadge -> common.refresh -> auth.restore -> cart.refresh
- 登录态恢复:auth.restore 根据 features.user 决定是否请求 user/index;失败仅在 UNAUTHORIZED 时清空 is_login
- 购物车角标:cartStore.badge 通过 computed 派生,autorun 驱动 wx.setTabBarBadge
sequenceDiagram
participant UI as "页面/组件"
participant Store as "stores/index.ts"
participant Common as "stores/common.ts"
participant Auth as "stores/auth.ts"
participant Cart as "stores/cart.ts"
participant Http as "services/http.ts"
UI->>Store : 应用启动
Store->>Common : hydrate()
Store->>Auth : hydrate()
Store->>Cart : bindCartBadge()
Store->>Common : refresh()
Common->>Http : GET bootstrap/index
Http-->>Common : data
Common->>Auth : restore()
Auth->>Http : GET user/index
Http-->>Auth : data
Auth->>Auth : applyAuthFlags()
Store->>Cart : refresh()
Cart->>Http : GET order/cart_number
Http-->>Cart : data
Cart->>Cart : number = n, badge 计算
Cart-->>UI : autorun 触发角标更新
依赖关系分析
- 低耦合高内聚:Store 只依赖 Service 暴露的 http 方法;Service 不感知 Store
- 可选模块开关:commonStore.features 作为单一真相源,authStore/cartStore 据此短路请求,降低不必要网络开销
- 错误处理集中:所有网络异常与业务异常统一为 ApiError,便于全局拦截与调试
graph LR
Common["commonStore"] --> Features["features 开关"]
Auth["authStore"] --> Features
Cart["cartStore"] --> Features
Auth --> Http["http.ts"]
Cart --> Http
Common --> Http
Http --> API["后端API"]
性能考虑
- 首屏秒开:commonStore.hydrate 优先从 storage 恢复,避免等待网络
- 网络优化:
- GET 请求去重:相同 URL+data 的请求在飞行中复用 Promise
- 内存缓存:cache + ttl 命中直接返回,减少重复请求
- SWR 模式:refresh 覆盖 hydrate,兼顾速度与新鲜度
- 渲染优化:
- 购物车角标通过 autorun 集中管理,避免多处 setTabBarBadge
- 可选模块短路:features.order=false 或无 token 时不请求购物车
- 调试友好:request_id 贯穿请求链路,便于前后端日志对照
故障排查指南
- 统一错误对象:所有网络/业务异常统一为 ApiError,包含 code、message、errors、request_id
- 全局错误拦截:
- UNAUTHORIZED:app.ts 中 http.onError 统一登出,避免状态不一致
- 非正式环境:开启 vConsole,并在服务端异常载荷下弹出堆栈提示
- 常见问题定位:
- 接口 401:检查 api_token 是否写入 storage,确认 Authorization 头注入
- 路由缺失:route('xxx') 抛错表示模块未启用,对应 store 已做 try/catch 兜底
- 缓存问题:clearCache(prefix) 可按前缀清理内存缓存;persist 版本升级后旧缓存自动失效
结论
DouPHP 小程序数据流以 MobX Store 为核心,配合统一的 HTTP 服务层与持久化机制,实现了清晰的单向数据流、可靠的错误处理与良好的首屏体验。通过 features 开关实现可选模块降级,结合缓存与去重提升性能,借助 request_id 与全局拦截器提升可观测性。该架构易于扩展与维护,适合多模块、多场景的小程序产品。
附录
- 关键概念速查
- 单向数据流:视图 -> Store -> Service -> API,数据变更仅通过 action 进行
- 状态管理:MobX observable/action/autorun/computed 组合
- 数据同步:hydrate(本地)-> refresh(网络)-> restore(会话)
- 本地存储:persist.ts 带 schema_version 的持久化与自动写回
- 错误处理:ApiError 统一封装,全局 onError 拦截