前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring-statemachine实现订单状态机

Spring-statemachine实现订单状态机

作者头像
才疏学浅的木子
发布2023-10-17 08:39:46
9050
发布2023-10-17 08:39:46
举报
文章被收录于专栏:CSDN文章

状态机简介

先从状态机的定义入手,StateMachine,其中:

  • StateMachine:状态机模型
  • state:S-状态,一般定义为一个枚举类,如创建、待风控审核、待支付等状态
  • event:E-事件,同样定义成一个枚举类,如订单创建、订单审核、支付等,代表一个动作。
  • 一个状态机的定义就由这两个主要的元素组成,状态及对对应的事件(动作)。

状态机的相关概念

  • Transition: 节点,是组成状态机引擎的核心
  • source:节点的当前状态
  • target:节点的目标状态
  • event:触发节点从当前状态到目标状态的动作
  • guard:起校验功能,一般用于校验是否可以执行后续action
  • action:用于实现当前节点对应的业务逻辑处理

状态机的持久化

每次用到的时候新创建一个状态机,太奢侈了,官方文档里面也提到过这点。而且创建出来的实例,其状态也跟当前订单的不符;spring statemachine暂时不支持每次创建时指定当前状态,所以对状态机引擎实例的持久化,就成了必须要考虑的问题。

spring statemachine 本身支持了内存、redis及db的持久化,内存持久化就不说了,看源码实现就是放在了hashmap里,平时也没谁项目中可以这么奢侈,啥啥都放在内存中,而且一旦重启…(嘿嘿嘿)。下面详细说下利用redis进行的持久化操作。

spring statemachine持久化时,采用了三层结构设计,persister —>persist —>repository。

  • 其中persister中封装了write和restore两个方法,分别用于持久化写及反序列化读出。
  • persist只是一层皮,主要还是调用repository中的实际实现,这里可以使用
  • repository中做了两件事儿
    • 序列化/反序列化数据,将引擎实例与二进制数组互相转换
    • 读、写redis
Persister

接口

代码语言:javascript
复制
public interface StateMachinePersister<S, E, T> {
    void persist(StateMachine<S, E> var1, T var2) throws Exception;

    StateMachine<S, E> restore(StateMachine<S, E> var1, T var2) throws Exception;
}

接口点进去其抽象类的实现,可以看到使用的是StateMachinePersist的read与write方法

代码语言:javascript
复制
public abstract class AbstractStateMachinePersister<S, E, T> implements StateMachinePersister<S, E, T> {
    private final StateMachinePersist<S, E, T> stateMachinePersist;
    public final void persist(StateMachine<S, E> stateMachine, T contextObj) throws Exception {
        this.stateMachinePersist.write(this.buildStateMachineContext(stateMachine), contextObj);
    }

    public final StateMachine<S, E> restore(StateMachine<S, E> stateMachine, T contextObj) throws Exception {
        final StateMachineContext<S, E> context = this.stateMachinePersist.read(contextObj);
        stateMachine.stop();
        stateMachine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction<StateMachineAccess<S, E>>() {
            public void apply(StateMachineAccess<S, E> function) {
                function.resetStateMachine(context);
            }
        });
        stateMachine.start();
        return stateMachine;
    }
}
Persist
代码语言:javascript
复制
public interface StateMachinePersist<S, E, T> {
    void write(StateMachineContext<S, E> var1, T var2) throws Exception;

    StateMachineContext<S, E> read(T var1) throws Exception;
}

自定义实现

代码语言:javascript
复制
@Component
public class OrderStateMachinePersist implements StateMachinePersist<OrderStatusEnum, OrderChangeEventEnum, PersisterDO> {

    @Autowired
    private TbUserOrderMapper userOrderMapper;

    //将订单状态写入数据库
    @Override
    public void write(StateMachineContext<OrderStatusEnum, OrderChangeEventEnum> stateMachineContext, PersisterDO persisterDO) throws Exception {
        String orderNumber = persisterDO.getOrderNumber();
        if(orderNumber == null){
            throw new RuntimeException("orderNumber 为空");
        }
        QueryWrapper<TbUserOrder> wrapper = new QueryWrapper<>();
        wrapper.eq("order_number",orderNumber);
        TbUserOrder order = userOrderMapper.selectOne(wrapper);
        if(order != null){
            order.setOrderState(stateMachineContext.getState().name());
            order.setUpdateTime(new Date());
            userOrderMapper.updateById(order);
        }
    }

