松鼠状态机流转-@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
注解配置了状态从 from
到 to
的转移条件,以及触发该状态转移的事件 (on
)。有些 @Transit
注解还指定了 callMethod
属性,表示当该转移发生时,应该调用哪个方法来执行相关逻辑。
注解分析
java
@Transit(from = "unsigned", to = "unsigned", on = "CREATE")
这个注解表示当事件 CREATE
发生时,状态从 unsigned
转移到 unsigned
。这是一种状态机的事件与状态的定义。
方法执行关联
-
方法名与注解中的
callMethod
对应: 在注解中,可以通过callMethod
属性指定状态转换时需要调用的方法。如果callMethod
被指定,状态机在状态转换时会执行这个方法。例如:java
@Transit(from = "unsigned", to = "executing", on = "ASSIGN_EXCUTING", callMethod = "assignExcuting")
这个注解表示当事件
ASSIGN_EXCUTING
发生时,状态从unsigned
转移到executing
,并且会调用assignExcuting
方法。 -
方法调用的方式: 对于状态机中的状态转换,当事件触发时,注解框架(在这个例子中是
Squirrel
状态机库)会通过反射查找并调用注解中指定的callMethod
方法。具体来说,框架会使用事件、状态和上下文信息来找到正确的方法并执行。 -
如何根据注解找到执行方法:
- 在代码执行的过程中,当事件发生并触发状态机的状态转换时,状态机框架会检查该状态转换是否带有
callMethod
。 - 如果
callMethod
指定了方法名,框架会使用反射机制去查找与该方法名匹配的实例方法,并调用它。 - 例如,当
on = "CREATE"
事件发生时,如果没有指定callMethod
,那么该状态转移会自动被处理(状态变化本身不需要调用方法)。但如果callMethod
被指定,状态转移发生时就会调用指定的方法(如transitFromunsignedTounsignedOnCREATE
方法)。
- 在代码执行的过程中,当事件发生并触发状态机的状态转换时,状态机框架会检查该状态转换是否带有
-
如何定义方法:
- 每个方法签名都应该与状态机的定义保持一致,即方法应该接受
fromState
、toState
、event
、context
等参数。 - 例如,在
transitFromunsignedTounsignedOnCREATE
方法中,签名是:java
public void transitFromunsignedTounsignedOnCREATE(DPStateEnum fromState, DPStateEnum toState, DPEventEnum dpEventEnum, DPContext dpContext)
这个方法的作用是处理从unsigned
到unsigned
状态的转换,并且根据CREATE
事件的触发执行特定的操作。其实现逻辑可以是创建运单等操作。
- 每个方法签名都应该与状态机的定义保持一致,即方法应该接受
事件触发与方法调用的流程
- 触发事件:当事件(如
CREATE
)被触发时,状态机会根据状态机的配置来查找与该事件相关的状态转移。 - 查找对应的
@Transit
注解:框架会检查所有的@Transit
注解,找出符合当前事件的注解。例如,如果当前事件是CREATE
,它会检查所有定义了on = "CREATE"
的注解。 - 调用指定的方法:如果某个
@Transit
注解指定了callMethod
属性(如transitFromunsignedTounsignedOnCREATE
),框架会使用反射来调用相应的方法。
例如,下面这个注解:
java
@Transit(from = "unsigned", to = "unsigned", on = "CREATE")
当 CREATE
事件发生时,框架会寻找与该事件相关的状态转移逻辑。如果没有指定 callMethod
,则仅处理状态转移。如果有指定方法(如 transitFromunsignedTounsignedOnCREATE
),它会调用该方法来处理相关逻辑。
总结
- 注解中的
@Transit
定义了事件与状态的转移规则。 callMethod
属性允许开发者为状态转移指定自定义的处理方法。- 状态机框架使用反射来根据注解动态调用对应的方法。
callMethod
指定的方法需要具备一定的签名(通常包括fromState
、toState
、event
和context
)。
通过这种方式,状态机可以动态地根据事件和状态转移配置,执行预定的操作。
如果直接根据事件on,callMethod 找到对应的方法,状态from to,有什么用
你提到的问题是关于 状态机 中 @Transit
注解中的 from
和 to
参数,如果在事件处理时,已经通过 on
和 callMethod
找到对应的方法,那么 from
和 to
的作用是什么?这两个参数的意义其实是非常重要的,下面我会逐一解释它们在状态机中的作用。
1. 状态 (from
和 to
) 用于验证状态转换
尽管 callMethod
指定了方法来执行特定的逻辑,但是 from
和 to
这两个参数仍然有它们的关键作用,尤其是在 验证和控制状态流转的合法性 方面。
主要作用:
- 状态的合法性检查:
from
和to
定义了状态机中状态的合法转换路径。它们确保了事件不会导致非法的状态跳跃,保证了状态流转的一致性。 - 保障状态流转的一致性:通过定义明确的状态流转路径(从
from
到to
),状态机可以防止不符合逻辑的状态转移。例如,如果状态机中定义了只能从 "未指派" (unsigned
) 到 "已指派" (signed
) 的状态流转,那么如果你尝试从 "执行中" (executing
) 到 "已指派" (signed
) 的转移,状态机应该阻止该转移。
示例:
java
@Transit(from = "unsigned", to = "signed", on = "ASSIGN_SIGNED")
这里,from = "unsigned"
和 to = "signed"
表示只有在状态是 unsigned
时,事件 ASSIGN_SIGNED
才能触发状态从 unsigned
到 signed
的转移。如果状态已经是 signed
,即使事件 ASSIGN_SIGNED
被触发,状态机会拒绝该转移。
2. 事件的路由控制
在复杂的状态机中,可能存在多个事件和方法都可以引发相似的状态转换。例如,你可能有多个事件(如 ASSIGN_EXCUTING
)可以从不同的源状态(如 unsigned
和 signed
)转换到同一个目标状态(如 executing
)。在这种情况下,from
和 to
就起到了关键作用,它们有助于确保事件在正确的上下文中被触发,并确保事件与状态之间的关系是明确的。
例如:
java
@Transit(from = "unsigned", to = "executing", on = "ASSIGN_EXCUTING", callMethod = "assignExcuting") @Transit(from = "signed", to = "executing", on = "ASSIGN_EXCUTING", callMethod = "assignExcuting")
这两个注解表明,ASSIGN_EXCUTING
事件可以发生在两种不同的源状态 (unsigned
或 signed
) 下,转移到相同的目标状态 (executing
)。但是,实际的状态流转只有在这些源状态下才会发生。因此,from
和 to
参数确保了事件和状态之间的明确关系。
3. 帮助调试和日志记录
from
和 to
的状态信息在调试和日志记录中也非常有用。在实际的运行时,日志会记录状态机的状态流转,包括当前的源状态 (fromState
) 和目标状态 (toState
),这些信息帮助开发者了解状态机的运行轨迹。
例如,在方法中,通常会记录每个状态转换的日志:
java
log.info("Transitioning from {} to {} via event {}. Context: {}", fromState, toState, event, context);
这种日志信息有助于在问题出现时快速定位问题并排查逻辑错误。
4. 确保不同的状态转换逻辑
from
和 to
不仅帮助确定状态转移是否合法,还能在不同的状态之间实现不同的逻辑。例如,你可能希望根据当前状态 (fromState
) 的不同执行不同的操作或方法。虽然 callMethod
指定了通用的执行方法,但你仍然可能希望根据不同的状态执行不同的逻辑。
例如:
java
@Transit(from = "unsigned", to = "signed", on = "ASSIGN_SIGNED", callMethod = "assignUnsignedToSigned") @Transit(from = "signed", to = "executing", on = "ASSIGN_EXCUTING", callMethod = "assignSignedToExecuting")
在这个例子中,尽管事件 ASSIGN_SIGNED
和 ASSIGN_EXCUTING
看起来可能是类似的动作,但它们发生在不同的状态下,因此你可能会希望它们触发不同的处理逻辑。from
和 to
的定义确保了在正确的上下文中执行正确的逻辑。
5. 保持代码清晰和可维护
from
和 to
提供了清晰的状态流转图,开发者可以从注解中快速了解状态机的行为和状态转换路径。这对于团队协作、文档编写以及代码的长期维护都非常有帮助。
总结
尽管 callMethod
可以直接指定要调用的方法,但是 from
和 to
参数在状态机中扮演着不可或缺的角色:
- 状态合法性验证:确保状态流转是符合预定义的规则的,避免出现不符合逻辑的状态跳跃。
- 路由控制:帮助事件在正确的上下文中触发,从正确的源状态转换到目标状态。
- 调试与日志记录:提供调试信息,帮助开发者追踪状态转移的路径。
- 不同状态的逻辑控制:根据
from
和to
确定具体的业务逻辑。
因此,from
和 to
并非多余,它们在状态机的结构化和规则验证中发挥了重要作用,确保状态转移的过程是可控和符合业务逻辑的。
具体如何状态合法性验证
状态合法性验证:确保状态流转是符合预定义的规则的,避免出现不符合逻辑的状态跳跃。 这个具体如何实现的?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
方法就会验证 from
和 to
是否匹配预定义的合法转换。
2.2. 通过注解与反射动态绑定
在使用注解驱动的状态机框架中,状态转移规则通常通过注解(如 @Transit
)来定义。状态机会通过反射机制读取这些注解,并验证事件发生时的状态转移是否合法。
例如,在基于注解的状态机实现中,可能会有如下的代码:
java
@Transit(from = "UNSIGNED", to = "SIGNED", on = "ASSIGN_SIGNED") @Transit(from = "SIGNED", to = "EXECUTING", on = "ASSIGN_EXECUTING")
在处理事件时,框架会根据注解定义的 from
和 to
来验证当前状态和目标状态是否匹配。可以通过以下方式实现:
- 读取注解:通过反射获取方法上的注解,提取出
from
和to
状态。 - 检查合法性:比对当前状态与目标状态是否符合注解中定义的合法状态流转。
假设我们有一个 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()); } } } } }
在这种方式下,from
和 to
都是通过注解定义的,在事件处理时,框架会动态检查当前状态和目标状态是否符合合法转移规则。
2.3. 状态机框架中的自动检查
一些状态机框架(如 Spring State Machine、Akka FSM 等)已经内建了状态转移规则的验证机制。它们通过状态机的图结构自动验证 from
和 to
之间的合法性,开发者只需要通过配置或者注解来定义状态转移,而框架会在运行时执行验证。
例如,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合法性校验,都是在代码中手动进行的吗,不是状态机自动进行的? 注解中from状态,代表当前状态 和谁校验:上下文中初始状态 怎么根据注解找到执行方法的 分析代码,创建运单,怎…...

