当前位置: 首页 > news >正文

SpringBoot 实战(四十)集成 Statemachine

目录

    • 一、Statemachine 简介
      • 1.1 核心概念
      • 1.2 主要特性
      • 1.3 注解驱动开发方式
      • 1.4 核心注解详解
        • 1)@WithStateMachine 注解
        • 2)@OnTransition 注解
        • 3)@OnTransitionStart 注解
        • 4)@OnTransitionEnd 注解
      • 1.5 包含的模块
    • 二、知识回顾——状态模式
      • 2.1 什么是状态模式?
      • 2.2 状态模式的优缺点
      • 2.3 状态模式的实现结构
    • 三、SpringBoot 集成
      • 3.1 Maven 依赖
      • 3.2 定义状态和事件枚举
      • 3.3 配置状态机
      • 3.4 使用注解实现状态监听器
      • 3.5 业务服务类
      • 3.6 控制器类
      • 3.7 测试结果
    • 四、升级:状态机持久化
      • 4.1 Maven 依赖
      • 4.2 自定义持久化类
      • 4.3 编写状态机工具类
      • 4.4 修改业务调用
      • 4.5 测试结果

  • 官网地址:https://spring.io/projects/spring-statemachine#learn
  • 官方文档:https://docs.spring.io/spring-statemachine/docs/3.2.1/reference/#statemachine-getting-started

一、Statemachine 简介

Spring Statemachine 是一个由 Spring 团队提供的 轻量级状态机框架,它允许开发者以简便且强大的方式管理复杂的状态流转逻辑。该框架建立在 有限状态机(FSM) 的概念之上,提供了一种简洁且灵活的方式来定义、管理和执行状态机。

1.1 核心概念

  • 状态(State):系统可能处于的不同条件或模式,是状态机的核心组成单元。
  • 事件(Event):触发状态转换的动作或消息,是引起状态机从当前状态迁移到新状态的原因。
  • 转换(Transition):描述了在何种条件下,当接收到特定事件时,系统可以从一个状态转移到另一个状态。
  • 动作(Action):在状态转换时执行的具体操作。

1.2 主要特性

Spring Statemachine 提供了丰富的功能特性:

  • 易于使用的平面(一级)状态机,适用于简单的用例;
  • 分层状态结构,以简化复杂的状态配置;
  • 状态机区域提供更复杂的状态配置;
  • 触发器、转换、守卫和动作的使用;
  • 类型安全的配置适配器;
  • 状态机事件监听器;
  • Spring IoC 集成将 Bean 与 状态机 相关联。

1.3 注解驱动开发方式

为了简化开发,可以使用 Statemachine 的 注解驱动开发方式,特别是 @OnTransition@OnTransitionStart@OnTransitionEnd@WithStateMachine 注解的使用,这些注解能够让我们以更加 声明式简介 的方式处理状态转换逻辑。

注解驱动的开发方式具有以下优势:

  • 代码简洁性:将状态转换逻辑直接注解在方法上,减少模板代码;
  • 关注点分离:业务逻辑与状态机配置清晰分离,提高可维护性;
  • 类型安全:编译时检查注解的正确性,减少运行时错误;
  • 可读性强:通过注解直观地表达状态转换的意图。

1.4 核心注解详解

1)@WithStateMachine 注解

@WithStateMachine 注解用于标识一个类是与状态机相关的监听器,它告诉 Spring 这个类中的方法需要接受状态机的事件通知。

  • 使用场景:标记状态机监听器类,是类中的状态转换注解生效。
2)@OnTransition 注解

@OnTrasition 注解用于标记在状态转换发生时执行的方法,它不区分转换的开始和结束。

  • 使用场景:当不关心转换的具体阶段,只需要在转换发生时执行某些逻辑时使用。
3)@OnTransitionStart 注解

@OnTransitionStart 注解用于标记在状态转换 开始时 执行的方法。

  • 使用场景:需要在状态转换刚开始时执行与处理逻辑,如参数验证、资源准备等。