    @Override
    public StateMachineContext<OrderStatusEnum, OrderChangeEventEnum> read(PersisterDO persisterDO) throws Exception {
        //将数据从数据库中读出,然后将订单的状态设置给参数orderStatusEnum
        OrderStatusEnum orderStatusEnum;
        QueryWrapper<TbUserOrder> wrapper = new QueryWrapper<>();
        wrapper.eq("order_number",persisterDO.getOrderNumber());
        TbUserOrder order = userOrderMapper.selectOne(wrapper);
        if(order != null){
            orderStatusEnum = OrderStatusEnum.valueOf(order.getOrderState());
        }else{
            orderStatusEnum = OrderStatusEnum.CREATE;
        }
        return new DefaultStateMachineContext<>(orderStatusEnum, null, null, null, null, persisterDO.getMachineId());
    }
}

状态变迁

状态枚举就是当前订单所处的状态,事件会导致订单的状态发生改变(但是也不一定,有些是内部事件,并不会导致状态发生变化)

状态枚举
代码语言:javascript
复制
public enum OrderStatusEnum {
    CREATE("新建"),
    WAIT_PAYMENT("待付款"),
    WAIT_SEND("待发货"),
    WAIT_RECEIVE("待收货"),
    COMPLETED("已完成"),
    CANCEL("取消");

    private final String name;
}
事件枚举
代码语言:javascript
复制
public enum OrderChangeEventEnum {
    CREATE_ORDER("创建订单"),
    PAY_ORDER("支付订单"),
    PAY_CANCEL("取消支付"),
    SEND_GOODS("发货"),
    RECEIVE_GOODS("收货"),
    ;
    private final String name;
}

状态机的配置

状态机的配置有两种方式

  1. 创建config类,实现StateMachineConfigurer(或者根据SE的不同,直接继承其子类StateMachineConfigurerAdapter、EnumStateMachineConfigurerAdapter),然后分别重写其不同的configure方法,用于指定对应配置。
  2. 依然实现StateMachineConfigurer(或继承其子类),不过通过StateMachineBuilder.builder()来指定对应配置。
自定义Builder

自定义一个Builder接口,用来规范不同业务状态机的配置

代码语言:javascript
复制
public interface IStateMachineBuilder<S, E> {
    String getName();

    StateMachine<S, E> build(BeanFactory beanFactory) throws Exception;

    //订单状态机构造器
    String ORDER_BUILDER_NAME = "orderStateMachineBuilder";
}
设置对应业务Builder

设置对应业务的Builder,外部调用的使用直接使用build()方法就行

这个里面的Guard就可以相当于一个条件,如果不满足Guard(即返回false)那么就不会执行接下来的action

Action就相当于一个执行的过程,其中errorHandlerAction就是对异常的处理,其中比较重要的是

  • withExternal 是当source和target不同时的写法,比如付款成功后状态发生的变化。
  • withInternal 当source和target相同时的串联写法,比如付款失败后都是待付款状态。
  • withExternal的source和target用于执行前后状态、event为触发的事件、guard判断是否执行action。同时满足source、target、event、guard的条件后执行最后的action。
  • withChoice 当执行一个动作,可能导致多种结果时,可以选择使用choice+guard来跳转
  • withChoice根据guard的判断结果执行first/then的逻辑。
  • withChoice不需要发送事件来进行触发。
代码语言:javascript
复制
public class OrderStateMachineBuilder implements IStateMachineBuilder<OrderStatusEnum, OrderChangeEventEnum> {

    @Autowired
    private Guard<OrderStatusEnum, OrderChangeEventEnum> orderCreateGuard;

    @Autowired
    private Action<OrderStatusEnum, OrderChangeEventEnum> errorHandlerAction;

    @Autowired
    private Action<OrderStatusEnum, OrderChangeEventEnum> orderCreateAction;


