buguge - Keep it simple,stupid

知识就是力量,但更重要的,是运用知识的能力why buguge?

导航

这儿也改状态,那儿也改状态,连AI都理不清楚了… 不妨试试这个状态机good-practice

状态机good-practice:让订单状态流转可控、可追溯、可维护

作者:buguge
2026-04-07


一、问题背景:状态流转的"野路子"

在我们的企业应用系统中,业务数据的状态流转是最常见的场景之一。以支付订单为例,状态可能经历:

初始 → 待付款 → 付款中 → 付款成功/付款失败

1.1 常见的"野路子"写法

// 方式一:直接硬编码
order.setStatus("PAY_SUCCESS");


// 方式二:用枚举,但“随便”赋值
order.setStatus(PaymentOrderStatusEnum.PAY_SUCCESS);


// 方式三:if-else 大杂烩
if (order.getStatus().equals("INIT")) {
    order.setStatus("PAY_WAIT");
} else if (order.getStatus().equals("PAY_WAIT")) {
    order.setStatus("PAYING");
}
// ... 继续套娃

1.2 这些写法的问题

问题 说明
状态跃迁不可控 哪儿都能改状态,INIT → PAY_SUCCESS 直接跳过中间态,业务逻辑断裂
难以追溯 状态怎么变的?哪儿改的(谁改的)?为什么改?——全靠日志“猜”
并发隐患 两个线程同时改状态,一个改成 PAYING,一个改成 PAY_SUCCESS,数据不一致bug
维护噩梦 加一个新状态,所有 if-else 都要改,漏改就会出 bug

二、状态机是什么?一句话解释

状态机 = 状态 + 事件 + 转换规则

  • 状态(State):订单当前所处阶段,如 INITPAY_WAITPAYINGPAY_SUCCESS
  • 事件(Event):触发状态转换的业务动作,如"风控通过"、"付款请求成功"
  • 转换规则:定义哪个事件能从哪个前置状态转换到哪个目标状态

核心约束:每个事件有明确的前置状态和目标状态,不能随意跃迁。


三、我们的最佳实践:基于状态机进行分层协作

1. ★★状态机(PaymentStateMachine 枚举)
   - 定义事件与状态转换规则(preState → targetState)
   - 前置状态校验(assertCurrentState)

2. ★★★持久层(PaymentOrderManager)提供统一的修改状态的 API:updateStateById
   - 该方法明确将状态机作为入参
   - 依赖 StateMachineDbUpdater 实现乐观锁更新

3. ★上层应用服务(PaymentTransService 等)
   - 约定禁止直接修改状态。改状态需调用 PaymentOrderManager#updateStateById
   - 必须指定具体的状态机事件,如:PaymentStateMachine.PAY_SUCCESS

四、核心代码解析

4.0 核心类清单

路径 职责
SMState sby-libs/.../sm/SMState.java (底层公共)可流转状态标记接口
StateMachine sby-libs/.../sm/StateMachine.java (底层公共)状态机接口
PaymentOrderStatusEnum monorepo/.../pay/PaymentOrderStatusEnum.java (应用层)支付订单状态枚举
PaymentStateMachine monorepo/.../pay/PaymentStateMachine.java (应用层)支付状态机
StateMachineDbUpdater sby-libs/.../sm/StateMachineDbUpdater.java (底层公共)持久化更新器
PaymentOrderManager monorepo/.../pay/manager/PaymentOrderManager.java (应用层)支付订单业务持久层
PaymentTransService monorepo/.../pay/service/PaymentTransService.java (应用层)支付订单业务编排service

4.1 (应用层)状态枚举:PaymentOrderStatusEnum

public enum PaymentOrderStatusEnum implements SMState {
    INIT("初始"),
    RISKING("风控中"),
    PAY_WAIT("待支付"),
    PAYING("付款中"),
    PAY_SUCCESS("付款成功"),
    PAY_FAIL("付款失败"),
    PAY_CANCEL("付款撤销");
}

设计点:

  • 实现 SMState 接口,标记这是一个"可流转的业务状态"
  • 状态即文档,一眼能看懂订单生命周期

4.2 (底层公共)状态机接口:StateMachine<State>

public interface StateMachine<State extends SMState> {
    
    /** 获取事件的前置状态 */
    State getPreState();
    
    /** 获取事件的目标状态 */
    State getTargetState();
    
    /** 断言当前状态是否允许转换 */
    default void assertCurrentState(State current) {
        if (!current.equals(getPreState())) {
            throw new IllegalStateException("当前状态不正确,请检查状态机配置");
        }
    }
}

