状态机方案解决订单状态扭转

开发 开发工具
状态机是一种非常强大的工具,可以用于实现复杂的业务逻辑。在实际应用中,我们应该根据具体的业务场景选择合适的状态机库,并结合拦截器、事件等机制,以实现状态机的灵活性和可扩展性。

状态机机制是一种常用的解决状态扭转问题的方法,通过定义状态以及状态之间的转移规则来控制状态的流转。对于订单系统,我们可以使用状态机机制来管理订单状态的扭转。

具体来说,我们可以使用状态机框架来实现订单状态的控制,比如使用 Spring State Machine 框架。以下是使用 Spring State Machine 框架来控制订单状态的示例:

定义订单状态

我们可以定义订单的各种状态,如下所示:

javaCopy code
public enum OrderState {
CREATED, // 订单已创建
PAID, // 订单已支付
SHIPPED, // 订单已发货
DELIVERED, // 订单已送达
CANCELED, // 订单已取消
CLOSED // 订单已关闭
}

定义状态机配置

我们需要定义状态机的配置,包括各个状态和状态之间的转移规则,如下所示:

javaCopy code
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderState, OrderEvent> {


@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
transitions
.withExternal()
.source(OrderState.CREATED).target(OrderState.PAID).event(OrderEvent.PAY)
.and()
.withExternal()
.source(OrderState.PAID).target(OrderState.SHIPPED).event(OrderEvent.SHIP)
.and()
.withExternal()
.source(OrderState.SHIPPED).target(OrderState.DELIVERED).event(OrderEvent.DELIVER)
.and()
.withExternal()
.source(OrderState.CREATED).target(OrderState.CANCELED).event(OrderEvent.CANCEL)
.and()
.withExternal()
.source(OrderState.PAID).target(OrderState.CANCELED).event(OrderEvent.CANCEL)
.and()
.withExternal()
.source(OrderState.SHIPPED).target(OrderState.CANCELED).event(OrderEvent.CANCEL);
}
}

定义事件

我们需要定义各种事件,如支付、发货、取消等事件,如下所示:

javaCopy code
public enum OrderEvent {
PAY, // 支付
SHIP, // 发货
DELIVER, // 送达
CANCEL // 取消
}

创建状态机

我们可以使用状态机工厂来创建状态机,如下所示:

javaCopy code
@Configuration
public class OrderStateMachineFactory {


@Autowired
private StateMachineFactory<OrderState, OrderEvent> stateMachineFactory;


public StateMachine<OrderState, OrderEvent> createStateMachine() {
StateMachine<OrderState, OrderEvent> stateMachine = stateMachineFactory.getStateMachine();
stateMachine.start();
return stateMachine;
}
}

处理状态机事件

当订单发生某种事件时,我们可以使用状态机来处理事件,如下所示:

  1. 确定事件:首先,您需要确定可能发生的事件。对于订单状态机,可能的事件可能包括创建订单、付款、取消订单等。
  2. 定义状态:然后,您需要定义订单状态。对于订单系统,可能的状态包括待付款、待发货、待收货、已完成、已取消等。
  3. 设计状态转换:接下来,您需要设计状态转换,以便在事件发生时自动更改订单状态。例如,当用户完成支付时,订单状态将从待付款转换为待发货。
  4. 实现状态机:最后,您需要使用代码实现状态机。在Java中,您可以使用开源库如Spring Statemachine、Squirrel-foundation等来实现状态机。您需要定义状态机的状态、事件和状态转换,以便自动处理订单状态的变化。

以下是一个简单的状态机示例,展示了订单状态的定义和转换:

scssCopy code
public enum OrderState {
CREATED,
PAID,
CANCELLED,
SHIPPED,
COMPLETED
}


public enum OrderEvent {
PAY,
CANCEL,
SHIP,
COMPLETE
}


public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
transitions
.withExternal().source(OrderState.CREATED).target(OrderState.PAID).event(OrderEvent.PAY)
.and()
.withExternal().source(OrderState.PAID).target(OrderState.SHIPPED).event(OrderEvent.SHIP)
.and()
.withExternal().source(OrderState.SHIPPED).target(OrderState.COMPLETED).event(OrderEvent.COMPLETE)
.and()
.withExternal().source(OrderState.CREATED).target(OrderState.CANCELLED).event(OrderEvent.CANCEL)
.and()
.withExternal().source(OrderState.PAID).target(OrderState.CANCELLED).event(OrderEvent.CANCEL)
.and()
.withExternal().source(OrderState.SHIPPED).target(OrderState.CANCELLED).event(OrderEvent.CANCEL);
}


