引言

在复杂的应用程序设计中,尤其是那些涉及多个状态变迁和业务流程控制的场景,有限状态机(Finite State Machine, FSM)是一种强大而有效的建模工具。Spring框架为此提供了Spring状态机(Spring State Machine)这一组件,它允许开发者以一种声明式且结构清晰的方式来管理和控制对象的状态流转。

提起Spring状态机,可能有些小伙伴还比较陌生。当你听到状态机时,一定会联想到状态设计模式。确实,状态机是状态模式的一种实际运用,在工作流引擎、订单系统等领域有大量的应用。在介绍状态机之前,我们先来回顾一下状态模式,以便更好地理解Spring状态机的概念和应用。

状态模式

状态模式是一种行为设计模式,用于管理对象的状态以及状态之间的转换。在状态模式中,对象在不同的状态下表现出不同的行为,而状态的转换是由外部条件触发的。状态模式将每个状态封装成一个独立的类,并将状态转换的逻辑分散在这些状态类中,从而使得状态的管理和转换变得简单和灵活。

状态模式通常由以下几个要素组成:

  1. 上下文(Context):上下文是包含了状态的对象,它定义了当前的状态以及可以触发状态转换的接口。上下文对象在不同的状态下会调用相应状态对象的方法来执行具体的行为。

  2. 抽象状态(State):抽象状态是一个接口或抽象类,定义了状态对象的通用行为接口。具体的状态类需要实现这个接口,并根据不同的状态来实现具体的行为。

  3. 具体状态(Concrete State):具体状态是实现了抽象状态接口的具体类,它实现了在特定状态下对象的行为。每个具体状态类负责管理该状态下的行为和状态转换规则。

状态模式使得对象在不同状态下的行为更加清晰和可维护,同时也使得对象的状态转换逻辑更加灵活和可扩展。状态模式常见于需要对象根据外部条件改变行为的场景,例如订单状态(如待提交,待发货,已发货,已签收,已完结等状态)的管理、工作流引擎中的状态(例如提交,审核中,驳回,审核通过,审核失败等)管理。

我们以订单状态的流转为例:

  • 首先我们定义一个订单抽象状态的接口
public interface OrderState {  

    void handlerOrder();
}
  • 在定义具体的订单状态,以及对应的订单状态的行为
public class OrderSubmitState implements OrderState{
@Override
public void handlerOrder() {
System.out.println("订单已提交");
}
} public class OrderOutboundState implements OrderState{ @Override
public void handlerOrder() {
System.out.println("订单已出库");
}
} public class OrderSignedState implements OrderState{
@Override
public void handlerOrder() {
System.out.println("订单已签收");
}
}
  • 在定义一个状态的上下文,用于维护当前状态对象,以及提供状态流转的方法
public class OrderContext {  

    private OrderState orderState;  

    public void setOrderState(OrderState orderState){
this.orderState = orderState;
} public void handleOrder(){
orderState.handlerOrder();
}
}
  • 编写具体业务,测试订单状态流转
public class OrderStateTest {  

    public static void main(String[] args) {
OrderSubmitState orderSubmitState = new OrderSubmitState();
OrderContext orderContext = new OrderContext();
orderContext.setOrderState(orderSubmitState);
orderContext.handleOrder(); OrderOutboundState orderOutboundState = new OrderOutboundState();
orderContext.setOrderState(orderOutboundState);
orderContext.handleOrder(); OrderSignedState orderSignedState = new OrderSignedState();
orderContext.setOrderState(orderSignedState);
orderContext.handleOrder();
}
}

执行结果如下:



使用状态模式中的状态类不仅能消除if-else逻辑校验,在一定程度上也增强了代码的可读性和可维护性。类似策略模式,但是状态机模式跟策略模式还有很大的区别的。

  1. 状态模式:

    • 关注对象在不同状态下的行为和状态之间的转换。
    • 通过封装每个状态为单独的类来实现状态切换,使得每个状态对象都能处理自己的行为。
    • 状态之间的转换通常是通过条件判断或外部事件触发的。
  2. 策略模式:

    • 关注对象在不同策略下的行为差异。
    • 将不同的算法或策略封装成单独的类,使得它们可以互相替换,并且在运行时动态地选择不同的策略。
    • 不涉及状态转换,而是更多地关注于执行特定行为时选择合适的策略。

虽然两种模式都涉及对象行为的管理,但它们的关注点和应用场景略有不同。

