找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

962

积分

0

好友

126

主题
发表于 昨天 06:36 | 查看: 0| 回复: 0

“这个审批流程又改了,要加两个新状态和三种流转条件...”

当我第N次听到产品经理这样说时,看着代码里层层嵌套的if-else,我知道是时候做出改变了。那个曾经“简单”的审批系统,已经变成了谁都不敢碰的 代码沼泽

一、if-else地狱:我们曾经走过的弯路

1.1 典型的复杂业务逻辑

想象一下这样一个订单状态流转逻辑:

public class OrderService {
    public void handleEvent(Order order, OrderEvent event) {
        if (order.getStatus() == OrderStatus.WAITING_PAYMENT) {
            if (event == OrderEvent.PAY_SUCCESS) {
                if (order.getAmount() > 10000) {
                    // 大额订单需要风控审核
                    order.setStatus(OrderStatus.WAITING_RISK_REVIEW);
                    riskReviewService.submit(order);
                } else {
                    order.setStatus(OrderStatus.PAID);
                    inventoryService.reduceStock(order);
                }
            } else if (event == OrderEvent.PAY_TIMEOUT) {
                order.setStatus(OrderStatus.CLOSED);
                inventoryService.restoreStock(order);
            } else if (event == OrderEvent.USER_CANCEL) {
                if (order.getCreateTime().isAfter(LocalDateTime.now().minusMinutes(30))) {
                    order.setStatus(OrderStatus.CLOSED);
                    inventoryService.restoreStock(order);
                } else {
                    throw new BusinessException("超过30分钟不能取消订单");
                }
            }
        } else if (order.getStatus() == OrderStatus.WAITING_RISK_REVIEW) {
            if (event == OrderEvent.RISK_APPROVE) {
                order.setStatus(OrderStatus.PAID);
                inventoryService.reduceStock(order);
            } else if (event == OrderEvent.RISK_REJECT) {
                order.setStatus(OrderStatus.CLOSED);
                notificationService.notifyUser(order.getUserId(), "风控审核未通过");
            }
        }
        // ... 更多状态和事件的嵌套判断
    }
}

1.2 if-else方案的致命缺陷

维护 nightmare:

  • 难以理解:新同事需要半天才能理清状态流转
  • 容易出错:修改一个分支可能影响其他逻辑
  • 测试困难:分支覆盖测试用例指数级增长
  • 需求变更痛苦:每次加新状态都要修改多个地方

二、状态机引擎:架构思维的转变

2.1 什么是状态机?

状态机的核心概念很简单:

  • 状态:系统所处的状况(如:待支付、已支付)
  • 事件:触发状态变更的动作(如:支付成功、取消订单)
  • 流转:状态 + 事件 → 新状态
  • 动作:状态变更时执行的业务逻辑

2.2 为什么选择状态机?

状态机的优势:

  • 可视化:状态流转一目了然
  • 可维护性:状态逻辑集中管理
  • 可扩展性:新增状态不影响现有逻辑
  • 可测试性:每个流转都可以独立测试

三、实战:Spring StateMachine解决方案

3.1 环境配置

<dependency>
    <groupId>org.springframework.statemachine</groupId>
    <artifactId>spring-statemachine-core</artifactId>
    <version>3.0.0</version>
</dependency>

3.2 定义订单状态机

