This commit is contained in:
2026-02-14 00:01:04 +08:00
commit cbd4a33eaf
23 changed files with 1265 additions and 0 deletions

323
README.md Normal file
View File

@@ -0,0 +1,323 @@
# 电商订单状态机 (Order FSM Demo)
## 什么是状态机?
**状态机**Finite State Machine, FSM是一种数学计算模型。它在任意时刻只能处于**一个状态**,通过接收**事件**触发**状态转换**,从一个状态跳到另一个状态。
用人话说就是:**状态机 = 当前状态 + 事件 → 新状态**
### 核心概念
| 概念 | 英文 | 说明 | 本项目中的例子 |
|------|------|------|----------------|
| **状态 (State)** | State | 事物当前所处的情况 | 待支付、已支付、已发货... |
| **事件 (Event)** | Event | 触发状态变化的信号 | 支付、发货、确认收货... |
| **转换 (Transition)** | Transition | 从一个状态到另一个状态的跳转规则 | 待支付 --[支付]--> 已支付 |
| **守卫 (Guard)** | Guard | 转换前的条件检查,不满足则拒绝转换 | 余额是否充足? |
| **动作 (Action)** | Action | 转换发生时执行的业务逻辑 | 扣减余额、发送通知... |
| **上下文 (Context)** | Context | 承载业务数据的对象 | 订单信息(金额、商品等) |
### 一图胜千言
```
┌──────────┐
┌───→│ 已取消 │
取消/超时 │ └──────────┘
┌──────────┐ 支付 ┌──────────┐ 发货 ┌──────────┐ 确认收货 ┌──────────┐ 完成 ┌──────────┐
│ 待支付 │──────→│ 已支付 │──────→│ 已发货 │────────→│ 已收货 │──────→│ 已完成 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
│ │
申请退款│ 申请退款│
▼ ▼
┌──────────┐
│ 退款中 │
└──────────┘
│ │
退款通过│ 退款拒绝│
▼ ▼
┌──────────┐ 恢复为已支付
│ 已退款 │
└──────────┘
```
---
## 项目结构
```
fsm_demo/
├── src/
│ └── com/example/fsm/
│ ├── core/ # 状态机核心引擎(通用,可复用)
│ │ ├── Action.java # 动作接口
│ │ ├── Guard.java # 守卫条件接口
│ │ ├── Transition.java # 转换规则定义
│ │ └── StateMachine.java # 状态机引擎 + Builder
│ │
│ ├── order/ # 订单业务实现
│ │ ├── OrderState.java # 订单状态枚举8种状态
│ │ ├── OrderEvent.java # 订单事件枚举9种事件
│ │ ├── OrderContext.java # 订单上下文(业务数据)
│ │ └── OrderStateMachineFactory.java # 订单状态机工厂(组装规则)
│ │
│ └── OrderFSMDemo.java # 演示程序入口5个场景
├── run.bat # Windows 一键运行脚本
└── README.md # 本文档
```
### 核心引擎 vs 业务实现
本项目刻意将代码分成两层:
```
┌─────────────────────────────┐
│ order/ (业务层) │ ← 定义"订单有哪些状态/事件/规则"
│ OrderState, OrderEvent, │
│ OrderContext, │
│ OrderStateMachineFactory │
├─────────────────────────────┤
│ core/ (引擎层) │ ← 提供"状态机怎么运转"
│ StateMachine, Transition, │
│ Action, Guard │
└─────────────────────────────┘
```
**引擎层**完全不知道"订单"的存在,它只认识泛型 `<S, E, C>`。这意味着你可以用同一个引擎去做物流状态机、审批流、游戏AI等任何状态驱动的场景。
---
## 快速运行
### 方式一:使用脚本(推荐)
```bash
# Windows
run.bat
```
### 方式二:手动编译运行
```bash
# 编译
javac -encoding UTF-8 -d out src/com/example/fsm/core/*.java src/com/example/fsm/order/*.java src/com/example/fsm/OrderFSMDemo.java
# 运行
java -Dfile.encoding=UTF-8 -cp out com.example.fsm.OrderFSMDemo
```
---
## 演示场景
程序会依次运行 5 个场景,覆盖电商订单的主要流程:
### 场景一:正常完整流程 (Happy Path)
```
待支付 → 已支付 → 已发货 → 已收货 → 已完成
```
这是最常见的订单生命周期。买家下单、付款、卖家发货、买家收货、完成评价。
### 场景二:支付超时自动取消
```
待支付 → 已取消
```
系统检测到订单超过 30 分钟未支付,自动触发 `TIMEOUT` 事件取消订单。
### 场景三:支付后申请退款
```
待支付 → 已支付 → 退款中 → 已退款
```
买家付款后反悔,申请退款。卖家审核通过后,系统自动退还金额。
### 场景四:余额不足 (Guard 守卫演示)
```
待支付 → (支付失败,状态不变) → 待支付
```
用户余额 50 元,商品 9999 元。Guard 守卫条件检测到余额不足,**拒绝状态转换**。状态保持不变。
### 场景五:非法操作 (状态防护演示)
```
待支付 → (尝试发货,被拒绝) → 待支付
```
在未支付状态下直接尝试发货。状态机找不到匹配的转换规则,**抛出异常**阻止非法操作。
---
## 代码详解
### 1. 状态定义 (`OrderState.java`)
```java
public enum OrderState {
WAIT_PAY("待支付", "订单已创建,等待买家付款"),
PAID("已支付", "买家已完成支付,等待卖家发货"),
SHIPPED("已发货", "卖家已发货,等待买家确认收货"),
// ...
}
```
用枚举定义状态是最佳实践:
- 类型安全,不可能出现拼写错误
- IDE 自动补全
- 可以附加元数据(中文名、描述等)
### 2. 事件定义 (`OrderEvent.java`)
```java
public enum OrderEvent {
PAY("支付", "买家完成付款"),
SHIP("发货", "卖家确认发货"),
CANCEL("取消订单", "买家主动取消未支付的订单"),
// ...
}
```
### 3. 转换规则 (`Transition.java`)
```java
// 一条转换 = 源状态 + 事件 + 目标状态 + (可选)守卫 + (可选)动作
public class Transition<S, E, C> {
private final S source; // 从哪个状态
private final E event; // 收到什么事件
private final S target; // 跳到哪个状态
private final Guard<C> guard; // 跳之前检查什么条件
private final Action<C> action; // 跳的时候做什么事
}
```
### 4. 状态机引擎 (`StateMachine.java`)
核心的 `fireEvent` 方法:
```java
public void fireEvent(E event, C context) {
// 1. 根据 "当前状态 + 事件" 查找转换规则
Transition<S, E, C> transition = transitionTable.get(currentState).get(event);
// 2. 检查守卫条件
if (transition.getGuard() != null && !transition.getGuard().evaluate(context)) {
throw new IllegalStateException("守卫条件不满足");
}
// 3. 执行动作
if (transition.getAction() != null) {
transition.getAction().execute(context);
}
// 4. 切换状态
currentState = transition.getTarget();
}
```
### 5. 工厂组装 (`OrderStateMachineFactory.java`)
用 Builder 模式流畅地注册所有转换规则:
```java
StateMachine.<OrderState, OrderEvent, OrderContext>builder()
.initialState(OrderState.WAIT_PAY)
// 待支付 --[支付]--> 已支付(带守卫和动作)
.addTransition(
OrderState.WAIT_PAY, OrderEvent.PAY, OrderState.PAID,
ctx -> ctx.isBalanceSufficient(), // 守卫:余额充足?
ctx -> ctx.deductBalance() // 动作:扣款
)
// 已支付 --[发货]--> 已发货
.addTransition(OrderState.PAID, OrderEvent.SHIP, OrderState.SHIPPED)
// ...更多规则
.build();
```
---
## 转换规则一览表
| 源状态 | 事件 | 目标状态 | 守卫条件 | 动作 |
|--------|------|----------|----------|------|
| 待支付 | 支付 | 已支付 | 余额充足 | 扣减余额 |
| 待支付 | 取消订单 | 已取消 | 无 | 无 |
| 待支付 | 支付超时 | 已取消 | 无 | 无 |
| 已支付 | 发货 | 已发货 | 无 | 记录发货时间 |
| 已支付 | 申请退款 | 退款中 | 无 | 提交退款申请 |
| 已发货 | 确认收货 | 已收货 | 无 | 无 |
| 已发货 | 申请退款 | 退款中 | 无 | 提交退款申请 |
| 已收货 | 完成 | 已完成 | 无 | 无 |
| 退款中 | 退款通过 | 已退款 | 无 | 退还余额 |
| 退款中 | 退款拒绝 | 已支付 | 无 | 无 |
---
## 状态机的优势(为什么不用 if-else
### 传统写法if-else 地狱)
```java
// 不用状态机的写法 —— 随着状态增多,代码将迅速失控
public void handleEvent(String event) {
if (state.equals("WAIT_PAY")) {
if (event.equals("PAY")) {
if (balance >= amount) {
state = "PAID";
deductBalance();
}
} else if (event.equals("CANCEL")) {
state = "CANCELLED";
} else if (event.equals("TIMEOUT")) {
state = "CANCELLED";
}
} else if (state.equals("PAID")) {
if (event.equals("SHIP")) {
state = "SHIPPED";
} else if (event.equals("REQUEST_REFUND")) {
state = "REFUNDING";
}
}
// ... 每增加一个状态,嵌套就多一层
}
```
### 状态机写法
```java
// 声明式,一目了然
.addTransition(WAIT_PAY, PAY, PAID, 余额守卫, 扣款动作)
.addTransition(WAIT_PAY, CANCEL, CANCELLED)
.addTransition(WAIT_PAY, TIMEOUT, CANCELLED)
.addTransition(PAID, SHIP, SHIPPED)
.addTransition(PAID, REQUEST_REFUND, REFUNDING)
```
### 对比总结
| 维度 | if-else | 状态机 |
|------|---------|--------|
| 可读性 | 嵌套深,难以理清 | 声明式,一行一规则 |
| 可维护性 | 新增状态需要改多处 | 新增一行 `addTransition` |
| 安全性 | 容易遗漏边界情况 | 未定义的转换自动拒绝 |
| 可视化 | 难以从代码推导出状态图 | 可直接导出/打印转换表 |
| 可测试性 | 需要覆盖所有分支组合 | 可针对每条转换单独测试 |
---
## 扩展思路
如果想进一步学习,可以尝试:
1. **持久化**:将状态存入数据库,重启后恢复
2. **并发安全**:给 `fireEvent` 加锁或使用 `synchronized`
3. **历史记录**:记录每次状态转换的日志(审计追踪)
4. **子状态机**:比如"退款中"内部还有子流程(审核→打款→到账)
5. **超时调度**:结合 `ScheduledExecutorService` 实现真实的超时自动取消
6. **Spring 集成**:用 Spring Statemachine 框架做企业级实现