关于消除if-else的方案请参考:代码整洁之道(一)之优化if-else的8种方案

什么是状态机

状态机,顾名思义,是一种数学模型,它通过定义一系列有限的状态以及状态之间的转换规则来模拟现实世界或抽象系统的动态行为。每个状态代表系统可能存在的条件或阶段,而状态间的转换则是由特定的输入(即事件)触发的。例如,在电商应用中,订单状态可能会经历创建、支付、打包、发货、完成等多个状态,每个状态之间的转变都由对应的业务动作触发。

在状态机中,有以下几个基本概念:

  1. 状态(State):系统处于的特定状态,可以是任何抽象的状态,如有限状态机中的“开”、“关”状态,或是更具体的状态如“运行”、“暂停”、“停止”等。

  2. 事件(Event):导致状态转换发生的触发器或输入,例如用户的输入、外部事件等。事件触发状态之间的转换。

  3. 转移(Transition):描述状态之间的变化或转换,即从一个状态到另一个状态的过程。转移通常由特定的事件触发,触发特定的转移规则。

  4. 动作(Action):在状态转换发生时执行的动作或操作,可以是一些逻辑处理、计算、输出等。动作可以与状态转移相关联。

  5. 初始状态(Initial State):系统的初始状态,即系统启动时所处的状态。

  6. 终止状态(Final State):状态机执行完成后所达到的状态,表示整个状态机的结束。

状态机可以分为有限状态机(Finite State Machine,FSM)和无限状态机(Infinite State Machine)两种。有限状态机是指状态的数量是有限的,而无限状态机则可以有无限多个状态。在系统设计中,有限状态机比较常见。

Spring状态机原理

Spring状态机建立在有限状态机(FSM)的概念之上,提供了一种简洁且灵活的方式来定义、管理和执行状态机。它将状态定义为Java对象,并通过配置来定义状态之间的转换规则。状态转换通常由外部事件触发,我们可以根据业务逻辑定义不同的事件类型,并与状态转换关联。Spring状态机还提供了状态监听器,用于在状态变化时执行特定的逻辑。同时,状态机的状态可以持久化到数据库或其他存储介质中,以便在系统重启或故障恢复时保持状态的一致性。

Spring状态机核心主要包括以下三个关键元素:

  1. 状态(State):定义了系统可能处于的各个状态,如订单状态中的待支付、已支付等。

  2. 转换(Transition):描述了在何种条件下,当接收到特定事件时,系统可以从一个状态转移到另一个状态。例如,接收到“支付成功”事件时,订单状态从“待支付”转变为“已支付”。

  3. 事件(Event):触发状态转换的动作或者消息,它是引起状态机从当前状态迁移到新状态的原因。

接下来,我们将上述状态模式中关于订单状态的示例转换为状态机实现。

Spring状态机的使用

对于状态机,Spring中封装了一个组件spring-statemachine,直接引入即可。

引入依赖

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

定义状态机的状态以及事件类型

在状态机(Finite State Machine, FSM)的设计中,“定义状态”和“定义转换”是构建状态机模型的基础元素。

定义状态(States): 状态是状态机的核心组成单元,代表了系统或对象在某一时刻可能存在的条件或模式。在状态机中,每一个状态都是系统可能处于的一种明确的条件或阶段。例如,在一个简单的咖啡机状态机中,可能有的状态包括“待机”、“磨豆”、“冲泡”和“完成”。每个状态都是独一无二的,且在任何给定时间,系统只能处于其中一个状态。

定义转换(Transitions): 转换则是指状态之间的转变过程,它是状态机模型动态性的体现。当一个外部事件(如用户按下按钮、接收到信号、满足特定条件等)触发时,状态机会从当前状态转移到另一个状态。在定义转换时,需要指出触发转换的事件(Event)以及事件发生时系统的响应,即从哪个状态(Source State)转到哪个状态(Target State)。

/**
*订单状态
*/
public enum OrderStatusEnum {
/**待提交*/
DRAFT,
/**待出库*/
SUBMITTED,
/**已出库*/
DELIVERING,
/**已签收*/
SIGNED,
/**已完成*/
FINISHED,
;
} /**
* 订单状态流转事件
*/
public enum OrderStatusOperateEventEnum {
/**确认,已提交*/
CONFIRMED,
/**发货*/
DELIVERY,
/**签收*/
RECEIVED,
/**完成*/
CONFIRMED_FINISH,
;
}

