news 2026/10/10 4:44:23

用工订单状态机:从待确认到已完成,我拿 6 个状态挡住了所有非法跳变

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用工订单状态机:从待确认到已完成,我拿 6 个状态挡住了所有非法跳变

零工平台的工单,生命周期比想象中长:用户提交申请 → 雇主确认 → 开工打卡 → 完工结算 → 双方评价 → 完成。早期版本里状态就是个字符串字段,service 里随手order.setOrderStatus("WORKING"),改着改着就乱了——有人从"待确认"直接改成"已完成",支付回调还没到,订单先跳到终态了,钱和状态对不上,客服天天来要人。

后来我把状态迁移收敛成一张"合法跳转表":OrderStatusMachine。凡是没登记的迁移,直接抛异常拒绝。这篇把设计思路和踩过的坑讲清楚。

六个状态,一张迁移表

状态常量在BizConstants里定义:WAIT_ENSURE(待确认)→ WAIT_START(待开工)→ WORKING(工作中)→ WAIT_PAY(待结算)→ WAIT_COMMENT(待评价)→ FINISH(已完成),外加 CANCEL(已取消)作为分支出口。

状态机核心就一个静态表,从哪个状态能到哪些状态,写死:

publicfinalclassOrderStatusMachine{privatestaticfinalMap>ALLOWED=newHashMap<>();static{// 待确认 → 待开工 / 取消allow(BizConstants.ORDER_STATUS_WAIT_ENSURE,BizConstants.ORDER_STATUS_WAIT_START,BizConstants.ORDER_STATUS_CANCEL);// 待开工 → 工作中 / 取消allow(BizConstants.ORDER_STATUS_WAIT_START,BizConstants.ORDER_STATUS_WORKING,BizConstants.ORDER_STATUS_CANCEL);// 工作中 → 待结算allow(BizConstants.ORDER_STATUS_WORKING,BizConstants.ORDER_STATUS_WAIT_PAY);// 待结算 → 待评价(支付回调)allow(BizConstants.ORDER_STATUS_WAIT_PAY,BizConstants.ORDER_STATUS_WAIT_COMMENT);// 待评价 → 已完成(仅评价双方完成后由评价服务迁移)allow(BizConstants.ORDER_STATUS_WAIT_COMMENT,BizConstants.ORDER_STATUS_FINISH);}publicstaticvoidassertTransition(StringfromStatus,StringtoStatus){if(fromStatus==null||toStatus==null){thrownewJeecgBootException("订单状态参数无效");}if(fromStatus.equals(toStatus)){thrownewJeecgBootException("订单状态未变化");}Setnext=ALLOWED.get(fromStatus);if(next==null||!next.contains(toStatus)){thrownewJeecgBootException("订单状态不允许从["+statusLabel(fromStatus)+"]变更为["+statusLabel(toStatus)+"]");}}publicstaticbooleanisTerminal(Stringstatus){returnBizConstants.ORDER_STATUS_FINISH.equals(status)||BizConstants.ORDER_STATUS_CANCEL.equals(status);}}

注意"待评价 → 已完成"这一条:注释里写了"仅评价服务迁移",意思是这段迁移不在通用的改状态接口里开放,谁都不许碰。

通用改状态接口:该挡的全挡住

对外有一个updateOrderStatus(id, orderStatus, imgs),它先做三道拦截:

// 1. 终态不可再变if(OrderStatusMachine.isTerminal(order.getOrderStatus())){throwBizException.of(BizErrorCodes.ORDER_STATUS_INVALID,"订单已结束,无法变更状态");}// 2. 高级状态必须走专用接口:打卡、结算、评价if(BizConstants.ORDER_STATUS_WORKING.equals(orderStatus)||BizConstants.ORDER_STATUS_WAIT_PAY.equals(orderStatus)||BizConstants.ORDER_STATUS_WAIT_COMMENT.equals(orderStatus)||BizConstants.ORDER_STATUS_FINISH.equals(orderStatus)){throwBizException.of(BizErrorCodes.ORDER_STATUS_INVALID,"该状态请使用打卡/结算/评价专用接口");}// 3. 状态机校验合法迁移OrderStatusMachine.assertTransition(order.getOrderStatus(),orderStatus);

第二道拦截是精髓。打卡、结算、评价这些高价值状态变更,必须走各自专用接口,里面有完整的业务校验(打卡要传图片、结算要核对金额),防止有人拿通用接口绕过业务逻辑直接改状态。

支付回调里迁移状态,还要记日志

工资结算后,微信支付回调触发WAIT_PAY → WAIT_COMMENT,迁移后立刻写一条订单日志,把状态流转轨迹留下来:

publicbooleanpaySalarySuccess(StringorderSn,BigDecimalpaidAmount){JobOrderorder=queryByOrderSn(orderSn);if(order==null||!BizConstants.ORDER_STATUS_WAIT_PAY.equals(order.getOrderStatus())){throwBizException.of(BizErrorCodes.ORDER_STATUS_INVALID,"订单非待结算状态,支付回调被忽略");}OrderStatusMachine.assertTransition(order.getOrderStatus(),BizConstants.ORDER_STATUS_WAIT_COMMENT);// 条件更新,防止重复回调重复迁移booleanupdated=updateLambda().eq("id",order.getId()).eq("order_status",BizConstants.ORDER_STATUS_WAIT_PAY)// 乐观锁:status 也参与条件.set("order_status",BizConstants.ORDER_STATUS_WAIT_COMMENT).set("pay_money",paidAmount).update();if(updated){orderLogService.addOrderLog(BizConstants.ORDER_STATUS_WAIT_COMMENT,order.getId(),null,null);}returnupdated;}

order_status同时出现在 WHERE 条件里,这就是最朴素的乐观锁——两个支付回调并发进来,只有一个能 update 成功,另一个 updated=false 直接丢弃,天然防重复迁移。

踩坑记录:重复回调把订单状态改穿了

现象:线上出现一笔订单,支付回调触发后订单直接到了"已完成",中间的"待评价"状态没出现过,评价表里也没有任何记录。

排查过程:查job_order_log,只有两条记录:待结算→待评价、然后直接就出现一条"已完成"。按代码逻辑,待评价→已完成只能由评价服务迁移,评价都没做怎么会完成?继续追日志,发现同一个 orderSn 的支付回调进来了两次——第一次成功把状态改成待评价,第二次回调进来时,旧代码里没有状态校验,直接又执行了评价服务的一个内部方法把状态顶到了已完成。

定位思路:问题出在"迁移入口太多"。评价服务内部为了省事,自己写了一段"如果状态是待支付就顺手改成已完成"的兼容逻辑,绕过了状态机,也绕过了日志。状态迁移没有统一收口到状态机 + 日志,谁都能偷偷改。

最终解决:两条硬规矩。第一,支付回调迁移必须带order_status乐观锁条件,重复回调只能有一次生效。第二,全项目 grep 掉所有绕过assertTransition的裸 setOrderStatus 调用,评价服务改成走专用接口,内部兼容代码删干净。从此订单日志完整,状态再没穿过。

可直接复用的清单

  • 状态迁移表 +assertTransition统一收口,非法迁移直接抛异常,别让 service 各自判断。
  • 高价值状态(工作中/待结算/待评价/已完成)禁用通用改状态接口,强制走打卡/结算/评价专用接口。
  • 回调类迁移用order_status参与 WHERE 做乐观锁,重复回调天然幂等。
  • 每次合法迁移写订单日志,状态轨迹可审计,排查"状态怎么变的"不用猜。
  • 终态(FINISH/CANCEL)一律不可再迁移,接口入口先挡。

项目源码:https://gitee.com/gzqkl/xllg

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 4:43:41

中断机制详解:从硬件触发到Linux内核处理的全链路解析

1. 中断不是“打断”&#xff0c;而是操作系统最精密的呼吸节奏你有没有想过&#xff0c;为什么键盘按下一个键&#xff0c;屏幕几乎立刻就出现字符&#xff1f;为什么鼠标轻轻一划&#xff0c;光标就能丝滑跟上&#xff1f;为什么后台正在压缩大文件&#xff0c;前台还能流畅播…

作者头像 李华
网站建设 2026/10/10 4:43:31

旧安卓手机微信一次只能发一个文件?试试这些批量传输方案

1. 问题背景&#xff1a;一次卡在“选文件”环节的日常崩溃先说说我自己的情况。手里有一台前几年买的真我手机&#xff0c;系统一直没怎么升级&#xff0c;安卓版本还停留在比较早的时代。平时用微信跟人传文件&#xff0c;照片、视频这些倒还好&#xff0c;直接从相册里选&am…

作者头像 李华
网站建设 2026/10/10 4:43:29

主从博弈下的共享储能与综合能源微网双层优化运行

第一次接触基于主从博弈的共享储能与综合能源微网优化运行问题&#xff0c;是在模拟项目X里。当时某课题组要从零搭建一个微网群的仿真环境&#xff0c;我拿到手的第一份资料里&#xff0c;共享储能站被设计成“第三方运营商”&#xff0c;综合能源微网则是买电、买气、买热服务…

作者头像 李华
网站建设 2026/10/10 4:42:20

WorkBuddy Skill实战指南:从工作任务到可复用AI能力单元

1. 项目本质解构&#xff1a;这不是一场普通征文&#xff0c;而是一次AI办公能力的“压力测试”WorkBuddy 这个名字在最近三个月里&#xff0c;已经从腾讯内部的一个实验性工具&#xff0c;悄然演变成国内AI办公领域一个绕不开的坐标。它不是另一个聊天窗口&#xff0c;也不是简…

作者头像 李华