spring状态机实战
一、什么是状态机
状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型,是一种概念性机器,它能采取某种操作来响应一个外部事件。这种操作不仅能取决于接收到的事件,还能取决于各个事件的相对发生顺序。状态机能够跟踪一个内部状态,这个状态会在收到事件后进行更新。因此,为一个事件而响应的行动不仅取决于事件本身,还取决于机器的内部状态。此外,采取的行动还会决定并更新机器的状态,这样一来,任何逻辑都可建模成一系列事件/状态组合。
状态机由以下几部分构成:
- 状态(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的一个简单示例,对于复杂流程控制和处理的业务逻辑很有用处,应该通过这个案例掌握使用方法。
相关文章:

spring状态机实战
一、什么是状态机 状态机是有限状态自动机的简称,是现实事物运行规则抽象而成的一个数学模型,是一种概念性机器,它能采取某种操作来响应一个外部事件。这种操作不仅能取决于接收到的事件,还能取决于各个事件的相对发生顺序。状态…...
Ubuntu系统上安装NVIDIA驱动【笔记】
Ubuntu上安装NVIDIA驱动,您可以按照以下步骤操作: 首先,您需要配置软件源(根据情况配置,否则影响更新和下载速度)。 接下来,您可以按照上一条回答中的步骤来安装新的NVIDIA驱动。首先ÿ…...

生成式AI导论2024-李宏毅
生成式AI导论2024-李宏毅 第0讲: 课程说明第1讲:生成式AI是什么第2講:今日的生成式人工智慧厲害在哪裡?從「工具」變為「工具人」 第0讲: 课程说明 生成式AI的入门课程 第1讲:生成式AI是什么 生成式人…...

跨平台之用VisualStudio开发APK嵌入OpenCV(三)
本篇将包含以下内容: 1.使用 Visual Studio 2019 开发一个 Android 的 App 2.导入前篇 C 编译好的 so 动态库 3.一些入门必须的其它设置 作为入门,我们直接使用真机进行调试,一方面运行速度远高于模拟器,另一方面模拟器使用的…...
渗透测试框架之CobaltStrike,Metasploit域名上线隐藏IP
概述 为什么要隐藏IP 在拿下了目标机之后,目标机在内网里面,使用msf或者CS时,用自己的VPS做服务器的话,导致很容易被溯源。 域名上线原理 当我们访问域名时会经过域名解析 域名解析就是域名到IP地址的转换过程,那么…...

vue.js对接海康威视摄像头web开发包
一、登录海康开放平台下载web开发包,下载需要先登录海康账号,没有的需先注册一个。 这里的appkey、ip、port、secret 和cameraIndexCodeasd是自己去申请的 appkey: "****", ip: "****", port: **, secret: "****", //必填…...
Selenium中使用的三种等待
文章目录 1.前言2.在selenium中常见的等待操作一般有3个 1.前言 在使用selenium时很多元素在使用的时候都需要加载,如果不等待加载结束直接使用就会报错,功能不能继续。一般解决的办法就是使用等待操作。 2.在selenium中常见的等待操作一般有3个 slee…...

推荐一款媒体影音嗅探神器—Chrome扩展插件(猫抓cat-catch)
目录 1.1、前言1.2、下载地址1.3、github Releases 版本说明1.4、安装步骤1.5、猫抓插件常规设置1.5.1、设置抓取文件的类型1.5.2、设置抓取文件的后缀名 1.1、前言 我们在日常上网的过程中,很多音频、视频网站下载资源都非常不方便,要么需要安装客户端&…...
LLaMA-Factory 微调训练
LLaMA-Factory 微调训练 该框架功能,标注-微调-导出-合并-部署,一整条流程都有,而且训练时消耗的gpu算力也会小一些 一,安装(推荐在linux中训练,win可以用wsldocker) git clone https://githu…...

阿里云ECS服务器怎么设置时区
在自己部署在阿里云服务器上的应用中,控制台打印的日志时间和本地不一致,于是决定修改阿里云服务器的时区为Asia/Shanghai。 具体操作如下: 第一步:连接服务器 通过finalshell等连接工具通过公网IP连接到服务器。 第二步&#…...

【698协议】帧校验算法
698协议,帧校验算法 帧格式 帧校验范围 校验算法 #include "fcs16.h" /* * u16 represents an unsigned 16-bit number. Adjust the typedef for * your hardware. * Drew D. Perkins at Carnegie Mellon University. * Code liberally borrowed from M…...

FileZilla“服务器发回了不可路由的地址,使用服务器地址代替
问题:在宝塔创建的FTP无法使用,提示“服务器回应不可路由的地址。使用服务器地址代替 第一种解决办法:由于宝塔把FTP被动模式端口范围设置成了39000-40000,所以只需要把阿里云服务器上相应的端口范围开放即可。 第二种解决办法&am…...
【路径规划】基于遗传算法GA实现最短距离 多起点多终点多旅行商问题求解附Matlab代码
基于遗传算法GA实现最短距离 多起点多终点多旅行商问题求解 研究背景:研究步骤:研究方法和技术路线:代码研究背景: 多起点多终点多旅行商问题是旅行商问题(TSP)的一个扩展,该问题要求确定多个旅行商从各自的起点出发,分别经过一系列目标点最终回到各自的终点,使得总路…...

计算机毕业设计 | springboot+vue房屋租赁管理系统(附源码)
1,绪论 1.1 课题来源 随着社会的不断发展以及大家生活水平的提高,越来越多的年轻人选择在大城市发展。在大城市发展就意味着要在外面有一处安身的地方。在租房的过程中,大家也面临着各种各样的问题,比如需要费时费力去现场看房&…...

重大活动网络安全保障建设及运营指南
在当今高度数字化的社会中,各类重大活动如会议、展览、赛事及庆典等正面临着日益复杂和严峻的网络安全威胁。这些威胁不限于网络入侵或数据泄露,更涉及到对基础设施、关键信息系统和公众舆论的复杂攻击,需要国际社会的密切合作和长期关注。因…...

基于信号分解方法的机械故障诊断方法存在的问题
一方面,由于结构共振、测试噪声的干扰,为了确保分解精度,需要给定准确的参数初值(例如,瞬时频率)。研究人员通常认为零部件特征频率与通过传动比和驱动转速计算的理论值基本吻合,并基于理论值设置参数初值。事实上&…...
faster_whisper语音识别
faster_whisper语音识别 检测可用设备:list_available_devices()函数 我这边usb摄像头带麦克风的,所以 DEV_index 8 1 使用 pyaudio 打开音频设备 2 从音频设备读取数据,传递给 faster_whisper 识别 按键 r 录制 s 停止 q退出 test.py #…...

Java锁的策略
White graces:个人主页 🙉专栏推荐:Java入门知识🙉 🙉 内容推荐:<多线程案例(线程池)>🙉 🐹今日诗词:"你我推心置腹, 岂能相负"🐹 目录 锁的策略 乐观锁和悲观锁 轻量级锁…...
521源码-免费代码基础学习-PHP如何运用变量教程
更多网站源码学习教程,请点击👉-521源码-👈获取最新资源 为什么要学习PHP?“我可以用JavaScript来实现程序编写。”但JavaScript的能力是有限的,JavaScript通常运行在浏览器(客户端)࿰…...
单选或者多选的知识问题调研系统,怎么使用Neo4j的图数据库来实现
为了使用Neo4j的图数据库实现单选或多选的知识问题调研系统,你需要设计和实现以下几个步骤: 设计节点和关系插入数据定义查询和更新逻辑开发前端和后端应用来与Neo4j进行交互 1. 设计节点和关系 节点类型 Question:表示一个问题ÿ…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成
厌倦手动写WordPress文章?AI自动生成,效率提升10倍! 支持多语言、自动配图、定时发布,让内容创作更轻松! AI内容生成 → 不想每天写文章?AI一键生成高质量内容!多语言支持 → 跨境电商必备&am…...

12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...

Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决
Spring Cloud Gateway 中自定义验证码接口返回 404 的排查与解决 问题背景 在一个基于 Spring Cloud Gateway WebFlux 构建的微服务项目中,新增了一个本地验证码接口 /code,使用函数式路由(RouterFunction)和 Hutool 的 Circle…...

USB Over IP专用硬件的5个特点
USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中,从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备(如专用硬件设备),从而消除了直接物理连接的需要。USB over IP的…...

免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...
uniapp 字符包含的相关方法
在uniapp中,如果你想检查一个字符串是否包含另一个子字符串,你可以使用JavaScript中的includes()方法或者indexOf()方法。这两种方法都可以达到目的,但它们在处理方式和返回值上有所不同。 使用includes()方法 includes()方法用于判断一个字…...