4)@OnTransitionEnd 注解

@OnTransitionEnd 注解用于标记在状态转换 结束时 执行的方法。

  • 使用场景:需要在状态转换完成后执行清理逻辑、记录日志、发送通知等。

1.5 包含的模块

Spring Statemachine 包含的模块如下:

模块描述
spring-statemachine-coreSpring Statemachine的核心系统。
spring-statemachine-recipes-common不需要核心框架之外的依赖项的常见配方。
spring-statemachine-kryoKryoSpring Statemachine的序列化程序。
spring-statemachine-data-commonSpring Data的通用支持模块。
spring-statemachine-data-jpa支持Spring Data JPA模块。
spring-statemachine-data-redis支持Spring Data Redis模块。
spring-statemachine-data-mongodb支持Spring Data MongoDB模块。
spring-statemachine-zookeeper分布式状态机的Zooeman集成。
spring-statemachine-test状态机测试支持模块。
spring-statemachine-clusterSpring Cloud Cluster的支持模块。请注意,Spring Cloud Cluster已被Spring Integration取代。
spring-statemachine-uml使用Eclipse Papyrus进行UI UML建模的支持模块。
spring-statemachine-autoconfigureSpring Boot的支持模块。
spring-statemachine-bom物料清单pom。
spring-statemachine-starter弹簧启动启动器。

二、知识回顾——状态模式

2.1 什么是状态模式?

状态模式(State Pattern) 是一种 行为型 设计模式,对有状态的对象,把复杂的 “判断逻辑” 提取到不同的状态对象中,允许状态对象在其内部状态发生改变时,改变其行为。

2.2 状态模式的优缺点

状态模式的优点:

  1. 结构清晰 :状态模式将与特定状态相关的行为局部化道一个状态中,并且将不同状态的行为分割开来,满足 “但一职责原则”。
  2. 将状态转换显示化:减少对象间的相互依赖,将不同的状态引入独立的对象中会是的状态转换变得更加明确,且减少对相见的相互依赖。
  3. 状态类职责明确:有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。

状态模式的缺点:

  1. 状态模式的使用必然会增加系统的类与对象的个数。
  2. 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
  3. 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。

2.3 状态模式的实现结构

状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。现在我们来分析其基本结构和实现方法。

状态模式主要包含三个角色:

  • Context(环境类):定义客户端感兴趣的接口,维护一个 State 子类的实例,这个示例定义当前状态。
  • State(抽象状态类):定义一个接口,用以封装 Context 的特定状态相关的行为。
  • ConcreteState(具体状态类):每一个子类实现一个与 Context 的一个状态相关的行为。

三、SpringBoot 集成

项目结构如下:

3.1 Maven 依赖

对于 SpringBoot 2.x 项目,可以使用 2.x 版本的 StateMachine 依赖:

<dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-starter</artifactId><version>2.2.3.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-kryo</artifactId><version>2.2.3.RELEASE</version>
</dependency>

3.2 定义状态和事件枚举

首先,我们需要定义状态机和事件的所有可能值。以订单系统为例:

OrderStatesEnum.javaOrderEventsEnum.java

public enum OrderStatesEnum {UNPAID,                 // 待支付WAITING_FOR_RECEIVE,    // 待收货DONE,                   // 完成CANCELLED               // 取消
}public enum OrderEventsEnum {PAY,                    // 支付RECEIVE,                // 收货CANCEL                  // 取消
}

3.3 配置状态机

接下来,我们需要配置状态机,定义状态转换规则:

StateMachineConfig.java

