当前位置: 首页 > news >正文

订单状态机实战:代码校验 + SQL 幂等一次讲清

这篇不是“先写 SQL 再补代码”,而是从设计层面把代码层状态机SQL 幂等更新绑定在一起。

状态流转(业务真实模型)

UNPAID -> PAID -> SHIPPED -> COMPLETED UNPAID -> CANCELED PAID -> REFUNDING -> REFUNDED SHIPPED-> REFUNDING -> REFUNDED

核心目标:

  1. 代码层禁止非法流转
  2. SQL 层保证幂等与并发安全

一、代码层状态机(先校验)

publicenumOrderStatus{// 未支付:只能去 PAID / CANCELEDUNPAID(EnumSet.of(PAID,CANCELED)),// 已支付:只能去 SHIPPED / REFUNDINGPAID(EnumSet.of(SHIPPED,REFUNDING)),// 已发货:只能去 COMPLETED / REFUNDINGSHIPPED(EnumSet.of(COMPLETED,REFUNDING)),// 退款中:只能去 REFUNDEDREFUNDING(EnumSet.of(REFUNDED)),// 终态COMPLETED(EnumSet.noneOf(OrderStatus.class)),CANCELED(EnumSet.noneOf(OrderStatus.class)),REFUNDED(EnumSet.noneOf(OrderStatus.class));// 允许的下一个状态集合privatefinalEnumSet<OrderStatus>next;OrderStatus(EnumSet<OrderStatus>next){this.next=next;}// 是否允许流转到目标状态publicbooleancanTransferTo(OrderStatusto){returnnext.contains(to);}// 断言:不允许就抛异常publicvoidassertCanTransferTo(OrderStatusto){if(!canTransferTo(to)){thrownewIllegalStateException("状态不允许流转: "+this+" -> "+to);}}}

代码层解决的是:
不允许“跳状态”(例如未支付直接发货)。


二、SQL 层幂等更新(落库安全)

示例:支付成功

UPDATEordersSETstatus='PAID',pay_time=NOW(),version=version+1WHEREid=?ANDstatus='UNPAID'ANDversion=?;

关键点:

  • status = 'UNPAID'防止乱序
  • version = ?防止并发覆盖
  • 更新行数为 0 ⇒ 被并发抢先处理或已完成

三、把两者真正结合起来

下面是一个“完整可运行”的服务层写法:

publicbooleanpayOrder(LongorderId,LongexpectedVersion){// 1) 读当前状态Orderorder=orderMapper.selectById(orderId);if(order==null){thrownewIllegalArgumentException("订单不存在");}// 2) 代码层状态机校验order.getStatus().assertCanTransferTo(OrderStatus.PAID);// 3) SQL 层幂等更新introws=orderMapper.updatePaid(orderId,expectedVersion);returnrows==1;}

对应 SQL(MyBatis / JPA 都适用):

UPDATEordersSETstatus='PAID',pay_time=NOW(),version=version+1WHEREid=?ANDstatus='UNPAID'ANDversion=?;

这才是真正意义的“状态机 + SQL”结合:

  • 代码层先挡住非法流转
  • SQL 层再挡住并发与幂等问题
  • 任意一层失败,流程终止

四、其它状态流转写法(可直接复用)

取消订单(未支付才可取消)

UPDATEordersSETstatus='CANCELED',cancel_time=NOW(),version=version+1WHEREid=?ANDstatus='UNPAID'ANDversion=?;

发货(已支付才可发货)

UPDATEordersSETstatus='SHIPPED',ship_time=NOW(),version=version+1WHEREid=?ANDstatus='PAID'ANDversion=?;

确认收货(已发货才可完成)

UPDATEordersSETstatus='COMPLETED',finish_time=NOW(),version=version+1WHEREid=?ANDstatus='SHIPPED'ANDversion=?;

申请退款(已支付或已发货可申请)

UPDATEordersSETstatus='REFUNDING',refund_apply_time=NOW(),version=version+1WHEREid=?ANDstatusIN('PAID','SHIPPED')ANDversion=?;

退款完成(退款中才可完成)

UPDATEordersSETstatus='REFUNDED',refund_time=NOW(),version=version+1WHEREid=?ANDstatus='REFUNDING'ANDversion=?;

五、经验总结(真正在项目里好用)

  1. 状态校验写在代码层,SQL 负责幂等与并发
  2. 所有更新都必须带“当前状态”条件
  3. 高并发场景必须带version或乐观锁

做到这三条,订单状态机会非常稳。

http://www.jsqmd.com/news/599336/

相关文章:

  • COMSOL超声相控阵仿真模型 模型介绍:本链接有两个模型,分别使用压力声学与固体力学对超声相...
  • 别再只认CRC了!聊聊FNV、Adler-32这些‘轻量级’哈希在Go项目里的实战选型
  • 编写程序实现钓鱼浮标刻度雕刻,防水不褪色,输出钓友精准看口,实用刚需。
  • 如何使用AICoverGen开源工具制作专业级AI翻唱歌曲
  • 微穿孔板吸声体设计避坑指南:Comsol优化模块的7种求解器怎么选?
  • seo中文网站如何应对算法更新
  • 扩展版进销存软件V1.3发布:集成BOM物料清单的多用户生产管理ERP系统
  • Windows服务器疯狂风扇报警?手把手教你排查计划任务中的隐藏挖矿病毒
  • 设计键盘键帽个性替换件,精准适配,输出,客制化键盘低成本平替。
  • 从Rocky Linux迁移到openEuler:我的K8s集群部署体验与配置差异全记录
  • 多智能体协作开发从入门到精通:Claude Teams完整攻略,收藏这篇就够了!
  • 施耐德M218与触摸屏通讯实战:从硬件连接到SoMachine配置(含Modbus-RTU避坑指南)
  • AtCoder Beginner Contest 433
  • 新手必看:从BUUCTF的[极客大挑战]入门SQL注入与代码审计(附PHPStudy环境搭建)
  • 晶体材料属性预测新范式:零基础掌握CGCNN晶体图卷积神经网络全流程
  • 微服务架构中的服务网格实践:构建更可靠的分布式系统
  • MindIE与vLLM框架深度集成实践指南
  • DotTrace 托管内存泄漏、CPU爆高、非托管内存泄漏
  • 从BSS138到SI2302:盘点那些年我们用过的SOT23 MOSFET及它们的‘平替’方案
  • Java 反应式编程最佳实践:构建响应式系统
  • Vue3 使用 Store 的注意事项:官方推荐的方式始终是在 setup 或 composable 函数内部调用 useStore()
  • 2025 ICPC 上海市大学生程序设计竞赛 个人补题笔记(正在补题中)
  • 第10章 Mosquitto桥接模式
  • 云原生应用的可观测性最佳实践
  • 别只盯着信号满格:手把手教你用IQview/nxn实测WiFi 2.4GHz的EVM与频谱平坦度
  • Spring Security 2026 最佳实践:构建安全的 Java 应用
  • 『NAS』在飞牛部署PDF全能工具-StirlingPDF
  • AI赋能分析:让快马平台自动完成数据探索与销售预测建模
  • 深度掌握NVIDIA显卡性能调优:5个实战技巧与进阶配置指南
  • MATLAB语音识别 matlab语音识别,可以识别数字0-9,有gui界面,注释齐全,有报告