定义状态机以及状态流转规则

状态机配置类是在使用Spring State Machine或其他状态机框架时的一个重要步骤,这个类主要用于定义状态机的核心结构,包括状态(states)、事件(events)、状态之间的转换规则(transitions),以及可能的状态迁移动作和决策逻辑。

Spring State Machine中,创建状态机配置类通常是通过继承StateMachineConfigurerAdapter类来实现的。这个适配器类提供了几个模板方法,允许开发者重写它们来配置状态机的各种组成部分:

  1. 配置状态configureStates(StateMachineStateConfigurer)): 在这个方法中,开发者定义状态机中所有的状态,包括初始状态(initial state)和结束状态(final/terminal states)。例如,定义状态A、B、C,并指定状态A作为初始状态。

  2. 配置转换configureTransitions(StateMachineTransitionConfigurer)): 在这里,开发者描述状态之间的转换规则,也就是当某个事件(event)发生时,状态机应如何从一个状态转移到另一个状态。例如,当事件X发生时,状态机从状态A转移到状态B。

  3. 配置初始状态configureInitialState(ConfigurableStateMachineInitializer)): 如果需要显式指定状态机启动时的初始状态,可以在该方法中设置。

@Configuration
@EnableStateMachine(name = "orderStateMachine")
public class OrderStatusMachineConfig extends StateMachineConfigurerAdapter<OrderStatusEnum, OrderStatusOperateEventEnum> { /**
* 设置状态机的状态
* StateMachineStateConfigurer 即 状态机状态配置
* @param states 状态机状态
* @throws Exception 异常
*/
@Override
public void configure(StateMachineStateConfigurer<OrderStatusEnum, OrderStatusOperateEventEnum> states) throws Exception {
states.withStates()
.initial(OrderStatusEnum.DRAFT)
.end(OrderStatusEnum.FINISHED)
.states(EnumSet.allOf(OrderStatusEnum.class));
} /**
* 设置状态机与订单状态操作事件绑定
* StateMachineTransitionConfigurer
* @param transitions
* @throws Exception
*/
@Override
public void configure(StateMachineTransitionConfigurer<OrderStatusEnum, OrderStatusOperateEventEnum> transitions) throws Exception {
transitions.withExternal().source(OrderStatusEnum.DRAFT).target(OrderStatusEnum.SUBMITTED)
.event(OrderStatusOperateEventEnum.CONFIRMED)
.and()
.withExternal().source(OrderStatusEnum.SUBMITTED).target(OrderStatusEnum.DELIVERING)
.event(OrderStatusOperateEventEnum.DELIVERY)
.and()
.withExternal().source(OrderStatusEnum.DELIVERING).target(OrderStatusEnum.SIGNED)
.event(OrderStatusOperateEventEnum.RECEIVED)
.and()
.withExternal().source(OrderStatusEnum.SIGNED).target(OrderStatusEnum.FINISHED)
.event(OrderStatusOperateEventEnum.CONFIRMED_FINISH); }
}

配置状态机持久化

状态机持久化是指将状态机在某一时刻的状态信息存储到数据库、缓存系统等中,使得即使在系统重启、网络故障或进程终止等情况下,状态机仍能从先前保存的状态继续执行,而不是从初始状态重新开始。

在业务场景中,例如订单处理、工作流引擎、游戏进度跟踪等,状态机通常用于表示某个实体在其生命周期内的状态变迁。如果没有持久化机制,一旦发生意外情况导致系统宕机或重启,未完成的状态变迁将会丢失,这对于业务连续性和一致性是非常不利的。

状态机持久化通常涉及以下几个方面:

  1. 状态记录:记录当前状态机实例处于哪个状态。
  2. 上下文数据:除了状态外,可能还需要持久化与状态关联的上下文数据,例如触发状态变迁的事件参数、额外的状态属性等。
  3. 历史轨迹:某些复杂场景下可能需要记录状态机的历史变迁轨迹,以便于审计、回溯分析或错误恢复。
  4. 并发控制:在多线程或多节点环境下,状态机的持久化还要考虑并发访问和同步的问题。

Spring Statemachine 提供了与RedisMongoDB等数据存储结合的持久化方案,可以将状态机的状态信息序列化后存储到Redis中。当状态机需要恢复时,可以从存储中读取状态信息并重新构造状态机实例,使其能够从上次中断的地方继续执行流程。