public void onPay() {
// 订单支付成功
}


public void onShip() {
// 订单发货
}


public void onComplete() {
// 订单完成
}


public void onCancel() {
// 订单取消
}

在此示例中,我们定义了订单状态(OrderState)和事件(OrderEvent)。然后,我们使用withExternal()方法定义状态转换,并指定源状态、目标状态和触发转换的事件。最后,我们定义了处理不同事件的方法。

当某个事件发生时,状态机会自动触发状态转换,并调用相应的事件处理方法,从而更改订单状态。

如果需要实现一个高可用的状态机,可以考虑使用分布式状态机,例如使用 ZooKeeper 或 etcd 实现分布式协同。在这种情况下,每个节点都可以处理事件并更新状态,节点之间可以通过协同算法保持一致性。这种方式可以保证状态机的可靠性和高可用性,但需要考虑一些复杂性,例如如何处理网络分区和节点故障等问题。

好的,下面是一个使用 ZooKeeper 实现分布式状态机的示例:

  1. 首先,需要引入 ZooKeeper 的 Java 客户端库:
xmlCopy code
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.6.3</version>
</dependency>
  1. 在代码中创建 ZooKeeper 客户端:
javaCopy code
public class ZooKeeperClient {
private static final String CONNECTION_STRING = "localhost:2181";
private static final int SESSION_TIMEOUT_MS = 5000;
private static final int CONNECT_TIMEOUT_MS = 5000;
private static final String BASE_PATH = "/order-state";


private final ZooKeeper zooKeeper;


public ZooKeeperClient() throws IOException {
this.zooKeeper = new ZooKeeper(CONNECTION_STRING, SESSION_TIMEOUT_MS, new Watcher() {
@Override
public void process(WatchedEvent event) {
// handle event
}
});
}


public void close() throws InterruptedException {
zooKeeper.close();
}


public String createNode(String path, byte[] data) throws KeeperException, InterruptedException {
return zooKeeper.create(BASE_PATH + path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT_SEQUENTIAL);
}


public byte[] getData(String path) throws KeeperException, InterruptedException {
return zooKeeper.getData(BASE_PATH + path, false, null);
}


public void setData(String path, byte[] data) throws KeeperException, InterruptedException {
zooKeeper.setData(BASE_PATH + path, data, -1);
}


public void deleteNode(String path) throws KeeperException, InterruptedException {
zooKeeper.delete(BASE_PATH + path, -1);
}
}

这个类封装了 ZooKeeper 客户端的一些基本操作,例如创建节点、获取数据、更新数据和删除节点等。

  1. 创建一个订单状态机:
