系统设计——状态机模型设计经验
摘要
本文主要介绍了状态机模型的设计经验,包括其定义、适用场景、建模示例、事件驱动设计以及配置数据化等内容。状态机模型通过事件驱动控制状态变化,适用于流程驱动系统、生命周期管理等场景,不适用于状态变化简单或不确定的场景。文中还提供了基于“信贷审批流程”的项目示例和事件驱动流转的Spring项目示例,最后总结了状态机模式设计的优势。
1. 状态机模型(State Machine Model)设计
1.1. 🧭 什么是数据状态机模型?
数据状态机模型是一种用来表示对象在不同“状态”之间流转的结构模型,通过“事件驱动”来控制状态变化,并可绑定“动作”、“条件”、“权限”等。
一个典型的状态机由以下几个部分组成:
组件 | 描述 |
状态(State) | 对象处于的某种业务阶段,例如“待审批”、“已发货” |
事件(Event) | 引起状态变化的触发,例如“审批通过”、“发货” |
转移(Transition) | 定义从哪一个状态,通过什么事件,转移到哪个状态 |
动作(Action) | 状态变化时要执行的操作(如通知、写日志等) |
条件(Guard) | 转移是否允许发生的判断条件(如权限、金额限制) |
1.2. 📦 状态机适合什么场景?
1.2.1. ✅ 适合的场景(推荐使用):
场景类型 | 示例 |
流程驱动系统 | 审批流程、工作流引擎、合同流转、风险审核等 |
生命周期管理 | 订单状态、任务状态、工单状态、用户状态、设备状态 |
多状态 + 严格控制流转 | 状态间只能在特定事件触发、满足条件下跳转的系统 |
规则复杂、流程分支多 | 比如信贷审批流程(审批 → 审批通过/拒绝 → 放款/结清) |
需要追踪、可视化流程 | 风控审核、OA 审批、ERP 物料流转、电子合同签署 |
1.2.2. ❌ 不适合的场景(无需使用):
场景类型 | 原因 |
状态变化简单 | 只有一个状态字段,状态转移无规则限制 |
状态变化不确定 | 状态变更不固定、难以建模,如动态标签分类 |
流程非常简单 | 没有“事件”,直接在业务代码里改状态即可 |
高性能极致场景 | 状态机带来一定开销,不适合毫秒级超高频场景 |
1.3. 📘 状态机建模示例(数据驱动设计)
假设你有一个“信贷审批流程”,包含以下状态:
APPLY → WAIT_APPROVE → APPROVED/REJECTED → LOANED → COMPLETED
1.3.1. 数据模型表结构设计:
状态表 fsm_state
id | code | name |
1 | APPLY | 已申请 |
2 | WAIT_APPROVE | 待审批 |
3 | APPROVED | 审批通过 |
4 | REJECTED | 审批拒绝 |
5 | LOANED | 已放款 |
6 | COMPLETED | 已结清 |
事件表 fsm_event
id | code | name |
1 | SUBMIT | 提交申请 |
2 | APPROVE | 审批通过 |
3 | REJECT | 审批拒绝 |
4 | LOAN | 放款 |
5 | CLOSE | 结清 |
状态转移表 fsm_transition
id | source_state | event | target_state | condition_class | action_class |
1 | APPLY | SUBMIT | WAIT_APPROVE | null | null |
2 | WAIT_APPROVE | APPROVE | APPROVED | auditGuard | notifyAction |
3 | WAIT_APPROVE | REJECT | REJECTED | null | notifyAction |
4 | APPROVED | LOAN | LOANED | checkLimitGuard | loanAction |
5 | LOANED | CLOSE | COMPLETED | null | null |
1.4. 基于“信贷审批流程”项目示例
1.4.1. 🧱 数据库结构
建表 SQL(推荐用 MySQL)
CREATE TABLE fsm_state (id BIGINT AUTO_INCREMENT PRIMARY KEY,state_code VARCHAR(50) NOT NULL,state_name VARCHAR(100) NOT NULL
);CREATE TABLE fsm_event (id BIGINT AUTO_INCREMENT PRIMARY KEY,event_code VARCHAR(50) NOT NULL,event_name VARCHAR(100) NOT NULL
);CREATE TABLE fsm_transition (id BIGINT AUTO_INCREMENT PRIMARY KEY,source_state VARCHAR(50) NOT NULL,event_code VARCHAR(50) NOT NULL,target_state VARCHAR(50) NOT NULL,condition_class VARCHAR(100),action_class VARCHAR(100)
);
初始化数据(data.sql)
-- 状态
INSERT INTO fsm_state (state_code, state_name) VALUES
('APPLY', '已申请'),
('WAIT_APPROVE', '待审批'),
('APPROVED', '审批通过'),
('REJECTED', '审批拒绝'),
('LOANED', '已放款'),
('COMPLETED', '已结清');-- 事件
INSERT INTO fsm_event (event_code, event_name) VALUES
('SUBMIT', '提交'),
('APPROVE', '审批通过'),
('REJECT', '审批拒绝'),
('LOAN', '放款'),
('CLOSE', '结清');-- 流转
INSERT INTO fsm_transition (source_state, event_code, target_state, condition_class, action_class) VALUES
('APPLY', 'SUBMIT', 'WAIT_APPROVE', NULL, NULL),
('WAIT_APPROVE', 'APPROVE', 'APPROVED', 'auditGuard', 'notifyAction'),
('WAIT_APPROVE', 'REJECT', 'REJECTED', NULL, 'notifyAction'),
('APPROVED', 'LOAN', 'LOANED', NULL, 'loanAction'),
('LOANED', 'CLOSE', 'COMPLETED', NULL, NULL);
1.4.2. 📦 项目依赖(pom.xml)
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-starter</artifactId><version>3.2.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency>
</dependencies>
1.4.3. ⚙️ 配置文件 application.yml
spring:datasource:url: jdbc:mysql://localhost:3306/yourdb?useSSL=falseusername: rootpassword: rootjpa:hibernate:ddl-auto: noneshow-sql: true
1.4.4. 🧩 实体类与 Repository
FsmState.java
@Entity
@Data
public class FsmState {@Id @GeneratedValue private Long id;private String stateCode;private String stateName;
}
FsmEvent.java
@Entity
@Data
public class FsmEvent {@Id @GeneratedValue private Long id;private String eventCode;private String eventName;
}
FsmTransition.java
@Entity
@Data
public class FsmTransition {@Id @GeneratedValue private Long id;private String sourceState;private String eventCode;private String targetState;private String conditionClass;private String actionClass;
}
1.4.5. Repository 接口
public interface FsmStateRepository extends JpaRepository<FsmState, Long> {}
public interface FsmEventRepository extends JpaRepository<FsmEvent, Long> {}
public interface FsmTransitionRepository extends JpaRepository<FsmTransition, Long> {}
1.4.6. 🔧 Action 和 Guard 实现
NotifyAction.java
@Component("notifyAction")
public class NotifyAction implements Action<String, String> {@Overridepublic void execute(StateContext<String, String> context) {System.out.println("【通知】状态变更:" + context.getSource().getId() + " → " + context.getTarget().getId());}
}
LoanAction.java
@Component("loanAction")
public class LoanAction implements Action<String, String> {@Overridepublic void execute(StateContext<String, String> context) {System.out.println("【放款动作】业务放款处理中...");}
}
AuditGuard.java
@Component("auditGuard")
public class AuditGuard implements Guard<String, String> {@Overridepublic boolean evaluate(StateContext<String, String> context) {return true; // 模拟审批通过}
}
1.4.7. 🏗️ 构建状态机
DynamicStateMachineBuilder.java
@Component
@RequiredArgsConstructor
public class DynamicStateMachineBuilder {private final ApplicationContext context;private final FsmStateRepository stateRepo;private final FsmTransitionRepository transRepo;public StateMachine<String, String> build() throws Exception {var builder = StateMachineBuilder.builder();Set<String> states = stateRepo.findAll().stream().map(FsmState::getStateCode).collect(Collectors.toSet());List<FsmTransition> transitions = transRepo.findAll();builder.configureStates().withStates().initial("APPLY").states(states);var config = builder.configureTransitions();for (FsmTransition t : transitions) {var transition = config.withExternal().source(t.getSourceState()).target(t.getTargetState()).event(t.getEventCode());if (t.getConditionClass() != null) {transition.guard((Guard<String, String>) context.getBean(t.getConditionClass()));}if (t.getActionClass() != null) {transition.action((Action<String, String>) context.getBean(t.getActionClass()));}}return builder.build();}
}
1.4.8. 🧪 状态机执行器
StateMachineExecutor.java
@Service
@RequiredArgsConstructor
public class StateMachineExecutor {private final DynamicStateMachineBuilder builder;public String process(String currentState, String event) throws Exception {StateMachine<String, String> machine = builder.build();machine.getStateMachineAccessor().doWithAllRegions(access -> access.resetStateMachine(new DefaultStateMachineContext<>(currentState, null, null, null)));machine.start();machine.sendEvent(event);return machine.getState().getId();}
}
1.4.9. 🌐 控制器示例
@RestController
@RequiredArgsConstructor
@RequestMapping("/fsm")
public class StateMachineController {private final StateMachineExecutor executor;@PostMapping("/trigger")public String trigger(@RequestParam String state, @RequestParam String event) throws Exception {String nextState = executor.process(state, event);return "当前状态:" + state + ",事件:" + event + ",转移后状态:" + nextState;}
}
1.4.10. ✅ 测试流程
# 初始状态 APPLY,事件 SUBMIT → WAIT_APPROVE
curl -X POST "http://localhost:8080/fsm/trigger?state=APPLY&event=SUBMIT"# 状态 WAIT_APPROVE,事件 APPROVE → APPROVED
curl -X POST "http://localhost:8080/fsm/trigger?state=WAIT_APPROVE&event=APPROVE"# 状态 APPROVED,事件 LOAN → LOANED
curl -X POST "http://localhost:8080/fsm/trigger?state=APPROVED&event=LOAN"# 状态 LOANED,事件 CLOSE → COMPLETED
curl -X POST "http://localhost:8080/fsm/trigger?state=LOANED&event=CLOSE"
1.4.11. 📘 后续扩展建议
扩展点 | 说明 |
多流程模型支持 | 添加流程 ID 字段,支持多状态机模型 |
可视化建模器 | 使用 Flowable 或自己实现状态流建模 |
状态持久化 | 实现状态存储,支持恢复流程(可加 redis/db) |
条件脚本执行 | 动态脚本引擎(Groovy/SpEL)处理条件判断 |
权限控制/审计日志支持 | 可集成权限服务和日志系统 |
1.5. ✅ 状态机模式设计总结
点 | 说明 |
状态机适用场景 | 审批流、订单、合同、用户状态管理等流程控制系统 |
状态机不适用场景 | 状态少、变化简单、无流程控制的轻量业务 |
建模建议 | 状态、事件、流转配置建议入库 + 动态加载 |
技术选型建议 | 小型项目可用枚举硬编码,大型可用 |
2. 事件驱动设计
2.1. ✅ 什么是“事件驱动流转”?
事件驱动流转(Event-Driven Transition) 是一种通过 事件触发来驱动系统状态变化或执行逻辑 的方式。它是“事件驱动架构(EDA)”的核心理念之一,常用于业务流程的状态机设计中。通俗的解释就是:把一个系统看成一个有限状态机(Finite State Machine, FSM),它有多个“状态”,而“事件”就像“命令”或“信号”,告诉系统应该从一个状态跳转到另一个状态。
2.1.1. 🧠 为什么要使用事件驱动流转?
- 提高可维护性:避免硬编码状态判断和修改。
- 支持复杂流程:比如分支判断、回退、并行状态等。
- 方便扩展:后期加新状态或事件不改业务核心逻辑。
- 天然支持异步/微服务架构:可通过消息总线发送事件,实现解耦。
2.1.2. 🧩 延伸:事件可以来源于
- 用户行为(点击、提交)
- 定时任务(定期检查过期状态)
- 消息队列(MQ 传入事件)
- 外部系统回调(比如支付平台通知)
2.1.3. 🎯 核心要素:
概念 | 说明 |
状态 | 系统或数据当前的情况,比如“待支付”、“已支付”、“已发货” |
事件 | 某个动作或输入,比如“支付成功”、“发货按钮点击” |
流转(Transition) | 在事件发生后状态的变更,比如“收到支付成功事件”后,状态从“待支付”变为“已支付” |
2.2. 📦 示例:订单状态流转
假设你有一个电商订单系统,订单的状态如下:
初始状态: 待支付
→ [支付成功事件] → 已支付
→ [发货事件] → 已发货
→ [签收事件] → 已完成
如果是事件驱动:你不是直接改状态,而是:
orderStateMachine.sendEvent(OrderEvent.PAY); // 自动将状态从“待支付”→“已支付”
2.3. 🚀 事件驱动 vs 手动流转(对比)
方式 | 手动流转 | 事件驱动流转 |
状态变更方式 | 代码直接修改字段: | 触发事件: |
可读性 | 状态逻辑分散在代码中 | 状态变更清晰、集中在状态机配置中 |
复杂流程管理 | 不容易维护 | 易于添加新状态、路径,支持条件、动作等复杂流转 |
异步支持 | 不方便 | 可结合消息队列实现异步事件触发 |
2.4. 事件驱动流转 spring项目示例
下面是一个基于 Spring Boot + Spring State Machine 实现“事件驱动流转” 的完整项目示例,模拟一个简单的“订单流转”场景。
2.5. ✅ Spring示例项目说明
业务场景:订单状态变更
- 初始状态:
WAITING_PAYMENT
- 事件驱动变更:
-
PAY
→ 状态变更为PAID
SHIP
→ 状态变更为SHIPPED
RECEIVE
→ 状态变更为RECEIVED
2.5.1. 📁 项目结构
spring-state-machine-demo/
├── pom.xml
└── src/main/java/com/example/statemachine/├── OrderStatus.java├── OrderEvent.java├── Order.java├── OrderController.java├── OrderService.java├── OrderStateMachineConfig.java└── StateMachineApplication.java
2.5.2. 1️⃣ pom.xml
依赖
<project><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>spring-state-machine-demo</artifactId><version>1.0.0</version><dependencies><!-- Spring Boot --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring State Machine --><dependency><groupId>org.springframework.statemachine</groupId><artifactId>spring-statemachine-starter</artifactId><version>3.2.0</version></dependency></dependencies>
</project>
2.5.3. 2️⃣ 状态&事件定义
OrderStatus.java
public enum OrderStatus {WAITING_PAYMENT,PAID,SHIPPED,RECEIVED
}
OrderEvent.java
public enum OrderEvent {PAY, SHIP, RECEIVE
}
2.5.4. 3️⃣ 状态机配置
OrderStateMachineConfig.java
@Configuration
@EnableStateMachine
public class OrderStateMachineConfig extends StateMachineConfigurerAdapter<OrderStatus, OrderEvent> {@Overridepublic void configure(StateMachineStateConfigurer<OrderStatus, OrderEvent> states) throws Exception {states.withStates().initial(OrderStatus.WAITING_PAYMENT).state(OrderStatus.PAID).state(OrderStatus.SHIPPED).end(OrderStatus.RECEIVED);}@Overridepublic void configure(StateMachineTransitionConfigurer<OrderStatus, OrderEvent> transitions) throws Exception {transitions.withExternal().source(OrderStatus.WAITING_PAYMENT).target(OrderStatus.PAID).event(OrderEvent.PAY).and().withExternal().source(OrderStatus.PAID).target(OrderStatus.SHIPPED).event(OrderEvent.SHIP).and().withExternal().source(OrderStatus.SHIPPED).target(OrderStatus.RECEIVED).event(OrderEvent.RECEIVE);}
}
2.5.5. 4️⃣ 业务模型
Order.java
@Data
public class Order {private String id;private OrderStatus status;public Order(String id) {this.id = id;this.status = OrderStatus.WAITING_PAYMENT;}
}
2.5.6. 5️⃣ 服务类
OrderService.java
@Service
public class OrderService {@Autowiredprivate StateMachine<OrderStatus, OrderEvent> stateMachine;private Map<String, Order> orders = new HashMap<>();public Order createOrder(String id) {Order order = new Order(id);orders.put(id, order);return order;}public Order sendEvent(String orderId, OrderEvent event) {Order order = orders.get(orderId);if (order == null) {throw new RuntimeException("Order not found");}stateMachine.stop();stateMachine.getStateMachineAccessor().doWithAllRegions(access -> access.resetStateMachine(new DefaultStateMachineContext<>(order.getStatus(), null, null, null)));stateMachine.start();boolean accepted = stateMachine.sendEvent(event);if (accepted) {order.setStatus(stateMachine.getState().getId());}return order;}public Order getOrder(String id) {return orders.get(id);}
}
2.5.7. 6️⃣ 控制器接口
OrderController.java
@RestController
@RequestMapping("/orders")
public class OrderController {@Autowiredprivate OrderService orderService;@PostMapping("/{id}")public Order create(@PathVariable String id) {return orderService.createOrder(id);}@PostMapping("/{id}/event")public Order trigger(@PathVariable String id, @RequestParam OrderEvent event) {return orderService.sendEvent(id, event);}@GetMapping("/{id}")public Order get(@PathVariable String id) {return orderService.getOrder(id);}
}
2.5.8. 7️⃣ 启动类
StateMachineApplication.java
@SpringBootApplication
public class StateMachineApplication {public static void main(String[] args) {SpringApplication.run(StateMachineApplication.class, args);}
}
2.5.9. 📬 示例调用流程(Postman / curl)
# 创建订单
POST http://localhost:8080/orders/123# 触发支付事件
POST http://localhost:8080/orders/123/event?event=PAY# 触发发货事件
POST http://localhost:8080/orders/123/event?event=SHIP# 触发签收事件
POST http://localhost:8080/orders/123/event?event=RECEIVE# 查看订单状态
GET http://localhost:8080/orders/123
2.5.10. ✅ 项目特点
- 使用 事件驱动方式 处理状态变更
- 状态机配置清晰
- 便于扩展、复用、统一管理业务流转
3. 状态机配置数据化,动态加载执行状态转移设计
3.1. ✅ 将下面这些配置信息持久化(存入数据库或配置中心):
类型 | 示例 |
状态列表 | WAITING, PAID, SHIPPED, ... |
事件列表 | PAY, SHIP, RECEIVE, ... |
流转表 | 从哪个状态,接收哪个事件,到哪个状态 |
条件/动作 | 流转时要不要执行校验/回调等业务 |
3.2. ✅ 数据库表设计(最基础版本)
3.2.1. 状态定义表 fsm_state
id | state_code | state_name |
1 | WAITING_PAYMENT | 待支付 |
2 | PAID | 已支付 |
3.2.2. 事件定义表 fsm_event
id | event_code | event_name |
1 | PAY | 支付成功 |
3.2.3. 状态流转配置表 fsm_transition
id | source_state | event_code | target_state | condition_class | action_class |
1 | WAITING_PAYMENT | PAY | PAID | null | null |
3.3. ✅ 动态状态机加载实现(核心代码结构)
3.3.1. 加载状态机模型
public class DynamicStateMachineModel {private Set<String> states;private Set<String> events;private List<Transition> transitions;public static class Transition {private String source;private String event;private String target;private String conditionBean;private String actionBean;}
}
3.3.2. 动态构建 Spring State Machine
@Component
public class DynamicStateMachineBuilder {@Autowiredprivate ApplicationContext context;public StateMachine<String, String> build(DynamicStateMachineModel model) throws Exception {StateMachineBuilder.Builder<String, String> builder = StateMachineBuilder.builder();builder.configureStates().withStates().initial(model.states.iterator().next()).states(new HashSet<>(model.states));StateMachineTransitionConfigurer<String, String> transitions = builder.configureTransitions();for (DynamicStateMachineModel.Transition t : model.getTransitions()) {ExternalTransitionConfigurer<String, String> config = transitions.withExternal().source(t.getSource()).target(t.getTarget()).event(t.getEvent());// 条件动态注入if (t.getConditionBean() != null) {Guard<String, String> guard = context.getBean(t.getConditionBean(), Guard.class);config.guard(guard);}// 动作动态注入if (t.getActionBean() != null) {Action<String, String> action = context.getBean(t.getActionBean(), Action.class);config.action(action);}}return builder.build();}
}
3.4. ✅ 业务调用示例
public class StateMachineExecutor {@Autowiredprivate DynamicStateMachineBuilder builder;@Autowiredprivate FsmRepository fsmRepository;public void process(String bizId, String currentState, String event) {// 1. 查询数据库加载流程模型DynamicStateMachineModel model = fsmRepository.loadModel();// 2. 构建状态机StateMachine<String, String> machine = builder.build(model);// 3. 初始化状态machine.getStateMachineAccessor().doWithAllRegions(access -> access.resetStateMachine(new DefaultStateMachineContext<>(currentState, null, null, null)));machine.start();// 4. 发送事件驱动流转machine.sendEvent(event);// 5. 保存新状态String newState = machine.getState().getId();System.out.println("新状态为:" + newState);}
}
3.5. ✅ Action 和 Guard 可插拔式实现(可动态配置 Bean)
3.5.1. 示例Action
@Component("logAction")
public class LogAction implements Action<String, String> {@Overridepublic void execute(StateContext<String, String> context) {System.out.println("动作:记录日志");}
}
3.5.2. 示例Guard
@Component("checkPaymentGuard")
public class CheckPaymentGuard implements Guard<String, String> {@Overridepublic boolean evaluate(StateContext<String, String> context) {// 模拟业务校验逻辑return true;}
}
3.6. ✅ 优势总结
特点 | 优势 |
动态配置 | 状态、事件、流转逻辑存数据库或配置中心 |
易于扩展 | 支持多个流程(订单、审批、签约等) |
可视化支持 | 可做可视化流程设计器 |
动作/条件可插拔 | 支持动态行为绑定 |
解耦 | 状态逻辑不写死在代码里 |