一、什么是状态机
状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型,是一种概念性机器,它能采取某种操作来响应一个外部事件。这种操作不仅能取决于接收到的事件,还能取决于各个事件的相对发生顺序。状态机能够跟踪一个内部状态,这个状态会在收到事件后进行更新。因此,为一个事件而响应的行动不仅取决于事件本身,还取决于机器的内部状态。此外,采取的行动还会决定并更新机器的状态,这样一来,任何逻辑都可建模成一系列事件/状态组合。
状态机由以下几部分构成:
- 状态(States):描述了系统可能存在的所有情况。
- 事件(Events):触发状态转换的动作或者条件。
- 动作(actions):当事件发生时,系统执行的动作或操作。
- 转换(Transitions):描述了系统从一个状态到另一个状态的变化过程。
状态机的类型主要有两种:有限状态机(Finite State Machine, FSM)和推进自动机(Pushdown Automata)。有限状态机是最基本的状态机类型,它只能处于有限个状态中的一个。状态机的应用非常广泛,包括但不限于协议设计、游戏开发、硬件设计、用户界面设计和软件工程等领域。
状态机图包含了六种元素:起始、终止、现态、条件、动作、次态(目标状态)。以下是一个订单的状态机图,以从待支付状态转换为待发货状态为例:
- 现态:是指当前所处的状态。待支付
- 条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。支付事件
- 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。状态转换为待发货
- 次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了,待发货。
有两点需要我们注意区分的事项:
-
避免把某个“程序动作”当作是一种“状态”来处理。那么如何区分“动作”和“状态”?“动作”是不稳定的,即使没有条件的触发,“动作”一旦执行完毕就结束了;而“状态”是相对稳定的,如果没有外部条件的触发,一个状态会一直持续下去。
-
状态划分时漏掉一些状态,导致跳转逻辑不完整。所以在设计状态机时,我们需要反复的查看设计的状态图或者状态表,最终达到一种牢不可破的设计方案。
二、spring状态机
spring官方为我们提供了一个状态机框架 Spring Statemachine
。Spring Statemachine 是 Spring 框架中的一个模块,专门用于实现状态机模式。它提供了丰富的功能来简化状态机的创建、配置和管理,特别适合于那些需要复杂状态管理和转换的应用场景。以下是 Spring Statemachine 的一些核心特点和优势:
- 高度集成:
Spring Statemachine 与 Spring 生态系统紧密集成,可以方便地利用 Spring 的依赖注入、事件发布/订阅等功能。
支持与 Spring Boot 应用无缝集成,简化配置和部署过程。 - 配置灵活:
支持基于 Java API 和 XML 配置两种方式来定义状态机模型,包括状态、事件、转换和动作。
提供了状态机构建器(StateMachineBuilder),允许以编程方式构建状态机模型。 - 事件驱动:
基于事件驱动模型,当状态机接收到外部事件时,会根据配置自动执行状态转换和关联的动作。
支持内部事件和外部事件,便于解耦状态机逻辑与其他组件。 - 状态监听和动作:
允许在状态进入、退出时执行自定义逻辑(动作),以及在状态转换前后执行动作。
支持状态改变监听器,可以监控状态机的运行时状态变化。 - 并发支持:
支持多线程环境下的状态机实例管理,可以为每个会话或请求创建独立的状态机实例。
提供并发策略配置,以适应不同的并发需求。 - 可视化和调试:
可以生成状态机的可视化模型,帮助开发者和业务人员理解状态机逻辑。
支持状态机执行跟踪,便于调试和分析状态机行为。 - 扩展性:
提供了丰富的扩展点,允许开发者根据需要定制状态机的行为,如自定义决策逻辑、动作执行器等。
支持与Spring Integration等其他Spring项目集成,进一步扩展应用功能。 - 持久化和恢复:
支持状态机状态的持久化,可以在应用重启后恢复到之前的状态。对于需要长期运行并保持状态的应用尤为重要。
通过使用 Spring Statemachine,开发者可以高效地构建和管理复杂的状态逻辑,同时保持代码的清晰度和可维护性。
三、代码实践
笔者这里还是以订单流转为例,使用springboot应用,简单展示spring状态机的用法。
下面展示的是主要核心代码,部分代码笔者没有展示了,完整代码库gitee地址:代码地址
3.1 建表tb_order
CREATE TABLE `tb_order` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',`order_code` varchar(128) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '订单编码',`status` smallint(3) DEFAULT NULL COMMENT '订单状态',`name` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '订单名称',`price` decimal(12,2) DEFAULT NULL COMMENT '价格',`delete_flag` tinyint(2) NOT NULL DEFAULT '0' COMMENT '删除标记,0未删除 1已删除',`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '创建时间',`update_time` timestamp COMMENT '更新时间',`create_user_code` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '创建人',`update_user_code` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '更新人',`version` int(11) NOT NULL DEFAULT '0' COMMENT '版本号',`remark` varchar(64) COLLATE utf8mb4_bin DEFAULT NULL COMMENT '备注',PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin COMMENT='订单表';
3.2 建立Spring Statemachine项目
笔者新建了一个spring-machine的springboot项目,因为要操作数据库,引入了jdbc和mybatis依赖。
yaml配置
server:port: 4455spring:datasource:hikari:connection-timeout: 6000000 # 设置为60000毫秒,即60秒# 数据库连接信息driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/spring-machine?useSSL=false&serverTimezone=UTCusername: rootpassword: rootdata:redis:# Redis服务器地址host: localhost# Redis服务器端口号port: 6379# 使用的数据库索引,默认是0database: 0# 连接超时时间timeout: 1800000# 设置密码password:lettuce:pool:# 最大阻塞等待时间,负数表示没有限制max-wait: -1# 连接池中的最大空闲连接max-idle: 5# 连接池中的最小空闲连接min-idle: 0# 连接池中最大连接数,负数表示没有限制max-active: 20
mybatis:# MyBatis配置mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.spring.statemachine.mapperconfiguration:map-underscore-to-camel-case: truelog-impl: org.apache.ibatis.logging.stdout.StdOutImpl
引入依赖
注意,笔者用的依赖基本都是最新的,使用的jdk是21,源码下载后根据个人环境版本配置做相应版本修改,直至不报错
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-undertow</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.mysql</groupId><artifactId>mysql-connector-j</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>3.0.3</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jdbc</artifactId></dependency><!-- redis持久化状态机 --><dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-redis</artifactId><version>1.2.14.RELEASE</version></dependency><!--状态机--><!--spring statemachine--><dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-core</artifactId><version>4.0.0</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.50</version> <!-- 确保使用最新的版本 --></dependency><!-- Spring AOP --><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>6.1.8</version> <!-- 替换为实际使用的Spring版本 --></dependency><!-- AspectJ --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.9.22</version> <!-- 替换为实际使用的AspectJ版本 --></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.22</version> <!-- 替换为实际使用的AspectJ版本 --></dependency>
订单状态枚举类
package com.spring.statemachine.orderEnum;public enum OrderStatus {// 待支付,待发货,待收货,已完成WAIT_PAYMENT(1, "待支付"),WAIT_DELIVER(2, "待发货"),WAIT_RECEIVE(3, "待收货"),FINISH(4, "已完成");private final Integer key;private final String desc;OrderStatus(Integer key, String desc) {this.key = key;this.desc = desc;}public Integer getKey() {return key;}public String getDesc() {return desc;}public static OrderStatus getByKey(Integer key) {for (OrderStatus e : values()) {if (e.getKey().equals(key)) {return e;}}throw new RuntimeException("enum not exists.");}
}
事件枚举
package com.spring.statemachine.orderEnum;public enum OrderStatusChangeEvent {// 支付,发货,确认收货PAYED, DELIVERY, RECEIVED;
}
定义状态机规则和配置状态机
package com.spring.statemachine.config;import com.spring.statemachine.orderEnum.OrderStatus;
import com.spring.statemachine.orderEnum.OrderStatusChangeEvent;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;import java.util.EnumSet;@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderStatusChangeEvent> {/*** 配置状态*/public void configure(StateMachineStateConfigurer<OrderStatus, OrderStatusChangeEvent> states) throws Exception {states.withStates().initial(OrderStatus.WAIT_PAYMENT).states(EnumSet.allOf(OrderStatus.class));}/*** 配置状态转换事件关系*/public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderStatusChangeEvent> transitions) throws Exception {transitions//支付事件:待支付-》待发货.withExternal().source(OrderStatus.WAIT_PAYMENT).target(OrderStatus.WAIT_DELIVER).event(OrderStatusChangeEvent.PAYED).and()//发货事件:待发货-》待收货.withExternal().source(OrderStatus.WAIT_DELIVER).target(OrderStatus.WAIT_RECEIVE).event(OrderStatusChangeEvent.DELIVERY).and()//收货事件:待收货-》已完成.withExternal().source(OrderStatus.WAIT_RECEIVE).target(OrderStatus.FINISH).event(OrderStatusChangeEvent.RECEIVED);}
}
配置持久化
package com.spring.statemachine.config;import com.alibaba.fastjson.JSON;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.persist.DefaultStateMachinePersister;
import org.springframework.statemachine.persist.RepositoryStateMachinePersist;
import org.springframework.statemachine.persist.StateMachinePersister;
import org.springframework.statemachine.redis.RedisStateMachineContextRepository;
import org.springframework.statemachine.redis.RedisStateMachinePersister;import java.util.HashMap;
import java.util.Map;@Configuration
@Slf4j
public class Persist<E, S> {@Resourceprivate RedisConnectionFactory redisConnectionFactory;/*** 持久化到内存map中*/@Bean(name = "stateMachineMemPersister")@SuppressWarnings("all")public static StateMachinePersister getPersister() {return new DefaultStateMachinePersister(new StateMachinePersist() {private final Map<Object, StateMachineContext> map = new HashMap<>();@Overridepublic void write(StateMachineContext context, Object contextObj) throws Exception {log.info("持久化状态机,context:{},contextObj:{}", JSON.toJSONString(context), JSON.toJSONString(contextObj));map.put(contextObj, context);}@Overridepublic StateMachineContext read(Object contextObj) throws Exception {log.info("获取状态机,contextObj:{}", JSON.toJSONString(contextObj));StateMachineContext stateMachineContext = map.get(contextObj);log.info("获取状态机结果,stateMachineContext:{}", JSON.toJSONString(stateMachineContext));return stateMachineContext;}});}/*** 持久化到redis中,在分布式系统中使用*/@Bean(name = "stateMachineRedisPersister")public RedisStateMachinePersister<E, S> getRedisPersister() {RedisStateMachineContextRepository<E, S> repository = new RedisStateMachineContextRepository<>(redisConnectionFactory);RepositoryStateMachinePersist<E, S> p = new RepositoryStateMachinePersist<>(repository);return new RedisStateMachinePersister<>(p);}
}
业务系统controller
package com.spring.statemachine.controller;import com.spring.statemachine.domain.Order;
import com.spring.statemachine.service.OrderService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;@RestController
@RequestMapping("/order")
public class OrderController {@Resourceprivate OrderService orderService;/*** 根据id查询订单*/@RequestMapping("/getById")public Order getById(@RequestParam("id") Long id) {//根据id查询订单return orderService.getById(id);}/*** 创建订单*/@RequestMapping("/create")public String create(@RequestBody Order order) {//创建订单orderService.create(order);return "success";}/*** 对订单进行支付*/@RequestMapping("/pay")public String pay(@RequestParam("id") Long id) {//对订单进行支付orderService.pay(id);return "success";}/*** 对订单进行发货*/@RequestMapping("/deliver")public String deliver(@RequestParam("id") Long id) {//对订单进行确认收货orderService.deliver(id);return "success";}/*** 对订单进行确认收货*/@RequestMapping("/receive")public String receive(@RequestParam("id") Long id) {//对订单进行确认收货orderService.receive(id);return "success";}
}
service服务
package com.spring.statemachine.service.impl;import com.spring.statemachine.domain.Order;
import com.spring.statemachine.interfaceVariable.CommonConstants;
import com.spring.statemachine.mapper.OrderMapper;
import com.spring.statemachine.orderEnum.OrderStatus;
import com.spring.statemachine.orderEnum.OrderStatusChangeEvent;
import com.spring.statemachine.service.OrderService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.persist.StateMachinePersister;
import org.springframework.stereotype.Service;import java.util.Objects;@Service("orderService")
@Slf4j
public class OrderServiceImpl implements OrderService {@Resourceprivate StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;@Resourceprivate StateMachinePersister<OrderStatus, OrderStatusChangeEvent, String> stateMachineMemPersister;@Resourceprivate StateMachinePersister<OrderStatus, OrderStatusChangeEvent, String> stateMachineRedisPersister;@Resourceprivate OrderMapper orderMapper;/*** 创建订单*/public Order create(Order order) {order.setStatus(OrderStatus.WAIT_PAYMENT.getKey());orderMapper.insert(order);return order;}/*** 对订单进行支付*/public void pay(Long id) {Order order = orderMapper.selectById(id);log.info("线程名称:{},尝试支付,订单号:{}", Thread.currentThread().getName(), id);if (!sendEvent(OrderStatusChangeEvent.PAYED, order,CommonConstants.payTransition)) {log.error("线程名称:{},支付失败, 状态异常,订单信息:{}", Thread.currentThread().getName(), order);throw new RuntimeException("支付失败, 订单状态异常");}}/*** 对订单进行发货*/public void deliver(Long id) {Order order = orderMapper.selectById(id);log.info("线程名称:{},尝试发货,订单号:{}", Thread.currentThread().getName(), id);if (!sendEvent(OrderStatusChangeEvent.DELIVERY, order,CommonConstants.deliverTransition)) {log.error("线程名称:{},发货失败, 状态异常,订单信息:{}", Thread.currentThread().getName(), order);throw new RuntimeException("发货失败, 订单状态异常");}}@Overridepublic Order getById(Long id) {return orderMapper.selectById(id);}/*** 对订单进行确认收货*/public void receive(Long id) {Order order = orderMapper.selectById(id);log.info("线程名称:{},尝试收货,订单号:{}", Thread.currentThread().getName(), id);if (!sendEvent(OrderStatusChangeEvent.RECEIVED, order,CommonConstants.receiveTransition)) {log.error("线程名称:{},收货失败, 状态异常,订单信息:{}", Thread.currentThread().getName(), order);throw new RuntimeException("收货失败, 订单状态异常");}}/*** 发送订单状态转换事件* synchronized修饰保证这个方法是线程安全的*/private synchronized boolean sendEvent(OrderStatusChangeEvent changeEvent, Order order,String key) {boolean result = false;try {//启动状态机orderStateMachine.startReactively();//内存持久化状态机尝试恢复状态机状态,单机环境下使用//stateMachineMemPersister.restore(orderStateMachine, String.valueOf(order.getId()));//redis持久化状态机尝试恢复状态机状态,分布式环境下使用stateMachineRedisPersister.restore(orderStateMachine, String.valueOf(order.getId()));Message<OrderStatusChangeEvent> message = MessageBuilder.withPayload(changeEvent).setHeader(CommonConstants.orderHeader, order).build();result = orderStateMachine.sendEvent(message);if(!result){return false;}//获取到监听的结果信息Integer o = (Integer) orderStateMachine.getExtendedState().getVariables().get(key + order.getId());//操作完成之后,删除本次对应的key信息orderStateMachine.getExtendedState().getVariables().remove(key+order.getId());//如果事务执行成功,则持久化状态机if(Objects.equals(1, o)){//使用内存持久化状态机状态,单机环境下使用//stateMachineMemPersister.persist(orderStateMachine, String.valueOf(order.getId()));//使用redis持久化状态机状态机状态,分布式环境下使用stateMachineRedisPersister.persist(orderStateMachine, String.valueOf(order.getId()));}else {//订单执行业务异常return false;}} catch (Exception e) {log.error("订单操作失败:{}", e.getMessage(),e);} finally {orderStateMachine.stopReactively();}return result;}
}
注解和切面类
注解
package com.spring.statemachine.aspect;import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;@Retention(RetentionPolicy.RUNTIME)
public @interface LogResult {/*** 执行的业务key** @return String*/String key();
}
切面类
package com.spring.statemachine.aspect;import com.spring.statemachine.domain.Order;
import com.spring.statemachine.interfaceVariable.CommonConstants;
import com.spring.statemachine.orderEnum.OrderStatus;
import com.spring.statemachine.orderEnum.OrderStatusChangeEvent;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.messaging.Message;
import org.springframework.statemachine.StateMachine;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;/*** 本注解主要是把OrderStateListener中注释的重复代码提取出来*/
@Component
@Aspect
@Slf4j
public class LogResultAspect {@Pointcut("@annotation(com.spring.statemachine.aspect.LogResult)")private void logResultPointCut() {//logResultPointCut 日志注解切点}@Resourceprivate StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;@Around("logResultPointCut()")@SuppressWarnings("all")public Object logResultAround(ProceedingJoinPoint pjp) throws Throwable {//获取参数Object[] args = pjp.getArgs();log.info("参数args:{}", args);Message message = (Message) args[0];Order order = (Order) message.getHeaders().get(CommonConstants.orderHeader);//获取方法Method method = ((MethodSignature) pjp.getSignature()).getMethod();LogResult logResult = method.getAnnotation(LogResult.class);String key = logResult.key();Object returnVal;try {//执行方法returnVal = pjp.proceed();//如果业务执行正常,则保存信息//成功 则为1assert order != null;orderStateMachine.getExtendedState().getVariables().put(key + order.getId(), 1);} catch (Throwable e) {log.error("e:{}", e.getMessage());//如果业务执行异常,则保存信息//将异常信息变量信息中,失败则为0assert order != null;orderStateMachine.getExtendedState().getVariables().put(key + order.getId(), 0);throw e;}return returnVal;}
}
状态变化监听
package com.spring.statemachine.listener;import com.spring.statemachine.aspect.LogResult;
import com.spring.statemachine.domain.Order;
import com.spring.statemachine.interfaceVariable.CommonConstants;
import com.spring.statemachine.mapper.OrderMapper;
import com.spring.statemachine.orderEnum.OrderStatus;
import com.spring.statemachine.orderEnum.OrderStatusChangeEvent;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.Message;
//import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;/*** 订单事件监听器(注释调的代码替换为aop拦截实现LogResultAspect)*/
@Component("orderStateListener")
@WithStateMachine(name = "orderStateMachine")
@Slf4j
public class OrderStateListener {@Resourceprivate OrderMapper orderMapper;
// @Resource
// private StateMachine<OrderStatus, OrderStatusChangeEvent> orderStateMachine;/*** 支付事件监听*/@OnTransition(source = "WAIT_PAYMENT", target = "WAIT_DELIVER")@Transactional(rollbackFor = Exception.class)@LogResult(key = CommonConstants.payTransition)public void payTransition(Message<OrderStatusChangeEvent> message) {Order order = (Order) message.getHeaders().get(CommonConstants.orderHeader);log.info("支付,状态机反馈信息:{}", message.getHeaders());//更新订单assert order != null;order.setStatus(OrderStatus.WAIT_DELIVER.getKey());orderMapper.updateById(order);
// try {
// //更新订单
// assert order != null;
// order.setStatus(OrderStatus.WAIT_DELIVER.getKey());
// orderMapper.updateById(order);
// //成功 则为1
// orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(),1);
// } catch (Exception e) {
// //如果出现异常,则进行回滚
// log.error("payTransition 出现异常:{}",e.getMessage(),e);
// //将异常信息变量信息中,失败则为0
// orderStateMachine.getExtendedState().getVariables().put(CommonConstants.payTransition+order.getId(), 0);
// throw e;
// }}/*** 发货事件监听*/@OnTransition(source = "WAIT_DELIVER", target = "WAIT_RECEIVE")@LogResult(key = CommonConstants.deliverTransition)public void deliverTransition(Message<OrderStatusChangeEvent> message) {Order order = (Order) message.getHeaders().get(CommonConstants.orderHeader);log.info("发货,状态机反馈信息:{}", message.getHeaders());//更新订单assert order != null;order.setStatus(OrderStatus.WAIT_RECEIVE.getKey());orderMapper.updateById(order);
// try {
// //更新订单
// assert order != null;
// order.setStatus(OrderStatus.WAIT_RECEIVE.getKey());
// orderMapper.updateById(order);
// //成功 则为1
// orderStateMachine.getExtendedState().getVariables().put(CommonConstants.deliverTransition+order.getId(),1);
// } catch (Exception e) {
// //如果出现异常,则进行回滚
// log.error("payTransition 出现异常:{}",e.getMessage(),e);
// //将异常信息变量信息中,失败则为0
// orderStateMachine.getExtendedState().getVariables().put(CommonConstants.deliverTransition+order.getId(), 0);
// throw e;
// }}/*** 确认收货事件监听*/@OnTransition(source = "WAIT_RECEIVE", target = "FINISH")@LogResult(key = CommonConstants.receiveTransition)public void receiveTransition(Message<OrderStatusChangeEvent> message) {Order order = (Order) message.getHeaders().get(CommonConstants.orderHeader);log.info("确认收货,状态机反馈信息:{}", message.getHeaders());//更新订单assert order != null;order.setStatus(OrderStatus.FINISH.getKey());orderMapper.updateById(order);
// try {
// //更新订单
// assert order != null;
// order.setStatus(OrderStatus.FINISH.getKey());
// orderMapper.updateById(order);
// //成功 则为1
// orderStateMachine.getExtendedState().getVariables().put(CommonConstants.receiveTransition+order.getId(),1);
// } catch (Exception e) {
// //如果出现异常,则进行回滚
// log.error("payTransition 出现异常:{}",e.getMessage(),e);
// //将异常信息变量信息中,失败则为0
// orderStateMachine.getExtendedState().getVariables().put(CommonConstants.receiveTransition+order.getId(), 0);
// throw e;
// }}
}
四、验证修改
主体流程
- 新增一个订单
http://localhost:8084/order/create
- 对订单进行支付
http://localhost:8084/order/pay?id=2
- 对订单进行发货
http://localhost:8084/order/deliver?id=2
- 对订单进行确认收货
http://localhost:8084/order/receive?id=2
具体实施步骤如下:
新增订单
工具为postman(一款非常强大好用的接口测试工具,地址:postman官网)
json格式的数据
{"id": 3,"orderCode": "ORDER-3","name": "Product C","price": "199.99","deleteFlag": 0,"createTime": "2024-05-24T14:41:00Z","updateTime": "2024-05-25T11:18:00Z","createUserCode": "USER123","updateUserCode": "USER456","version": 1,"remark": "Sample order for testing"
}
使用新增订单接口http://localhost:8084/order/create
创建了几个订单
支付订单
对id为2的订单进行支付
控制台信息如下
查看数据库,id为2的订单状态,已被修改为2待发货状态
对于这个id为2的订单,我们再次调用支付,会发生什么?
可以看到已经支付的订单,无法再次支付了。
持久化方式
笔者选择的是redis的方式持久化状态机,当然也有内存持久化的方式,前者适用于分布式,后者适用于单机环境。比如同一个订单支付操作,第二次再重复支付,二者都会报错,但是redis持久化方式,服务重启重复支付会继续报错,而内存持久化方式,服务重启后,再进行重复支付操作,却不会报错了,因为内存中缓存的状态机状态随着服务重启重置了,而redis缓存仍然存在。
redis持久化的key即为订单的id
总结:以上就是spring statemachine的一个简单示例,对于复杂流程控制和处理的业务逻辑很有用处,应该通过这个案例掌握使用方法。