@Configuration
public class OrderPersist { /**
* 持久化配置
* 在实际使用中,可以配合数据库或者Redis等进行持久化操作
* @return
*/
@Bean
public DefaultStateMachinePersister<OrderStatusEnum, OrderStatusOperateEventEnum, OrderDO> stateMachinePersister(){
Map<OrderDO, StateMachineContext<OrderStatusEnum, OrderStatusOperateEventEnum>> map = new HashMap();
return new DefaultStateMachinePersister<>(new StateMachinePersist<OrderStatusEnum, OrderStatusOperateEventEnum, OrderDO>() {
@Override
public void write(StateMachineContext<OrderStatusEnum, OrderStatusOperateEventEnum> context, OrderDO order) throws Exception {
//持久化操作
map.put(order, context);
} @Override
public StateMachineContext<OrderStatusEnum, OrderStatusOperateEventEnum> read(OrderDO order) throws Exception {
//从库中或者redis中读取order的状态信息
return map.get(order);
}
});
}
}

定义状态机监听器

状态机监听器(State Machine Listener)是一种组件,它可以监听并响应状态机在运行过程中的各种事件,例如状态变迁、进入或退出状态、转换被拒绝等。

Spring Statemachine中,监听器可以通过实现StateMachineListener接口来定义。该接口提供了一系列回调方法,如transitionTriggeredstateEnteredstateExited等,当状态机触发转换、进入新状态或离开旧状态时,这些方法会被调用。同时,我们也可以通过注解实现监听器。注解方式可以在类的方法上直接声明该方法应该在何种状态下被调用,简化监听器的编写和配置。例如@OnTransition@OnTransitionEnd@OnTransitionStart

@Component
@WithStateMachine(name = "orderStateMachine")
public class OrderStatusListener { @OnTransition(source = "DRAFT", target = "SUBMITTED")
public boolean payTransition(Message<OrderStatusOperateEventEnum> message) {
OrderDO order = (OrderDO) message.getHeaders().get("order");
order.setOrderStatusEnum(OrderStatusEnum.SUBMITTED);
System.out.println(String.format("出库订单[%s]确认,状态机信息:%s", order.getOrderNo(), message.getHeaders()));
return true;
} @OnTransition(source = "SUBMITTED", target = "DELIVERING")
public boolean deliverTransition(Message<OrderStatusOperateEventEnum> message) {
OrderDO order = (OrderDO) message.getHeaders().get("order");
order.setOrderStatusEnum(OrderStatusEnum.DELIVERING);
System.out.println(String.format("出库订单[%s]发货出库,状态机信息:%s", order.getOrderNo(), message.getHeaders()));
return true;
} @OnTransition(source = "DELIVERING", target = "SIGNED")
public boolean receiveTransition(Message<OrderStatusOperateEventEnum> message){
OrderDO order = (OrderDO) message.getHeaders().get("order");
order.setOrderStatusEnum(OrderStatusEnum.SIGNED);
System.out.println(String.format("出库订单[%s]签收,状态机信息:%s", order.getOrderNo(), message.getHeaders()));
return true;
} @OnTransition(source = "SIGNED", target = "FINISHED")
public boolean finishTransition(Message<OrderStatusOperateEventEnum> message){
OrderDO order = (OrderDO) message.getHeaders().get("order");
order.setOrderStatusEnum(OrderStatusEnum.FINISHED);
System.out.println(String.format("出库订单[%s]完成,状态机信息:%s", order.getOrderNo(), message.getHeaders()));
return true;
}
}

而监听器需要监听到状态流转的事件才会发挥他的作用,才能监听到某个状态事件之后,完成状态的变更。