    @Override
    public StateMachine<OrderStatusEnum, OrderChangeEventEnum> build(BeanFactory beanFactory) throws Exception {
        StateMachineBuilder.Builder<OrderStatusEnum, OrderChangeEventEnum> builder = StateMachineBuilder.builder();
        //设置id
        builder.configureConfiguration()
                .withConfiguration()
                .autoStartup(true)
                .beanFactory(beanFactory)
                .machineId(ORDER_BUILDER_NAME + "Id");

//初始化状态机,并指定状态集合
        builder.configureStates()
                .withStates()
                //初始状态
                .initial(OrderStatusEnum.CREATE)
                .end(OrderStatusEnum.COMPLETED)
                .states(EnumSet.allOf(OrderStatusEnum.class));

        //定义状态机节点,即迁移动作
        builder.configureTransitions()
                .withExternal()
                .source(OrderStatusEnum.CREATE)
                .target(OrderStatusEnum.WAIT_PAYMENT)
                .event(OrderChangeEventEnum.CREATE_ORDER)
                .guard(orderCreateGuard)
                .action(orderCreateAction, errorHandlerAction)
            
            ...这可以使用and()继续写
        ;

        return builder.build();
    }

    @Override
    public String getName() {
        return ORDER_BUILDER_NAME;
    }
}
状态机工厂

在实际项目中一般都会有多个状态机并发执行,比如订单,同一时刻会有不止一个订单在运行,而每个订单都有自己的订单状态机流程。所以如果使用配置类的话就只有一个状态机,所以需要使用Builder,同时因为可以会有多种类型的状态机,所以定义了一个接口,后续类型的状态机只要实现这个状态机接口就可以开发

代码语言:javascript
复制
@Component
public class StateMachineBuildFactory<S, E> implements ApplicationContextAware {

    @Autowired
    private BeanFactory beanFactory;
    /**
     * 用来存储builder-name及builder的map
     */
    public static final Map<String, IStateMachineBuilder> builderMap = new ConcurrentHashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        String[] beanNamesForType = applicationContext.getBeanNamesForType(IStateMachineBuilder.class);
        for (String beanName : beanNamesForType) {
            IStateMachineBuilder bean = (IStateMachineBuilder) applicationContext.getBean(beanName);
            builderMap.put(bean.getName(), bean);
        }
    }

    public StateMachine<S, E> createStateMachine(String machineType) throws Exception{
        if (StringUtils.isBlank(org.apache.commons.lang3.StringUtils.trim(machineType))) {
            throw new RuntimeException("无效的状态机类型");
        }
        IStateMachineBuilder builder = builderMap.get(machineType);
        StateMachine<S, E> stateMachine;
        try {
            stateMachine = builder.build(beanFactory);
        } catch (Exception e) {
            throw new RuntimeException("创建状态机异常");
        }

        return stateMachine;
    }
}

状态机的使用

外部调用状态机需要以下三步

  1. 通过创建/读取的方式获取当前订单对应的状态机引擎实例
  2. 构造message
  3. 发送message

需要注意的是当发送完message之后,spring statemachine才会通过监听器来监听走那个action,只有在message完成之后才会更新为target状态

所以为了更加方便管理使用,所以做了以下代码封装

接口Manager

这规定了这个Manager必须实现的方法,就是来发送message

代码语言:javascript
复制
public interface IStateMachineEventManager<E>{
    /**
     * 状态发生改变变更事件
     * @param statusModelDO
     * @param eventEnum
     * @param <R>
     * @return
     */
    <R> R sendStatusChangeEvent(StatusModelDO statusModelDO, E eventEnum);
}
抽象类

这个里面主要实现初始化状态机,构造message与发送message

代码语言:javascript
复制
public abstract class AbstractStateMachineEventManager<S, E> implements IStateMachineEventManager<E> {
    @Autowired
    private StateMachineBuildFactory<S, E> stateMachineBuildFactory;

    //用于状态机上下文持久化
    public abstract void stateMachinePersist(StateMachine<S, E> stateMachine, PersisterDO persisterDO) throws Exception;

    //用于状态机上下文初始化
    public abstract StateMachine<S, E> stateMachineRestore(StateMachine<S, E> stateMachine, PersisterDO persisterDO) throws Exception;

