从一次预约到一扇门:无人门店系统如何可靠交付
本文目录
做预约系统久了,我越来越觉得:用户路径越短,背后的系统往往越复杂。
在无人门店里,用户只完成几个动作:选场地、选时间、付款、到店开门。看起来是一条很直的链路,但任何一个环节都连接着不同系统:小程序、支付平台、商家后台、预约服务、定时任务,以及真实世界里的门禁设备。
网页上的错误可以刷新,门打不开却是一次真实的服务失败。
这篇文章不讨论某个具体框架,而是整理我在预约与 IoT 项目中反复使用的一套思路:怎样把一条看似简单的用户路径,拆成能够可靠运行的系统。
先定义业务事实,再设计页面
很多项目从页面开始:先画一个日历,再放一个支付按钮,最后补接口。这样做演示很快,但一进入真实运营,问题会集中出现。
更稳妥的起点,是先回答几个业务问题:
- 哪一个状态代表时段已经被占用?
- 用户支付成功,但回调尚未到达时,订单是什么状态?
- 退款成功后,时段何时重新释放?
- 用户提前到店,设备是否允许开门?
- 同一条开门指令被重复发送,会发生什么?
这些问题的答案不是 UI 状态,而是领域事实。页面、小程序和设备控制都应该消费同一套事实,而不是分别创造自己的判断。
以预约为例,我通常会把核心事实拆成四组:
- 订单事实:待支付、已支付、已取消、退款中、已退款;
- 履约事实:未开始、可入场、进行中、已结束;
- 资产事实:余额、次卡、优惠券分别如何占用和退回;
- 设备事实:设备在线状态、授权时间窗、指令执行结果。
它们彼此有关,但不能混成一个巨大的 status 字段。一个订单已支付,不等于现在允许开门;一次退款已发起,也不等于会员资产已经回滚完成。
状态机比大量 if 更可靠
当业务刚开始时,几个 if 足够解决问题。随着微信、支付宝、抖音和商家后台陆续接入,相同规则会在多个端重复出现,并慢慢产生差异。
我更倾向于把状态变化写成明确的转换:
| 当前状态 | 事件 | 下一个状态 | 伴随动作 |
|---|---|---|---|
| 待支付 | 支付成功 | 已支付 | 确认时段、生成设备授权 |
| 待支付 | 超时 | 已取消 | 释放临时时段 |
| 已支付 | 商家同意退款 | 退款中 | 提交支付平台退款 |
| 退款中 | 退款成功 | 已退款 | 退回资产、撤销设备授权 |
这张表的价值不只是方便开发。产品、客服和工程师可以围绕同一套语言讨论异常:究竟是事件没有发生,还是转换失败,还是伴随动作没有完成。
状态不是给页面显示的标签,而是系统对已经发生之事实的承诺。
把支付回调当成消息,而不是一次请求
支付平台的回调可能延迟、重复,甚至先于前端的“支付成功”页面到达。系统如果假设每个请求只出现一次,线上很快就会出现重复扣减、重复通知和重复发放。
我的处理原则是:
- 每一种外部事件都有稳定的业务唯一键;
- 先记录收到的原始事件,再执行状态转换;
- 状态转换必须具备幂等性;
- 耗时的副作用交给队列处理;
- 每个失败动作都可以单独重试。
例如支付成功后,确认订单、发通知、生成门禁授权不是一个不可拆分的大事务。订单确认属于核心状态变化;短信、数据统计和设备同步可以作为后续任务。这样,即使某个设备服务短暂不可用,也不会否定已经发生的支付事实。
设备控制要接受“现实世界不可靠”
纯软件系统里,我们容易把一次 HTTP 200 当作成功。但对门禁设备来说,接口返回成功可能只表示平台接收了指令,不代表门真的打开。
因此设备层需要比普通业务接口多几个概念:
- 统一设备身份:不同厂商型号映射到内部设备 ID;
- 统一操作语义:开门、常开、关闭等动作有稳定接口;
- 请求编号:每次控制都有可追踪、可去重的编号;
- 结果分层:已接收、已下发、已执行、执行失败;
- 人工兜底:线上失败时,客服能够看到足够信息并采取替代方案。
接入多种门禁和控制器时,我不希望业务代码知道具体厂商。预约服务只表达“这个用户在这个时间窗拥有开门权限”,适配层再处理每种设备的协议差异。
这层隔离让更换设备供应商不至于侵入订单、会员和小程序代码。
多端一致,不等于多端复制
同一个预约业务可能同时存在于用户微信小程序、商家小程序、支付宝、抖音与 PC 后台。最危险的做法,是让每个端各自实现一遍业务判断。
真正应该复用的不是页面组件,而是:
- 可用时段的计算规则;
- 价格、优惠和会员资产的计算结果;
- 订单允许执行的动作;
- 面向用户的状态解释;
- 错误码及恢复建议。
服务端返回的不应只有原始状态,还可以返回当前允许的操作。例如 can_pay、can_cancel、can_open_door。前端仍然负责体验,但不再猜测业务规则。
为运营留下可观察性
系统稳定,不只是“没有报错”。对于交易和设备联动,我至少会保留三条可追踪链路:
- 业务链路:用户、订单、支付单、退款单和会员资产;
- 任务链路:事件何时进入队列、执行几次、最终结果是什么;
- 设备链路:授权、控制指令、厂商响应和设备回报。
当客服说“用户付了钱但门打不开”,工程师应该能从订单出发,在几分钟内定位到具体环节,而不是登录几台服务器翻找没有关联 ID 的日志。
可观察性不是项目完成后的附加工作。它决定了系统出问题时,团队是否有能力恢复服务。
最后
一套无人门店系统真正交付的,不是几个页面,也不是一组接口,而是一条从数字订单延伸到物理空间的承诺。
要兑现这份承诺,需要先定义业务事实,用状态机约束变化,用幂等和异步任务吸收外部不确定性,再通过统一设备层与可观察性应对现实世界。
用户最终只需要看到一件事:预约完成,走到门前,门如期打开。