
在业务开发中状态流转是非常常见的场景比如订单、工单、物流单如果直接用if-else硬编码处理状态变更会导致代码臃肿、扩展性差。本文将教你实现一个通用的状态机引擎并结合订单场景演示如何使用让状态流转逻辑更清晰、更易维护。一、状态机核心概念先明确状态机的核心要素理解这些概念后再写代码会更清晰概念说明状态State业务对象的当前状态比如订单的「待支付」「已支付」「已取消」事件Event触发状态变更的动作比如订单的「支付」「取消」「发货」迁移Transition「当前状态 事件 → 目标状态」的规则比如「待支付 支付 → 已支付」守卫Guard可选的迁移条件比如「只有未超时的订单才能取消」动作Action迁移成功后执行的逻辑比如「支付成功后扣减库存」二、通用状态机引擎实现我们先实现一个不依赖任何框架的通用状态机核心后续可直接复用在任意业务场景。2.1 迁移规则定义Transition首先定义「状态迁移」的模型包含基础的状态、事件以及可选的守卫和动作packagecom.example.demo.statemachine;importjava.util.function.Supplier;/** * 状态迁移定义当前状态 事件 - 目标状态 * 可选守卫条件 guard是否允许迁移、迁移时执行的动作 action * * param S 状态类型建议用枚举 * param E 事件类型建议用枚举 */publicrecordTransitionS,E(Sfrom,// 源状态Eevent,// 触发事件Sto,// 目标状态SupplierBooleanguard,// 守卫条件null表示无条件Runnableaction// 迁移动作null表示无动作){// 简化构造器无守卫、无动作publicTransition(Sfrom,Eevent,Sto){this(from,event,to,null,null);}// 简化构造器无守卫、有动作publicTransition(Sfrom,Eevent,Sto,Runnableaction){this(from,event,to,null,action);}/** 是否允许迁移无 guard 或 guard 返回 true */publicbooleanallowed(){returnguardnull||Boolean.TRUE.equals(guard.get());}/** 执行迁移动作若有 */publicvoidrunAction(){if(action!null)action.run();}}2.2 状态机核心引擎StateMachine实现状态机的核心逻辑注册迁移规则、触发事件、校验迁移合法性packagecom.example.demo.statemachine;importjava.util.*;/** * 通用状态机引擎 * 支持注册迁移规则、触发事件、校验迁移合法性 * * param S 状态枚举/类型 * param E 事件枚举/类型 */publicclassStateMachineS,E{/** 迁移规则存储key源状态事件value迁移规则 */privatefinalMapString,TransitionS,EtransitionsnewHashMap();/** 所有已注册的状态 */privatefinalSetSstatesnewHashSet();/** 所有已注册的事件 */privatefinalSetEeventsnewHashSet();/** 注册一条迁移规则 */publicStateMachineS,EaddTransition(TransitionS,Et){states.add(t.from());states.add(t.to());events.add(t.event());Stringkeykey(t.from(),t.event());transitions.put(key,t);returnthis;}/** 便捷方法仅注册「源状态事件→目标状态」无守卫/动作 */publicStateMachineS,EaddTransition(Sfrom,Eevent,Sto){returnaddTransition(newTransition(from,event,to));}/** 便捷方法注册「源状态事件→目标状态」 迁移动作无守卫 */publicStateMachineS,EaddTransition(Sfrom,Eevent,Sto,Runnableaction){returnaddTransition(newTransition(from,event,to,action));}/** 生成迁移规则的唯一key源状态:事件 */privatestaticStringkey(Objectfrom,Objectevent){returnfrom:event;}/** * 触发事件执行状态迁移 * param currentState 当前状态 * param event 触发的事件 * return 迁移后的目标状态null表示非法迁移 */publicSfire(ScurrentState,Eevent){TransitionS,Ettransitions.get(key(currentState,event));if(tnull||!t.allowed())returnnull;t.runAction();// 执行迁移动作returnt.to();}/** 校验当前状态下触发指定事件是否合法 */publicbooleancanFire(ScurrentState,Eevent){TransitionS,Ettransitions.get(key(currentState,event));returnt!nullt.allowed();}/** 获取目标状态仅查询不执行迁移/动作 */publicSgetTargetState(ScurrentState,Eevent){TransitionS,Ettransitions.get(key(currentState,event));returntnull||!t.allowed()?null:t.to();}// 辅助方法获取所有已注册的状态/事件publicSetSgetStates(){returnCollections.unmodifiableSet(states);}publicSetEgetEvents(){returnCollections.unmodifiableSet(events);}}三、订单场景实战基于上面的通用状态机我们以「订单状态流转」为例完整演示如何落地。3.1 定义订单状态和事件枚举先定义订单的核心状态和触发事件packagecom.example.demo.statemachine.order;/** * 订单状态枚举 */publicenumOrderState{UNPAID(待支付),PAID(已支付),SHIPPED(已发货),COMPLETED(已完成),CANCELLED(已取消);privatefinalStringdesc;OrderState(Stringdesc){this.descdesc;}}packagecom.example.demo.statemachine.order;/** * 订单事件枚举触发状态变更的动作 */publicenumOrderEvent{PAY(支付),SHIP(发货),CONFIRM(确认收货),CANCEL(取消);privatefinalStringdesc;OrderEvent(Stringdesc){this.descdesc;}}3.2 订单实体类定义订单的基础模型包含核心的状态字段packagecom.example.demo.statemachine.order;importlombok.AllArgsConstructor;importlombok.Builder;importlombok.Data;importlombok.NoArgsConstructor;/** * 订单实体示例简化版 */DataBuilderNoArgsConstructorAllArgsConstructorpublicclassOrder{privateLongid;// 订单IDprivateStringsn;// 订单编号privateOrderStatestate;// 订单状态/** 创建订单默认初始状态待支付 */publicstaticOrdercreate(Stringsn){returnOrder.builder().sn(sn).state(OrderState.UNPAID).build();}}3.3 配置订单状态机规则通过配置类定义订单的合法状态迁移规则比如「待支付→支付→已支付」packagecom.example.demo.statemachine.order;importcom.example.demo.statemachine.StateMachine;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importstaticcom.example.demo.statemachine.order.OrderEvent.*;importstaticcom.example.demo.statemachine.order.OrderState.*;/** * 订单状态机配置定义所有合法的状态迁移规则 * 迁移规则 * UNPAID --PAY-- PAID * UNPAID --CANCEL-- CANCELLED * PAID --SHIP-- SHIPPED * PAID --CANCEL-- CANCELLED * SHIPPED --CONFIRM-- COMPLETED */ConfigurationpublicclassOrderStateMachineConfig{BeanpublicStateMachineOrderState,OrderEventorderStateMachine(){StateMachineOrderState,OrderEventsmnewStateMachine();// 注册迁移规则sm.addTransition(UNPAID,PAY,PAID);sm.addTransition(UNPAID,CANCEL,CANCELLED);sm.addTransition(PAID,SHIP,SHIPPED);sm.addTransition(PAID,CANCEL,CANCELLED);sm.addTransition(SHIPPED,CONFIRM,COMPLETED);returnsm;}}3.4 订单状态服务业务层封装订单状态机的使用逻辑对外提供创建订单、触发事件、查询状态等接口packagecom.example.demo.statemachine.order;importcom.example.demo.statemachine.StateMachine;importorg.springframework.stereotype.Service;importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;importjava.util.concurrent.atomic.AtomicLong;/** * 订单状态服务封装状态机的使用对外提供业务接口 */ServicepublicclassOrderStateService{privatefinalStateMachineOrderState,OrderEventstateMachine;/** 模拟订单存储实际替换为数据库 */privatefinalMapLong,OrderordersnewConcurrentHashMap();/** 订单ID生成器 */privatefinalAtomicLongidGennewAtomicLong(1);// 注入订单状态机publicOrderStateService(StateMachineOrderState,OrderEventstateMachine){this.stateMachinestateMachine;}/** 创建订单初始状态待支付 */publicOrdercreate(Stringsn){OrderorderOrder.create(sn);order.setId(idGen.getAndIncrement());orders.put(order.getId(),order);returnorder;}/** * 触发订单事件执行状态迁移 * param orderId 订单ID * param event 触发的事件 * return 迁移后的新状态 * throws IllegalArgumentException 订单不存在 * throws IllegalStateException 非法状态迁移 */publicOrderStatefireEvent(LongorderId,OrderEventevent){Orderorderorders.get(orderId);if(ordernull)thrownewIllegalArgumentException(订单不存在: orderId);OrderStatecurrentorder.getState();OrderStatenextstateMachine.fire(current,event);if(nextnull){thrownewIllegalStateException(非法操作: 当前状态 current 无法响应事件 event);}order.setState(next);returnnext;}/** 查询订单当前状态 */publicOrderStategetState(LongorderId){Orderorderorders.get(orderId);if(ordernull)thrownewIllegalArgumentException(订单不存在: orderId);returnorder.getState();}/** 校验订单当前状态能否执行指定事件 */publicbooleancanFire(LongorderId,OrderEventevent){Orderorderorders.get(orderId);if(ordernull)returnfalse;returnstateMachine.canFire(order.getState(),event);}/** 获取订单详情 */publicOrdergetOrder(LongorderId){returnorders.get(orderId);}}四、测试验证我们可以写一个简单的测试类验证状态机的核心逻辑packagecom.example.demo.statemachine.order;importorg.junit.jupiter.api.Test;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;importstaticorg.junit.jupiter.api.Assertions.*;SpringBootTestpublicclassOrderStateServiceTest{AutowiredprivateOrderStateServiceorderStateService;TestpublicvoidtestOrderStateFlow(){// 1. 创建订单初始状态UNPAIDOrderorderorderStateService.create(TEST_20240501_001);LongorderIdorder.getId();assertEquals(OrderState.UNPAID,orderStateService.getState(orderId));// 2. 触发「支付」事件UNPAID → PAIDOrderStateafterPayorderStateService.fireEvent(orderId,OrderEvent.PAY);assertEquals(OrderState.PAID,afterPay);// 3. 校验PAID状态下不能直接「确认收货」非法迁移assertThrows(IllegalStateException.class,()-{orderStateService.fireEvent(orderId,OrderEvent.CONFIRM);});// 4. 触发「发货」事件PAID → SHIPPEDOrderStateafterShiporderStateService.fireEvent(orderId,OrderEvent.SHIP);assertEquals(OrderState.SHIPPED,afterShip);// 5. 触发「确认收货」事件SHIPPED → COMPLETEDOrderStateafterConfirmorderStateService.fireEvent(orderId,OrderEvent.CONFIRM);assertEquals(OrderState.COMPLETED,afterConfirm);// 6. 校验已完成的订单不能取消assertFalse(orderStateService.canFire(orderId,OrderEvent.CANCEL));}TestpublicvoidtestCancelOrder(){// 1. 创建订单OrderorderorderStateService.create(TEST_20240501_002);LongorderIdorder.getId();// 2. 待支付状态取消UNPAID → CANCELLEDOrderStateafterCancelorderStateService.fireEvent(orderId,OrderEvent.CANCEL);assertEquals(OrderState.CANCELLED,afterCancel);}}五、扩展与优化这个通用状态机已经满足基础场景实际项目中可以做以下扩展持久化将订单状态、状态机规则存储到数据库而非内存Map守卫条件比如「只有30分钟内的待支付订单能取消」可通过guard实现// 示例添加带守卫的迁移规则sm.addTransition(newTransition(OrderState.UNPAID,OrderEvent.CANCEL,OrderState.CANCELLED,()-order.getCreateTime().plusMinutes(30).isAfter(LocalDateTime.now())// 守卫条件));迁移动作比如「支付成功后扣减库存」「取消订单后恢复库存」可通过action实现// 示例添加带动作的迁移规则sm.addTransition(OrderState.UNPAID,OrderEvent.PAY,OrderState.PAID,()-stockService.deduct(order.getSkuId(),order.getQuantity())// 支付成功扣库存);日志与监控在fire方法中添加日志记录状态迁移过程分布式场景结合分布式锁避免并发修改订单状态。六、总结通过实现通用状态机我们将「状态迁移规则」和「业务逻辑」解耦相比传统的if-else硬编码可读性更强状态迁移规则集中配置一目了然扩展性更好新增状态/事件只需添加迁移规则无需修改业务代码可维护性更高状态校验逻辑统一封装减少重复代码。本文的状态机实现不依赖任何框架可直接嵌入Spring Boot、Spring MVC等项目也可适配任意业务场景工单、物流、审批流等。