    @Override
    @SuppressWarnings("unchecked")
    public <R> R sendStatusChangeEvent(StatusModelDO statusModelDO, E eventEnum) {
        StateMachine<S, E> stateMachine = initializeMachine(statusModelDO);
        Boolean result = statusChangeExecute(stateMachine, statusModelDO, eventEnum);
        if (!result) {
            throw new RuntimeException("状态机状态执行失败");
        }
        RuntimeException exception = (RuntimeException) stateMachine.getExtendedState().getVariables().get(RuntimeException.class);
        if (exception != null) {
        }
        try {
            PersisterDO persisterDO = new PersisterDO();
            persisterDO.setOrderNumber(statusModelDO.getOrderNumber());
            stateMachinePersist(stateMachine, persisterDO);
        } catch (Exception e) {
            throw new RuntimeException("状态机持久化失败");
        }

        return (R) stateMachine.getExtendedState().getVariables().get(StateMachineConstants.RETURN_PARAM);
    }

    private StateMachine<S, E> initializeMachine(StatusModelDO statusModelDO) {
        StateMachine<S, E> stateMachine;
        try {
            StateMachine<S, E> srcStateMachine = stateMachineBuildFactory.createStateMachine(getStateMachineType());
            PersisterDO persisterDO = new PersisterDO();
            persisterDO.setOrderNumber(statusModelDO.getOrderNumber());
            persisterDO.setCurrentState(statusModelDO.getCurrentState());
            stateMachine = stateMachineRestore(srcStateMachine, persisterDO);
        } catch (Exception e) {
            throw new RuntimeException("初始化状态机失败");
        }
        if (stateMachine == null) {
            throw new RuntimeException("没有找到可用的状态机");
        }
        return stateMachine;
    }

    private boolean statusChangeExecute(StateMachine<S, E> stateMachine, StatusModelDO statusModelDO, E eventEnum) {
        Message<E> eventMsg = MessageBuilder.withPayload(eventEnum)
                .setHeader(StateMachineConstants.STATE_MODEL_DTO, statusModelDO).build();
        if (!acceptEvent(stateMachine, eventMsg)) {
            throw new RuntimeException("找不到对应状态机事件触发定义");
        }

        return stateMachine.sendEvent(eventMsg);
    }
    private static <S, E> boolean acceptEvent(StateMachine<S, E> stateMachine, Message<E> eventMsg) {
        //获取当前状态
        State<S, E> currentState = stateMachine.getState();

        for (Transition<S, E> transition : stateMachine.getTransitions()) {
            State<S, E> source = transition.getSource();
            Trigger<S, E> trigger = transition.getTrigger();
            if (currentState != null && trigger != null &&
                    StateMachineUtils.containsAtleastOne(source.getIds(), currentState.getIds()) &&
                    trigger.evaluate(new DefaultTriggerContext<>(eventMsg.getPayload()))) {
                return true;
            }
        }

        return false;
    }

    public abstract String getStateMachineType();
}
实现类

这里主要实现的是上下文的初始化与持久化,这个就可以直接调用状态机的持久化里面的read与write就行

代码语言:javascript
复制
@Component
public class OrderStateMachineEventManager extends AbstractStateMachineEventManager<OrderStatusEnum, OrderChangeEventEnum> {
    @Resource(name = "orderStateMachinePersister")
    private StateMachinePersister<OrderStatusEnum, OrderChangeEventEnum, PersisterDO> stateMachinePersister;

    @Override
    public void stateMachinePersist(StateMachine<OrderStatusEnum, OrderChangeEventEnum> stateMachine,
                                    PersisterDO persisterDO) throws Exception {
        stateMachinePersister.persist(stateMachine, persisterDO);
    }

    @Override
    public StateMachine<OrderStatusEnum, OrderChangeEventEnum> stateMachineRestore(
            StateMachine<OrderStatusEnum, OrderChangeEventEnum> stateMachine,
            PersisterDO persisterDO) throws Exception {
        return stateMachinePersister.restore(stateMachine, persisterDO);
    }

    @Override
    public String getStateMachineType() {
        return IStateMachineBuilder.ORDER_BUILDER_NAME;
    }
}

参考文档

Github代码

状态机引擎选型

官网

reference

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-07-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 状态机简介
  • 状态机的持久化
    • Persister
      • Persist
      • 状态变迁
        • 状态枚举
          • 事件枚举
          • 状态机的配置
            • 自定义Builder
              • 设置对应业务Builder
                • 状态机工厂
                • 状态机的使用
                  • 接口Manager
                    • 抽象类
                      • 实现类
                      • 参考文档
                      相关产品与服务
                      数据库
                      云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档