设计点:

  • 泛型设计,可复用于任何业务场景
  • assertCurrentState 提供前置状态校验,状态跃迁前先断言

4.3 (应用层)支付状态机:PaymentStateMachine

@Getter
@AllArgsConstructor
public enum PaymentStateMachine implements StateMachine<PaymentOrderStatusEnum> {

    // 保存数据
    SAVE_DATA(null, PaymentOrderStatusEnum.INIT),
    SAVE_DATA_SCENE(null, PaymentOrderStatusEnum.PAY_WAIT),

    // 风控相关事件
    RISK_VERIFY_RETURN_SUCCESS(PaymentOrderStatusEnum.INIT, PaymentOrderStatusEnum.PAY_WAIT),
    RISK_VERIFY_RETURN_FAIL(PaymentOrderStatusEnum.INIT, PaymentOrderStatusEnum.PAY_FAIL),
    RISK_VERIFY_RETURN_ONGOING(PaymentOrderStatusEnum.INIT, PaymentOrderStatusEnum.RISKING),
    RISK_VERIFY_ASYNC_SUCCESS(PaymentOrderStatusEnum.RISKING, PaymentOrderStatusEnum.PAY_WAIT),
    RISK_VERIFY_ASYNC_FAIL(PaymentOrderStatusEnum.RISKING, PaymentOrderStatusEnum.PAY_FAIL),

    // 付款相关事件
    PAY_REQUEST_RETURN_NORMAL(PaymentOrderStatusEnum.PAY_WAIT, PaymentOrderStatusEnum.PAYING),
    PAY_REQUEST_RETURN_PAY_FAIL(PaymentOrderStatusEnum.PAY_WAIT, PaymentOrderStatusEnum.PAY_FAIL),
    PAY_SUCCESS(PaymentOrderStatusEnum.PAYING, PaymentOrderStatusEnum.PAY_SUCCESS),
    PAY_FAIL(PaymentOrderStatusEnum.PAYING, PaymentOrderStatusEnum.PAY_FAIL),
    PAY_CANCEL(PaymentOrderStatusEnum.PAYING, PaymentOrderStatusEnum.PAY_CANCEL),
    ;

    private final PaymentOrderStatusEnum preState;
    private final PaymentOrderStatusEnum targetState;
}

每个枚举值代表一个状态机事件。从这个枚举类可以清晰看到下面的 状态流转图:

                    ┌─────────┐
                    │  INIT   │
                    └────┬────┘
                         │
        ┌────────────────┼────────────────┐
        │                │                │
        ▼                ▼                ▼
   ┌─────────┐     ┌─────────┐     ┌─────────┐
   │ PAY_WAIT│     │ RISKING │     │ PAY_FAIL│
   └────┬────┘     └─────┬───┘     └─────────┘
        │                │
        │         ┌──────┴──────┐
        │         │             │
        │         ▼             ▼
        │    ┌─────────┐   ┌─────────┐
        │    │ PAY_WAIT│   │ PAY_FAIL│
        │    └─────────┘   └─────────┘
        │
        ▼
   ┌─────────┐
   │  PAYING │
   └────┬────┘
        │
   ┌────┴────┬───────────────┐
   │         │               │
   ▼         ▼               ▼
┌─────────┐┌────────────┐┌──────────┐
│PAY_FAIL ││PAY_SUCCUES ││PAY_CANCEL│
└─────────┘└────────────┘└──────────┘

设计点:

  • 每个事件名直接体现业务含义:RISK_VERIFY_RETURN_SUCCESS = "风控校验返回成功"
  • preState = null 表示允许从任意状态转换(用于初始创建)
  • 一眼能看出所有合法的状态转换路径

4.4 (底层公共)持久化更新器:StateMachineDbUpdater

public static <Entity, StateEnum extends SMState> int updateStateById(
        Entity entity, BaseMapper<Entity> mapper,
        StateMachine<StateEnum> stateMachine, 
        SFunction<Entity, StateEnum> stateField,
        SFunction<Entity, ?>... otherUpdateFields) {
    
    // 1. 设置目标状态
    setTargetState(entity, stateMachine, stateField);
    
    // 2. 构建更新条件(乐观锁)
    UpdateWrapper<Entity> where = new UpdateWrapper<>();
    where.eq(pkColumn, pkValue)           // 主键
          .eq(stateField, stateMachine.getPreState())  // 前置状态作为条件
          .set(stateField, stateMachine.getTargetState());  // 目标状态
    
    // 3. 设置其他业务字段
    applyFieldValuesToUpdateWrapper(entity, where, otherUpdateFields);
    
    // 4. 执行更新
    return mapper.update(null, where);
}

