You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何基于Spring State Machine实现订单单状态映射多个status?

Handling One-to-Many State-to-Status Mapping in Spring State Machine

Hey there! Let's break down how to handle this scenario where a single logical STATE maps to multiple concrete status values in Spring State Machine. This is a super common use case when you need to track granular sub-states under broader categories, and there are two solid approaches to implement this effectively.

Approach 1: Use Extended State Attributes for Logical Grouping

The simplest way is to treat each status as the actual state in your state machine, and attach a logical STATE attribute to each status. This lets you group granular states under a broader category while keeping your state machine configuration straightforward.

Step 1: Define Enums with Logical State Mapping

First, create an enum for your logical top-level states, then another enum for the concrete status values that reference their parent logical state:

// Logical top-level states
public enum LogicalOrderState {
    NEW, PROCESSING, COMPLETE
}

// Concrete status values (actual states in the machine)
public enum OrderStatus {
    PENDING(LogicalOrderState.NEW),
    PAYMENT_PENDING(LogicalOrderState.NEW),
    PROCESSING_NEW(LogicalOrderState.PROCESSING),
    PROCESSING_COMPLETE(LogicalOrderState.PROCESSING),
    COMPLETE_SHIPPED(LogicalOrderState.COMPLETE),
    DELIVERED(LogicalOrderState.COMPLETE);

    private final LogicalOrderState logicalState;

    OrderStatus(LogicalOrderState logicalState) {
        this.logicalState = logicalState;
    }

    public LogicalOrderState getLogicalState() {
        return logicalState;
    }
}

Step 2: Configure the State Machine

Use OrderStatus as your state type, and configure transitions between the concrete status values. When you need to check the logical state later, just call getLogicalState() on the current status:

@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));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions) throws Exception {
        transitions
            // Transition within NEW logical state
            .withExternal()
                .source(OrderStatus.PENDING)
                .target(OrderStatus.PAYMENT_PENDING)
                .event(OrderEvent.INITIATE_PAYMENT)
            // Transition from NEW to PROCESSING
            .withExternal()
                .source(OrderStatus.PAYMENT_PENDING)
                .target(OrderStatus.PROCESSING_NEW)
                .event(OrderEvent.PAYMENT_SUCCESS)
            // Add other transitions as needed for your workflow
            .and()
            .withExternal()
                .source(OrderStatus.PROCESSING_NEW)
                .target(OrderStatus.PROCESSING_COMPLETE)
                .event(OrderEvent.FINISH_PROCESSING)
            .and()
            .withExternal()
                .source(OrderStatus.PROCESSING_COMPLETE)
                .target(OrderStatus.COMPLETE_SHIPPED)
                .event(OrderEvent.SHIP_ORDER)
            .and()
            .withExternal()
                .source(OrderStatus.COMPLETE_SHIPPED)
                .target(OrderStatus.DELIVERED)
                .event(OrderEvent.MARK_DELIVERED);
    }
}

Usage in Business Logic

When you need to check the logical state of an order:

// Get current status from state machine
OrderStatus currentStatus = stateMachine.getState().getId();
// Get the logical parent state
LogicalOrderState logicalState = currentStatus.getLogicalState();

if (logicalState == LogicalOrderState.NEW) {
    // Handle actions for all NEW-related statuses
}

Approach 2: Hierarchical State Machine (Substates)

If you want to leverage Spring State Machine's hierarchical state features (like shared entry/exit actions, or transitioning from any substate of a parent), you can define the logical STATE as parent states and the status values as their substates.

Step 1: Define State Enums

Keep separate enums for parent logical states and concrete substates:

public enum LogicalOrderState {
    NEW, PROCESSING, COMPLETE
}

public enum OrderStatus {
    PENDING, PAYMENT_PENDING, PROCESSING_NEW, PROCESSING_COMPLETE, COMPLETE_SHIPPED, DELIVERED
}

Step 2: Configure Hierarchical States

In your state machine config, assign each substate to its parent logical state. You can also define transitions from parent states (which apply to all their substates):