import com.demo.enums.OrderEventsEnum;
import com.demo.enums.OrderStatesEnum;
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 StateMachineConfig extends StateMachineConfigurerAdapter<OrderStatesEnum, OrderEventsEnum> {@Overridepublic void configure(StateMachineStateConfigurer<OrderStatesEnum, OrderEventsEnum> states) throws Exception {states.withStates().initial(OrderStatesEnum.UNPAID).states(EnumSet.allOf(OrderStatesEnum.class));}@Overridepublic void configure(StateMachineTransitionConfigurer<OrderStatesEnum, OrderEventsEnum> transitions) throws Exception {transitions.withExternal().source(OrderStatesEnum.UNPAID).target(OrderStatesEnum.WAITING_FOR_RECEIVE).event(OrderEventsEnum.PAY).and().withExternal().source(OrderStatesEnum.WAITING_FOR_RECEIVE).target(OrderStatesEnum.DONE).event(OrderEventsEnum.RECEIVE).and().withExternal().source(OrderStatesEnum.UNPAID).target(OrderStatesEnum.CANCELLED).event(OrderEventsEnum.CANCEL);}
}

3.4 使用注解实现状态监听器

这段代码展示如何使用注解来监听状态转换:

OrderStateListener.java

import com.demo.enums.OrderEventsEnum;
import com.demo.enums.OrderStatesEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.statemachine.StateContext;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.OnTransitionEnd;
import org.springframework.statemachine.annotation.OnTransitionStart;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.stereotype.Component;@Slf4j
@Component
@WithStateMachine(name = "orderStateMachine")
public class OrderStateListener {/*** 支付转换开始时的处理*/@OnTransitionStart(source = "UNPAID", target = "WAITING_FOR_RECEIVE")public void onPayStart(StateContext<OrderStatesEnum, OrderEventsEnum> context) {log.info("【支付转换开始】开始处理支付逻辑");// 获取转换相关的数据Object paymentData = context.getMessageHeader("paymentData");if (paymentData != null) {log.info("支付数据:{}", paymentData);}// 执行支付前的验证逻辑log.info("验证支付参数...");log.info("检查库存...");log.info("预扣库存...");}/*** 支付转换结束时的处理*/@OnTransitionEnd(source = "UNPAID", target = "WAITING_FOR_RECEIVE")public void onPayEnd(StateContext<OrderStatesEnum, OrderEventsEnum> context) {log.info("【支付转换结束】支付处理完成");// 执行支付后的清理逻辑log.info("更新库存...");log.info("生成支付凭证...");log.info("发送支付成功通知...");// 记录转换耗时Long startTime = (Long) context.getMessageHeader("startTime");if (startTime != null) {long duration = System.currentTimeMillis() - startTime;log.info("支付处理耗时:{}ms", duration);}}/*** 收货转换开始时的处理*/@OnTransitionStart(source = "WAITING_FOR_RECEIVE", target = "DONE")public void onReceiveStart() {log.info("【收货转换开始】开始确认收货");log.info("验证收货权限...");log.info("检查物流信息...");}/*** 收货转换结束时的处理*/@OnTransitionEnd(source = "WAITING_FOR_RECEIVE", target = "DONE")public void onReceiveEnd() {log.info("【收货转换结束】收货确认完成");log.info("更新订单完成时间...");log.info("计算商家评分...");log.info("发送订单完成通知...");}/*** 取消订单转换开始时的处理*/@OnTransitionStart(source = "UNPAID", target = "CANCELLED")public void onCancelStart(StateContext<OrderStatesEnum, OrderEventsEnum> context) {log.info("【取消转换开始】开始取消订单");String cancelReason = context.getMessageHeaders().get("cancelReason", String.class);log.info("取消原因: {}", cancelReason);log.info("验证取消权限...");}/*** 取消订单转换结束时的处理*/@OnTransitionEnd(source = "UNPAID", target = "CANCELLED")public void onCancelEnd() {log.info("【取消转换结束】订单取消完成");log.info("释放库存...");log.info("发送取消通知...");log.info("记录取消日志...");}/*** 通用的状态转换处理(不区分开始和结束)*/@OnTransitionpublic void onAnyTransition() {log.info("【通用转换】状态发生变化");}/*** 从任意状态到指定状态的转换结束处理*/@OnTransitionEnd(target = "DONE")public void onTransitionToDone() {log.info("【到达完成状态】订单流程结束");log.info("执行订单完成后的统计任务...");log.info("更新用户积分...");}
}

3.5 业务服务类

在业务服务类中使用状态机:

OrderService.java

import com.demo.enums.OrderStatesEnum;import java.util.Map;public interface OrderService {/*** 处理支付*/void payOrder(String orderId, Map<String, Object> paymentData);/*** 确认收货*/void confirmReceive(String orderId);/*** 取消订单*/void cancelOrder(String orderId, String reason);/*** 获取当前状态*/OrderStatesEnum getCurrentState();
}

OrderServiceImpl.java

import com.demo.enums.OrderEventsEnum;
import com.demo.enums.OrderStatesEnum;
import com.demo.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.state.State;
import org.springframework.stereotype.Service;import java.util.HashMap;
import java.util.Map;@Slf4j
@Service
public class OrderServiceImpl implements OrderService {@Autowiredprivate StateMachine<OrderStatesEnum, OrderEventsEnum> stateMachine;@Overridepublic void payOrder(String orderId, Map<String, Object> paymentData) {log.info("开始处理订单任务状态事件,orderId: {},paymentData:{}", orderId, paymentData);// 设置消息头,传递业务数据Map<String, Object> headers = new HashMap<>();headers.put("orderId", orderId);headers.put("paymentData", paymentData);headers.put("startTime", System.currentTimeMillis());sendStateMachineEvent(OrderStatesEnum.UNPAID, OrderEventsEnum.PAY, headers);}@Overridepublic void confirmReceive(String orderId) {log.info("开始处理确认收货状态事件,orderId: {}", orderId);Map<String, Object> headers = new HashMap<>();headers.put("orderId", orderId);sendStateMachineEvent(OrderStatesEnum.WAITING_FOR_RECEIVE, OrderEventsEnum.RECEIVE, headers);}@Overridepublic void cancelOrder(String orderId, String reason) {log.info("开始处理取消订单状态事件,orderId: {},reason:{}", orderId, reason);Map<String, Object> headers = new HashMap<>();headers.put("orderId", orderId);headers.put("cancelReason", reason);sendStateMachineEvent(OrderStatesEnum.UNPAID, OrderEventsEnum.CANCEL, headers);}/*** 发送状态机事件的通用方法* @param currentState 当前状态* @param event 要发送的事件* @param headers 消息头数据*/private void sendStateMachineEvent(OrderStatesEnum currentState, OrderEventsEnum event, Map<String, Object> headers) {// 启动状态机stateMachine.start();// 根据当前任务状态设置状态机状态log.info("当前任务状态: {}", currentState);// 将状态机的状态设置为业务对象的实际状态stateMachine.getStateMachineAccessor().doWithAllRegions(accessor -> {accessor.resetStateMachine(new org.springframework.statemachine.support.DefaultStateMachineContext<>(currentState, null, null, null));});// 构建并发送消息Message<OrderEventsEnum> message = MessageBuilder.withPayload(event).copyHeaders(headers).build();stateMachine.sendEvent(message);}@Overridepublic OrderStatesEnum getCurrentState() {State<OrderStatesEnum, OrderEventsEnum> state = stateMachine.getState();return state == null ? null : state.getId();}
}

3.6 控制器类

提供 REST API 接口:

OrderController.java

import com.demo.common.Result;
import com.demo.enums.OrderStatesEnum;
import com.demo.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.Map;/*** <p> @Title DemoController* <p> @Description 测试Controller** @author ACGkaka* @date 2023/4/24 18:02*/
@Slf4j
@RestController
@RequestMapping("/orders")
public class OrderController {@Autowiredprivate OrderService orderService;@PostMapping("/{orderId}/pay")public Result<Object> payOrder(@PathVariable String orderId,@RequestBody Map<String, Object> paymentData) {try {orderService.payOrder(orderId, paymentData);return Result.succeed("支付处理中");} catch (Exception e) {log.error(e.getMessage(), e);return Result.failed("支付失败:" + e.getMessage());}}@PostMapping("/{orderId}/receive")public Result<Object> receiveOrder(@PathVariable String orderId) {try {orderService.confirmReceive(orderId);return Result.succeed("收货确认处理中");} catch (Exception e) {log.error(e.getMessage(), e);return Result.failed("收货确认失败:" + e.getMessage());}}@PostMapping("/{orderId}/cancel")public Result<Object> cancelOrder(@PathVariable String orderId,@RequestParam String reason) {try {orderService.cancelOrder(orderId, reason);return Result.succeed("取消订单处理中");} catch (Exception e) {log.error(e.getMessage(), e);return Result.failed("取消订单失败:" + e.getMessage());}}@GetMapping("/{orderId}/status")public Result<OrderStatesEnum> getOrderStatus() {try {Result<OrderStatesEnum> result = new Result<>();OrderStatesEnum currentState = orderService.getCurrentState();return result.setData(currentState);} catch (Exception e) {log.error(e.getMessage(), e);return Result.failed("获取订单状态失败:" + e.getMessage());}}
}

3.7 测试结果

测试1:订单-支付订单接口

