spring状态机实战

引言

工作中经常会遇到有复杂状态的转换问题,经常需要编写大量重复的业务代码来进行业务流转,此时状态机就派上了用场,帮助我们简洁优雅的自动实现业务状态的转换。

本篇文章的完整代码库gitee地址:代码地址

一、什么是状态机

状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型,是一种概念性机器,它能采取某种操作来响应一个外部事件。这种操作不仅能取决于接收到的事件,还能取决于各个事件的相对发生顺序。状态机能够跟踪一个内部状态,这个状态会在收到事件后进行更新。因此,为一个事件而响应的行动不仅取决于事件本身,还取决于机器的内部状态。此外,采取的行动还会决定并更新机器的状态,这样一来,任何逻辑都可建模成一系列事件/状态组合。

状态机由以下几部分构成:

  • 状态(States):描述了系统可能存在的所有情况。
  • 事件(Events):触发状态转换的动作或者条件。
  • 动作(actions):当事件发生时,系统执行的动作或操作。
  • 转换(Transitions):描述了系统从一个状态到另一个状态的变化过程。

状态机的类型主要有两种:有限状态机(Finite State Machine, FSM)和推进自动机(Pushdown Automata)。有限状态机是最基本的状态机类型,它只能处于有限个状态中的一个。状态机的应用非常广泛,包括但不限于协议设计、游戏开发、硬件设计、用户界面设计和软件工程等领域。

状态机图包含了六种元素:起始、终止、现态、条件、动作、次态(目标状态)。以下是一个订单的状态机图,以从待支付状态转换为待发货状态为例:

在这里插入图片描述

  • 现态:是指当前所处的状态。待支付
  • 条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。支付事件
  • 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。状态转换为待发货
  • 次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了,待发货。

有两点需要我们注意区分的事项:

  1. 避免把某个“程序动作”当作是一种“状态”来处理。那么如何区分“动作”和“状态”?“动作”是不稳定的,即使没有条件的触发,“动作”一旦执行完毕就结束了;而“状态”是相对稳定的,如果没有外部条件的触发,一个状态会一直持续下去。

  2. 状态划分时漏掉一些状态,导致跳转逻辑不完整。所以在设计状态机时,我们需要反复的查看设计的状态图或者状态表,最终达到一种牢不可破的设计方案。

二、spring状态机

spring官方为我们提供了一个状态机框架 Spring Statemachine。Spring Statemachine 是 Spring 框架中的一个模块,专门用于实现状态机模式。它提供了丰富的功能来简化状态机的创建、配置和管理,特别适合于那些需要复杂状态管理和转换的应用场景。以下是 Spring Statemachine 的一些核心特点和优势:

  1. 高度集成
    Spring Statemachine 与 Spring 生态系统紧密集成,可以方便地利用 Spring 的依赖注入、事件发布/订阅等功能。
    支持与 Spring Boot 应用无缝集成,简化配置和部署过程。
  2. 配置灵活
    支持基于 Java API 和 XML 配置两种方式来定义状态机模型,包括状态、事件、转换和动作。
    提供了状态机构建器(StateMachineBuilder),允许以编程方式构建状态机模型。
  3. 事件驱动
    基于事件驱动模型,当状态机接收到外部事件时,会根据配置自动执行状态转换和关联的动作。
    支持内部事件和外部事件,便于解耦状态机逻辑与其他组件。
  4. 状态监听和动作
    允许在状态进入、退出时执行自定义逻辑(动作),以及在状态转换前后执行动作。
    支持状态改变监听器,可以监控状态机的运行时状态变化。
  5. 并发支持
    支持多线程环境下的状态机实例管理,可以为每个会话或请求创建独立的状态机实例。
    提供并发策略配置,以适应不同的并发需求。
  6. 可视化和调试
    可以生成状态机的可视化模型,帮助开发者和业务人员理解状态机逻辑。
    支持状态机执行跟踪,便于调试和分析状态机行为。
  7. 扩展性
    提供了丰富的扩展点,允许开发者根据需要定制状态机的行为,如自定义决策逻辑、动作执行器等。
    支持与Spring Integration等其他Spring项目集成,进一步扩展应用功能。
  8. 持久化和恢复
    支持状态机状态的持久化,可以在应用重启后恢复到之前的状态。对于需要长期运行并保持状态的应用尤为重要。

