spring状态机

状态机的四个概念

状态:一个状态机至少包含两个状态
事件:执行某个操作的触发条件或者口令
动作:事件发生以后要执行的动作
变换:从一个状态变换到另一个状态

状态机图

要素:起始,终止,现态,次态(目标状态),动作,条件
订单为例:由待支付状态转到代发货状态
起始/终止:描述整个状态机的生命周期边界(如订单从"创建"开始,到"完成/取消"结束)
现态:待支付状态
触发事件(条件):支付成功
动作:更新库存、记录支付流水、通知仓库等
次态:待发货
误区1:将某个程序动作作为一种状态处理,状态转换是结果,动作是为了完成状态转换过程中执行的业务流程
实践流程:以订单的创建,支付,代发货,接受流程为例,制定状态机:使用spring statemachine
补充:sprint statemachine可以提供的功能:
提供易于使用的简单状态机,可实现分层结构,以简化复杂的状态配置;
状态机区域可以提供更加复杂的配置,使用触发器,转换和警卫以及操作;键入安全适配器;生成器模式;基于zookeeper的分布式状态机;状态机事件监听器;将计算机配置存储在永久存储中,spring ioc集成将bean与状态机进行关联
1 创建订单表
2 引入依赖 spring statemachine, redis对应的持久化状态机
3 定义状态机和事件
枚举类:orderStatus(待支付,待发货,待收货,已完成)
枚举类:orderStatucChangeEvent(支付,发货,确认收货)
4 定义状态机规则和配置状态机
orderStatusMachineConfig <orderStatus, OrderStatusChangeEvent>
配置状态(定义起始状态),配置状态转移事件关系(规则):现态 -> 目标次态,触发事件,状态转移执行动作,守卫条件,异常处理
** 收到事件(event) + 当前状态(source) -> 匹配转换规则 -> 检查守卫条件(Guard) ──若不满足──→ 拒绝转换 -> (满足时)执行动作(Action) -> 状态变更 source → target -> 异常处理(如有)** 这条链涉及到了一致性问题,因为状态转移和动作执行是两个分开的动作
5 配置持久化
保证订单不会因为redis或者服务器网络波动造成数据丢失,采用的数据结构时hashmap,其中的key时订单业务中用户的唯一标识如订单id,value时状态上下文,存放着状态机的完整上下文如
数据库订单表 +

{
    "state": "WAIT_DELIVER",           // ← 当前状态(核心)
    "event": "PAYED",                   // ← 上次触发的事件
    "extendedState": {                  // ← 扩展数据(自定义变量)
        "orderId": "ORDER_001",
        "payAmount": 199.00
    },
    "history": [...],                   // ← 状态历史(可选)
    "childRefs": [...]                  // ← 子状态机引用(可选)
}

6 业务系统控制层代码:订单创建,查询,支付,发货,确认收货
7 业务系统服务层代码:
具体业务流程:依赖注入,订单创建+状态机初始化到redis服务1,订单支付(内部会传递事件状态自动触发状态转移并执行业务流程),订单发货(同订单支付),订单收货,订单接受,核心函数:状态流转执行(判断订单号是否存在,,获取当前执行顺序,尝试获取分布式锁,获取锁后再次进行数据库乐观锁校验
校验数据与数据库一致继续执行,否则报错;执行状态机流转,流转不成功进行数据库回滚,记录相应执行日志,最后一定要进行锁的释放),状态机发送事件:
具体代码:订单表 记录订单, 状态流转表 记录订单的状态变化结果

-- 订单主表
CREATE TABLE `order` (
    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
    `order_no` VARCHAR(32) NOT NULL UNIQUE,
    `status` TINYINT NOT NULL COMMENT '0-待支付 1-待发货 2-待收货 3-已完成 4-已取消',
    `amount` DECIMAL(10,2) NOT NULL,
    `user_id` BIGINT NOT NULL,
    `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
    `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX `idx_status` (`status`),
    INDEX `idx_user_id` (`user_id`)
) ENGINE=InnoDB;

-- 状态流转日志表(审计 + 对账 + 重试)
CREATE TABLE `order_state_log` (
    `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
    `order_id` BIGINT NOT NULL,
    `order_no` VARCHAR(32) NOT NULL,
    `from_status` TINYINT NOT NULL,
    `to_status` TINYINT NOT NULL,
    `event` VARCHAR(32) NOT NULL,
    `success` TINYINT NOT NULL DEFAULT 0,
    `error_msg` VARCHAR(500),
    `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
    INDEX `idx_order_id` (`order_id`),
    INDEX `idx_success` (`success`)
) ENGINE=InnoDB;

