当前位置: 首页 > news >正文

系统设计——状态机模型设计经验

摘要

本文主要介绍了状态机模型的设计经验,包括其定义、适用场景、建模示例、事件驱动设计以及配置数据化等内容。状态机模型通过事件驱动控制状态变化,适用于流程驱动系统、生命周期管理等场景,不适用于状态变化简单或不确定的场景。文中还提供了基于“信贷审批流程”的项目示例和事件驱动流转的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. ✅ 状态机模式设计总结

说明

状态机适用场景

审批流、订单、合同、用户状态管理等流程控制系统

状态机不适用场景

状态少、变化简单、无流程控制的轻量业务

建模建议

状态、事件、流转配置建议入库 + 动态加载

技术选型建议

小型项目可用枚举硬编码,大型可用spring-statemachine+ 数据驱动

2. 事件驱动设计

2.1. ✅ 什么是“事件驱动流转”?

事件驱动流转(Event-Driven Transition) 是一种通过 事件触发来驱动系统状态变化或执行逻辑 的方式。它是“事件驱动架构(EDA)”的核心理念之一,常用于业务流程的状态机设计中。通俗的解释就是:把一个系统看成一个有限状态机(Finite State Machine, FSM),它有多个“状态”,而“事件”就像“命令”或“信号”,告诉系统应该从一个状态跳转到另一个状态

2.1.1. 🧠 为什么要使用事件驱动流转?

  1. 提高可维护性:避免硬编码状态判断和修改。
  2. 支持复杂流程:比如分支判断、回退、并行状态等。
  3. 方便扩展:后期加新状态或事件不改业务核心逻辑。
  4. 天然支持异步/微服务架构:可通过消息总线发送事件,实现解耦。

2.1.2. 🧩 延伸:事件可以来源于

  • 用户行为(点击、提交)
  • 定时任务(定期检查过期状态)
  • 消息队列(MQ 传入事件)
  • 外部系统回调(比如支付平台通知)

2.1.3. 🎯 核心要素:

概念

说明

状态

系统或数据当前的情况,比如“待支付”、“已支付”、“已发货”

事件

某个动作或输入,比如“支付成功”、“发货按钮点击”

流转(Transition)

在事件发生后状态的变更,比如“收到支付成功事件”后,状态从“待支付”变为“已支付”

2.2. 📦 示例:订单状态流转

假设你有一个电商订单系统,订单的状态如下:

初始状态: 待支付
→ [支付成功事件] → 已支付
→ [发货事件] → 已发货
→ [签收事件] → 已完成

如果是事件驱动:你不是直接改状态,而是:

orderStateMachine.sendEvent(OrderEvent.PAY); // 自动将状态从“待支付”→“已支付”

2.3. 🚀 事件驱动 vs 手动流转(对比)

方式

手动流转

事件驱动流转

状态变更方式

代码直接修改字段:order.setStatus(...)

触发事件:stateMachine.sendEvent(OrderEvent.X)

可读性

状态逻辑分散在代码中

状态变更清晰、集中在状态机配置中

复杂流程管理

不容易维护

易于添加新状态、路径,支持条件、动作等复杂流转

异步支持

不方便

可结合消息队列实现异步事件触发

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. ✅ 优势总结

特点

优势

动态配置

状态、事件、流转逻辑存数据库或配置中心

易于扩展

支持多个流程(订单、审批、签约等)

可视化支持

可做可视化流程设计器

动作/条件可插拔

支持动态行为绑定

解耦

状态逻辑不写死在代码里

博文参考

http://www.xdnf.cn/news/738343.html

相关文章:

  • Linux ClearOS yum无法使用解决备忘
  • Qt Dial(旋钮)
  • 智慧赋能充电桩管理:我国新能源充电桩建设现状与突破路径
  • 【Doris基础】Apache Doris业务场景全解析:从实时数仓到OLAP分析的完美选择
  • Linux操作系统 使用共享内存实现进程通信和同步
  • 近期手上的一个基于Function Grap(类AWS的Lambda)小项目的改造引发的思考
  • URAT接收实验日志,传输无效
  • 第29次CCF计算机软件能力认证-2-垦田计划
  • espefuse.py烧录MAC地址
  • leetcode1201. 丑数 III -medium
  • (23)JNI 内存泄漏诊断
  • day16 数组的常见操作和形状
  • ES6解构赋值与传统数据提取方式的对比分析
  • LangChain-Tool和Agent结合智谱AI大模型应用实例2
  • 数据库笔记
  • 近屿智能第六代 AI 得贤招聘官首秀 —— 解锁「拟人化智能交互」AI面试新体验
  • 《计算机操作系统-慕课版》期末复习题库与内容梳理
  • 5G 核心网 NGAP UE-TNL 偶联和绑定
  • azure web app创建分步指南系列之一
  • Bootstrap:精通级教程(VIP10万字版)
  • Splunk Attack Analyzer 深度解析:技术、技巧与最佳实践
  • 目标人群精准洞察,打造超差异化内容
  • 投稿 IEEE Transactions on Knowledge and Data Engineering 注意事项
  • RAG中的chunk以及评测方法
  • 详解Seata的四种事务模式:AT、TCC、SAGA、XA
  • 深入浅出网络分析与故障检测工具
  • Chrome插件学习笔记(二)
  • C++核心编程_赋值运算符重载
  • 2025最新Nginx安装配置保姆级教程(Windows)
  • 《JavaScript高级程序设计》读书笔记 34 - 代理基础