通过使用 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的一个简单示例,对于复杂流程控制和处理的业务逻辑很有用处,应该通过这个案例掌握使用方法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/337005.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux-阻塞,非阻塞,异步】

Linux-阻塞&#xff0c;非阻塞&#xff0c;异步 ■ Linux-阻塞-非阻塞 IO-异步■ Linux-阻塞IO■ 阻塞IO简介■ open■ 等待队列■ 示例一&#xff1a;APP阻塞IO读取进入睡眠后-等待队列唤醒进程■■ ■ Linux-非阻塞IO■ 非阻塞IO简介■ open■ 轮询■ 1、select 函数■ 2、po…

20240531在飞凌的OK3588-C开发板上跑原厂的Buildroot测试USB摄像头

20240531在飞凌的OK3588-C开发板上跑原厂的Buildroot测试USB摄像头 2024/5/31 20:04 USB摄像头分辨率&#xff1a;1080p&#xff08;1920x1080&#xff09; 默认编译Buildroot的SDK即可点亮USB摄像头。v4l2-ctl --list-devices v4l2-ctl --list-formats-ext -d /dev/video74 …

【mysql】ubuntu下安装数据库

1更新软件包 sudo apt update //更新软件包2安装数据库 sudo apt install mysql-server//安装数据库注意后面mysql-server是个整体 3安全设置配置 sudo mysql_secure_installation//安全设置配置你要设置验证密码吗&#xff1f; 输入设置密码安全等级&#xff08;0,1&am…

【PostgreSQL17新特性之-冗余IS [NOT] NULL限定符的处理优化】

在执行一个带有IS NOT NULL或者NOT NULL的SQL的时候&#xff0c;通常会对表的每一行&#xff0c;都会进行检查以确保列为空/不为空&#xff0c;这是符合常理的。 但是如果本身这个列上有非空&#xff08;NOT NULL&#xff09;约束&#xff0c;再次检查就会浪费资源。甚至有时候…

U盘损坏打不开?数据恢复攻略全解析

随着信息技术的飞速发展&#xff0c;U盘已成为我们日常工作和生活中不可或缺的数据存储工具。然而&#xff0c;当U盘突然损坏&#xff0c;无法打开时&#xff0c;我们往往会陷入焦虑和无助之中。本文将为大家详细解析U盘损坏打不开的原因&#xff0c;并提供两种有效的数据恢复方…

Git基本使用教程(学习记录)

参考文章链接&#xff1a; Git教程&#xff08;超详细&#xff0c;一文秒懂&#xff09; RUNOOB Git教程 Git学习记录 1Git概述 1.1版本控制软件功能 版本管理&#xff1a;更新或回退到历史上任何版本&#xff0c;数据备份共享代码&#xff1a;团队间共享代码&#xff0c;…

Gavin Wood 访谈|Polkadot 从何而来,又将如何面对 AI 时代?

如果没有宏观经济&#xff0c;加密世界可能无法存在。或许&#xff0c;Satoshi Nakamoto 也永远不会写出那篇开创性的白皮书。区块链技术作为指数时代的核心之一&#xff0c;在宏观经济理论中占有重要地位。传统的经济增长公式是人口增长加生产率增长加债务增长。然而&#xff…

Python导出Jira列表

import requests import urllib3 urllib3.disable_warnings() from jira import JIRA import pandas as pd def login_jira(username,password):jira JIRA("https://jira.cn/",basic_auth(username,password))#projectsjira.project(id13)# jqlproject"云链-…

3D轻量化的三大应用解决方案

老子云平台https://www.laozicloud.com/ 为不同应用场景提供了三大解决方案。 01 单模型轻量化解决方案 数字化时代&#xff0c;越来越多的C2M定制、文旅、电商等行业&#xff0c;为了开拓市场&#xff0c;提升企业竞争力&#xff0c;开始把目光投向产品的3D展示交互。 单模…

CSS 空间转换 动画