javaCopy code
public class OrderStateMachine {
private final ZooKeeperClient client;
private final String orderId;
private final Map<OrderState, Set<OrderState>> transitions;
private OrderState currentState;


public OrderStateMachine(ZooKeeperClient client, String orderId) {
this.client = client;
this.orderId = orderId;
this.transitions = new HashMap<>();
this.currentState = OrderState.CREATED;


// Define transitions
transitions.put(OrderState.CREATED, EnumSet.of(OrderState.PAYED, OrderState.CANCELED));
transitions.put(OrderState.PAYED, EnumSet.of(OrderState.SHIPPED, OrderState.CANCELED));
transitions.put(OrderState.SHIPPED, EnumSet.of(OrderState.DELIVERED, OrderState.CANCELED));
transitions.put(OrderState.DELIVERED, EnumSet.noneOf(OrderState.class));
transitions.put(OrderState.CANCELED, EnumSet.noneOf(OrderState.class));


// Initialize state
try {
byte[] data = client.getData(orderId);
if (data != null) {
this.currentState = OrderState.valueOf(new String(data));
}
} catch (Exception e) {
// Handle exception
}
}


public synchronized void handleEvent(OrderEvent event) {
Set<OrderState> validTransitions = transitions.get(currentState);
if (validTransitions != null && validTransitions.contains(event.getTargetState())) {
try {
// Update state in ZooKeeper
client.setData(orderId, event.getTargetState().name().getBytes());


// Update local state
currentState = event.getTargetState();
} catch (Exception e) {
// Handle exception
}

创建订单状态机的过程可以分为以下几个步骤:

确定状态和事件

首先,需要确定订单状态和可能触发的事件。对于一个简单的订单系统,可能的状态和事件如下:

  • 订单已创建(CREATED)支付(PAY)取消(CANCEL)
  • 订单已支付(PAID)发货(SHIP)取消(CANCEL)
  • 订单已发货(SHIPPED)确认收货(RECEIVE)取消(CANCEL)
  • 订单已完成(RECEIVED)

设计状态转移图

然后,根据状态和事件,设计状态转移图。状态转移图用于表示状态之间的转换关系,以及何时触发转换。

下图是一个简单的订单状态转移图:

luaCopy code  
+-------+
| |
| 待支付 +---------+
| | |
+-------+ |
| |
| |
v |
+-------+ |
| | |
| 已支付 +------+ |
| | | |
+-------+ | |
| | |
| | |
v | |
+-------+ | |
| | | |
| 已发货 +------+ |
| | |
+-------+ |
| |
| |
v |
+-------+ |
| | |
| 已收货 | <---------+
| |
+-------+

在状态转移图中,每个圆圈代表一个状态,每个箭头代表一条转移。箭头上标注的是触发转移的事件。

实现状态机

最后,使用代码实现状态机。具体实现方式可能因编程语言和状态机库而异,这里以 Java 和 Spring 状态机为例:

scssCopy code
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStatus, OrderEvent> {
@Override
public void configure(StateMachineStateConfigurer<OrderStatus, OrderEvent> states) throws Exception {
states.withStates()
.initial(OrderStatus.CREATED)
.states(EnumSet.allOf(OrderStatus.class));
}


@Override
public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions) throws Exception {
transitions
.withExternal()
.source(OrderStatus.CREATED).target(OrderStatus.PAID).event(OrderEvent.PAY)
.and()
.withExternal()
.source(OrderStatus.PAID).target(OrderStatus.SHIPPED).event(OrderEvent.SHIP)
.and()
.withExternal()
.source(OrderStatus.SHIPPED).target(OrderStatus.RECEIVED).event(OrderEvent.RECEIVE)
.and()
.withExternal()
.source(OrderStatus.CREATED).target(OrderStatus.CANCELED).event(OrderEvent.CANCEL)
.and()
.withExternal()
.source(OrderStatus.PAID).target(OrderStatus.CANCELED).event(OrderEvent.CANCEL)
.and()
.withExternal()
.source(OrderStatus.SHIPPED).target(OrderStatus.CANCELED).event(OrderEvent.CANCEL);
}
}

以下是一个基于 Java 和 Spring 状态机的订单状态机的代码示例:

javaCopy code
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderStatus, OrderEvent> {


@Autowired
private OrderStateChangeInterceptor orderStateChangeInterceptor;


@Override
public void configure(StateMachineConfigurationConfigurer<OrderStatus, OrderEvent> config) throws Exception {
config
.withConfiguration()
.autoStartup(true)
.listener(orderStateChangeInterceptor);
}


@Override
public void configure(StateMachineStateConfigurer<OrderStatus, OrderEvent> states) throws Exception {
states
.withStates()
.initial(OrderStatus.CREATED)
.state(OrderStatus.PAID)
.state(OrderStatus.CONFIRMED)
.state(OrderStatus.SHIPPED)
.state(OrderStatus.DELIVERED)
.end(OrderStatus.COMPLETED)
.end(OrderStatus.CANCELED);
}


@Override
public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions) throws Exception {
transitions
.withExternal()
.source(OrderStatus.CREATED)
.target(OrderStatus.PAID)
.event(OrderEvent.PAY)
.and()
.withExternal()
.source(OrderStatus.PAID)
.target(OrderStatus.CONFIRMED)
.event(OrderEvent.CONFIRM)
.and()
.withExternal()
.source(OrderStatus.CONFIRMED)
.target(OrderStatus.SHIPPED)
.event(OrderEvent.SHIP)
.and()
.withExternal()
.source(OrderStatus.SHIPPED)
.target(OrderStatus.DELIVERED)
.event(OrderEvent.DELIVER)
.and()
.withExternal()
.source(OrderStatus.DELIVERED)
.target(OrderStatus.COMPLETED)
.event(OrderEvent.COMPLETE)
.and()
.withExternal()
.source(OrderStatus.CREATED)
.target(OrderStatus.CANCELED)
.event(OrderEvent.CANCEL)
.and()
.withExternal()
.source(OrderStatus.PAID)
.target(OrderStatus.CANCELED)
.event(OrderEvent.CANCEL)
.and()
.withExternal()
.source(OrderStatus.CONFIRMED)
.target(OrderStatus.CANCELED)
.event(OrderEvent.CANCEL)
.and()
.withExternal()
.source(OrderStatus.SHIPPED)
.target(OrderStatus.CANCELED)
.event(OrderEvent.CANCEL)
.and()
.withExternal()
.source(OrderStatus.DELIVERED)
.target(OrderStatus.CANCELED)
.event(OrderEvent.CANCEL);
}
}

在这个示例中,我们定义了订单状态机的状态和事件。在 configure(StateMachineConfigurationConfigurer<OrderStatus, OrderEvent> config) 方法中,我们配置了状态机的启动和事件监听器。在 configure(StateMachineStateConfigurer<OrderStatus, OrderEvent> states) 方法中,我们定义了订单状态机的状态。在 configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions) 方法中,我们定义了状态机的事件和状态之间的转换。在 source 中指定起始状态,在 target 中指定目标状态,在 event 中指定事件。