微信小程序调用 WebAssembly 烹饪指南
我们都是在夜里崩溃过的俗人,所幸终会天亮。明天就是新的开始,我们会变得与昨天不同。 一、Rust 导出 wasm 参考 wasm-bindgen 官方指南 https://wasm.rust-lang.net.cn/wasm-bindgen/introduction.html wasm-bindgen,这是一个 Rust 库和 CLI…...
# LeetCode Problem 2038: 如果相邻两个颜色均相同则删除当前颜色 (Winner of the Game)
LeetCode Problem 2038: 如果相邻两个颜色均相同则删除当前颜色 (Winner of the Game) 在本篇博客中,我们将深入探讨 LeetCode 第2038题——如果相邻两个颜色均相同则删除当前颜色。该问题涉及字符串处理与游戏策略,旨在考察如何在给定规则下判断游戏的…...

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

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 业务异常与未手动确认场景及解决方案
消费端消费异常,业务异常 与 未手动确认是不是一个场景,因为执行完业务逻辑,再确认。解决方案就一个,就是重试一定次数,然后加入死信队列。还有就是消费重新放入队列,然后重新投递给其他消费者,…...
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:设置Apache启动端口为4005 –mport 3…...
java基础之代理
代理模式(Proxy Pattern) 简介 是一种结构型设计模式,主要用于为某对象提供一个代理对象,以控制对该对象的访问。通过引入一个代理对象来控制对原对象的访问。代理对象在客户端和目标对象之间充当中介,负责将客户端的…...

