返回全部文章

从一次预约到一扇门:无人门店系统如何可靠交付

本文目录

做预约系统久了,我越来越觉得:用户路径越短,背后的系统往往越复杂。

在无人门店里,用户只完成几个动作:选场地、选时间、付款、到店开门。看起来是一条很直的链路,但任何一个环节都连接着不同系统:小程序、支付平台、商家后台、预约服务、定时任务,以及真实世界里的门禁设备。

网页上的错误可以刷新,门打不开却是一次真实的服务失败。

这篇文章不讨论某个具体框架,而是整理我在预约与 IoT 项目中反复使用的一套思路:怎样把一条看似简单的用户路径,拆成能够可靠运行的系统。

先定义业务事实,再设计页面

很多项目从页面开始:先画一个日历,再放一个支付按钮,最后补接口。这样做演示很快,但一进入真实运营,问题会集中出现。

更稳妥的起点,是先回答几个业务问题:

  • 哪一个状态代表时段已经被占用?
  • 用户支付成功,但回调尚未到达时,订单是什么状态?
  • 退款成功后,时段何时重新释放?
  • 用户提前到店,设备是否允许开门?
  • 同一条开门指令被重复发送,会发生什么?

这些问题的答案不是 UI 状态,而是领域事实。页面、小程序和设备控制都应该消费同一套事实,而不是分别创造自己的判断。

以预约为例,我通常会把核心事实拆成四组:

  1. 订单事实:待支付、已支付、已取消、退款中、已退款;
  2. 履约事实:未开始、可入场、进行中、已结束;
  3. 资产事实:余额、次卡、优惠券分别如何占用和退回;
  4. 设备事实:设备在线状态、授权时间窗、指令执行结果。

它们彼此有关,但不能混成一个巨大的 status 字段。一个订单已支付,不等于现在允许开门;一次退款已发起,也不等于会员资产已经回滚完成。

状态机比大量 if 更可靠

当业务刚开始时,几个 if 足够解决问题。随着微信、支付宝、抖音和商家后台陆续接入,相同规则会在多个端重复出现,并慢慢产生差异。

我更倾向于把状态变化写成明确的转换:

当前状态事件下一个状态伴随动作
待支付支付成功已支付确认时段、生成设备授权
待支付超时已取消释放临时时段
已支付商家同意退款退款中提交支付平台退款
退款中退款成功已退款退回资产、撤销设备授权

这张表的价值不只是方便开发。产品、客服和工程师可以围绕同一套语言讨论异常:究竟是事件没有发生,还是转换失败,还是伴随动作没有完成。

状态不是给页面显示的标签,而是系统对已经发生之事实的承诺。

把支付回调当成消息,而不是一次请求

支付平台的回调可能延迟、重复,甚至先于前端的“支付成功”页面到达。系统如果假设每个请求只出现一次,线上很快就会出现重复扣减、重复通知和重复发放。

我的处理原则是:

  • 每一种外部事件都有稳定的业务唯一键;
  • 先记录收到的原始事件,再执行状态转换;
  • 状态转换必须具备幂等性;
  • 耗时的副作用交给队列处理;
  • 每个失败动作都可以单独重试。

例如支付成功后,确认订单、发通知、生成门禁授权不是一个不可拆分的大事务。订单确认属于核心状态变化;短信、数据统计和设备同步可以作为后续任务。这样,即使某个设备服务短暂不可用,也不会否定已经发生的支付事实。

设备控制要接受“现实世界不可靠”

纯软件系统里,我们容易把一次 HTTP 200 当作成功。但对门禁设备来说,接口返回成功可能只表示平台接收了指令,不代表门真的打开。

因此设备层需要比普通业务接口多几个概念:

  • 统一设备身份:不同厂商型号映射到内部设备 ID;
  • 统一操作语义:开门、常开、关闭等动作有稳定接口;
  • 请求编号:每次控制都有可追踪、可去重的编号;
  • 结果分层:已接收、已下发、已执行、执行失败;
  • 人工兜底:线上失败时,客服能够看到足够信息并采取替代方案。

接入多种门禁和控制器时,我不希望业务代码知道具体厂商。预约服务只表达“这个用户在这个时间窗拥有开门权限”,适配层再处理每种设备的协议差异。

这层隔离让更换设备供应商不至于侵入订单、会员和小程序代码。

多端一致,不等于多端复制

同一个预约业务可能同时存在于用户微信小程序、商家小程序、支付宝、抖音与 PC 后台。最危险的做法,是让每个端各自实现一遍业务判断。

真正应该复用的不是页面组件,而是:

  • 可用时段的计算规则;
  • 价格、优惠和会员资产的计算结果;
  • 订单允许执行的动作;
  • 面向用户的状态解释;
  • 错误码及恢复建议。

服务端返回的不应只有原始状态,还可以返回当前允许的操作。例如 can_pay、can_cancel、can_open_door。前端仍然负责体验,但不再猜测业务规则。

为运营留下可观察性

系统稳定,不只是“没有报错”。对于交易和设备联动,我至少会保留三条可追踪链路:

  1. 业务链路:用户、订单、支付单、退款单和会员资产;
  2. 任务链路:事件何时进入队列、执行几次、最终结果是什么;
  3. 设备链路:授权、控制指令、厂商响应和设备回报。

当客服说“用户付了钱但门打不开”,工程师应该能从订单出发,在几分钟内定位到具体环节,而不是登录几台服务器翻找没有关联 ID 的日志。

可观察性不是项目完成后的附加工作。它决定了系统出问题时,团队是否有能力恢复服务。

最后

一套无人门店系统真正交付的,不是几个页面,也不是一组接口,而是一条从数字订单延伸到物理空间的承诺。

要兑现这份承诺,需要先定义业务事实,用状态机约束变化,用幂等和异步任务吸收外部不确定性,再通过统一设备层与可观察性应对现实世界。

用户最终只需要看到一件事:预约完成,走到门前,门如期打开。

LET’S BUILD SOMETHING THAT LASTS

持续构建,保持好奇。

如果你也在构建需要长期运行的产品,欢迎聊聊。