最后,我们还需要创建一个状态机事件拦截器,用于在状态机状态转换时执行自定义逻辑,比如修改订单状态、记录状态转换日志等。下面是

以下是一个简单的状态机事件拦截器的代码示例:

javaCopy code
public class OrderStateChangeInterceptor extends StateMachineInterceptorAdapter<OrderStatus, OrderStatusChangeEvent> {
@Override
public void preStateChange(State<OrderStatus, OrderStatusChangeEvent> state, Message<OrderStatusChangeEvent> message, Transition<OrderStatus, OrderStatusChangeEvent> transition, StateMachine<OrderStatus, OrderStatusChangeEvent> stateMachine) {
// 根据状态转移情况更新订单状态
if (transition.getTarget().getId() == OrderStatus.PAID) {
// 更新订单为已支付状态
updateOrderStatus(message.getHeaders().get("orderId"), OrderStatus.PAID);
} else if (transition.getTarget().getId() == OrderStatus.CANCELED) {
// 更新订单为已取消状态
updateOrderStatus(message.getHeaders().get("orderId"), OrderStatus.CANCELED);
}
// 记录状态转移日志
recordStateChangeLog(message.getHeaders().get("orderId"), transition.getSource().getId(), transition.getTarget().getId());
}


private void updateOrderStatus(String orderId, OrderStatus status) {
// 执行更新订单状态的业务逻辑
}


private void recordStateChangeLog(String orderId, OrderStatus fromStatus, OrderStatus toStatus) {
// 记录状态转移日志的业务逻辑
}
}

在拦截器的 preStateChange 方法中,我们可以根据状态转移情况执行自定义的业务逻辑,比如更新订单状态、记录状态转移日志等。在这个示例中,我们根据状态转移目标状态更新订单状态,并记录状态转移日志。在具体业务实现中,我们可以根据需要扩展拦截器的逻辑。

完整的状态机实现和拦截器代码示例如下:

javaCopy code
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<OrderState, OrderEvent> {


@Autowired
private OrderService orderService;


@Override
public void configure(StateMachineConfigurationConfigurer<OrderState, OrderEvent> config) throws Exception {
config
.withConfiguration()
.autoStartup(true)
.listener(new StateMachineListenerAdapter<OrderState, OrderEvent>() {
@Override
public void stateChanged(State<OrderState, OrderEvent> from, State<OrderState, OrderEvent> to) {
log.info("Order status changed from {} to {}", from.getId(), to.getId());
}
});
}


@Override
public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states) throws Exception {
states
.withStates()
.initial(OrderState.SUBMITTED)
.state(OrderState.PAID)
.state(OrderState.FULFILLED)
.state(OrderState.CANCELLED)
.end(OrderState.COMPLETED);
}


@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions) throws Exception {
transitions
.withExternal()
.source(OrderState.SUBMITTED).target(OrderState.PAID).event(OrderEvent.PAY)
.and()
.withExternal()
.source(OrderState.PAID).target(OrderState.FULFILLED).event(OrderEvent.FULFILL)
.and()
.withExternal()
.source(OrderState.SUBMITTED).target(OrderState.CANCELLED).event(OrderEvent.CANCEL)
.and()
.withExternal()
.source(OrderState.PAID).target(OrderState.CANCELLED).event(OrderEvent.CANCEL);
}


@Bean
public StateMachineInterceptor<OrderState, OrderEvent> stateMachineInterceptor() {
return new StateMachineInterceptorAdapter<OrderState, OrderEvent>() {
@Override
public void preStateChange(State<OrderState, OrderEvent> state, Message<OrderEvent> message, Transition<OrderState, OrderEvent> transition, StateMachine<OrderState, OrderEvent> stateMachine) {
OrderState targetState = transition.getTarget().getId();
Order order = stateMachine.getExtendedState().get("order", Order.class);
if (order != null) {
order.setState(targetState);
orderService.updateOrder(order);
log.info("Order status updated to {}", targetState);
log.info("Record order state change log, from {} to {}", state.getId(), targetState);
}
}
};
}