计算机网络——期末复习(6)期末考试样例2(含答案)
一、单项选择题(每题1分,共10小题,共计10分) 1.因特网多播采用的管理协议是( )协议。 A.UDP B.BGP C.IGMP D.TCP 2.采用CIDR时,某计算机的IP地址是202.35.79.88/19,该计算机所处子网的地址是( )。 A.202.35.0.…...

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

一文讲明白朴素贝叶斯算法及其计算公式(入门普及)
1、贝叶斯算法 贝叶斯定理由英国数学家托马斯贝叶斯 ( Thomas Bayes) 提出的,用来描述两个条件概率之间的关系。通常,事件A在事件B 发生的条件下与事件 B 在事件 A 发生的条件下,它们两者的概率并不相同,但是它们两者之间存在一定…...

实际开发中,常见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自学 - 递归函数 递归函数是一种在函数体内调用自己的函数,就像“左脚踩着右脚,再右脚踩着左脚… 嗯,你就可以上天了!”。递归函数虽然不能上天,但在处理某些场景时非常好用, 一种典型的场景就是遍…...

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

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

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

企业内管信息化系统
本文结尾处获取源码。 本文结尾处获取源码。 本文结尾处获取源码。 一、相关技术 后端:Java、JavaWeb / Springboot。前端:Vue、HTML / CSS / Javascript 等。数据库:MySQL 二、相关软件(列出的软件其一均可运行) I…...

【python因果库实战15】因果生存分析4
这里写目录标题 加权标准化生存分析总结个体层面的生存曲线 加权标准化生存分析 我们还可以将加权与标准化结合起来,使用 WeightedStandardizedSurvival 模块。在这里,我们将逆倾向得分加权模型(根据基线协变量重新加权人群)与加…...

Linux 线程详解
目录 一、线程概述 二、线程创建 三、线程终止 四、线程回收 五、线程取消 六、线程分离 七、线程安全 一、线程概述 线程是进程内的一个执行单元,是进程内可调度的实体。一个进程可以包含多个线程,这些线程共享进程的资源,如内存空…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...

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

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

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路
进入2025年以来,尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断,但全球市场热度依然高涨,入局者持续增加。 以国内市场为例,天眼查专业版数据显示,截至5月底,我国现存在业、存续状态的机器人相关企…...

汽车生产虚拟实训中的技能提升与生产优化
在制造业蓬勃发展的大背景下,虚拟教学实训宛如一颗璀璨的新星,正发挥着不可或缺且日益凸显的关键作用,源源不断地为企业的稳健前行与创新发展注入磅礴强大的动力。就以汽车制造企业这一极具代表性的行业主体为例,汽车生产线上各类…...
Linux云原生安全:零信任架构与机密计算
Linux云原生安全:零信任架构与机密计算 构建坚不可摧的云原生防御体系 引言:云原生安全的范式革命 随着云原生技术的普及,安全边界正在从传统的网络边界向工作负载内部转移。Gartner预测,到2025年,零信任架构将成为超…...

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

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...

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

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