  • 请求地址:http://localhost:8080/orders/1/pay
  • 请求截图:

在这里插入图片描述

  • 日志打印:

在这里插入图片描述

测试2:订单-收货确认接口

  • 请求地址:http://localhost:8080/orders/1/receive
  • 请求截图:

在这里插入图片描述

  • 日志打印:

在这里插入图片描述

测试3:订单-取消订单接口

  • 请求地址:http://localhost:8080/orders/1/cancel?reason=收货地址填写有误
  • 请求截图:

在这里插入图片描述

  • 日志打印:

在这里插入图片描述

测试4:查询状态接口

  • 请求地址:http://localhost:8080/orders/1/status
  • 请求截图:

在这里插入图片描述

问题点:所有订单共享一个状态

虽然已经完成了状态机的基础操作,但是这里会发现一个问题:整个状态机只有一个状态。也就是说不管是哪个订单的状态都是一样的,那么有一个订单的状态为 DONE 的话,其余所有订单都走不了流程了,只能重启程序才能还原。

这肯定不行,所以就需要 将状态进行持久化,根据订单编号分别保存各自的状态


四、升级:状态机持久化

使用 spring-statemachine 状态机持久化时,可以通过内存、spring-statemachine-redis 或 spring-statemachine-data-jpa 现有方式进行持久化处理。

因项目状态变化操作记录频繁,数据量大,使用 内存 或 spring-statemachine-redis 模式不可取,而项目使用的是 MyBatis,使用 spring-statemachine-data-jpa 也不合适,需要自定义实现。

项目结构如下:

4.1 Maven 依赖

<!-- Statemachine -->
<dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-starter</artifactId><version>2.2.3.RELEASE</version>
</dependency>
<dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-kryo</artifactId><version>2.2.3.RELEASE</version>
</dependency>

4.2 自定义持久化类

CustomStateMachinePersist.java

import com.demo.common.redis.util.RedisUtil;
import com.demo.domain.OrderInfo;
import com.demo.enums.OrderEventsEnum;
import com.demo.enums.OrderStatesEnum;
import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.MessageHeaders;
import org.springframework.statemachine.StateMachineContext;
import org.springframework.statemachine.StateMachinePersist;
import org.springframework.statemachine.kryo.MessageHeadersSerializer;
import org.springframework.statemachine.kryo.StateMachineContextSerializer;
import org.springframework.statemachine.kryo.UUIDSerializer;
import org.springframework.statemachine.persist.DefaultStateMachinePersister;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.UUID;/*** 自定义状态机持久化*/
@Slf4j
@Configuration
public class CustomStateMachinePersist {private static final String REDIS_KEY_PREFIX = "ORDER_STATE_V1_";@Autowiredprivate RedisUtil redisUtil;private static final ThreadLocal<Kryo> KRYO_THREAD_LOCAL = ThreadLocal.withInitial(() -> {Kryo kryo = new Kryo();kryo.addDefaultSerializer(StateMachineContext.class, new StateMachineContextSerializer<>());kryo.addDefaultSerializer(MessageHeaders.class, new MessageHeadersSerializer());kryo.addDefaultSerializer(UUID.class, new UUIDSerializer());// 设置引用追踪策略kryo.setReferences(true);return kryo;});private <S, E> byte[] serialize(StateMachineContext<S, E> context) {Kryo kryo = KRYO_THREAD_LOCAL.get();// 重置引用状态,避免状态污染kryo.reset();ByteArrayOutputStream out = new ByteArrayOutputStream();Output output = new Output(out);try {kryo.writeObject(output, context);output.flush();return out.toByteArray();} catch (Exception e) {log.error("序列化状态机上下文失败", e);throw new RuntimeException("序列化失败", e);} finally {output.close();}}private <S, E> StateMachineContext<S, E> deserialize(byte[] data) {if (data == null || data.length == 0) {log.info("反序列化数据为空");return null;}Kryo kryo = KRYO_THREAD_LOCAL.get();// 重置引用状态,避免状态污染kryo.reset();ByteArrayInputStream in = new ByteArrayInputStream(data);Input input = new Input(in);try {return kryo.readObject(input, StateMachineContext.class);} catch (IndexOutOfBoundsException e) {log.error("反序列化失败,可能是数据损坏或版本不兼容. 数据长度: {}, 错误: {}",data.length, e.getMessage());throw new RuntimeException("反序列化失败", e);} catch (Exception e) {log.error("反序列化状态机上下文时发生未知错误", e);throw new RuntimeException("反序列化失败", e);} finally {input.close();}}/*** 状态机持久化*/@Beanpublic DefaultStateMachinePersister<OrderStatesEnum, OrderEventsEnum, OrderInfo> stateMachinePersister(){return new DefaultStateMachinePersister<>(new StateMachinePersist<OrderStatesEnum, OrderEventsEnum, OrderInfo>() {@Overridepublic void write(StateMachineContext<OrderStatesEnum, OrderEventsEnum> context, OrderInfo info) throws Exception {String key = REDIS_KEY_PREFIX + info.getId();try {byte[] value = serialize(context);log.info("正在写入任务 {} 的状态机上下文,状态为 {}", info.getId(), context.getState());redisUtil.set(key, value);log.info("任务 {} 的状态机上下文已成功写入Redis", info.getId());} catch (Exception e) {log.error("写入任务 {} 的状态机上下文失败", info.getId(), e);throw e;}}@Overridepublic StateMachineContext<OrderStatesEnum, OrderEventsEnum> read(OrderInfo info) throws Exception {String key = REDIS_KEY_PREFIX + info.getId();log.info("正在读取任务 {} 的状态机上下文", info.getId());try {byte[] value = (byte[]) redisUtil.get(key);if (value == null) {log.info("未找到任务 {} 的状态机上下文", info.getId());return null;}StateMachineContext<OrderStatesEnum, OrderEventsEnum> context = deserialize(value);if (context == null) {log.error("反序列化任务 {} 的状态机上下文失败,可能需要重新初始化", info.getId());// 清理损坏的数据redisUtil.delete(key);return null;}log.info("已从Redis读取任务 {} 的状态机上下文,状态为 {}", info.getId(), context.getState());return context;} catch (Exception e) {log.error("读取任务 {} 的状态机上下文时发生错误", info.getId(), e);throw e;}}});}
}

4.3 编写状态机工具类

CustomStateMachineUtil.java

import com.demo.enums.OrderStatesEnum;
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.Component;import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;@Slf4j
@Component
public class CustomStateMachineUtil<S, E, T> {@Resourceprivate StateMachine<S, E> orderStateMachine;@Resourceprivate StateMachinePersister<S, E, T> orderStateMachinePersister;/*** 发送状态机事件的通用方法* @param currentState 当前状态* @param event 要发送的事件* @param info 消息实体*/public synchronized void sendEvent(S currentState, E event, T info) {log.info("开始处理状态机事件: {}", info);try {// 启动状态机orderStateMachine.start();// 设置消息头,传递业务数据Map<String, Object> headers = new HashMap<>();headers.put("info", info);headers.put("startTime", System.currentTimeMillis());// 根据当前状态设置状态机状态log.info("当前状态: {}", currentState);// 方式一:从Redis恢复状态机状态orderStateMachinePersister.restore(orderStateMachine, info);// 方式二:将状态机的状态设置为业务对象的实际状态//        stateMachine.getStateMachineAccessor().doWithAllRegions(accessor -> {//            accessor.resetStateMachine(new org.springframework.statemachine.support.DefaultStateMachineContext<>(//                    currentState, null, null, null));//        });// 构建并发送消息Message<E> message = MessageBuilder.withPayload(event).copyHeaders(headers).build();orderStateMachine.sendEvent(message);// 持久化状态机状态boolean persistSuccess = persist(info);if (!persistSuccess) {throw new RuntimeException("状态机持久化状态失败");}} catch (RuntimeException e) {throw e;} catch (Exception e) {log.error("状态机发送事件失败. 事件: {}, 异常: {}", info, e.getMessage(), e);throw new RuntimeException("状态机发送事件失败");} finally {if (Objects.nonNull(info)) {log.info("当前状态: {}", currentState);if (Arrays.asList(OrderStatesEnum.DONE, OrderStatesEnum.CANCELLED).contains(currentState)) {log.info("已完成或已取消,停止状态机");orderStateMachine.stop();}}}}/*** 持久化状态机状态* @param info  实体* @return      是否持久化成功*/public synchronized boolean persist(T info) {try {log.info("持久化状态机开始,此时状态: {}", orderStateMachine.getState().getId());// 启用持久化:将状态机状态持久化到Redislog.info("持久化已启用,将状态机状态持久化到Redis");orderStateMachinePersister.persist(orderStateMachine, info);// 不启用持久化:跳过持久化步骤
//            log.info("持久化未启用,跳过持久化步骤");return true;} catch (Exception e) {log.error("持久化状态机状态失败. 异常: {}", e.getMessage(), e);return false;}}
}

4.4 修改业务调用

OrderServiceImpl.java

import com.demo.common.stashmachine.util.CustomStateMachineUtil;
import com.demo.domain.OrderInfo;
import com.demo.enums.OrderEventsEnum;
import com.demo.enums.OrderStatesEnum;
import com.demo.service.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.persist.StateMachinePersister;
import org.springframework.statemachine.state.State;
import org.springframework.stereotype.Service;@Slf4j
@Service
public class OrderServiceImpl implements OrderService {@Autowiredprivate StateMachine<OrderStatesEnum, OrderEventsEnum> orderStateMachine;@Autowiredprivate StateMachinePersister<OrderStatesEnum, OrderEventsEnum, OrderInfo> orderStateMachinePersister;@Autowiredprivate CustomStateMachineUtil<OrderStatesEnum, OrderEventsEnum, OrderInfo> customStateMachineUtil;@Overridepublic void payOrder(String orderId) {log.info("开始处理订单任务状态事件,orderId: {}", orderId);// 获取订单信息OrderInfo orderInfo = new OrderInfo();orderInfo.setId(orderId);orderInfo.setState(OrderStatesEnum.UNPAID);customStateMachineUtil.sendEvent(orderInfo.getState(), OrderEventsEnum.PAY, orderInfo);}@Overridepublic void confirmReceive(String orderId) {log.info("开始处理确认收货状态事件,orderId: {}", orderId);// 获取订单信息OrderInfo orderInfo = new OrderInfo();orderInfo.setId(orderId);orderInfo.setState(OrderStatesEnum.WAITING_FOR_RECEIVE);customStateMachineUtil.sendEvent(orderInfo.getState(), OrderEventsEnum.RECEIVE, orderInfo);}@Overridepublic void cancelOrder(String orderId, String reason) {log.info("开始处理取消订单状态事件,orderId: {},reason:{}", orderId, reason);// 获取订单信息OrderInfo orderInfo = new OrderInfo();orderInfo.setId(orderId);orderInfo.setState(OrderStatesEnum.UNPAID);orderInfo.setReason(reason);customStateMachineUtil.sendEvent(orderInfo.getState(), OrderEventsEnum.CANCEL, orderInfo);}@Overridepublic OrderStatesEnum getCurrentState(String orderId) throws Exception {OrderInfo info = new OrderInfo();info.setId(orderId);// 从Redis恢复状态机状态orderStateMachinePersister.restore(orderStateMachine, info);State<OrderStatesEnum, OrderEventsEnum> state = orderStateMachine.getState();return state == null ? null : state.getId();}
}

4.5 测试结果

这次升级之后,再次调用接口可以发现,不同的订单编号已经可以分别存储不同的状态了,Redis 缓存内容如下:

在这里插入图片描述

整理完毕,完结撒花~🌻





参考地址:

1.SpringBoot集成spring-statemachine状态机实现业务流程,https://blog.csdn.net/weixin_37598243/article/details/140907763

2.spring-statemachine 状态机自定义持久化入库,https://blog.csdn.net/sjy_2010/article/details/133862831

3.SpringBoot集成Spring Statemachine(状态机)完整示例,https://juejin.cn/post/7441760738458779684

整理完毕,完结撒花~🌻

http://www.dtcms.com/a/573385.html

相关文章:

  • 网站制作教程手机杭州酒店网站设计公司推荐
  • 【设计题】如何实现限流器
  • 场外衍生品架构解析:TRS收益互换与场外个股期权的技术实现
  • 小程序定制开发实战:需求拆解、UI 设计与个性化功能落地流程
  • MATLAB基于变权理论和灰色云模型的海岛旅游地生态安全评价
  • 威联通nas 做网站长沙装修公司名单
  • 机器学习中的 fit()、transform() 与 fit_transform():原理、用法与最佳实践
  • 旅游景区网站建设的必要性织梦论坛
  • 【YashanDB认证】之三:用Docker制作YMP容器
  • 图文生视频的原理与应用
  • Java Spring Boot 项目 Docker 容器化部署教程
  • YOLOv8 模型 NMS 超时问题解决方案总结
  • 苏州网站设计公司有哪些行业网站导航
  • 福建外贸网站dw做网站注册页代码
  • VBA信息获取与处理专题五第三节:发送带附件的电子邮件
  • Linux上kafka部署和使用
  • 天河网站建设策划如何做阿里巴巴的网站
  • 网站建设自主开发的三种方式南充移动网站建设
  • 自动化测试用例的编写和管理
  • 头歌MySQL——数据库与表的基本操作
  • DUOATTENTION:结合检索与流式注意力机制的高效长上下文大语言模型推理方法
  • SAMWISE:为文本驱动的视频分割注入SAM2的智慧
  • Linux 进程状态:内核角度与应用层角度
  • A与非A、综合分析技巧
  • java之jvm堆内存占用问题
  • 江门网站制作设计网站地址栏图标文字
  • 做游戏网站多少钱网站做好了怎么上线
  • taro UI 的icon和自定义iconfont的icon冲突
  • 【开发】Git处理分支的指令
  • Linux 进程的写时拷贝(Copy-On-Write, COW)详解