@Component
public class StateEventUtil { private StateMachine<OrderStatusEnum, OrderStatusOperateEventEnum> orderStateMachine; private StateMachinePersister<OrderStatusEnum, OrderStatusOperateEventEnum, OrderDO> stateMachinePersister; /**
* 发送状态转换事件
* synchronized修饰保证这个方法是线程安全的
* @param message
* @return
*/
public synchronized boolean sendEvent(Message<OrderStatusOperateEventEnum> message) {
boolean result = false;
try {
//启动状态机
orderStateMachine.start();
OrderDO order = (OrderDO) message.getHeaders().get("order");
//尝试恢复状态机状态
stateMachinePersister.restore(orderStateMachine, order);
result = orderStateMachine.sendEvent(message);
//持久化状态机状态
stateMachinePersister.persist(orderStateMachine, order);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (Objects.nonNull(message)) {
OrderDO order = (OrderDO) message.getHeaders().get("order");
if (Objects.nonNull(order) && Objects.equals(order.getOrderStatusEnum(), OrderStatusEnum.FINISHED)) {
orderStateMachine.stop();
}
}
}
return result;
} @Autowired
public void setOrderStateMachine(StateMachine<OrderStatusEnum, OrderStatusOperateEventEnum> orderStateMachine) {
this.orderStateMachine = orderStateMachine;
} @Autowired
public void setStateMachinePersister(StateMachinePersister<OrderStatusEnum, OrderStatusOperateEventEnum, OrderDO> stateMachinePersister) {
this.stateMachinePersister = stateMachinePersister;
}
}

到这里,我们的状态机就定义好了,下面我们就可以在业务代码中使用状态机完成的订单状态的流转。

业务代码使用

@Service
public class OrderServiceImpl implements IOrderService { private StateEventUtil stateEventUtil; private static final AtomicInteger ID_COUNTER = new AtomicInteger(0); private static final Map<Long, OrderDO> ORDER_MAP = new ConcurrentHashMap<>(); /**
* 创建新订单
*
* @param orderDO
*/
@Override
public Long createOrder(OrderDO orderDO) {
long orderId = ID_COUNTER.incrementAndGet();
orderDO.setOrderId(orderId);
orderDO.setOrderNo("OC20240306" + orderId);
orderDO.setOrderStatusEnum(OrderStatusEnum.DRAFT);
ORDER_MAP.put(orderId, orderDO);
System.out.println(String.format("订单[%s]创建成功:", orderDO.getOrderNo()));
return orderId;
} /**
* 确认订单
*
* @param orderId
*/
@Override
public void confirmOrder(Long orderId) {
OrderDO order = ORDER_MAP.get(orderId);
System.out.println("确认订单,订单号:" + order.getOrderNo());
Message message = MessageBuilder.withPayload(OrderStatusOperateEventEnum.CONFIRMED).
setHeader("order", order).build();
if (!stateEventUtil.sendEvent(message)) {
System.out.println(" 确认订单失败, 状态异常,订单号:" + order.getOrderNo());
}
} /**
* 订单发货
*
* @param orderId
*/
@Override
public void deliver(Long orderId) {
OrderDO order = ORDER_MAP.get(orderId);
System.out.println("订单出库,订单号:" + order.getOrderNo());
Message message = MessageBuilder.withPayload(OrderStatusOperateEventEnum.DELIVERY).
setHeader("order", order).build();
if (!stateEventUtil.sendEvent(message)) {
System.out.println(" 订单出库失败, 状态异常,订单号:" + order.getOrderNo());
}
} /**
* 签收订单
*
* @param orderId
*/
@Override
public void signOrder(Long orderId) {
OrderDO order = ORDER_MAP.get(orderId);
System.out.println("订单签收,订单号:" + order.getOrderNo());
Message message = MessageBuilder.withPayload(OrderStatusOperateEventEnum.RECEIVED).
setHeader("order", order).build();
if (!stateEventUtil.sendEvent(message)) {
System.out.println(" 订单签收失败, 状态异常,订单号:" + order.getOrderNo());
}
} /**
* 确认完成
*
* @param orderId
*/
@Override
public void finishOrder(Long orderId) {
OrderDO order = ORDER_MAP.get(orderId);
System.out.println("订单完成,订单号:" + order.getOrderNo());
Message message = MessageBuilder.withPayload(OrderStatusOperateEventEnum.CONFIRMED_FINISH).
setHeader("order", order).build();
if (!stateEventUtil.sendEvent(message)) {
System.out.println(" 订单完成失败, 状态异常,订单号:" + order.getOrderNo());
}
} /**
* 获取所有订单信息
*/
@Override
public List<OrderDO> listOrders() {
return new ArrayList<>(ORDER_MAP.values());
} @Autowired
public void setStateEventUtil(StateEventUtil stateEventUtil) {
this.stateEventUtil = stateEventUtil;
}
}

我们在定义一个接口,模拟订单的状态流转:

@RestController
public class OrderController { private IOrderService orderService; @GetMapping("testOrderStatusMachine")
public void testOrderStatusMachine(){
Long orderId1 = orderService.createOrder(new OrderDO());
Long orderId2 = orderService.createOrder(new OrderDO()); orderService.confirmOrder(orderId1);
new Thread("客户线程"){
@Override
public void run() {
orderService.deliver(orderId1);
orderService.signOrder(orderId1);
orderService.finishOrder(orderId1);
}
}.start(); orderService.confirmOrder(orderId2);
orderService.deliver(orderId2);
orderService.signOrder(orderId2);
orderService.finishOrder(orderId2); System.out.println("全部订单状态:" + orderService.listOrders()); } @Autowired
public void setOrderService(IOrderService orderService) {
this.orderService = orderService;
}
}

我们调用接口:

我们在日志中可以看到订单状态在状态机的控制下,流转的很丝滑。。。

注意事项

  • 一致性保证:确保状态机的配置正确反映了业务逻辑,并保持其在并发环境下的状态一致性。

  • 异常处理:在状态转换过程中可能出现异常情况,需要适当地捕获和处理这些异常,防止状态机进入无效状态。

  • 监控与审计:在实际应用中,为了便于调试和追溯,可以考虑集成日志记录或事件监听器来记录状态机的每一次状态变迁。

  • 扩展性与维护性:随着业务的发展,状态机的设计应当具有足够的灵活性,以便于新增状态或调整转换规则。

一点思考

除了直接使用如Spring状态机这样的专门状态管理工具外,还可以使用其他的哪些方法实现状态机的功能呢?比如:

  1. 消息队列方式

    状态的变更通过发布和消费消息来驱动。每当发生状态变更所需的事件时,生产者将事件作为一个消息发布到特定的消息队列(Topic),而消费者则监听这些消息,根据消息内容和业务规则对订单状态进行更新。这种方式有利于解耦各个服务,实现异步处理,同时增强系统的伸缩性和容错能力。

  2. 定时任务驱动

    使用定时任务定期检查系统中的订单状态,根据预设的业务规则判断是否满足状态变迁条件。比如,每隔一段时间执行一次Job,查询数据库中处于特定状态的订单,并决定是否进行状态更新。这种方法适用于具有一定时效性的状态变迁,但实时性相对较低,对于瞬时响应要求高的场景不太适用。

有关SpringBoot下几种定时任务的实现方式请参考:玩转SpringBoot:SpringBoot的几种定时任务实现方式

  1. 规则引擎方式

    利用规则引擎(如DroolsLiteFlow等)实现状态机,业务团队可以直接在规则引擎中定义状态及状态之间的转换规则,当新的事实数据(如订单信息)输入到规则引擎时,引擎会自动匹配并执行相应的规则,触发状态改变。这种方式的优点在于业务规则高度集中,易于管理和修改,同时也具备较高的灵活性,能够快速应对业务规则的变化。

SpringBoot下使用LiteFlow规则引擎请参考:轻松应对复杂业务逻辑:LiteFlow-编排式规则引擎框架的优势

总结

Spring状态机提供了一种强大的工具,使得在Java应用中实现复杂的业务流程变得更为简洁和规范。不仅可以提升代码的可读性和可维护性,还能有效降低不同模块之间的耦合度,提高系统的整体稳定性与健壮性。

本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等

