一、状态机概述
状态机(State Machine)是一种非常重要的业务逻辑设计模式:
核心概念:
- 有限个状态(State)
- 状态之间的转换(Transition)
- 触发转换的事件(Event)
- 每个状态对应的动作(Action)
解决的问题:
- 订单状态流转的合法性
- 工单流程的规范管理
- 状态转换的可追溯性
- 并发操作的安全性
二、状态机核心概念
1. 状态机模型
┌─────────────────────────────────────────────────────────────────┐
│ 订单状态机 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ │
│ │ PENDING │ 创建订单 │
│ └────┬────┘ │
│ │ pay() │
│ ▼ │
│ ┌─────────┐ │
│ ┌───▶│ PAID │ 支付成功 │
│ │ └────┬────┘ │
│ │ │ deliver() │
│ │ ▼ │
│ │ ┌─────────┐ │
│ │ │SHIPPING │ 发货 │
│ │ └────┬────┘ │
│ │ │ receive() │
│ │ ▼ │
│ │ ┌─────────┐ │
│ │ │COMPLETED│ 完成 │
│ │ └─────────┘ │
│ │ │
│ │ ┌─────────┐ │
│ └─────│ CANCELLED│ 取消 │
│ cancel()└─────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
2. 状态机组成
StateMachine = (States, Events, Transitions, Actions, Guards)
- States: 状态的集合
- Events: 触发事件
- Transitions: 状态转换规则
- Actions: 转换时执行的动作
- Guards: 转换条件判断
三、状态机实现
1. 状态枚举
java
// 订单状态枚举
public enum OrderStatus {
PENDING("待支付"),
PAID("已支付"),
SHIPPING("配送中"),
COMPLETED("已完成"),
CANCELLED("已取消");
private final String description;
OrderStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
// 订单事件枚举
public enum OrderEvent {
PAY("支付"),
CANCEL("取消"),
DELIVER("发货"),
RECEIVE("收货"),
REFUND("退款");
}
2. 状态转换定义
java
// 状态转换配置
@Data
public class StateTransition {
private OrderStatus from;
private OrderEvent event;
private OrderStatus to;
private String action; // 执行的动作
private String guard; // 守卫条件
}
// 状态机配置
@Configuration
public class OrderStateMachineConfig {
@Bean
public Map<String, StateTransition> orderTransitions() {
Map<String, StateTransition> transitions = new HashMap<>();
// 待支付 -> 已支付(支付事件)
transitions.put("PENDING:PAY", StateTransition.builder()
.from(OrderStatus.PENDING)
.event(OrderEvent.PAY)
.to(OrderStatus.PAID)
.action("paymentService.processPayment")
.build());
// 已支付 -> 配送中(发货事件)
transitions.put("PAID:DELIVER", StateTransition.builder()
.from(OrderStatus.PAID)
.event(OrderEvent.DELIVER)
.to(OrderStatus.SHIPPING)
.action("logisticsService.ship")
.build());
// 配送中 -> 已完成(收货事件)
transitions.put("SHIPPING:RECEIVE", StateTransition.builder()
.from(OrderStatus.SHIPPING)
.event(OrderEvent.RECEIVE)
.to(OrderStatus.COMPLETED)
.action("orderService.complete")
.build());
// 待支付 -> 已取消(取消事件)
transitions.put("PENDING:CANCEL", StateTransition.builder()
.from(OrderStatus.PENDING)
.event(OrderEvent.CANCEL)
.to(OrderStatus.CANCELLED)
.action("orderService.cancel")
.build());
// 已支付 -> 已取消(取消事件,需要退款)
transitions.put("PAID:CANCEL", StateTransition.builder()
.from(OrderStatus.PAID)
.event(OrderEvent.CANCEL)
.to(OrderStatus.CANCELLED)
.action("refundService.refund")
.guard("paymentAmount > 0")
.build());
return transitions;
}
}
3. 状态机实现
java
@Service
public class OrderStateMachine {
@Autowired
private Map<String, StateTransition> transitions;
// 执行状态转换
public void fire(Order order, OrderEvent event) {
String key = order.getStatus() + ":" + event;
StateTransition transition = transitions.get(key);
if (transition == null) {
throw new InvalidTransitionException(
"状态转换不存在: " + order.getStatus() + " + " + event);
}
// 检查守卫条件
if (!checkGuard(order, transition.getGuard())) {
throw new GuardViolationException("不满足转换条件");
}
// 执行动作
executeAction(order, transition.getAction());
// 更新状态
order.setStatus(transition.getTo());
orderRepository.save(order);
// 记录状态变更日志
logStatusChange(order, transition);
}
// 获取可用的转换
public List<OrderEvent> getAvailableEvents(Order order) {
return transitions.values().stream()
.filter(t -> t.getFrom() == order.getStatus())
.filter(t -> checkGuard(order, t.getGuard()))
.map(StateTransition::getEvent)
.collect(Collectors.toList());
}
private boolean checkGuard(Order order, String guard) {
if (guard == null || guard.isEmpty()) {
return true;
}
// 简单的守卫实现
switch (guard) {
case "paymentAmount > 0":
return order.getPaymentAmount() != null && order.getPaymentAmount() > 0;
default:
return true;
}
}
private void executeAction(Order order, String action) {
if (action == null || action.isEmpty()) {
return;
}
// 简单的动作执行实现
// 实际项目中可以使用Spring的MethodInvoker或规则引擎
}
}
四、状态机持久化
1. 状态变更记录
java
// 状态变更记录
@Entity
@Table(name = "order_status_log")
public class OrderStatusLog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String orderId;
private OrderStatus fromStatus;
private OrderStatus toStatus;
private OrderEvent event;
private String operator;
private String remark;
private LocalDateTime createdAt;
}
// 保存状态变更记录
@Service
public class OrderStateMachine {
@Autowired
private OrderStatusLogRepository logRepository;
private void logStatusChange(Order order, StateTransition transition) {
OrderStatusLog log = OrderStatusLog.builder()
.orderId(order.getId().toString())
.fromStatus(transition.getFrom())
.toStatus(transition.getTo())
.event(transition.getEvent())
.operator(getCurrentOperator())
.createdAt(LocalDateTime.now())
.build();
logRepository.save(log);
}
}
2. 乐观锁实现
java
// 使用乐观锁防止并发问题
@Entity
public class Order {
@Id
private Long id;
@Enumerated(EnumType.STRING)
private OrderStatus status;
@Version
private Long version; // 乐观锁版本
// 状态转换时检查版本
public void changeStatus(OrderStatus newStatus, Long expectedVersion) {
if (!this.version.equals(expectedVersion)) {
throw new OptimisticLockException("订单状态已被其他操作修改");
}
this.status = newStatus;
this.version = expectedVersion + 1;
}
}
// 使用服务
@Service
public class OrderService {
@Transactional
public void payOrder(Long orderId, PaymentInfo paymentInfo) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException(orderId));
// 获取当前版本
Long currentVersion = order.getVersion();
// 检查状态
if (order.getStatus() != OrderStatus.PENDING) {
throw new InvalidStateException("订单状态不允许支付");
}
// 执行支付
paymentService.processPayment(paymentInfo);
// 更新状态
order.changeStatus(OrderStatus.PAID, currentVersion);
orderRepository.save(order);
}
}
五、工单状态机设计
1. 工单状态定义
java
// 工单状态
public enum TicketStatus {
CREATED("已创建"),
ASSIGNED("已分配"),
PROCESSING("处理中"),
PENDING_APPROVAL("待审批"),
RESOLVED("已解决"),
CLOSED("已关闭"),
REJECTED("已拒绝");
}
// 工单事件
public enum TicketEvent {
ASSIGN("分配"),
ACCEPT("接受"),
REJECT("拒绝"),
PROCESS("处理"),
APPROVE("审批"),
RESOLVE("解决"),
CLOSE("关闭"),
REOPEN("重新打开");
}
// 工单状态机配置
@Configuration
public class TicketStateMachineConfig {
public static final Map<String, StateTransition> TRANSITIONS;
static {
TRANSITIONS = new HashMap<>();
// 创建 -> 分配
TRANSITIONS.put("CREATED:ASSIGN",
StateTransition.builder()
.from(TicketStatus.CREATED)
.event(TicketEvent.ASSIGN)
.to(TicketStatus.ASSIGNED)
.action("ticketService.assign")
.build());
// 分配 -> 处理中
TRANSITIONS.put("ASSIGNED:ACCEPT",
StateTransition.builder()
.from(TicketStatus.ASSIGNED)
.event(TicketEvent.ACCEPT)
.to(TicketStatus.PROCESSING)
.action("ticketService.startProcessing")
.build());
// 处理中 -> 待审批
TRANSITIONS.put("PROCESSING:APPROVE",
StateTransition.builder()
.from(TicketStatus.PROCESSING)
.event(TicketEvent.APPROVE)
.to(TicketStatus.PENDING_APPROVAL)
.action("ticketService.submitForApproval")
.guard("requireApproval()")
.build());
// 处理中 -> 已解决
TRANSITIONS.put("PROCESSING:RESOLVE",
StateTransition.builder()
.from(TicketStatus.PROCESSING)
.event(TicketEvent.RESOLVE)
.to(TicketStatus.RESOLVED)
.action("ticketService.resolve")
.build());
// 待审批 -> 已解决
TRANSITIONS.put("PENDING_APPROVAL:APPROVE",
StateTransition.builder()
.from(TicketStatus.PENDING_APPROVAL)
.event(TicketEvent.APPROVE)
.to(TicketStatus.RESOLVED)
.action("ticketService.approve")
.build());
// 待审批 -> 处理中(驳回)
TRANSITIONS.put("PENDING_APPROVAL:REJECT",
StateTransition.builder()
.from(TicketStatus.PENDING_APPROVAL)
.event(TicketEvent.REJECT)
.to(TicketStatus.PROCESSING)
.action("ticketService.reject")
.build());
// 已解决 -> 已关闭
TRANSITIONS.put("RESOLVED:CLOSE",
StateTransition.builder()
.from(TicketStatus.RESOLVED)
.event(TicketEvent.CLOSE)
.to(TicketStatus.CLOSED)
.action("ticketService.close")
.build());
// 已关闭 -> 处理中(重新打开)
TRANSITIONS.put("CLOSED:REOPEN",
StateTransition.builder()
.from(TicketStatus.CLOSED)
.event(TicketEvent.REOPEN)
.to(TicketStatus.PROCESSING)
.action("ticketService.reopen")
.build());
}
}
2. 工单状态机服务
java
@Service
public class TicketStateMachine {
@Autowired
private TicketRepository ticketRepository;
@Autowired
private TicketStatusLogRepository logRepository;
public void fire(Ticket ticket, TicketEvent event) {
String key = ticket.getStatus() + ":" + event;
StateTransition transition = TicketStateMachineConfig.TRANSITIONS.get(key);
if (transition == null) {
throw new InvalidTransitionException(
"工单状态不允许此操作: " + ticket.getStatus() + " + " + event);
}
// 检查守卫
if (!checkGuard(ticket, transition.getGuard())) {
throw new GuardViolationException("不满足操作条件: " + transition.getGuard());
}
// 记录原状态
TicketStatus fromStatus = ticket.getStatus();
// 执行动作
executeAction(transition.getAction(), ticket);
// 更新状态
ticket.setStatus(transition.getTo());
ticketRepository.save(ticket);
// 记录日志
saveStatusLog(ticket, fromStatus, transition.getTo(), event);
}
private void executeAction(String action, Ticket ticket) {
if (action == null) return;
switch (action) {
case "ticketService.assign":
ticket.setAssignedAt(LocalDateTime.now());
break;
case "ticketService.startProcessing":
ticket.setProcessingStartedAt(LocalDateTime.now());
break;
case "ticketService.resolve":
ticket.setResolvedAt(LocalDateTime.now());
break;
}
}
private void saveStatusLog(Ticket ticket, TicketStatus from,
TicketStatus to, TicketEvent event) {
TicketStatusLog log = TicketStatusLog.builder()
.ticketId(ticket.getId())
.fromStatus(from)
.toStatus(to)
.event(event)
.operator(getCurrentOperator())
.createdAt(LocalDateTime.now())
.build();
logRepository.save(log);
}
// 获取可用操作
public List<TicketEvent> getAvailableEvents(Ticket ticket) {
return TicketStateMachineConfig.TRANSITIONS.values().stream()
.filter(t -> t.getFrom() == ticket.getStatus())
.filter(t -> checkGuard(ticket, t.getGuard()))
.map(StateTransition::getEvent)
.collect(Collectors.toList());
}
}
六、状态机可视化
1. 状态图生成
java
// 生成Mermaid格式的状态图
@Service
public class StateDiagramGenerator {
public String generateMermaidDiagram(Map<String, StateTransition> transitions) {
StringBuilder diagram = new StringBuilder();
diagram.append("stateDiagram-v2\n");
for (StateTransition t : transitions.values()) {
String line = " " + t.getFrom() + " --> " + t.getTo() +
" : " + t.getEvent();
if (t.getGuard() != null) {
line += " [" + t.getGuard() + "]";
}
diagram.append(line).append("\n");
}
return diagram.toString();
}
// 使用示例输出:
// stateDiagram-v2
// CREATED --> ASSIGNED : ASSIGN
// ASSIGNED --> PROCESSING : ACCEPT
// PROCESSING --> PENDING_APPROVAL : APPROVE [requireApproval()]
// PROCESSING --> RESOLVED : RESOLVE
// PENDING_APPROVAL --> RESOLVED : APPROVE
// PENDING_APPROVAL --> PROCESSING : REJECT
// RESOLVED --> CLOSED : CLOSE
// CLOSED --> PROCESSING : REOPEN
}
2. 状态机监控
java
// 状态转换统计
@Service
public class StateTransitionMetrics {
@Autowired
private TicketStatusLogRepository logRepository;
public Map<String, Long> getTransitionStats(LocalDate start, LocalDate end) {
List<TicketStatusLog> logs = logRepository
.findByCreatedAtBetween(start.atStartOfDay(), end.plusDays(1).atStartOfDay());
return logs.stream()
.collect(Collectors.groupingBy(
log -> log.getFromStatus() + "->" + log.getToStatus(),
Collectors.counting()
));
}
// 统计平均处理时间
public Map<TicketStatus, Double> getAvgTimeInStatus(LocalDate start, LocalDate end) {
List<TicketStatusLog> logs = logRepository.findByCreatedAtBetween(
start.atStartOfDay(), end.plusDays(1).atStartOfDay());
Map<TicketStatus, List<Long>> durations = new HashMap<>();
for (int i = 0; i < logs.size() - 1; i++) {
TicketStatusLog current = logs.get(i);
TicketStatusLog next = logs.get(i + 1);
if (current.getTicketId().equals(next.getTicketId())) {
long duration = java.time.Duration.between(
current.getCreatedAt(), next.getCreatedAt()).toMinutes();
durations.computeIfAbsent(current.getToStatus(), k -> new ArrayList<>())
.add(duration);
}
}
return durations.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
e -> e.getValue().stream()
.mapToLong(Long::longValue).average().orElse(0)
));
}
}
七、状态机框架
1. Spring StateMachine
java
// Spring StateMachine配置
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderEvent> {
@Override
public void configure(StateMachineStateConfigurer<OrderStatus, OrderEvent> states) throws Exception {
states
.withStates()
.initial(OrderStatus.PENDING)
.states(EnumSet.allOf(OrderStatus.class))
.end(OrderStatus.COMPLETED)
.end(OrderStatus.CANCELLED);
}
@Override
public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions) throws Exception {
transitions
.withExternal()
.source(OrderStatus.PENDING).target(OrderStatus.PAID)
.event(OrderEvent.PAY)
.action(paymentAction())
.and()
.withExternal()
.source(OrderStatus.PAID).target(OrderStatus.SHIPPING)
.event(OrderEvent.DELIVER)
.action(deliveryAction())
.and()
.withExternal()
.source(OrderStatus.SHIPPING).target(OrderStatus.COMPLETED)
.event(OrderEvent.RECEIVE)
.and()
.withExternal()
.source(OrderStatus.PENDING).target(OrderStatus.CANCELLED)
.event(OrderEvent.CANCEL)
.and()
.withExternal()
.source(OrderStatus.PAID).target(OrderStatus.CANCELLED)
.event(OrderEvent.CANCEL)
.action(refundAction());
}
@Bean
public Action<OrderStatus, OrderEvent> paymentAction() {
return context -> {
System.out.println("执行支付逻辑");
};
}
@Bean
public Action<OrderStatus, OrderEvent> deliveryAction() {
return context -> {
System.out.println("执行发货逻辑");
};
}
@Bean
public Action<OrderStatus, OrderEvent> refundAction() {
return context -> {
System.out.println("执行退款逻辑");
};
}
}
// 使用状态机
@Service
public class OrderServiceWithStateMachine {
@Autowired
private StateMachine<OrderStatus, OrderEvent> stateMachine;
public void payOrder(Long orderId) {
stateMachine.start();
// 设置订单ID到上下文
stateMachine.getStateMachineAccessor()
.doWithAllRegions(accessor -> {
accessor.addStateMachineInterceptor(new StateMachineInterceptorAdapter<>() {
@Override
public void preStateChange(State<OrderStatus, OrderEvent> state) {
System.out.println("状态即将变更为: " + state.getId());
}
});
});
// 发送支付事件
boolean result = stateMachine.sendEvent(OrderEvent.PAY);
System.out.println("支付结果: " + result);
System.out.println("当前状态: " + stateMachine.getState().getId());
}
}
八、总结
状态机是复杂业务逻辑的利器:
- 状态流转规范:明确的状态转换规则
- 可追溯性:完整的状态变更记录
- 安全性:防止非法状态转换
- 可视化:清晰的状态图
最佳实践:
- 清晰定义所有状态和事件
- 做好守卫条件检查
- 记录完整的状态变更日志
- 使用乐观锁防止并发问题
个人观点,仅供参考