目录 1. 空间转换1.1 视距 - perspective1.2 空间转换 - 旋转1.3 立体呈现 - transform-style1.4 空间转换 - 缩放 2. 动画 - animation2.1 动画的基本用法2.1 animation 复合属性2.2 animation 拆分属性2.3 多组动画 正文开始 1. 空间转换 空间&#xff1a;是从坐标轴角度定义…

基于编译型语言鲲鹏应用开发小技巧

编译型语言应用执行过程 大部分应用可以通过重新编译即可移植到鲲鹏平台 预处理命令: gcc -E hello.c -o hello.i&#xff0c;预处理完成后使用命令: cat hello.i可以看到预处理后的代码 编译命令: gcc -s hello.i -o hello.s 汇编命令: gcc -c hello.c -o hello.o 链接处理…

pytorch学习笔记2

首先如果遇到conda找不到包&#xff0c;pip老是超时的情况建议添加一下镜像源 conda的 conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/ cond…

【ETAS CP AUTOSAR基础软件】EcuM模块详解

文章包含了AUTOSAR基础软件&#xff08;BSW&#xff09;中EcuM模块相关的内容详解。本文从AUTOSAR规范解析&#xff0c;ISOLAR-AB配置以及模块相关代码分析三个维度来帮读者清晰的认识和了解EcuM。文中涉及的SOLAR-AB配置以及模块相关代码都是依托于ETAS提供的工具链来配置与生…

Java集合【超详细】

文章目录 一、集合框架1.1 概述1.2 数组和集合的区别1.3 Java集合框架体系1.4 数据结构1.4.1 栈、队列、数组、队列1.4.2 二叉树【理解】1.4.3 二叉查找树【理解】1.4.4 平衡二叉树【理解】1.4.5 红黑树【理解】 1.5 泛型 二、Collection集合2.1 Collection 集合概述和使用【应…

如何利用智能算法降低成本,扩大收益?

算法交易&#xff08;Algorithm Trading&#xff09;是一种投资策略&#xff0c;它使用计算机算法来分析市场数据&#xff0c;制定交易决策&#xff0c;并自动执行交易。算法交易的主要目标是利用市场价格波动来获取利润&#xff0c;同时降低人为干预的风险和成本。 量化交易中…

【漏洞复现】WordPress Country State City Dropdown CF7插件 SQL注入漏洞(CVE-2024-3495)

0x01 产品简介 Country State City Dropdown CF7插件是一个功能强大、易于使用的 WordPress 插件&#xff0c;它为用户在联系表单中提供国家.州/省和城市的三级下拉菜单功能&#xff0c;帮助用户更准确地填写地区信息。同时&#xff0c;插件的团队和支持也非常出色&#xff0c…

MySQL 索引的使用

本篇主要介绍MySQL中索引使用的相关内容。 目录 一、最左前缀法则 二、索引失效的场景 索引列运算 字符串无引号 模糊查询 or连接条件 数据分布 一、最左前缀法则 当我们在使用多个字段构成的索引时&#xff08;联合索引&#xff09;&#xff0c;需要考虑最左前缀法则…

COMSOL中液晶材料光学特性模拟

前面我们根据FDTD官方文档设置了液晶指向的模型。COMSOL也可以根据相似的方法设置各项异性的周期性变化的材料。 该方法参考了luneburg_lens的COMSOL文档 在给出的文件中&#xff0c;可以发现定义-变量中可以使用默认坐标作为变量&#xff0c;即xyz。因此&#xff0c;折射率也可…

德人合科技——天锐绿盾内网安全管理软件 | -文档透明加密模块

天锐绿盾文档加密功能能够为各种模式的电子文档提供高强度加密保护&#xff0c;丰富的权限控制以及灵活的应用管理&#xff0c;帮助企业构建更严密的立体保密体系。 PC地址&#xff1a; https://isite.baidu.com/site/wjz012xr/2eae091d-1b97-4276-90bc-6757c5dfedee ————…

【算法】MT2 棋子翻转

✨题目链接&#xff1a; MT2 棋子翻转 ✨题目描述 在 4x4 的棋盘上摆满了黑白棋子&#xff0c;黑白两色棋子的位置和数目随机&#xff0c;其中0代表白色&#xff0c;1代表黑色&#xff1b;左上角坐标为 (1,1) &#xff0c;右下角坐标为 (4,4) 。 现在依次有一些翻转操作&#…