@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderEvent> {
    @Override
    public void configure(StateMachineStateConfigurer<OrderStatus, OrderEvent> states)
            throws Exception {
        states
            .withStates()
                .initial(OrderStatus.WAITING_PAYMENT)
                .state(OrderStatus.PAID)
                .state(OrderStatus.WAITING_RISK_REVIEW)
                .state(OrderStatus.DELIVERED)
                .state(OrderStatus.COMPLETED)
                .state(OrderStatus.CLOSED)
                .state(OrderStatus.CANCELLED);
    }
    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions)
            throws Exception {
        transitions
            // 支付相关流转
            .withExternal()
                .source(OrderStatus.WAITING_PAYMENT)
                .target(OrderStatus.WAITING_RISK_REVIEW)
                .event(OrderEvent.PAY_SUCCESS)
                .action(riskReviewAction())
                .guard(amountGreaterThan10000Guard())
            .and()
            .withExternal()
                .source(OrderStatus.WAITING_PAYMENT)
                .target(OrderStatus.PAID)
                .event(OrderEvent.PAY_SUCCESS)
                .action(paymentSuccessAction())
                .guard(amountLessThanOrEqual10000Guard())
            .and()
            .withExternal()
                .source(OrderStatus.WAITING_PAYMENT)
                .target(OrderStatus.CLOSED)
                .event(OrderEvent.PAY_TIMEOUT)
                .action(paymentTimeoutAction())
            .and()
            .withExternal()
                .source(OrderStatus.WAITING_PAYMENT)
                .target(OrderStatus.CANCELLED)
                .event(OrderEvent.USER_CANCEL)
                .action(userCancelAction())
                .guard(within30MinutesGuard())
            // 风控审核流转
            .and()
            .withExternal()
                .source(OrderStatus.WAITING_RISK_REVIEW)
                .target(OrderStatus.PAID)
                .event(orderEvent.RISK_APPROVE)
                .action(riskApproveAction())
            .and()
            .withExternal()
                .source(OrderStatus.WAITING_RISK_REVIEW)
                .target(OrderStatus.CLOSed)
                .event(orderEvent.RISK_REJECT)
                .action(riskRejectAction());
    }
    @Bean
    public Action<OrderStatus, OrderEvent> paymentSuccessAction() {
        return context -> {
            Order order = context.getExtendedState().get("order", Order.class);
            inventoryService.reduceStock(order);
            log.info("订单支付成功: {}", order.getOrderNo());
        };
    }
}

3.3 状态机Guard(条件判断)

@Component
public class OrderGuard {
    public Guard<OrderStatus, OrderEvent> amountGreaterThan10000Guard() {
        return context -> {
            Order order = context.getExtendedState().get("order", Order.class);
            return order.getAmount().compareTo(new BigDecimal("10000")) > 0;
        };
    }
    public Guard<OrderStatus, OrderEvent> within30MinutesGuard() {
        return context -> {
            Order order = context.getExtendedState().get("order", Order.class);
            return Duration.between(order.getCreateTime(), LocalDateTime.now())
                           .toMinutes() <= 30;
        };
    }
}

3.4 使用状态机处理业务逻辑

@Service
public class OrderStateMachineService {
    @Autowired
    private StateMachine<OrderStatus, OrderEvent> stateMachine;
    @Transactional
    public void handleEvent(Long orderId, OrderEvent event) {
        Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new OrderNotFoundException(orderId));
        // 重置状态机到订单当前状态
        stateMachine.stop();
        stateMachine.getStateMachineAccessor()
                   .doWithAllRegions(access -> access.resetStateMachine(
                new DefaultStateMachineContext<>(
                        order.getStatus(), null, null, null)));
        stateMachine.start();
        // 设置订单到扩展状态
        stateMachine.getExtendedState().getVariables().put("order", order);
        // 发送事件
        boolean accepted = stateMachine.sendEvent(event);
        if (accepted) {
            // 更新订单状态
            OrderStatus newStatus = stateMachine.getState().getId();
            order.setStatus(newStatus);
            orderRepository.save(order);
        } else {
            throw new IllegalStateException("状态流转失败: " + order.getStatus() + " -> " + event);
        }
    }
}

四、更轻量的选择:Cola StateMachine

如果你觉得Spring StateMachine太重,可以试试阿里的Cola StateMachine:

4.1 Cola StateMachine配置

