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

松鼠状态机流转-@Transit

疑问

状态from to合法性校验,都是在代码中手动进行的吗,不是状态机自动进行的?

注解中from状态,代表当前状态

和谁校验:上下文中初始状态

怎么根据注解找到执行方法的

分析代码,创建运单,怎么关联到注解 @Transit(from = "unsigned", to = "unsigned", on = "CREATE"),的,怎么根据注解找到执行方法的?

package com.smcv.mall.tasks.statemachine;import com.alibaba.fastjson.JSON;
import com.smcv.mall.api.tasks.model.JghcTaskDTO.JghcTaskCreateForDP;
import com.smcv.mall.tasks.constant.ErrConstant;
import com.smcv.mall.tasks.ddd.domain.impl.TaskStatusTriggerService;
import com.smcv.mall.tasks.ddd.domain.inf.IDPStatusService;
import com.smcv.mall.tasks.exception.YlTaskException;
import com.smcv.mall.tasks.model.dto.DPTaskStatusChangeReqDTO;
import com.smcv.mall.tasks.statemachine.constant.DPEventEnum;
import com.smcv.mall.tasks.statemachine.constant.DPStateEnum;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.squirrelframework.foundation.fsm.annotation.StateMachineParameters;
import org.squirrelframework.foundation.fsm.annotation.Transit;
import org.squirrelframework.foundation.fsm.annotation.Transitions;
import org.squirrelframework.foundation.fsm.impl.AbstractUntypedStateMachine;/**
*/
@Transitions({
//创建运单
@Transit(from = "unsigned", to = "unsigned", on = "CREATE"),/*** 未指派 - 执行中* 1.扫码抢单* 2.派单员指派* 3.自提单*/@Transit(from = "unsigned", to = "executing", on = "ASSIGN_EXCUTING", callMethod = "assignExcuting"),/*** 已指派 - 执行中* 1.扫码抢单* 2.派单员指派*/@Transit(from = "signed", to = "executing", on = "ASSIGN_EXCUTING", callMethod = "assignExcuting"),/*** 未指派 - 已指派* 1.超时指派* 2.司机抢单*/@Transit(from = "unsigned", to = "signed", on = "ASSIGN_SIGNED"),//执行运单@Transit(from = "signed", to = "executing", on = "EXECUTING"),//前往途经点@Transit(from = "waitStart", to = "toStart", on = "CHILD_TOSTART", callMethod = "childTostart"),//前往途经点 兼容老版本@Transit(from = "contacted", to = "toStart", on = "CHILD_TOSTART", callMethod = "childTostart"),//到达途经点@Transit(from = "toStart", to = "toEnd", on = "CHILD_TOEND"),//待出发 - 到达途经点@Transit(from = "waitStart", to = "toEnd", on = "CHILD_WAITTOEND"),//前往下一个途经点@Transit(from = "toEnd", to = "toNextStart", on = "CHILD_TONEXTSTART", callMethod = "childTonextstart"),//前往下一个途经点  兼容老版本@Transit(from = "payed", to = "toNextStart", on = "CHILD_TONEXTSTART", callMethod = "childTonextstart"),//运单完成@Transit(from = "executing", to = "finished", on = "FINISH"),//运单取消@Transit(from = "unsigned", to = "canceled", on = "CANCEL", callMethod = "cancel"),@Transit(from = "signed", to = "canceled", on = "CANCEL", callMethod = "cancel"),@Transit(from = "executing", to = "canceled", on = "CANCEL", callMethod = "cancel"),@Transit(from = "canceled", to = "canceled", on = "CANCEL", callMethod = "cancel"),//幂等性
})
@StateMachineParameters(stateType = DPStateEnum.class, eventType = DPEventEnum.class, contextType = DPContext.class)
@Slf4j(topic = "task")
public class DPStateMachine extends AbstractUntypedStateMachine {private static final String LOG_PRE = "【{}】地跑状态流转,orderNumber=[{}],fromState=[{}],toState=[{}],event=[{}],context=[{}]";protected ApplicationContext applicationContext;private IDPStatusService idpStatusService;/*** 初始化时候会调用,类似后置处理器** @param applicationContext*/
public void postConstruct(ApplicationContext applicationContext) {this.applicationContext = applicationContext;
}/*** 开始执行之前的方法** @param fromState* @param event* @param context*/
@Override
protected void beforeTransitionBegin(Object fromState, Object event, Object context) {log.info(LOG_PRE, Thread.currentThread().getStackTrace()[1].getMethodName(), ((DPContext) context).getOrderNumber(), fromState, null, event, context);//获取状态机服务类idpStatusService = (IDPStatusService) ((DPContext) context).getIStatusService();
}/*** 事件触发之前被调用** @param fromState* @param toState* @param event* @param context*/
@Override
protected void beforeActionInvoked(Object fromState, Object toState, Object event, Object context) {log.info(LOG_PRE, Thread.currentThread().getStackTrace()[1].getMethodName(), ((DPContext) context).getOrderNumber(), fromState, toState, event, context);/*** 状态合理性校验*/DPContext dpContext = (DPContext) context;if (!dpContext.isReentrantLock()) {//重入锁,不需要状态合理性校验DPEventEnum dpEventEnum = (DPEventEnum) event;((TaskStatusTriggerService) dpContext.getIStatusService()).checkFromState(fromState, toState, context, dpEventEnum);}
}/*** 非法状态流转** @param fromState* @param event* @param context*/
@Override
protected void afterTransitionDeclined(Object fromState, Object event, Object context) {log.info(LOG_PRE, Thread.currentThread().getStackTrace()[1].getMethodName(), ((DPContext) context).getOrderNumber(), fromState, null, event, context);throw new YlTaskException(ErrConstant.BUSI_RETURN_ERR, "状态流转异常");
}/*** 正常执行完毕后执行的方法** @param fromState* @param toState* @param event* @param context*/
@Override
protected void afterTransitionCompleted(Object fromState, Object toState, Object event, Object context) {log.info(LOG_PRE, Thread.currentThread().getStackTrace()[1].getMethodName(), ((DPContext) context).getOrderNumber(), fromState, toState, event, context);
}/*** 执行异常** @param fromState* @param toState* @param event* @param context*/
@SneakyThrows
@Override
protected void afterTransitionCausedException(Object fromState, Object toState, Object event, Object context) {log.info(LOG_PRE, Thread.currentThread().getStackTrace()[1].getMethodName(), ((DPContext) context).getOrderNumber(), fromState, toState, event, context);//此处不调用父类方法,异常不会被抛出//super.afterTransitionCausedException(fromState, toState, event, context);if (getLastException().getTargetException() != null) {//重写父类方法,向外抛出自定义异常throw getLastException().getTargetException();} else {super.afterTransitionCausedException(fromState, toState, event, context);}
}/*** 状态机执行完毕必须执行的方法,不管成功/失败与否** @param fromState* @param toState* @param event* @param context*/
@Override
protected void afterTransitionEnd(Object fromState, Object toState, Object event, Object context) {log.info(LOG_PRE, Thread.currentThread().getStackTrace()[1].getMethodName(), ((DPContext) context).getOrderNumber(), fromState, toState, event, context);super.afterTransitionEnd(fromState, toState, event, context);
}/*** 创建运单** @param fromState* @param toState* @param dpEventEnum* @param dpContext*/
public void transitFromunsignedTounsignedOnCREATE(DPStateEnum fromState, DPStateEnum toState, DPEventEnum dpEventEnum, DPContext dpContext) {log.info(LOG_PRE, Thread.currentThread().getStackTrace()[1].getMethodName(), dpContext.getOrderNumber(), fromState, toState, dpEventEnum, dpContext);JghcTaskCreateForDP jghcTaskCreateForDP = dpContext.getJghcTaskCreateForDP();jghcTaskCreateForDP.setServiceCode(dpContext.getCreateChannel());idpStatusService.createTask(jghcTaskCreateForDP);
}/*** 指派运单[状态流转到执行中]** @param fromState* @param toState* @param dpEventEnum* @param dpContext*/
public void assignExcuting(DPStateEnum fromState, DPStateEnum toState, DPEventEnum dpEventEnum, DPContext dpContext) {log.info(LOG_PRE, Thread.currentThread().getStackTrace()[1].getMethodName(), dpContext.getOrderNumber(), fromState, toState, dpEventEnum, dpContext);DPTaskStatusChangeReqDTO dpTaskStatusChangeReqDTO = dpContext.getDpTaskStatusChangeReqDTO();dpTaskStatusChangeReqDTO.setFromState(fromState);dpTaskStatusChangeReqDTO.setToState(toState);idpStatusService.assignExcuting(dpTaskStatusChangeReqDTO);
}/*** 指派运单[状态流转到已指派]** @param fromState* @param toState* @param dpEventEnum* @param dpContext*/
public void transitFromunsignedTosignedOnASSIGN_SIGNED(DPStateEnum fromState, DPStateEnum toState, DPEventEnum dpEventEnum, DPContext dpContext) {log.info(LOG_PRE, Thread.currentThread().getStackTrace()[1].getMethodName(), dpContext.getOrderNumber(), fromState, toState, dpEventEnum, dpContext);DPTaskStatusChangeReqDTO dpTaskStatusChangeReqDTO = dpContext.getDpTaskStatusChangeReqDTO();dpTaskStatusChangeReqDTO.setFromState(fromState);dpTaskStatusChangeReqDTO.setToState(toState);idpStatusService.assignSigned(dpTaskStatusChangeReqDTO);
}/*** 执行运单** @param fromState* @param toState* @param dpEventEnum* @param dpContext*/
public void transitFromsignedToexecutingOnEXECUTING(DPStateEnum fromState, DPStateEnum toState, DPEventEnum dpEventEnum, DPContext dpContext) {log.info(LOG_PRE, Thread.currentThread().getStackTrace()[1].getMethodName(), dpContext.getOrderNumber(), fromState, toState, dpEventEnum, dpContext);DPTaskStatusChangeReqDTO dpTaskStatusChangeReqDTO = dpContext.getDpTaskStatusChangeReqDTO();dpTaskStatusChangeReqDTO.setFromState(fromState);dpTaskStatusChangeReqDTO.setToState(toState);
//        dpContext.setDpTaskStatusChan

在给定的代码中,状态机的行为是通过 @Transit 注解定义的,这些注解指明了状态转移的规则和事件,并且关联了执行的方法。具体来说,@Transit 注解配置了状态从 fromto 的转移条件,以及触发该状态转移的事件 (on)。有些 @Transit 注解还指定了 callMethod 属性,表示当该转移发生时,应该调用哪个方法来执行相关逻辑。

注解分析

 

java

@Transit(from = "unsigned", to = "unsigned", on = "CREATE")

这个注解表示当事件 CREATE 发生时,状态从 unsigned 转移到 unsigned。这是一种状态机的事件与状态的定义。

方法执行关联

  1. 方法名与注解中的 callMethod 对应: 在注解中,可以通过 callMethod 属性指定状态转换时需要调用的方法。如果 callMethod 被指定,状态机在状态转换时会执行这个方法。例如:

     

    java

    @Transit(from = "unsigned", to = "executing", on = "ASSIGN_EXCUTING", callMethod = "assignExcuting")

    这个注解表示当事件 ASSIGN_EXCUTING 发生时,状态从 unsigned 转移到 executing,并且会调用 assignExcuting 方法。

  2. 方法调用的方式: 对于状态机中的状态转换,当事件触发时,注解框架(在这个例子中是 Squirrel 状态机库)会通过反射查找并调用注解中指定的 callMethod 方法。具体来说,框架会使用事件、状态和上下文信息来找到正确的方法并执行。

  3. 如何根据注解找到执行方法

    • 在代码执行的过程中,当事件发生并触发状态机的状态转换时,状态机框架会检查该状态转换是否带有 callMethod
    • 如果 callMethod 指定了方法名,框架会使用反射机制去查找与该方法名匹配的实例方法,并调用它。
    • 例如,当 on = "CREATE" 事件发生时,如果没有指定 callMethod,那么该状态转移会自动被处理(状态变化本身不需要调用方法)。但如果 callMethod 被指定,状态转移发生时就会调用指定的方法(如 transitFromunsignedTounsignedOnCREATE 方法)。
  4. 如何定义方法

    • 每个方法签名都应该与状态机的定义保持一致,即方法应该接受 fromStatetoStateeventcontext 等参数。
    • 例如,在 transitFromunsignedTounsignedOnCREATE 方法中,签名是:
       

      java

      public void transitFromunsignedTounsignedOnCREATE(DPStateEnum fromState, DPStateEnum toState, DPEventEnum dpEventEnum, DPContext dpContext) 这个方法的作用是处理从 unsigned 到 unsigned 状态的转换,并且根据 CREATE 事件的触发执行特定的操作。其实现逻辑可以是创建运单等操作。

事件触发与方法调用的流程

  1. 触发事件:当事件(如 CREATE)被触发时,状态机会根据状态机的配置来查找与该事件相关的状态转移。
  2. 查找对应的 @Transit 注解:框架会检查所有的 @Transit 注解,找出符合当前事件的注解。例如,如果当前事件是 CREATE,它会检查所有定义了 on = "CREATE" 的注解。
  3. 调用指定的方法:如果某个 @Transit 注解指定了 callMethod 属性(如 transitFromunsignedTounsignedOnCREATE),框架会使用反射来调用相应的方法。

例如,下面这个注解:

 

java

@Transit(from = "unsigned", to = "unsigned", on = "CREATE")

CREATE 事件发生时,框架会寻找与该事件相关的状态转移逻辑。如果没有指定 callMethod,则仅处理状态转移。如果有指定方法(如 transitFromunsignedTounsignedOnCREATE),它会调用该方法来处理相关逻辑。

总结

  • 注解中的 @Transit 定义了事件与状态的转移规则。
  • callMethod 属性允许开发者为状态转移指定自定义的处理方法。
  • 状态机框架使用反射来根据注解动态调用对应的方法。
  • callMethod 指定的方法需要具备一定的签名(通常包括 fromStatetoStateevent 和 context)。

通过这种方式,状态机可以动态地根据事件和状态转移配置,执行预定的操作。

如果直接根据事件on,callMethod 找到对应的方法,状态from to,有什么用

你提到的问题是关于 状态机@Transit 注解中的 fromto 参数,如果在事件处理时,已经通过 oncallMethod 找到对应的方法,那么 fromto 的作用是什么?这两个参数的意义其实是非常重要的,下面我会逐一解释它们在状态机中的作用。

1. 状态 (from 和 to) 用于验证状态转换

尽管 callMethod 指定了方法来执行特定的逻辑,但是 fromto 这两个参数仍然有它们的关键作用,尤其是在 验证和控制状态流转的合法性 方面。

主要作用:
  • 状态的合法性检查from 和 to 定义了状态机中状态的合法转换路径。它们确保了事件不会导致非法的状态跳跃,保证了状态流转的一致性。
  • 保障状态流转的一致性:通过定义明确的状态流转路径(从 from 到 to),状态机可以防止不符合逻辑的状态转移。例如,如果状态机中定义了只能从 "未指派" (unsigned) 到 "已指派" (signed) 的状态流转,那么如果你尝试从 "执行中" (executing) 到 "已指派" (signed) 的转移,状态机应该阻止该转移。
示例:
 

java

@Transit(from = "unsigned", to = "signed", on = "ASSIGN_SIGNED")

这里,from = "unsigned"to = "signed" 表示只有在状态是 unsigned 时,事件 ASSIGN_SIGNED 才能触发状态从 unsignedsigned 的转移。如果状态已经是 signed,即使事件 ASSIGN_SIGNED 被触发,状态机会拒绝该转移。

2. 事件的路由控制

在复杂的状态机中,可能存在多个事件和方法都可以引发相似的状态转换。例如,你可能有多个事件(如 ASSIGN_EXCUTING)可以从不同的源状态(如 unsignedsigned)转换到同一个目标状态(如 executing)。在这种情况下,fromto 就起到了关键作用,它们有助于确保事件在正确的上下文中被触发,并确保事件与状态之间的关系是明确的。

例如:
 

java

@Transit(from = "unsigned", to = "executing", on = "ASSIGN_EXCUTING", callMethod = "assignExcuting") @Transit(from = "signed", to = "executing", on = "ASSIGN_EXCUTING", callMethod = "assignExcuting")

这两个注解表明,ASSIGN_EXCUTING 事件可以发生在两种不同的源状态 (unsignedsigned) 下,转移到相同的目标状态 (executing)。但是,实际的状态流转只有在这些源状态下才会发生。因此,fromto 参数确保了事件和状态之间的明确关系。

3. 帮助调试和日志记录

fromto 的状态信息在调试和日志记录中也非常有用。在实际的运行时,日志会记录状态机的状态流转,包括当前的源状态 (fromState) 和目标状态 (toState),这些信息帮助开发者了解状态机的运行轨迹。

例如,在方法中,通常会记录每个状态转换的日志:

 

java

log.info("Transitioning from {} to {} via event {}. Context: {}", fromState, toState, event, context);

这种日志信息有助于在问题出现时快速定位问题并排查逻辑错误。

4. 确保不同的状态转换逻辑

fromto 不仅帮助确定状态转移是否合法,还能在不同的状态之间实现不同的逻辑。例如,你可能希望根据当前状态 (fromState) 的不同执行不同的操作或方法。虽然 callMethod 指定了通用的执行方法,但你仍然可能希望根据不同的状态执行不同的逻辑。

例如:

 

java

@Transit(from = "unsigned", to = "signed", on = "ASSIGN_SIGNED", callMethod = "assignUnsignedToSigned") @Transit(from = "signed", to = "executing", on = "ASSIGN_EXCUTING", callMethod = "assignSignedToExecuting")

在这个例子中,尽管事件 ASSIGN_SIGNEDASSIGN_EXCUTING 看起来可能是类似的动作,但它们发生在不同的状态下,因此你可能会希望它们触发不同的处理逻辑。fromto 的定义确保了在正确的上下文中执行正确的逻辑。

5. 保持代码清晰和可维护

fromto 提供了清晰的状态流转图,开发者可以从注解中快速了解状态机的行为和状态转换路径。这对于团队协作、文档编写以及代码的长期维护都非常有帮助。

总结

尽管 callMethod 可以直接指定要调用的方法,但是 fromto 参数在状态机中扮演着不可或缺的角色:

  • 状态合法性验证:确保状态流转是符合预定义的规则的,避免出现不符合逻辑的状态跳跃。
  • 路由控制:帮助事件在正确的上下文中触发,从正确的源状态转换到目标状态。
  • 调试与日志记录:提供调试信息,帮助开发者追踪状态转移的路径。
  • 不同状态的逻辑控制:根据 from 和 to 确定具体的业务逻辑。

因此,fromto 并非多余,它们在状态机的结构化和规则验证中发挥了重要作用,确保状态转移的过程是可控和符合业务逻辑的。

具体如何状态合法性验证

状态合法性验证:确保状态流转是符合预定义的规则的,避免出现不符合逻辑的状态跳跃。 这个具体如何实现的?from代表当前状态?这个参数怎么传入的

在状态机的实现中,状态合法性验证是确保状态转移符合预定义的规则、避免非法的状态跳跃的关键。具体来说,from 代表当前的状态,to 代表目标状态,验证这两个状态的合法转移可以通过一系列的机制来实现。

1. from 代表当前状态,to 代表目标状态

  • from:表示当前的状态,即事件触发时所处的状态。
  • to:表示目标状态,即状态转换后将进入的状态。

状态合法性验证的核心任务就是在事件发生时,检查当前状态(from)和目标状态(to)是否符合预定义的合法转换规则。如果不合法,状态机应该拒绝该转换并抛出异常或返回错误。

2. 如何实现状态合法性验证?

实现状态合法性验证的方式可以分为几种,不同的状态机框架或自定义实现可能会有不同的方法。以下是常见的几种实现方式:

2.1. 手动映射与检查

一种最简单的实现方式是手动维护一个合法的状态转换表,在处理事件时检查当前状态和目标状态是否允许转换。

例如:

  • 使用一个状态转换表来存储所有合法的 from -> to 映射。
  • 当事件发生时,根据当前状态(from)和目标状态(to)查表,检查转换是否合法。

假设我们有如下状态和状态转换表:

 

java

enum State { UNSIGNED, SIGNED, EXECUTING, COMPLETED } Map<State, Set<State>> validTransitions = new HashMap<>(); validTransitions.put(State.UNSIGNED, new HashSet<>(Arrays.asList(State.SIGNED))); validTransitions.put(State.SIGNED, new HashSet<>(Arrays.asList(State.EXECUTING))); validTransitions.put(State.EXECUTING, new HashSet<>(Arrays.asList(State.COMPLETED)));

当事件触发时,我们可以检查状态是否合法:

 

java

public boolean isValidTransition(State fromState, State toState) { Set<State> validToStates = validTransitions.get(fromState); return validToStates != null && validToStates.contains(toState); }

在状态转移时,isValidTransition 方法就会验证 fromto 是否匹配预定义的合法转换。

2.2. 通过注解与反射动态绑定

在使用注解驱动的状态机框架中,状态转移规则通常通过注解(如 @Transit)来定义。状态机会通过反射机制读取这些注解,并验证事件发生时的状态转移是否合法。

例如,在基于注解的状态机实现中,可能会有如下的代码:

 

java

@Transit(from = "UNSIGNED", to = "SIGNED", on = "ASSIGN_SIGNED") @Transit(from = "SIGNED", to = "EXECUTING", on = "ASSIGN_EXECUTING")

在处理事件时,框架会根据注解定义的 fromto 来验证当前状态和目标状态是否匹配。可以通过以下方式实现:

  1. 读取注解:通过反射获取方法上的注解,提取出 from 和 to 状态。
  2. 检查合法性:比对当前状态与目标状态是否符合注解中定义的合法状态流转。

假设我们有一个 StateMachine 类,在事件发生时,我们通过反射查找匹配的转移规则:

 

java

public void handleEvent(String event, State currentState) throws IllegalStateException { // 获取所有标注了 @Transit 注解的方法 Method[] methods = StateMachine.class.getDeclaredMethods(); for (Method method : methods) { if (method.isAnnotationPresent(Transit.class)) { Transit transit = method.getAnnotation(Transit.class); if (transit.from().equals(currentState.name()) && transit.on().equals(event)) { // 验证目标状态是否合法 if (validTransitions(currentState, transit.to())) { // 执行状态转换方法 method.invoke(this); } else { throw new IllegalStateException("Invalid state transition from " + currentState + " to " + transit.to()); } } } } }

在这种方式下,fromto 都是通过注解定义的,在事件处理时,框架会动态检查当前状态和目标状态是否符合合法转移规则。

2.3. 状态机框架中的自动检查

一些状态机框架(如 Spring State MachineAkka FSM 等)已经内建了状态转移规则的验证机制。它们通过状态机的图结构自动验证 fromto 之间的合法性,开发者只需要通过配置或者注解来定义状态转移,而框架会在运行时执行验证。

例如,Spring State Machine 提供了一些 API 来配置状态转换:

 

java

StateMachine<State, Event> stateMachine = new StateMachineBuilder.Builder<State, Event>() .configure() .withStates() .initial(State.UNSIGNED) .state(State.SIGNED) .state(State.EXECUTING) .state(State.COMPLETED) .and() .withTransitions() .between(State.UNSIGNED, State.SIGNED).perform(assignSigned()) .between(State.SIGNED, State.EXECUTING).perform(assignExecuting()) .between(State.EXECUTING, State.COMPLETED).perform(assignCompleted()) .and() .build();

在这里,框架会在 StateMachine 被激活后自动检查每个状态转移是否合法。

3. from 参数如何传入

from 代表当前状态,它通常是从当前的 状态机实例 中获取的。具体来说:

  • 如果状态机是基于事件驱动的,那么 from 状态通常是触发事件时状态机当前的状态。
  • 在基于注解的状态机中,from 状态会在处理事件时动态传入。
  • 在手动配置的状态机中,from 状态会随着事件的触发而改变,通常由当前的 state 属性表示。

例如,在事件处理方法中,from 通常是方法的参数或者是类中的实例变量:

 

java

public void handleEvent(State currentState, Event event) { // 当前状态:currentState if (isValidTransition(currentState, event)) { // 执行状态转移 } }

在注解驱动的框架中,from 可能会自动由框架通过反射从注解中获取,或者从事件的上下文中获取当前状态。

4. 总结

状态合法性验证的关键是在事件触发时检查当前状态(from)和目标状态(to)是否符合预定义的合法转换规则。具体实现可以通过手动维护转换表、反射检查注解或使用状态机框架的自动验证来实现。通过这些方法,我们能够确保状态机的状态流转始终是符合业务逻辑的,并避免发生不符合规则的状态跳跃。

状态合法性校验实现例子

触发点,方法执行前

    /*** 事件触发之前被调用** @param fromState* @param toState* @param event* @param context*/@Overrideprotected void beforeActionInvoked(Object fromState, Object toState, Object event, Object context) {log.info(LOG_PRE, Thread.currentThread().getStackTrace()[1].getMethodName(), ((DPContext) context).getOrderNumber(), fromState, toState, event, context);/*** 状态合理性校验*/DPContext dpContext = (DPContext) context;if (!dpContext.isReentrantLock()) {//重入锁,不需要状态合理性校验DPEventEnum dpEventEnum = (DPEventEnum) event;((TaskStatusTriggerService) dpContext.getIStatusService()).checkFromState(fromState, toState, context, dpEventEnum);}}
    public void checkFromState(DPStateEnum dpStateEnum_from, DPStateEnum dpStateEnum_to, DPContext dpContext, DPEventEnum eventEnum) {
// from statusgetInitialState(dpContext.getEvent(), Optional.ofNullable(dpContext.getDpTaskStatusChangeReqDTO()).map(dpTaskStatusChangeReqDTO -> dpTaskStatusChangeReqDTO.getWayBill()).orElse(""), dpContext);if (!dpContext.getInitialState().equals(dpStateEnum_from)) {log.error("状态异常");throw new YlTaskException(ErrConstant.BUSI_RETURN_ERR, "状态异常");}// to status//幂等性处理if (Arrays.asList(DPEventEnum.CANCEL).contains(eventEnum)) {String taskStatus_db = ttTask.getTaskStatus();if (taskStatus_db.equals(dpStateEnum_to.getStatusCode())) {log.warn("当前运单已处理");dpContext.setIdempotent(true);return;}}}

相关文章:

松鼠状态机流转-@Transit

疑问 状态from to合法性校验&#xff0c;都是在代码中手动进行的吗&#xff0c;不是状态机自动进行的&#xff1f; 注解中from状态&#xff0c;代表当前状态 和谁校验&#xff1a;上下文中初始状态 怎么根据注解找到执行方法的 分析代码&#xff0c;创建运单&#xff0c;怎…...

微信小程序调用 WebAssembly 烹饪指南

我们都是在夜里崩溃过的俗人&#xff0c;所幸终会天亮。明天就是新的开始&#xff0c;我们会变得与昨天不同。 一、Rust 导出 wasm 参考 wasm-bindgen 官方指南 https://wasm.rust-lang.net.cn/wasm-bindgen/introduction.html wasm-bindgen&#xff0c;这是一个 Rust 库和 CLI…...

# LeetCode Problem 2038: 如果相邻两个颜色均相同则删除当前颜色 (Winner of the Game)

LeetCode Problem 2038: 如果相邻两个颜色均相同则删除当前颜色 (Winner of the Game) 在本篇博客中&#xff0c;我们将深入探讨 LeetCode 第2038题——如果相邻两个颜色均相同则删除当前颜色。该问题涉及字符串处理与游戏策略&#xff0c;旨在考察如何在给定规则下判断游戏的…...

Redis面试相关

Redis开篇 使用场景 缓存 缓存穿透 解决方法一&#xff1a; 方法二&#xff1a; 通过多次hash来获取对应的值。 小结 缓存击穿 缓存雪崩 打油诗 双写一致性 两种不同的要求 强一致 读锁代码 写锁代码 强一致&#xff0c;性能低。 延迟一致 方案一&#xff1a;消息队列 方…...

4.CSS文本属性

4.1文本颜色 div { color:red; } 属性值预定义的颜色值red、green、blue、pink十六进制#FF0000,#FF6600,#29D794RGB代码rgb(255,0,0)或rgb(100%,0%,0%) 4.2对齐文本 text-align 属性用于设置元素内文本内容的水平对齐方式。 div{ text-align:center; } 属性值解释left左对齐ri…...

Mongo高可用架构解决方案

Mongo主从复制哪些事(仅适用特定场景) 对数据强一致性要求不高的场景,一般微服务架构中不推荐 master节点可读可写操作,当数据有修改时,会将Oplog(操作日志)同步到所有的slave节点上。那么对于从节点来说仅只读,所有slave节点从master节点同步数据,然而从节点之间互相…...

Rabbitmq 业务异常与未手动确认场景及解决方案

消费端消费异常&#xff0c;业务异常 与 未手动确认是不是一个场景&#xff0c;因为执行完业务逻辑&#xff0c;再确认。解决方案就一个&#xff0c;就是重试一定次数&#xff0c;然后加入死信队列。还有就是消费重新放入队列&#xff0c;然后重新投递给其他消费者&#xff0c;…...

linux,centos7.6安装禅道

1.cd /opt 2.wget https://www.zentao.net/dl/zentao/18.5/ZenTaoPMS.18.5.zbox_64.tar.gz 3.tar xvzf ZenTaoPMS.18.5.zbox_64.tar.gz 4./opt/zbox/zbox --aport 4005 --mport 3307 start 安全组开启一下两个端口 –aport 4005&#xff1a;设置Apache启动端口为4005 –mport 3…...

java基础之代理

代理模式&#xff08;Proxy Pattern&#xff09; 简介 是一种结构型设计模式&#xff0c;主要用于为某对象提供一个代理对象&#xff0c;以控制对该对象的访问。通过引入一个代理对象来控制对原对象的访问。代理对象在客户端和目标对象之间充当中介&#xff0c;负责将客户端的…...

计算机网络——期末复习(6)期末考试样例2(含答案)

一、单项选择题(每题1分&#xff0c;共10小题&#xff0c;共计10分) 1.因特网多播采用的管理协议是( )协议。 A.UDP B.BGP C.IGMP D.TCP 2.采用CIDR时&#xff0c;某计算机的IP地址是202.35.79.88/19&#xff0c;该计算机所处子网的地址是( )。 A.202.35.0.…...

JavaScript 获取DOM对象

html的标签被js获取到之后就变成了js对象&#xff0c;对象里面包含了标签的属性和方法 。同一时间获取多个对象则会翻译一个数组&#xff0c;数组元素是对象 获取方法 1. const a document.getElementById("id")&#xff0c;根据标签的id来获取。因为id是唯一的、…...

一文讲明白朴素贝叶斯算法及其计算公式(入门普及)

1、贝叶斯算法 贝叶斯定理由英国数学家托马斯贝叶斯 ( Thomas Bayes) 提出的&#xff0c;用来描述两个条件概率之间的关系。通常&#xff0c;事件A在事件B 发生的条件下与事件 B 在事件 A 发生的条件下&#xff0c;它们两者的概率并不相同&#xff0c;但是它们两者之间存在一定…...

实际开发中,常见pdf|word|excel等文件的预览和下载

实际开发中,常见pdf|word|excel等文件的预览和下载 背景相关类型数据之间的转换1、File转Blob2、File转ArrayBuffer3、Blob转ArrayBuffer4、Blob转File5、ArrayBuffer转Blob6、ArrayBuffer转File 根据Blob/File类型生成可预览的Base64地址基于Blob类型的各种文件的下载各种类型…...

Python自学 - 递归函数

1 Python自学 - 递归函数 递归函数是一种在函数体内调用自己的函数&#xff0c;就像“左脚踩着右脚&#xff0c;再右脚踩着左脚… 嗯&#xff0c;你就可以上天了&#xff01;”。递归函数虽然不能上天&#xff0c;但在处理某些场景时非常好用&#xff0c; 一种典型的场景就是遍…...

Spark-Streaming有状态计算

一、上下文 《Spark-Streaming初识》中的NetworkWordCount示例只能统计每个微批下的单词的数量&#xff0c;那么如何才能统计从开始加载数据到当下的所有数量呢&#xff1f;下面我们就来通过官方例子学习下Spark-Streaming有状态计算。 二、官方例子 所属包&#xff1a;org.…...

Markdown如何导出Html文件Markdown文件

Markdown如何导出Html文件Markdown文件 前言语法详解小结其他文章快来试试吧☺️ Markdown 导出 HTML &#x1f448;点击这里也可查看 前言 Markdown的源文件以md为后缀。Markdown是HTML语法的简化版本&#xff0c;它本身不带有任何样式信息。我们所看到的Markdown网页(如&…...

使用Python进行图像裁剪和直方图分析

一、简介 在数字图像处理领域&#xff0c;裁剪和分析图像的直方图是两个非常基本且重要的操作。本文将通过一个简单的Python项目&#xff0c;展示如何使用skimage和matplotlib库来裁剪图像并分析其RGB通道的直方图。 二、环境准备 在开始之前&#xff0c;请确保你已经安装了以…...

企业内管信息化系统

本文结尾处获取源码。 本文结尾处获取源码。 本文结尾处获取源码。 一、相关技术 后端&#xff1a;Java、JavaWeb / Springboot。前端&#xff1a;Vue、HTML / CSS / Javascript 等。数据库&#xff1a;MySQL 二、相关软件&#xff08;列出的软件其一均可运行&#xff09; I…...

【python因果库实战15】因果生存分析4

这里写目录标题 加权标准化生存分析总结个体层面的生存曲线 加权标准化生存分析 我们还可以将加权与标准化结合起来&#xff0c;使用 WeightedStandardizedSurvival 模块。在这里&#xff0c;我们将逆倾向得分加权模型&#xff08;根据基线协变量重新加权人群&#xff09;与加…...

Linux 线程详解

目录 一、线程概述 二、线程创建 三、线程终止 四、线程回收 五、线程取消 六、线程分离 七、线程安全 一、线程概述 线程是进程内的一个执行单元&#xff0c;是进程内可调度的实体。一个进程可以包含多个线程&#xff0c;这些线程共享进程的资源&#xff0c;如内存空…...

[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解

突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 ​安全措施依赖问题​ GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

7.4.分块查找

一.分块查找的算法思想&#xff1a; 1.实例&#xff1a; 以上述图片的顺序表为例&#xff0c; 该顺序表的数据元素从整体来看是乱序的&#xff0c;但如果把这些数据元素分成一块一块的小区间&#xff0c; 第一个区间[0,1]索引上的数据元素都是小于等于10的&#xff0c; 第二…...

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

汽车生产虚拟实训中的技能提升与生产优化​

在制造业蓬勃发展的大背景下&#xff0c;虚拟教学实训宛如一颗璀璨的新星&#xff0c;正发挥着不可或缺且日益凸显的关键作用&#xff0c;源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例&#xff0c;汽车生产线上各类…...

Linux云原生安全:零信任架构与机密计算

Linux云原生安全&#xff1a;零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言&#xff1a;云原生安全的范式革命 随着云原生技术的普及&#xff0c;安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测&#xff0c;到2025年&#xff0c;零信任架构将成为超…...

华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建

华为云FlexusDeepSeek征文&#xff5c;DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色&#xff0c;华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型&#xff0c;能助力我们轻松驾驭 DeepSeek-V3/R1&#xff0c;本文中将分享如何…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码

目录 一、&#x1f468;‍&#x1f393;网站题目 二、✍️网站描述 三、&#x1f4da;网站介绍 四、&#x1f310;网站效果 五、&#x1fa93; 代码实现 &#x1f9f1;HTML 六、&#x1f947; 如何让学习不再盲目 七、&#x1f381;更多干货 一、&#x1f468;‍&#x1f…...

排序算法总结(C++)

目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指&#xff1a;同样大小的样本 **&#xff08;同样大小的数据&#xff09;**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...

Linux 下 DMA 内存映射浅析

序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存&#xff0c;但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程&#xff0c;可以参考这篇文章&#xff0c;我觉得写的非常…...