外卖霸王餐系统中基于状态机(State Machine)管理试吃订单生命周期

外卖霸王餐系统中基于状态机(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开发者团队,转载请注明出处!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值