导读:一个看似简单的"停车预约",背后藏着怎样精妙的状态机设计?本文带你深入「共享车位」的订单系统,看7个状态、3条分支、2个定时任务如何织就一张滴水不漏的订单闭环。
本文结合实际项目设计进行拆解,建议先看看设计成果《车位共享预约云平台》,再阅读本文。
一、状态总览:从0到6的数字密码
在 ZnOrderEntity 中,订单状态 Status 字段取值0~6,对应7个业务阶段:
0-已取消 ← 1-待支付 → 2-已支付 → 3-停车中 → 4-已离场 → 5-待退款 → 6-已完成
它不是简单的线性流动——从"4-已离场"开始,会根据费用情况分裂为三条路径。这正是整个设计的精妙所在。
二、正向流程:从预约到完成
1-待支付 → 2-已支付
用户在小程序端选择车牌、车位、时间后提交订单,系统做三件事:
- 碰撞检测:检查该时段车位是否已被占用(带缓冲期)
- 物联锁定:在
ZnParkingSpaceUsed表中插入占用记录 - 自动记忆:若车牌从未使用过,自动保存到
ZnVehicle
// 创建订单 + 锁定车位 + 自动记忆车牌 — 一个事务搞定
await currentRepository.AddAsync(order, autoSaveChange);
await parkingSpaceUsedRepository.AddAsync(usedRecord, autoSaveChange);
if (!flag) await vehicleRepository.AddAsync(vehicle, autoSaveChange);
status = await unitOfWork.SaveChangesAsync() > 0;
随后用户调用微信支付,支付成功回调后订单进入 2-已支付。
2-已支付 → 3-停车中
用户到达车位,点击"打开地锁":
- 前端判断:预约开始时间前30分钟内才可开锁
- 后端调用 IoT 云平台指令,控制地锁下降
- 订单记录入场时间
ST = DateTime.Now,状态变为 3-停车中 - 计费开始计时
3-停车中 → 4-已离场
IoT 地锁检测到车辆离开后,上报关锁事件,系统触发 Leave 方法——这是整个系统最核心的逻辑。
三、三条分支:离场时的分水岭
离场时,系统根据"实付金额 vs 预缴金额"的差额走三条不同的路:
var debtAmount = amount - order.PreAmount;
// 分支A:刚好结清 (debtAmount == 0)
order.Status = 6; // 直接完成
// 分支B:预缴多了 (debtAmount < 0)
创建退款记录 → order.Status = 5 // 待退款
// 分支C:停车超时 (debtAmount > 0)
创建欠费记录 → order.Status = 6 // 已完成,欠费待补缴
为什么欠费了也能直接完成? 因为系统允许"先离场后补缴"——欠费记录会推送到用户的账户中,下次使用时提醒补缴,而不是锁在原地不让走。这种设计兼顾了用户体验和资金安全。
四、异常兜底:两个定时任务
真实场景中,用户可能预约了却不支付,或者支付了却不到场。系统通过两个 Quartz 定时任务兜底:
AutoCancel — 自动取消超时未支付
触发条件:订单创建时间 + Delay(15分钟) <= 当前时间
且 状态 == 1(待支付)
处理逻辑:订单置为0(已取消),释放车位,全额退款
AutoExpire — 自动过期未入场
触发条件:预约开始时间 + Delay(15分钟) <= 当前时间
且 状态 == 2(已支付) 且 未入场(入场时间为空)
处理逻辑:扣除违约金(预缴×Deduction比例),退还剩余金额,释放车位
这两个定时任务就像是系统的"安全员",在用户"掉链子"的时候自动补位,确保车位资源不会因为僵尸订单而被浪费。
五、设计精髓
- 单向不可逆:状态只向前走,取消/过期也是独立的终态,不走回头路
- 每次操作留痕:每个状态变更都追加
Desc日志,[{旧状态}] -> [{新状态}],可追溯 - 事务一致性:订单变更、资金处理、车位释放都在同一个
UnitOfWork中完成 - 边界明确:15分钟超时阈值、缓冲时间、双倍计费等参数全部可配置,不硬编码
一个订单状态机,串联了用户操作、IoT设备、支付系统和定时任务,背后是严谨的业务建模和对真实停车场景的深刻理解。
(未完待续)有疑问或者想讨论的点,留言区见。