外卖霸王餐系统中基于状态机(State Machine)管理试吃订单生命周期
在“外卖霸王餐”业务中,试吃订单需经历从申请、审核、核销到完成或取消的多个阶段,且各状态间存在严格转换规则。若采用传统if-else或硬编码方式处理状态流转,将导致逻辑混乱、扩展困难、易出错。本文基于baodanbao.com.cn.*包结构,使用Spring State Machine实现清晰、可维护、事务安全的试吃订单状态机模型。
1. 定义状态与事件枚举
首先明确试吃订单的全生命周期状态:
package baodanbao.com.cn.statemachine;
public enum TrialOrderStatus {
PENDING, // 待审核
APPROVED, // 已通过
REJECTED, // 已拒绝
CONFIRMED, // 用户已确认(准备到店)
REDEEMED, // 已核销(到店完成)
COMPLETED, // 已完成(自动或手动)
CANCELLED // 已取消
}
触发状态变更的事件:
package baodanbao.com.cn.statemachine;
public enum TrialOrderEvent {
APPLY, // 用户提交申请
APPROVE, // 运营审核通过
REJECT, // 运营审核拒绝
CONFIRM, // 用户确认参与
REDEEM, // 商户核销
COMPLETE, // 系统自动完成(如超时)
CANCEL // 用户/系统取消
}