Spring状态机(FSM),让订单状态流转如丝般顺滑的更多相关文章

  1. 彻底搞懂Spring状态机原理,实现订单与物流解耦

    本文节选自<设计模式就该这样学> 1 状态模式的UML类图 状态模式的UML类图如下图所示. 2 使用状态模式实现登录状态自由切换 当我们在社区阅读文章时,如果觉得文章写得很好,我们就会评 ...

  2. 管理订单状态,该上状态机吗?轻量级状态机COLA StateMachine保姆级入门教程

    前言 在平常的后端项目开发中,状态机模式的使用其实没有大家想象中那么常见,笔者之前由于不在电商领域工作,很少在业务代码中用状态机来管理各种状态,一般都是手动get/set状态值.去年笔者进入了电商领域 ...

  3. Lua中使用状态机FSM简单例子

    FSM 有限状态机: 一个有限状态机是一个设备,或者是一个设备模型,具有有限数量的状态,它可以在任何给定的时间根据输入进行操作,使得一个状态变换到另一个状态,或者是使一个输入或者一种行为的发生.一个有 ...

  4. Magento后台手动修改订单状态方法及手动修改方法php

    订单详细内容页手动修改订单状态方法: 打开此文件:app\design\adminhtml\default\default\template\sales\order\view\history.phtm ...

  5. ecshop 订单-》订单状态 2

    // 判断订单状态 public function get_order_status($os,$ps,$ss){ $arr = array('已取消','待付款','待发货','待收货','确认收货' ...

  6. ecshop的订单状态

    ecshop的订单状态都是在ecs_order_info表中的字段里. 订单状态 未确认 取消 确认 已付款 配货中 已发货 已收货 退货 order_status 0 2 1 1 1 5 5 4 s ...

  7. magento设置订单状态

    <?php require_once('app/Mage.php');umask(0);Mage::app('default'); $order = Mage::getModel('sales/ ...

  8. 如何在magento后台增加一个自定义订单状态

    magento后台订单状态(order status)只有Pending.Processing.On Hold.Closed.Canceled.Pending Payment 等等,如何在magent ...

  9. ECSHOP的订单状态在数据库中的表现(order_status, shipping_status, pay_status)

    echop的订单状态都是在ecs_order_info表中的字段里. 订单状态 未确认 取消 确认 已付款 配货中 已发货 已收货 退货 order_status 0 2 1 1 1 5 5 4 sh ...

  10. R语言diagram包画订单状态流图

    代码如下: library("diagram") #a <- read.table(file="clipboard",header=TRUE) write ...

随机推荐

  1. 并发编程-JUC的三个常用工具类

    1.CountDownLatch:减法计数器 代码实例 public class CountDownLatchTest { public static void main(String[] args) ...

  2. 深入操作系统内核!细致剖析 MIT 6.S081 课程 Lab 2 : system calls - 1

    本文细致的剖析了2021 FALL MIT 6.S081 课程的一项实验, Lab 链接 Lab: System calls (mit.edu) . 大家的点赞将会是我继续更新的巨大动力,对文中内容或 ...

  3. Gitee一个仓库存储多个项目

    需求:     平时会做一些小项目,有时候一个小项目就几行代码,十几K的项目,给这些小项目建一个库保存太奢侈了太浪费了,所以换个思路,根据项目类型来创建库,然后每个小项目以孤立分支的方式存到该库中,这 ...

  4. ARKit的理解与使用

    AR概述 AR的意义:让虚拟世界套与现实世界建立联系,并可以进行互动. AR的技术实现:通过实时地计算摄影机输出影像的位置及角度,并在内部通过算法识别将场景中的事物,然后在内部模拟的三维坐标系中给识别 ...

  5. 心动了!iPhone 15 Pro超窄边框感受下:1.5mm破历史纪录 “跑马框”再见

    综合目前已知爆料来看,iPhone 15系列将有7大升级.其中一个比较明显的直观变化是,新款iPhone全系边框都会更窄.iPhone 15 Pro.Pro Max的边框宽度仅为1.55mm,破历史记 ...

  6. Umi配置路由

    一.Umi路由的概念 在 Umi 中,你可以在 config/config.js 文件中使用 routes 属性来配置路由.routes 属性是一个数组,每个元素都表示一个路由对象.每个路由对象都包含 ...

  7. Android 开机流程介绍

    目录 一.目的 二.环境 三.相关概念 3.1 Android平台架构 3.2 Android启动架构 3.3 zImage 3.4 RAMDISK 3.5 RC文件 四.详细设计 4.1 Boot ...

  8. idea启动springboot项目报错java.lang.ClassNotFoundException: com.gctl.bpm.GctlBpmApplication解决方案

    有时候父子工程改造springboot项目时会报错java.lang.ClassNotFoundException: com.gctl.bpm.GctlBpmApplication如下图所示 此时不要 ...

  9. CF1499

    A 氵 B 如果 11 后出现了 00 就不行. C 枚举走几段. 横竖可以分开算. 一定是:除了费用最小的都是走长度 \(1\),费用最小的包揽剩下的. D \(c\cdot lcm(a,b)-d\ ...

  10. UVA12390 Distributing Ballot Boxes 题解

    题目传送门 题意 有 \(n\) 个城市,\(b\) 个投票箱,第 \(i\) 个城市有 \(a_i\) 人,每个人均有一张票,将 \(b\) 个投票箱分给 \(n\) 个城市,每个城市的票分摊在投票 ...