@Override
public void configure(StateMachineInterceptorConfigurer<OrderState, OrderEvent> interceptors) throws Exception {
interceptors
.withInterceptor(stateMachineInterceptor())
.order(-1);
}
}

在上述示例中,我们创建了一个 Spring 状态机,并定义了四个订单状态:SUBMITTED、PAID、FULFILLED 和 CANCELLED。我们还定义了三个状态转移事件:PAY、FULFILL 和 CANCEL。

我们在拦截器的 preStateChange 方法中,根据状态转移目标状态更新订单状态,并记录状态转移日志。在具体业务实现中,我们可以根据需要扩展拦截器的逻辑。

好的,我们可以在拦截器的 preStateChange 方法中,根据状态转移目标状态更新订单状态,并记录状态转移日志。在具体业务实现中,我们可以根据需要扩展拦截器的逻辑。以下是示例代码:

javaCopy code
@Componentpublic class OrderStateChangeInterceptor extends StateMachineInterceptorAdapter<OrderStates, OrderEvents> {


@Autowired
private OrderService orderService;


@Override
public void preStateChange(State<OrderStates, OrderEvents> state, Message<OrderEvents> message, Transition<OrderStates, OrderEvents> transition, StateMachine<OrderStates, OrderEvents> stateMachine) {
// 获取订单号
String orderId = message.getHeaders().get(OrderConstant.ORDER_ID_HEADER, String.class);
// 获取目标状态
OrderStates targetState = transition.getTarget().getId();
// 更新订单状态
orderService.updateOrderStatus(orderId, targetState);
// 记录状态转移日志
orderService.saveOrderLog(orderId, targetState);
}
}

在上述示例中,我们通过 OrderService 来更新订单状态和保存状态转移日志。具体的实现可以根据业务需求自行实现。

然后,我们需要将拦截器配置到状态机中,如下所示:

javaCopy code
@Configuration@EnableStateMachine
public class StateMachineConfig extends StateMachineConfigurerAdapter<OrderStates, OrderEvents> {


// ... 状态机配置代码 ...


@Autowired
private OrderStateChangeInterceptor orderStateChangeInterceptor;


@Override
public void configure(StateMachineInterceptorConfigurer<OrderStates, OrderEvents> configurer) throws Exception {
configurer
.withInterceptor(orderStateChangeInterceptor);
}
}

在上述示例中,我们将拦截器通过 withInterceptor 方法添加到状态机中。这样,在状态转移时,就会触发拦截器中的 preStateChange 方法,从而执行自定义的业务逻辑。

是的,当状态机执行状态转移时,会调用拦截器中的 preStateChange 方法,从而执行自定义的业务逻辑。拦截器可以在状态转移前后执行逻辑,比如记录日志、更新状态等,从而实现状态机的扩展。在具体的业务场景中,我们可以根据需要扩展拦截器的逻辑,以满足具体的需求。

需要注意的是,拦截器中的业务逻辑应该尽量简单,不要耗时过长,以避免影响状态转移的性能。同时,拦截器中的逻辑也应该考虑异常情况的处理,比如事务回滚、异常记录等。

总的来说,状态机是一种非常强大的工具,可以用于实现复杂的业务逻辑。在实际应用中,我们应该根据具体的业务场景选择合适的状态机库,并结合拦截器、事件等机制,以实现状态机的灵活性和可扩展性。


责任编辑:武晓燕 来源: 德哥很ok
相关推荐

2010-06-18 12:38:38

UML状态机视图

2013-09-03 09:57:43

JavaScript有限状态机

2010-06-18 13:25:44

UML状态机视图

2020-03-27 10:50:29

DSL 状态机工具

2021-07-08 09:15:20

单片机编程状态机编程语言

2021-04-29 09:31:05

前端开发技术

2011-06-24 16:09:24

Qt 动画 状态机

2010-07-08 13:03:31

UML状态机图

2010-07-12 15:00:56

UML状态机视图

2021-08-19 09:00:00

微服务开发架构

2020-10-15 10:38:35

C语言状态模型

2011-08-22 10:52:30

iptables状态

2020-12-02 13:33:58

函数指针编程语言

2010-06-18 13:15:07

UML状态机图

2010-02-24 09:32:38

Visual Stud

2024-01-08 09:46:47

2014-05-21 11:09:56

前端有限状态机

2011-06-29 18:36:59

Qt 动画 状态机

2019-04-12 14:22:40

马蜂窝机票订单

2010-02-22 09:09:02

Visual Stud
点赞
收藏

51CTO技术栈公众号