@Configuration
public class ColaStateMachineConfig {
    @Bean
    public StateMachine<OrderStatus, OrderEvent, OrderContext> orderStateMachine() {
        StateMachineBuilder<OrderStatus, OrderEvent, OrderContext> builder =
            StateMachineBuilder.create();
        // 待支付状态的流转
        builder.externalTransition()
                .from(OrderStatus.WAITING_PAYMENT)
                .to(OrderStatus.WAITING_RISK_REVIEW)
                .on(OrderEvent.PAY_SUCCESS)
                .when(checkAmountGreaterThan10000())
                .perform(doRiskReview());
        builder.externalTransition()
                .from(OrderStatus.WAITING_PAYMENT)
                .to(OrderStatus.PAID)
                .on(OrderEvent.PAY_SUCCESS)
                .when(checkAmountLessThanOrEqual10000())
                .perform(doPaymentSuccess());
        return builder.build("orderStateMachine");
    }
}

4.2 Cola状态机使用

@Service
public class ColaOrderService {
    @Autowired
    private StateMachine<OrderStatus, OrderEvent, OrderContext> stateMachine;
    public void handleEvent(Order order, OrderEvent event) {
        OrderContext context = new OrderContext();
        context.setOrder(order);
        OrderStatus newStatus = stateMachine.fireEvent(order.getStatus(), event, context);
        if (newStatus != null) {
            order.setStatus(newStatus);
            orderRepository.save(order);
        } else {
            throw new IllegalStateException("无效的状态流转");
        }
    }
}

五、可视化:让状态流转一目了然

5.1 生成状态图

使用Graphviz生成状态流转图:

@Component
public class StateMachineVisualizer {
    public String generateGraphviz(StateMachine<OrderStatus, OrderEvent> stateMachine) {
        StringBuilder dot = new StringBuilder();
        dot.append("digraph OrderStateMachine {\n");
        dot.append("    rankdir=LR;\n");
        dot.append("    node [shape=circle];\n\n");
        // 添加状态节点
        for (OrderStatus state : OrderStatus.values()) {
            dot.append("    ").append(state.name()).append(";\n");
        }
        dot.append("\n");
        // 添加流转边
        // 这里需要根据实际配置生成
        dot.append("    WAITING_PAYMENT -> WAITING_RISK_REVIEW [label=\"PAY_SUCCESS(amount>10000)\"];\n");
        dot.append("    WAITING_PAYMENT -> PAID [label=\"PAY_SUCCESS(amount<=10000)\"];\n");
        dot.append("    WAITING_PAYMENT -> CLOSED [label=\"PAY_TIMEOUT\"];\n");
        dot.append("}");
        return dot.toString();
    }
}

5.2 可视化效果

生成的状态图清晰展示了整个业务流程:

WAITING_PAYMENT → PAY_SUCCESS(amount>10000) → WAITING_RISK_REVIEW
WAITING_PAYMENT → PAY_SUCCESS(amount<=10000) → PAID
WAITING_PAYMENT → PAY_TIMEOUT → CLOSED
WAITING_PAYMENT → USER_CANCEL(within30min) → CANCELLED
WAITING_RISK_REVIEW → RISK_APPROVE → PAID
WAITING_RISK_REVIEW → RISK_REJECT → CLOSED

六、测试:状态机的可测试性优势

6.1 单元测试变得简单

@SpringBootTest
class OrderStateMachineTest {
    @Autowired
    private OrderStateMachineService stateMachineService;
    @Test
    void should_transition_to_risk_review_when_large_amount_paid() {
        // Given
        Order order = createOrderWithAmount(new BigDecimal("15000"));
        // When
        stateMachineService.handleEvent(order.getId(), OrderEvent.PAY_SUCCESS);
        // Then
        Order updatedOrder = orderRepository.findById(order.getId()).get();
        assertThat(updatedOrder.getStatus()).isEqualTo(OrderStatus.WAITING_RISK_REVIEW);
    }
    @Test
    void should_reject_cancel_after_30_minutes() {
        // Given
        Order order = createOrder30MinutesAgo();
        // When & Then
        assertThatThrownBy(() ->
            stateMachineService.handleEvent(order.getId(), OrderEvent.USER_CANCEL))
            .isInstanceOf(IllegalStateException.class);
    }
}