2. 配置状态机
使用Java Config方式定义合法状态转换:
package baodanbao.com.cn.config;
import baodanbao.com.cn.statemachine.TrialOrderEvent;
import baodanbao.com.cn.statemachine.TrialOrderStatus;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachine;
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
import java.util.EnumSet;
@Configuration
@EnableStateMachine(name = "trialOrderStateMachine")
public class TrialOrderStateMachineConfig extends EnumStateMachineConfigurerAdapter<TrialOrderStatus, TrialOrderEvent> {
@Override
public void configure(StateMachineStateConfigurer<TrialOrderStatus, TrialOrderEvent> states) throws Exception {
states
.withStates()
.initial(TrialOrderStatus.PENDING)
.states(EnumSet.allOf(TrialOrderStatus.class));
}
@Override
public void configure(StateMachineTransitionConfigurer<TrialOrderStatus, TrialOrderEvent> transitions) throws Exception {
transitions
.withExternal().source(TrialOrderStatus.PENDING).target(TrialOrderStatus.APPROVED).event(TrialOrderEvent.APPROVE)
.and()
.withExternal().source(TrialOrderStatus.PENDING).target(TrialOrderStatus.REJECTED).event(TrialOrderEvent.REJECT)
.and()
.withExternal().source(TrialOrderStatus.APPROVED).target(TrialOrderStatus.CONFIRMED).event(TrialOrderEvent.CONFIRM)
.and()
.withExternal().source(TrialOrderStatus.CONFIRMED).target(TrialOrderStatus.REDEEMED).event(TrialOrderEvent.REDEEM)
.and()
.withExternal().source(TrialOrderStatus.REDEEMED).target(TrialOrderStatus.COMPLETED).event(TrialOrderEvent.COMPLETE)
.and()
.withExternal().source(TrialOrderStatus.PENDING).target(TrialOrderStatus.CANCELLED).event(TrialOrderEvent.CANCEL)
.and()
.withExternal().source(TrialOrderStatus.APPROVED).target(TrialOrderStatus.CANCELLED).event(TrialOrderEvent.CANCEL)
.and()
.withExternal().source(TrialOrderStatus.CONFIRMED).target(TrialOrderStatus.CANCELLED).event(TrialOrderEvent.CANCEL);
}
}
3. 状态变更监听器(执行业务逻辑)
在状态切换前后执行持久化、通知等操作:
package baodanbao.com.cn.listener;
import baodanbao.com.cn.statemachine.TrialOrderStatus;
import baodanbao.com.cn.statemachine.TrialOrderEvent;
import baodanbao.com.cn.service.TrialOrderService;
import org.springframework.messaging.Message;
import org.springframework.statemachine.annotation.OnTransition;
import org.springframework.statemachine.annotation.WithStateMachine;
import org.springframework.stereotype.Component;
@Component
@WithStateMachine(name = "trialOrderStateMachine")
public class TrialOrderStateChangeListener {
private final TrialOrderService trialOrderService;
public TrialOrderStateChangeListener(TrialOrderService trialOrderService) {
this.trialOrderService = trialOrderService;
}
@OnTransition(source = "APPROVED", target = "CONFIRMED")
public void onConfirm(Message<TrialOrderEvent> message) {
String orderId = (String) message.getHeaders().get("orderId");
trialOrderService.updateStatus(orderId, TrialOrderStatus.CONFIRMED);
// 发送确认成功通知
}
@OnTransition(source = "CONFIRMED", target = "REDEEMED")
public void onRedeem(Message<TrialOrderEvent> message) {
String orderId = (String) message.getHeaders().get("orderId");
trialOrderService.updateStatus(orderId, TrialOrderStatus.REDEEMED);
// 触发奖励发放
}
@OnTransition(target = "CANCELLED")
public void onCancel(Message<TrialOrderEvent> message) {
String orderId = (String) message.getHeaders().get("orderId");
trialOrderService.updateStatus(orderId, TrialOrderStatus.CANCELLED);
// 释放库存、发送取消通知
}
}
4. 服务层调用状态机
封装统一入口,传入订单ID与事件:
package baodanbao.com.cn.service;
import baodanbao.com.cn.statemachine.TrialOrderEvent;
import baodanbao.com.cn.statemachine.TrialOrderStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.access.StateMachineAccess;
import org.springframework.statemachine.state.State;
import org.springframework.stereotype.Service;
@Service
public class TrialOrderStateMachineService {
@Autowired
private StateMachine<TrialOrderStatus, TrialOrderEvent> stateMachine;
public boolean sendEvent(String orderId, TrialOrderStatus currentState, TrialOrderEvent event) {
stateMachine.stop();
stateMachine.getStateMachineAccessor().doWithAllRegions(access -> {
access.resetStateMachine(new org.springframework.statemachine.support.DefaultStateMachineContext<>(currentState, null, null, null));
});
stateMachine.start();
Message<TrialOrderEvent> msg = MessageBuilder
.withPayload(event)
.setHeader("orderId", orderId)
.build();
return stateMachine.sendEvent(msg);
}
}
5. 控制器示例:用户确认试吃
package baodanbao.com.cn.controller;
import baodanbao.com.cn.service.TrialOrderStateMachineService;
import baodanbao.com.cn.statemachine.TrialOrderEvent;
import baodanbao.com.cn.statemachine.TrialOrderStatus;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/trial")
public class TrialOrderController {
private final TrialOrderService orderService;
private final TrialOrderStateMachineService stateMachineService;
public TrialOrderController(TrialOrderService orderService,
TrialOrderStateMachineService stateMachineService) {
this.orderService = orderService;
this.stateMachineService = stateMachineService;
}
@PostMapping("/{orderId}/confirm")
public void confirm(@PathVariable String orderId) {
var order = orderService.getById(orderId);
if (!order.getStatus().equals(TrialOrderStatus.APPROVED)) {
throw new IllegalStateException("当前状态不可确认");
}
boolean success = stateMachineService.sendEvent(orderId, order.getStatus(), TrialOrderEvent.CONFIRM);
if (!success) {
throw new IllegalStateException("状态转换失败");
}
}
}
6. 异常处理与幂等性
状态机天然拒绝非法转换(如REDEEMED → CONFIRMED),返回false。结合数据库乐观锁(version字段)可保证并发安全。
本文著作权归吃喝不愁app开发者团队,转载请注明出处!
3391

被折叠的 条评论
为什么被折叠?