枚举类:状态机状态枚举 + 事件枚举类

//状态:当前订单处于的状态
@Getter
@AllArgsConstructor
public enum OrderStatus {
    WAIT_PAYMENT(0, "待支付"),
    WAIT_DELIVER(1, "待发货"),
    WAIT_RECEIVE(2, "待收货"),
    FINISH(3, "已完成"),
    CANCEL(4, "已取消");

    private final int key;
    private final String desc;

    public static OrderStatus of(int key) {
        return Arrays.stream(values())
                .filter(s -> s.key == key)
                .findFirst()
                .orElseThrow(() -> new IllegalArgumentException("无效状态: " + key));
    }
}
//事件:变换信号,
@Getter
@AllArgsConstructor
public enum OrderStatusChangeEvent {
    PAYED("支付成功"),
    DELIVERY("发货"),
    RECEIVED("确认收货"),
    CANCEL("取消订单");

    private final String desc;
}

状态机的配置:定义状态转移的前置处理和事件触发的状态以及状态的转移和后序执行的业务动作(理解为制定状态转移的规则)

@Configuration
@EnableStateMachineFactory(name = "orderStateMachineFactory")
@Slf4j
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderStatusChangeEvent> {

    @Resource
    private OrderService orderService;

    @Override
    public void configure(StateMachineStateConfigurer<OrderStatus, OrderStatusChangeEvent> states) throws Exception {
        states.withStates()
                .initial(OrderStatus.WAIT_PAYMENT)
                .states(EnumSet.allOf(OrderStatus.class));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderStatusChangeEvent> transitions) throws Exception {
        transitions
            // 支付:待支付 -> 待发货
            .withExternal()
                .source(OrderStatus.WAIT_PAYMENT)
                .target(OrderStatus.WAIT_DELIVER)
                .event(OrderStatusChangeEvent.PAYED)
                .guard(payGuard())           // 守卫条件
                .action(payAction())          // 执行业务动作
                .and()
            // 发货:待发货 -> 待收货
            .withExternal()
                .source(OrderStatus.WAIT_DELIVER)
                .target(OrderStatus.WAIT_RECEIVE)
                .event(OrderStatusChangeEvent.DELIVERY)
                .guard(deliverGuard())
                .action(deliverAction())
                .and()
            // 收货:待收货 -> 已完成
            .withExternal()
                .source(OrderStatus.WAIT_RECEIVE)
                .target(OrderStatus.FINISH)
                .event(OrderStatusChangeEvent.RECEIVED)
                .action(receiveAction());
    }

    // ========== Guard(守卫条件)==========

    @Bean
    public Guard<OrderStatus, OrderStatusChangeEvent> payGuard() {
        return context -> {
            Order order = getOrderFromHeader(context);
            // 幂等检查:已支付则拒绝
            if (order.getStatus() != OrderStatus.WAIT_PAYMENT.getKey()) {
                log.warn("订单{}状态{},非待支付,拒绝重复支付", order.getId(), order.getStatus());
                return false;
            }
            // 业务校验:金额>0
            if (order.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
                log.warn("订单{}金额异常", order.getId());
                return false;
            }
            return true;
        };
    }

    @Bean
    public Guard<OrderStatus, OrderStatusChangeEvent> deliverGuard() {
        return context -> {
            Order order = getOrderFromHeader(context);
            // 检查库存(示例)
            return true;
        };
    }

    // ========== Action(业务动作)==========

    @Bean
    public Action<OrderStatus, OrderStatusChangeEvent> payAction() {
        return context -> {
            Order order = getOrderFromHeader(context);
            log.info("执行支付动作,订单号:{}", order.getOrderNo());
            
            // 1. 调用支付服务(实际扣款)
            // paymentService.charge(order);
            
            // 2. 扣减库存
            // inventoryService.deduct(order);
            
            // 3. 记录支付流水
            // paymentLogService.save(...);
            
            // 4. 发送支付成功消息(异步)
            // mqProducer.send("pay_success", order);
        };
    }

    @Bean
    public Action<OrderStatus, OrderStatusChangeEvent> deliverAction() {
        return context -> {
            Order order = getOrderFromHeader(context);
            log.info("执行发货动作,订单号:{}", order.getOrderNo());
            // 调用物流服务、生成运单号等
        };
    }

    @Bean
    public Action<OrderStatus, OrderStatusChangeEvent> receiveAction() {
        return context -> {
            Order order = getOrderFromHeader(context);
            log.info("执行收货动作,订单号:{}", order.getOrderNo());
            // 结算、积分发放等
        };
    }

    // 工具方法:从消息头取订单
    private Order getOrderFromHeader(StateContext<OrderStatus, OrderStatusChangeEvent> context) {
        return (Order) context.getMessage().getHeaders().get("order");
    }
}

