Java状态机实战:打造高扩展性的订单流程引擎(含源码详解与快照设计)
在业务开发中,订单、支付、审批等场景往往都涉及“状态变更”这一核心需求。随着流程复杂化,仅靠 if-else 处理状态流转不仅维护困难,还容易出错。因此,我们团队基于实际项目需求,设计并实现了一套可复用的状态机框架。
本文将系统讲解:
-
为什么要写状态机?
-
状态机的业务场景适用性;
-
我们自研状态机模块的核心架构;
-
如何在业务中接入使用;
-
核心源码详解(含快照与缓存机制)。
一、为什么要写状态机?
简单来说,状态机的目标是:让状态变更有据可依、逻辑可控、行为可复用。
✅ 状态机的优势
作用 | 说明 |
---|---|
明确状态流转 | 限定合法状态变化路径,防止“越级跳转” |
避免状态错乱 | 禁止非法状态变更,提高系统健壮性 |
解耦处理逻辑 | 状态控制与业务处理解耦,逻辑更清晰 |
快照审计回溯 | 每次状态变更都可记录快照,实现审计和回滚 |
易于维护扩展 | 统一管理所有状态逻辑,提升可维护性 |
可视化流程 | 状态图可辅助产品/测试理解流程 |
二、什么场景适合用状态机?
以下场景强烈建议引入状态机:
-
📦 订单、支付等具有生命周期控制的业务;
-
🔁 审核、审批、分阶段流程;
-
🔐 需严控状态安全、防止非法状态跳转;
-
🧾 快照记录、状态回溯需求;
-
📈 状态驱动型架构(事件+状态控制);
而对于一些只存在“启用/禁用”这类两状态的小功能模块,状态机反而会增加不必要的复杂度。
三、我们自研状态机模块设计
1. 状态定义接口
public interface StatusDefine {Integer getStatus();String getDesc();String getCode();
}
订单状态示例:
public enum OrderStatusEnum implements StatusDefine {WAIT_PAY(0, "待支付", "WAIT_PAY"),PAID(1, "已支付", "PAID"),DELIVERING(2, "发货中", "DELIVERING"),FINISHED(3, "已完成", "FINISHED");
}
2. 事件定义接口
public interface StatusChangeEvent {StatusDefine getSourceStatus();StatusDefine getTargetStatus();String getDesc();String getCode();
}
订单事件示例:
public enum OrderStatusEventEnum implements StatusChangeEvent {PAY(WAIT_PAY, PAID, "支付成功", "PAY"),DELIVER(PAID, DELIVERING, "发货", "DELIVER"),COMPLETE(DELIVERING, FINISHED, "完成订单", "COMPLETE");
}
3. 快照抽象类
public abstract class StateMachineSnapshot {public abstract String getSnapshotId();public abstract void setSnapshotId(String id);public abstract Integer getSnapshotStatus();public abstract void setSnapshotStatus(Integer status);
}
订单快照示例:
@Data
public class OrderSnapshot extends StateMachineSnapshot {private String snapshotId;private Integer snapshotStatus;private String customerName;private String address;private List<String> itemList;
}
4. 状态处理器(可选)
@Component("order_PAY")
public class PayHandler implements StatusChangeHandler<OrderSnapshot> {public void handler(String bizId, StatusChangeEvent event, OrderSnapshot snapshot) {// 扣库存、发消息等}
}
5. 状态机子类
@Component
public class OrderStateMachine extends AbstractStateMachine<OrderSnapshot> {public OrderStateMachine(StateMachinePersister p, BizSnapshotService s, RedisTemplate r) {super(p, s, r);}protected String getName() { return "order"; }protected StatusDefine getInitState() { return OrderStatusEnum.WAIT_PAY; }protected void postProcessor(OrderSnapshot snapshot) {log.info("订单状态变更为:{}", snapshot.getSnapshotStatus());}
}
四、实际使用示例
@Autowired
private OrderStateMachine orderStateMachine;public void 创建订单(String orderId) {OrderSnapshot snapshot = new OrderSnapshot();snapshot.setSnapshotId(orderId);snapshot.setCustomerName("张三");orderStateMachine.start(orderId, OrderStatusEnum.WAIT_PAY, snapshot);
}public void 支付订单(String orderId) {orderStateMachine.changeStatus(orderId, OrderStatusEventEnum.PAY, new OrderSnapshot());
}
五、为什么要保存业务快照?
状态表示“发生了什么”,快照表示“发生时的详细数据”。
以订单为例,不同状态下的快照记录不同数据:
状态 | 快照信息 |
---|---|
待支付 | 商品列表、价格、地址 |
已支付 | 支付流水号、金额、支付时间 |
发货中 | 快递公司、运单号 |
已完成 | 签收时间、评价信息 |
📌 快照的用途
-
✅ 审计回溯:出问题时可还原状态当时的数据;
-
✅ 支持缓存:提升快照查询性能;
-
✅ 保留历史记录:用于日志、数据分析;
六、源码详解:AbstractStateMachine
AbstractStateMachine<T>
是整个状态机的“大脑”,封装了初始化、变更、缓存、快照存储等功能。
🔁 changeStatus 流程简述:
-
查询当前状态 → 校验合法性;
-
加载事件处理器 → 执行业务逻辑(可选);
-
状态入库 → 快照入库;
-
清除 Redis 缓存;
-
调用
postProcessor()
执行收尾逻辑。
🧠 核心机制亮点:
-
状态合法校验:防止越级跳转;
-
事件处理器动态注入:
order_PAY
命名方式插件化; -
快照深合并机制:保留旧值,叠加新数据;
-
Redis 缓存机制:避免频繁查库。
七、小项目是否适合使用?
情况 | 是否适用 | 理由 |
---|---|---|
状态极少、流程简单 | ❌ 不建议 | if-else 足够 |
状态有多个且需审计快照 | ✅ 推荐 | 状态机更规范 |
未来状态扩展可能性大 | ✅ 推荐 | 易维护扩展 |
✅ 总结
状态机框架适合中大型系统,将状态流转逻辑结构化、业务处理解耦、数据可回溯,是复杂系统必备的技术选项之一。
欢迎点赞、收藏、评论交流你在项目中的状态管理经验!