七、高级特性:应对复杂业务场景

7.1 分层状态机

对于更复杂的业务,可以使用分层状态机:

public class HierarchicalStateMachineConfig {
    public void configureHierarchicalStates(
            StateMachineStateConfigurer<OrderStatus, OrderEvent> states)
            throws Exception {
        states
            .withStates()
                .initial(OrderStatus.WAITING_PAYMENT)
                .state(OrderStatus.PAID)
                .state(OrderStatus.DELIVERED)
                .and()
                .withStates()
                    .parent(OrderStatus.DELIVERED)
                    .initial(OrderStatus.WAITING_RECEIVE)
                    .state(OrderStatus.RECEIVED)
                    .state(OrderStatus.APPLYING_RETURN)
                    .state(OrderStatus.RETURNED);
    }
}

7.2 状态机持久化

@Component
public class OrderStateMachinePersist
    implements StateMachinePersist<OrderStatus, OrderEvent, String> {
    @Autowired
    private OrderRepository orderRepository;
    @Override
    public void write(StateMachineContext<OrderStatus, OrderEvent> context, String orderId) {
        Order order = orderRepository.findById(Long.valueOf(orderId)).get();
        order.setStateMachineContext(objectMapper.writeValueAsString(context));
        orderRepository.save(order);
    }
    @Override
    public StateMachineContext<OrderStatus, OrderEvent> read(String orderId) {
        Order order = orderRepository.findById(Long.valueOf(orderId)).get();
        if (order.getStateMachineContext() != null) {
            return objectMapper.readValue(order.getStateMachineContext(),
                                         StateMachineContext.class);
        }
        return null;
    }
}

八、迁移策略:从if-else平滑过渡

8.1 渐进式迁移步骤

  1. 识别核心状态:找出业务中的主要状态和事件
  2. 创建状态机配置:先实现核心流转逻辑
  3. 并行运行:新老逻辑同时存在,逐步切换
  4. 全面迁移:所有逻辑都迁移到状态机
  5. 清理老代码:删除原有的if-else逻辑

8.2 迁移后的架构对比

迁移前:

  • 代码:2000行if-else逻辑
  • 维护:每次修改需要仔细测试所有分支
  • 理解:新同事需要2天熟悉代码

迁移后:

  • 代码:500行状态机配置 + 300行业务动作
  • 维护:修改配置即可,不影响其他逻辑
  • 理解:新同事2小时看懂状态图

总结:状态机的适用场景

适合使用状态机的场景:

  • ✅ 复杂的业务流程(审批流、订单流、工单流)
  • ✅ 明确的状态和事件
  • ✅ 频繁的业务规则变更
  • ✅ 需要可视化业务流程

可能不适用的场景:

  • ❌ 简单的CRUD操作
  • ❌ 没有明确状态概念的场景
  • ❌ 实时性要求极高的场景(状态机有轻微开销)

我们的收获:

  1. 代码更清晰:状态流转一目了然
  2. 维护更简单:新增状态不影响现有逻辑
  3. 测试更容易:每个流转可以独立测试
  4. 沟通更高效:产品经理也能看懂状态图

通过引入Java状态机引擎,我们成功地将错综复杂的业务逻辑转化为清晰可管理的状态流转图。这不仅提升了代码质量,也让整个团队的协作效率上了一个台阶。这种设计模式的转变,本质上是从“过程式”思维向“声明式”思维的提升。如果你也在为复杂的业务逻辑头疼,不妨试试用状态机来理清头绪,或许会有意想不到的收获。更多关于架构设计、分布式系统的深度讨论,欢迎在云栈社区的技术论坛中与我们交流。




上一篇:Clawdbot(Moltbot)AI Agent实战:从婚恋平台自动化到智能购物比价
下一篇:MySQL主从复制延迟从3600秒降到0:问题排查与8.0并行复制优化
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-2-1 01:28 , Processed in 0.379549 second(s), 43 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表