redis持久化配置:引入依赖库,创建配置类制定对象持久化到redis里还是内存中
业务层实现:核心是状态的流转,状态机事件的发送

@Service
@Slf4j
public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements OrderService {

    @Resource
    private StateMachineFactory<OrderStatus, OrderStatusChangeEvent> orderStateMachineFactory;
    
    @Resource(name = "stateMachineRedisPersister")
    private StateMachinePersister<OrderStatus, OrderStatusChangeEvent, String> stateMachinePersister;
    
    @Resource
    private OrderMapper orderMapper;
    
    @Resource
    private OrderStateLogMapper stateLogMapper;
    
    @Resource
    private StringRedisTemplate redisTemplate;
    
    @Resource
    private RedissonClient redissonClient;  // 分布式锁

    private static final String LOCK_PREFIX = "order:lock:";
    private static final String STATE_KEY_PREFIX = "statemachine:order:";

    // ========== 创建订单 ==========
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Order create(OrderCreateDTO dto) {
        Order order = new Order();
        order.setOrderNo(generateOrderNo());
        order.setUserId(dto.getUserId());
        order.setAmount(dto.getAmount());
        order.setStatus(OrderStatus.WAIT_PAYMENT.getKey());
        
        orderMapper.insert(order);
        
        // 初始化状态机上下文到Redis
        initStateMachine(order);
        
        log.info("订单创建成功,订单号:{}", order.getOrderNo());
        return order;
    }

    // ========== 支付 ==========
    
    @Override
    public Order pay(Long orderId) {
        return executeTransition(orderId, OrderStatusChangeEvent.PAYED, "支付");
    }

    // ========== 发货 ==========
    
    @Override
    public Order deliver(Long orderId) {
        return executeTransition(orderId, OrderStatusChangeEvent.DELIVERY, "发货");
    }

    // ========== 收货 ==========
    
    @Override
    public Order receive(Long orderId) {
        return executeTransition(orderId, OrderStatusChangeEvent.RECEIVED, "收货");
    }

    // ========== 核心:状态流转执行 ==========
    
    private Order executeTransition(Long orderId, OrderStatusChangeEvent event, String actionName) {
        Order order = orderMapper.selectById(orderId);
        if (order == null) {
            throw new BusinessException("订单不存在");
        }

        String orderNo = order.getOrderNo();
        String lockKey = LOCK_PREFIX + orderId;
        RLock lock = redissonClient.getLock(lockKey);

        try {
            // 1. 获取分布式锁(等待10秒,持有30秒自动续期)
            boolean locked = lock.tryLock(10, 30, TimeUnit.SECONDS);
            if (!locked) {
                throw new BusinessException("系统繁忙,请稍后重试");
            }

            log.info("线程:{},尝试{},订单号:{}", 
                    Thread.currentThread().getName(), actionName, orderNo);

            // 2. 数据库乐观锁校验(双重保险)
            int updated = orderMapper.updateStatus(orderId, order.getStatus(), 
                    getTargetStatus(event).getKey());
            if (updated == 0) {
                log.warn("订单{}数据库状态{}与预期不符,拒绝{}", orderId, order.getStatus(), actionName);
                throw new BusinessException(actionName + "失败,订单状态已变更");
            }

            // 3. 执行状态机流转
            boolean success = sendEvent(event, order);
            
            if (!success) {
                // 状态机拒绝(Guard未通过),回滚数据库(抛异常触发事务回滚)
                throw new BusinessException(actionName + "失败,状态机校验未通过");
            }

            // 4. 记录流转日志
            recordStateLog(order, event, true, null);
            
            log.info("线程:{},{}成功,订单号:{}", 
                    Thread.currentThread().getName(), actionName, orderNo);
            
            return orderMapper.selectById(orderId);

        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("订单{} {}异常", orderId, actionName, e);
            recordStateLog(order, event, false, e.getMessage());
            throw new BusinessException(actionName + "异常:" + e.getMessage());
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    // ========== 发送状态机事件 ==========
    
    private boolean sendEvent(OrderStatusChangeEvent event, Order order) {
        StateMachine<OrderStatus, OrderStatusChangeEvent> sm = null;
        String machineId = String.valueOf(order.getId());
        
        try {
            // 创建新的状态机实例(线程安全)
            sm = orderStateMachineFactory.getStateMachine(machineId);
            sm.start();

            // 从Redis恢复状态
            stateMachinePersister.restore(sm, machineId);

            // 构建消息
            Message<OrderStatusChangeEvent> message = MessageBuilder
                    .withPayload(event)
                    .setHeader("order", order)
                    .build();

            // 发送事件
            boolean accepted = sm.sendEvent(message);
            
            if (accepted) {
                // 持久化新状态到Redis
                stateMachinePersister.persist(sm, machineId);
            }
            
            return accepted;

        } catch (Exception e) {
            log.error("状态机事件发送异常,订单:{}", order.getId(), e);
            return false;
        } finally {
            if (sm != null) {
                sm.stop();
            }
        }
    }

    // ========== 辅助方法 ==========
    
    private void initStateMachine(Order order) {
        try {
            StateMachine<OrderStatus, OrderStatusChangeEvent> sm = 
                    orderStateMachineFactory.getStateMachine(String.valueOf(order.getId()));
            sm.start();
            stateMachinePersister.persist(sm, String.valueOf(order.getId()));
            sm.stop();
        } catch (Exception e) {
            log.error("初始化状态机失败,订单:{}", order.getId(), e);
        }
    }

    private void recordStateLog(Order order, OrderStatusChangeEvent event, 
                                boolean success, String errorMsg) {
        try {
            OrderStateLog log = new OrderStateLog();
            log.setOrderId(order.getId());
            log.setOrderNo(order.getOrderNo());
            log.setFromStatus(order.getStatus());
            log.setToStatus(getTargetStatus(event).getKey());
            log.setEvent(event.name());
            log.setSuccess(success ? 1 : 0);
            log.setErrorMsg(errorMsg);
            stateLogMapper.insert(log);
        } catch (Exception e) {
            log.error("记录状态日志失败", e);
        }
    }

    private OrderStatus getTargetStatus(OrderStatusChangeEvent event) {
        return switch (event) {
            case PAYED -> OrderStatus.WAIT_DELIVER;
            case DELIVERY -> OrderStatus.WAIT_RECEIVE;
            case RECEIVED -> OrderStatus.FINISH;
            default -> throw new IllegalArgumentException("未知事件");
        };
    }

    private String generateOrderNo() {
        return "ORD" + System.currentTimeMillis() + RandomUtil.randomNumbers(4);
    }
}

辅助方法:日志的记录,乐观锁校验状态,定时对账

//乐观锁
@Mapper
public interface OrderMapper extends BaseMapper<Order> {
    
    // 乐观锁更新:只有当前状态匹配时才更新
    @Update("UPDATE `order` SET status = #{newStatus}, update_time = NOW() " +
            "WHERE id = #{orderId} AND status = #{expectedStatus}")
    int updateStatus(@Param("orderId") Long orderId, 
                     @Param("expectedStatus") int expectedStatus,
                     @Param("newStatus") int newStatus);
}

//定时队长任务
@Component
@Slf4j
public class OrderStateCheckJob {

    @Resource
    private OrderMapper orderMapper;
    
    @Resource
    private OrderStateLogMapper stateLogMapper;

    // 每5分钟检查一次
    @Scheduled(fixedRate = 5 * 60 * 1000)
    public void check() {
        // 1. 找出最近1小时状态流转失败的记录
        List<OrderStateLog> failedLogs = stateLogMapper.selectFailedRecent(1);
        
        for (OrderStateLog log : failedLogs) {
            Order order = orderMapper.selectById(log.getOrderId());
            if (order == null) continue;
            
            // 如果数据库状态还是原状态,说明需要补偿
            if (order.getStatus() == log.getFromStatus()) {
                log.warn("订单{}状态流转失败需补偿,事件:{}", log.getOrderId(), log.getEvent());
                // 发送告警或自动重试
            }
        }
        
        // 2. 检查Redis和数据库状态不一致的订单
        // ...
    }
}

导航