万家袋同时包含互联网交易系统和实体设备控制系统。一次领袋可能涉及用户身份、活动或支付、设备库存、MQTT 指令、机械执行回执和代理收益,因此“接口返回成功”与“设备真实出袋”必须被建模为两个阶段。
代码库与运行单元
| 项目 | 技术组成 | 源码职责 | 运行方式 |
|---|---|---|---|
wanjiadai |
ThinkPHP 6.0、多应用、Predis、EasyWeChat、OSS、phpMQTT | 用户和代理接口、订单、设备、库存、回调、统计、权限与结算 | PHP API 服务,加定时或命令进程 |
wanjiadai_admin |
Vue 2.5、Element UI、Vuex、ECharts | 商品、订单、代理、设备、库存、财务、流量、分享和权限后台 | Webpack 构建的静态管理站 |
wanjiadai_express |
Node.js、Express、MQTT、PM2 | 特定设备协议适配、HTTP 指令、MQTT 消息和状态回调 | 独立常驻设备桥接进程 |
管理后台和 API 是典型 Web 系统;设备桥接服务则是长连接和厂商协议适配层,必须独立监控和重启。三者发布节奏可以不同,但订单编号、设备命令编号和回执格式需要保持兼容。
总体拓扑
flowchart TB User[扫码用户 / 微信 / 移动端] --> H5[h5 / mobile / wechat] Mini[代理与商户小程序] --> MM[mini_manage] Admin[wanjiadai_admin
运营管理后台] --> WA[web_admin] H5 --> Core[wanjiadai
共享业务与数据访问] MM --> Core WA --> Core Third[支付 / 广告任务 / 第三方平台] --> Notify[notify / third] Notify --> Core Core --> DB[(MySQL
业务事实与流水)] Core --> Cache[(Redis
会话 / 锁 / 实时状态)] Core --> Bridge[wanjiadai_express
厂商协议桥接] Bridge --> MQ[MQTT Broker] MQ --> Device[环保袋发放设备] Device --> Bridge Bridge --> Notify
API 应用入口
wanjiadai 按调用方和任务类型拆成多个 ThinkPHP 应用。
| 入口 | 主要职责 | 身份与信任边界 |
|---|---|---|
h5 |
扫码、用户登录、设备信息、领袋订单、积分、广告与数据展示 | 面向普通用户,Token 与设备上下文均需校验 |
mobile |
App 用户登录、附近设备、积分和出袋 | 面向移动应用用户,不继承后台权限 |
mini_manage |
代理资料、商户、设备、补货、收入、钱包与提现 | 面向代理和商户,数据范围受代理层级约束 |
web_admin |
平台账号、RBAC、代理、设备、供应链、订单、广告与财务 | 平台运营权限,可跨代理治理但需资源级授权 |
notify |
支付、DSP、MQTT 和不同设备厂商的回调 | 无用户会话,依赖验签、来源校验和幂等键 |
wechat、third |
公众号、小程序、开放平台授权与代码审核 | 保存第三方授权关系,凭据不得暴露给前端 |
statistics、task |
设备、钱包、积分统计与异步任务 | 由内部任务调度触发,不应直接公开访问 |
不同入口目前共享模型和辅助函数。维护时应把订单、库存、出袋和结算规则放进公共领域服务,入口层只处理身份、参数和响应格式。
管理后台模块
从路由和页面可以识别出以下管理域:
- 商品与订单:商品、分类、属性、品牌、发货、退货和订单设置;
- 设备运营:设备、代理商、商家、设备发货、库存预警、补货和出袋测试;
- 财务与收益:提现审核、代理分润、用户积分和收益记录;
- 流量业务:广告、群二维码、公众号获客、渠道广告、分享任务、裂变奖励;
- 权限中心:管理员、角色、菜单、资源及资源分类。
这些页面会产生跨领域操作。例如“给代理发设备”同时改变设备归属和库存,“审核提现”同时改变提现单与钱包余额。此类操作需要数据库事务和不可变流水,不能只更新页面展示字段。
核心领域模型
| 领域 | 主要数据对象 | 应保持的不变量 |
|---|---|---|
| 用户与渠道 | 用户、公众号关注、应用、二维码、积分记录 | 同一渠道身份可追溯到平台用户,积分变化都有来源 |
| 代理与商户 | 代理、地址、银行或微信账户、返佣关系 | 代理层级不能形成环,数据范围沿授权层级向下 |
| 设备与库存 | 设备、位置、在线日志、出袋计数、补货与发货记录 | 设备唯一归属,当前袋数可由库存流水核对 |
| 订单与任务 | 订单、DSP 请求、MQTT 命令、回调日志 | 一个业务单只触发一次有效出袋,重试不重复结算 |
| 钱包与结算 | 收益流水、提现单、返佣 | 余额由流水汇总,审核与打款状态分离 |
| 内容与运营 | 广告、文章、分享任务、积分任务 | 任务完成证据与奖励发放记录一一对应 |
鉴权和数据范围
源码中存在三类会话:
- H5 使用
Authorization关联缓存中的用户身份,并同时读取设备上下文; - 小程序管理端使用独立 Token 关联代理身份;
- Web 管理后台使用管理员 Token,再通过角色、菜单和资源表校验接口权限。
会话身份解决“你是谁”,查询范围解决“你能看到谁的数据”。代理端接口还必须从当前代理推导可管理的子代理、商户和设备,不能信任客户端传入的代理编号。
设备二维码是公开入口,只能定位设备,不能直接授予出袋权限。服务端还需要检查设备状态、位置、库存、用户资格和本次订单状态。
数据与缓存
MySQL
数据库保存账号、代理关系、设备档案、订单、库存记录、积分、收益和提现等可审计事实。对于扣减袋数、确认出袋和分配收益,应在一个明确的事务边界内写入,并保存业务单号与设备命令编号。
Redis
Redis 承担登录 Token、短信校验、短期任务状态、锁、用户 IP 或实时设备信息。缓存可以加速查询和防止并发重复处理,但不能成为订单、钱包和库存的唯一数据源。
建议给关键缓存统一前缀、TTL 和失效规则,并将“未命中”与“业务不存在”区分开。订单幂等最好同时依赖数据库唯一约束,而不是只依赖短期缓存锁。
对象存储与日志
OSS 保存图片、二维码和运营素材;日志服务记录第三方回调与设备异常。上传接口需要校验文件类型、大小和对象路径,回调日志必须脱敏,不能记录支付密钥、设备密码、完整 Token 或用户隐私。
设备协议桥接
wanjiadai_express 对外提供出袋、状态查询、广告更换和设备激活等 HTTP 能力,再把业务请求转换为设备厂商协议或 MQTT 消息。设备结果反向通知 notify 入口,由业务 API 更新命令、订单、库存与收益。
桥接层需要承担以下职责:
- 把统一业务命令转换为厂商 Topic 和报文;
- 为每条命令携带唯一
cmdid,并关联原始订单; - 订阅设备结果与在线状态,验证消息来源;
- 对超时、重复消息和无法解析的报文进行记录;
- 重启后恢复订阅,不依赖进程内存保存唯一状态。
源码中还存在多个设备或流量渠道适配逻辑。适配器应该返回统一结果,例如“已受理、明确成功、明确失败、状态未知”,业务层不直接理解各厂商私有字段。
同步请求与异步事实
一次领袋包含两条时间线:
- 同步链路:用户请求、资格校验、订单创建、命令受理;
- 异步链路:MQTT 下发、设备执行、回执、库存确认、收益分配。
同步接口应快速返回任务编号。设备没有及时回执时,订单进入“确认中”,而不是直接判失败。支付回调、广告任务回调和设备回执都可能重复到达,每个处理器必须使用渠道流水号或业务单号保证幂等。
部署与故障域
| 运行单元 | 依赖 | 故障时的表现 | 恢复重点 |
|---|---|---|---|
| 管理后台静态站 | API | 运营页面不可用 | 不影响已创建订单和设备回执 |
| ThinkPHP API | MySQL、Redis、外部渠道 | 无法创建订单或查询状态 | 无状态扩容,先恢复事务写入 |
| Node 设备桥接 | MQTT、厂商接口、回调 API | 命令不能下发或回执延迟 | PM2 守护、健康检查、重订阅 |
| MQTT Broker | 设备与桥接连接 | 设备实时通信中断 | 连接告警、离线补偿、客户端重连 |
| 第三方渠道 | 支付、微信、广告平台 | 资格或支付结果延迟 | 回调重试、主动查询、对账任务 |
| 定时统计任务 | 数据库、Redis | 看板和收益统计延迟 | 加锁运行,可从事实流水重算 |
当前架构债务与改进顺序
- 立即治理密钥:源码中存在固定设备或消息服务参数,应迁移到环境变量或密钥服务并完成轮换;
- 统一设备适配器:将不同厂商的命令、回执和错误码映射到统一协议;
- 建立订单状态机:明确资格确认、命令下发、执行成功、失败、未知和补偿状态;
- 完善库存账本:当前数量与库存流水双轨校验,补货、出袋和人工修正都保留原因;
- 拆出领域服务:逐步减少控制器直接操作模型、缓存和第三方 SDK;
- 增加链路观测:用订单号、
cmdid、设备号和渠道流水串联日志与告警; - 制定依赖升级计划:PHP 7.1 基线、Vue 2 和旧版前端依赖需要独立评估,不与业务重构一次完成。
具体的一次扫码请求如何跨越这些组件,可继续阅读扫码领袋与设备出袋流程。