@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<Enum<?>, OrderEvent> {

    @Override
    public void configure(StateMachineStateConfigurer<Enum<?>, OrderEvent> states) throws Exception {
        states
            .withStates()
                // Set initial concrete state
                .initial(OrderStatus.PENDING)
                // Define parent logical states
                .state(LogicalOrderState.NEW)
                .state(LogicalOrderState.PROCESSING)
                .state(LogicalOrderState.COMPLETE)
                // Assign substates to parents
                .and()
                .withStates()
                    .parent(LogicalOrderState.NEW)
                    .states(EnumSet.of(OrderStatus.PENDING, OrderStatus.PAYMENT_PENDING))
                .and()
                .withStates()
                    .parent(LogicalOrderState.PROCESSING)
                    .states(EnumSet.of(OrderStatus.PROCESSING_NEW, OrderStatus.PROCESSING_COMPLETE))
                .and()
                .withStates()
                    .parent(LogicalOrderState.COMPLETE)
                    .states(EnumSet.of(OrderStatus.COMPLETE_SHIPPED, OrderStatus.DELIVERED));
    }

    @Override
    public void configure(StateMachineTransitionConfigurer<Enum<?>, OrderEvent> transitions) throws Exception {
        transitions
            // Transition within NEW substate
            .withExternal()
                .source(OrderStatus.PENDING)
                .target(OrderStatus.PAYMENT_PENDING)
                .event(OrderEvent.INITIATE_PAYMENT)
            // Transition from ANY NEW substate to PROCESSING_NEW
            .withExternal()
                .source(LogicalOrderState.NEW)
                .target(OrderStatus.PROCESSING_NEW)
                .event(OrderEvent.PAYMENT_SUCCESS)
            // Transition within PROCESSING substate
            .withExternal()
                .source(OrderStatus.PROCESSING_NEW)
                .target(OrderStatus.PROCESSING_COMPLETE)
                .event(OrderEvent.FINISH_PROCESSING)
            // Transition from ANY PROCESSING substate to COMPLETE_SHIPPED
            .withExternal()
                .source(LogicalOrderState.PROCESSING)
                .target(OrderStatus.COMPLETE_SHIPPED)
                .event(OrderEvent.SHIP_ORDER)
            // Transition within COMPLETE substate
            .withExternal()
                .source(OrderStatus.COMPLETE_SHIPPED)
                .target(OrderStatus.DELIVERED)
                .event(OrderEvent.MARK_DELIVERED);
    }

    // Listener to track state changes and logical state mapping
    @Bean
    public StateMachineListener<Enum<?>, OrderEvent> stateMachineListener() {
        return new StateMachineListenerAdapter<>() {
            @Override
            public void stateChanged(State<Enum<?>, OrderEvent> from, State<Enum<?>, OrderEvent> to) {
                if (to != null && to.getId() instanceof OrderStatus status) {
                    LogicalOrderState logicalState = mapStatusToLogicalState(status);
                    System.out.printf("Current status: %s | Logical state: %s%n", status, logicalState);
                }
            }
        };
    }

    private LogicalOrderState mapStatusToLogicalState(OrderStatus status) {
        return switch (status) {
            case PENDING, PAYMENT_PENDING -> LogicalOrderState.NEW;
            case PROCESSING_NEW, PROCESSING_COMPLETE -> LogicalOrderState.PROCESSING;
            case COMPLETE_SHIPPED, DELIVERED -> LogicalOrderState.COMPLETE;
        };
    }
}

Key Benefits of This Approach

  • Shared Behavior: You can add entry/exit actions to parent states that apply to all their substates (e.g., log something whenever entering the PROCESSING logical state).
  • Simplified Transitions: Transitioning from a parent state means any of its substates can trigger the transition, reducing redundant config.

Which Approach Should You Choose?

  • Use Approach 1 if you just need logical grouping without hierarchical state features—it's simpler and easier to maintain.
  • Use Approach 2 if you need to leverage hierarchical state behaviors like shared actions or bulk transitions.

内容的提问来源于stack exchange,提问作者RahulAggarwal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:28:06