核心机制:乐观锁(CAS 思想)

UPDATE trans_payment_order 
SET status = 'PAY_SUCCESS', update_time = NOW()
WHERE id = 12345 
  AND status = 'PAYING';  -- 关键:前置状态作为条件
  • 如果 status 已经被其他线程改成 PAY_FAIL,WHERE 条件不匹配,updateCount = 0
  • 天然防止并发覆盖

4.5 (应用层)业务持久层:PaymentOrderManager.updateStateById

public void updateStateById(
        PaymentStateMachine event, 
        PaymentOrder paymentOrder, 
        CodeMsgVO<String> codeMsgVO,
        SFunction<PaymentOrder, ?>... updateFields) {
    
    // 1. 前置状态断言
    event.assertCurrentState(paymentOrder.getStatus());
    
    // 2. 设置目标状态和业务字段
    paymentOrder.setStatus(event.getTargetState());
    if (codeMsgVO != null) {
        paymentOrder.setResultCode(codeMsgVO.getCode());
        paymentOrder.setResultMsg(codeMsgVO.getMsg());
    }
    
    // 3. 调用底层公共能力(利用乐观锁实现持久化更新)
    int updateCount = StateMachineDbUpdater.updateStateById(
        paymentOrder, paymentOrderMapper, event, 
        PaymentOrder::getStatus, updateFields);
    
    // 4. 更新失败处理
    if (updateCount != 1) {
        // 发送告警 + 抛异常
        WechatMessageSend.sendWechatWarningMsg(...);
        throw BizException.build("订单状态变更失败");
    }
}

4.5 (应用层)上层PaymentTransService

@Service
public class PaymentTransService {
    
    public void handlePaymentNotify(PaymentNotifyVO paymentNotifyVO) {
        // ...
        if (!"S".equals(paymentNotifyVO.getPayState)) {
            log.warn("支付结果通知,非成功态,中止处理");
            return;
        }
        
        PaymentOrder paymentOrder = paymentOrderManager.selectByOrderId(paymentNotifyVO.getOrderId());
        // ....
        PaymentStateMachine event = PaymentStateMachine.PAY_SUCCESS;
        // 前置状态断言(可选,下层Manager里也有这个断言)
        event.assertCurrentState(paymentOrder.getStatus());
        
        paymentOrder.setPaymentFinishTime(paymentNotifyVO.getPaymentFinishTime());
        
        // 付款成功,持久化更新
        paymentOrderManager.updateStateById(
                event,    // 事件
                paymentOrder,                        // 订单实体
                CodeMsgVO.build("200", "付款成功"),   // 结果码
                PaymentOrder::getPaymentFinishTime   // 额外更新字段
        );
        
        // 订单完成的处理
        paySuccessMqProducer.produce(paymentOrder.getOrderId);
        // ...
    }
}


五、收益:解决了什么问题

问题 状态机方案
状态跃迁不可控 每个事件明确定义前置状态,非法转换直接报错
难以追溯 事件名即业务动作,日志一看就知道发生了什么
并发隐患 乐观锁保证同一时刻只有一个线程能成功更新
维护噩梦 加新状态只需在枚举里加一行,所有转换规则一目了然

六、任何有状态流转的场景都可以复用本状态机方案

状态机不是银弹,但它让状态流转变得:可控、可追溯、可维护。

核心思想:

  1. 事件驱动:每个事件有明确的 preStatetargetState
  2. 前置校验:转换前断言当前状态是否合法
  3. 乐观锁更新:WHERE 条件带上前置状态,防止并发覆盖
  4. 失败告警:更新失败必有异常,必须告警

任何有状态流转的场景都可以复用本状态机方案

场景 状态示例
退款订单 INIT → REFUNDING → REFUND_SUCCESS/REFUND_FAIL
任务订单 DRAFT → PUBLISHED → ACCEPTED → COMPLETED
审批流程 PENDING → APPROVED/REJECTED

再来总结一下复用步骤

  1. 定义状态枚举,实现 SMState
  2. 定义状态机枚举,实现 StateMachine<StateEnum>
  3. 业务Manager统一一个更新状态的方法,并将状态机作为入参。(依赖StateMachineDbUpdater.updateStateById

posted on 2026-08-04 21:56  buguge  阅读(8)  评论(0)    收藏  举报