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

View File

@@ -0,0 +1,8 @@
{
"permissions": {
"allow": [
"Bash(javac:*)",
"Bash(java:*)"
]
}
}

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 框架做企业级实现

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

25
run.bat Normal file
View File

@@ -0,0 +1,25 @@
@echo off
chcp 65001 >nul 2>&1
echo.
echo ========================================
echo 编译并运行 订单状态机演示程序
echo ========================================
echo.
if not exist out mkdir out
echo [1/2] 编译中...
javac -encoding UTF-8 -d out src\com\example\fsm\core\*.java src\com\example\fsm\order\*.java src\com\example\fsm\OrderFSMDemo.java
if %errorlevel% neq 0 (
echo 编译失败!请检查 Java 环境。
pause
exit /b 1
)
echo [2/2] 运行中...
echo.
java -Dfile.encoding=UTF-8 -cp out com.example.fsm.OrderFSMDemo
echo.
pause

View File

@@ -0,0 +1,214 @@
package com.example.fsm;
import com.example.fsm.core.StateMachine;
import com.example.fsm.order.OrderContext;
import com.example.fsm.order.OrderEvent;
import com.example.fsm.order.OrderState;
import com.example.fsm.order.OrderStateMachineFactory;
import java.math.BigDecimal;
/**
* 订单状态机演示程序
*
* 本程序演示三个典型场景:
* 场景一:正常下单 → 支付 → 发货 → 收货 → 完成Happy Path
* 场景二:下单 → 支付超时自动取消
* 场景三:下单 → 支付 → 申请退款 → 退款成功
* 场景四:余额不足支付失败(演示守卫条件)
* 场景五:非法操作(演示状态机的防护能力)
*/
public class OrderFSMDemo {
public static void main(String[] args) {
System.out.println("╔══════════════════════════════════════════════════════╗");
System.out.println("║ 电商订单状态机演示程序 ║");
System.out.println("╚══════════════════════════════════════════════════════╝");
System.out.println();
scenario1_happyPath();
scenario2_timeout();
scenario3_refund();
scenario4_insufficientBalance();
scenario5_illegalTransition();
}
/**
* 场景一正常完整流程Happy Path
*
* 待支付 → 已支付 → 已发货 → 已收货 → 已完成
*/
private static void scenario1_happyPath() {
printScenarioHeader("场景一正常完整流程Happy Path");
// 1. 创建状态机
StateMachine<OrderState, OrderEvent, OrderContext> fsm = OrderStateMachineFactory.create();
// 2. 注册监听器 —— 每次状态变化都会被记录
fsm.addListener((from, event, to, ctx) ->
System.out.println(" [状态变更] " + from + " --[" + event + "]--> " + to)
);
// 3. 打印转换表(方便理解)
fsm.printTransitionTable();
// 4. 创建订单上下文
OrderContext order = new OrderContext("ORD-2024-001", "Java编程思想(第4版)", new BigDecimal("99.00"), new BigDecimal("500.00"));
System.out.println("创建订单:" + order);
printState(fsm);
// 5. 依次触发事件,推动状态流转
System.out.println("\n--- 买家点击[立即支付] ---");
fsm.fireEvent(OrderEvent.PAY, order);
printState(fsm);
System.out.println("\n--- 卖家点击[确认发货] ---");
fsm.fireEvent(OrderEvent.SHIP, order);
printState(fsm);
System.out.println("\n--- 买家点击[确认收货] ---");
fsm.fireEvent(OrderEvent.RECEIVE, order);
printState(fsm);
System.out.println("\n--- 买家完成评价 ---");
fsm.fireEvent(OrderEvent.COMPLETE, order);
printState(fsm);
System.out.println();
}
/**
* 场景二:支付超时自动取消
*
* 待支付 → 已取消
*/
private static void scenario2_timeout() {
printScenarioHeader("场景二:支付超时自动取消");
StateMachine<OrderState, OrderEvent, OrderContext> fsm = OrderStateMachineFactory.create();
fsm.addListener((from, event, to, ctx) ->
System.out.println(" [状态变更] " + from + " --[" + event + "]--> " + to)
);
OrderContext order = new OrderContext("ORD-2024-002", "设计模式:可复用面向对象软件的基础", new BigDecimal("68.00"), new BigDecimal("500.00"));
System.out.println("创建订单:" + order);
printState(fsm);
System.out.println("\n--- 30分钟后系统触发超时事件 ---");
fsm.fireEvent(OrderEvent.TIMEOUT, order);
printState(fsm);
System.out.println();
}
/**
* 场景三:申请退款
*
* 待支付 → 已支付 → 退款中 → 已退款
*/
private static void scenario3_refund() {
printScenarioHeader("场景三:支付后申请退款");
StateMachine<OrderState, OrderEvent, OrderContext> fsm = OrderStateMachineFactory.create();
fsm.addListener((from, event, to, ctx) ->
System.out.println(" [状态变更] " + from + " --[" + event + "]--> " + to)
);
OrderContext order = new OrderContext("ORD-2024-003", "深入理解Java虚拟机(第3版)", new BigDecimal("129.00"), new BigDecimal("500.00"));
System.out.println("创建订单:" + order);
printState(fsm);
System.out.println("\n--- 买家支付 ---");
fsm.fireEvent(OrderEvent.PAY, order);
printState(fsm);
System.out.println("\n--- 买家申请退款 ---");
order.setRefundReason("买错了想要第2版");
fsm.fireEvent(OrderEvent.REQUEST_REFUND, order);
printState(fsm);
System.out.println("\n--- 卖家审核通过退款 ---");
fsm.fireEvent(OrderEvent.REFUND_APPROVE, order);
printState(fsm);
System.out.println();
}
/**
* 场景四:余额不足,支付失败
*
* 演示 Guard守卫条件的作用
*/
private static void scenario4_insufficientBalance() {
printScenarioHeader("场景四:余额不足,支付被守卫条件拦截");
StateMachine<OrderState, OrderEvent, OrderContext> fsm = OrderStateMachineFactory.create();
// 注意余额只有 50 元,但商品需要 999 元
OrderContext order = new OrderContext("ORD-2024-004", "MacBook Pro 14寸", new BigDecimal("9999.00"), new BigDecimal("50.00"));
System.out.println("创建订单:" + order);
printState(fsm);
System.out.println("\n--- 买家尝试支付(余额不足)---");
try {
fsm.fireEvent(OrderEvent.PAY, order);
} catch (IllegalStateException e) {
System.out.println(" [拦截] " + e.getMessage());
}
// 状态应该还是"待支付",没有被错误地改变
printState(fsm);
System.out.println();
}
/**
* 场景五:非法状态转换
*
* 演示状态机的"防护能力"——不允许不合法的操作
*/
private static void scenario5_illegalTransition() {
printScenarioHeader("场景五:非法操作(在待支付状态下尝试发货)");
StateMachine<OrderState, OrderEvent, OrderContext> fsm = OrderStateMachineFactory.create();
OrderContext order = new OrderContext("ORD-2024-005", "算法导论(第3版)", new BigDecimal("128.00"), new BigDecimal("500.00"));
System.out.println("创建订单:" + order);
printState(fsm);
System.out.println("\n--- 尝试在未支付时直接发货 ---");
try {
fsm.fireEvent(OrderEvent.SHIP, order); // 这是非法的!
} catch (IllegalStateException e) {
System.out.println(" [拦截] " + e.getMessage());
}
printState(fsm);
System.out.println("\n说明状态机保证了业务流程的正确性不可能跳过支付直接发货。");
System.out.println("\n当前状态下可执行的事件" + fsm.getAvailableEvents());
System.out.println();
}
// ===== 辅助方法 =====
private static void printState(StateMachine<OrderState, OrderEvent, OrderContext> fsm) {
System.out.println(" ★ 当前状态:" + fsm.getCurrentState());
}
private static void printScenarioHeader(String title) {
System.out.println("┌─────────────────────────────────────────────────┐");
System.out.printf("│ %s%s│%n", title, " ".repeat(Math.max(0, 47 - title.length() - getChineseCount(title))));
System.out.println("└─────────────────────────────────────────────────┘");
}
/** 计算字符串中中文字符的数量(中文占两个宽度,需要额外减去) */
private static int getChineseCount(String str) {
int count = 0;
for (char c : str.toCharArray()) {
if (Character.UnicodeScript.of(c) == Character.UnicodeScript.HAN) {
count++;
}
}
return count;
}
}

View File

@@ -0,0 +1,20 @@
package com.example.fsm.core;
/**
* 动作接口 —— 状态转换时要执行的业务逻辑
*
* 例如:支付成功后扣减库存、发货后发送短信通知等。
* 使用泛型 C 代表上下文(Context),这样不同业务可以传入自己的数据对象。
*
* @param <C> 上下文类型,承载业务数据(如订单信息)
*/
@FunctionalInterface
public interface Action<C> {
/**
* 执行动作
*
* @param context 当前业务上下文
*/
void execute(C context);
}

View File

@@ -0,0 +1,25 @@
package com.example.fsm.core;
/**
* 守卫条件接口 —— 判断某个状态转换是否允许发生
*
* 举个例子:
* "待支付 -> 已支付" 这个转换,需要守卫条件检查余额是否充足。
* 如果余额不足,即使触发了"支付"事件,转换也不会发生。
*
* 守卫条件是状态机中非常重要的概念,它让转换不再是"无脑跳转"
* 而是可以根据业务规则动态决定是否放行。
*
* @param <C> 上下文类型
*/
@FunctionalInterface
public interface Guard<C> {
/**
* 判断是否满足转换条件
*
* @param context 当前业务上下文
* @return true=允许转换, false=阻止转换
*/
boolean evaluate(C context);
}

View File

@@ -0,0 +1,223 @@
package com.example.fsm.core;
import java.util.*;
/**
* 状态机核心引擎
*
* 这是整个状态机的"大脑"。它做三件事:
* 1. 维护当前状态
* 2. 存储所有合法的转换规则
* 3. 接收事件,查找匹配的转换,执行状态跳转
*
* 工作流程(当外部触发一个事件时):
* ┌─────────────────────────────────────────────────────┐
* │ 1. 根据 "当前状态 + 事件" 查找转换规则 │
* │ 2. 如果找到了转换规则: │
* │ a. 检查守卫条件Guard不满足则拒绝 │
* │ b. 执行转换动作Action
* │ c. 将当前状态切换为目标状态 │
* │ 3. 如果没找到:抛出异常,说明这个事件在当前状态下 │
* │ 是非法的 │
* └─────────────────────────────────────────────────────┘
*
* 用法:通过 Builder 构建(见下方内部类)
*
* @param <S> 状态类型
* @param <E> 事件类型
* @param <C> 上下文类型
*/
public class StateMachine<S extends Enum<S>, E extends Enum<E>, C> {
/** 当前状态 */
private S currentState;
/**
* 转换规则表
*
* 结构Map< 源状态, Map< 事件, 转换规则 > >
*
* 这是一个两级 Map
* 第一级 key = 源状态 → 得到该状态下所有可能的转换
* 第二级 key = 事件 → 得到具体的转换规则
*
* 例如:
* transitionTable.get(待支付).get(支付事件) → Transition{待支付→已支付}
*/
private final Map<S, Map<E, Transition<S, E, C>>> transitionTable;
/** 状态变更监听器列表(观察者模式) */
private final List<StateChangeListener<S, E, C>> listeners;
/**
* 私有构造 —— 只能通过 Builder 创建
*/
private StateMachine(S initialState, Map<S, Map<E, Transition<S, E, C>>> transitionTable) {
this.currentState = initialState;
this.transitionTable = transitionTable;
this.listeners = new ArrayList<>();
}
/**
* 获取当前状态
*/
public S getCurrentState() {
return currentState;
}
/**
* 触发事件 —— 这是状态机对外的核心方法
*
* @param event 要触发的事件
* @param context 业务上下文
* @throws IllegalStateException 如果当前状态下该事件不合法
*/
public void fireEvent(E event, C context) {
// 第一步:根据当前状态查找该状态下的所有转换
Map<E, Transition<S, E, C>> stateTransitions = transitionTable.get(currentState);
if (stateTransitions == null || !stateTransitions.containsKey(event)) {
throw new IllegalStateException(
String.format("非法的状态转换!当前状态=[%s],触发事件=[%s],没有匹配的转换规则。", currentState, event)
);
}
// 第二步:取出对应的转换规则
Transition<S, E, C> transition = stateTransitions.get(event);
// 第三步:检查守卫条件
if (transition.getGuard() != null && !transition.getGuard().evaluate(context)) {
throw new IllegalStateException(
String.format("转换被守卫条件拒绝!转换=[%s]", transition)
);
}
// 记录旧状态(用于通知监听器)
S oldState = currentState;
// 第四步:执行转换动作(业务逻辑)
if (transition.getAction() != null) {
transition.getAction().execute(context);
}
// 第五步:切换状态
currentState = transition.getTarget();
// 第六步:通知所有监听器
for (StateChangeListener<S, E, C> listener : listeners) {
listener.onStateChanged(oldState, event, currentState, context);
}
}
/**
* 添加状态变更监听器
*/
public void addListener(StateChangeListener<S, E, C> listener) {
listeners.add(listener);
}
/**
* 获取当前状态下可以触发的所有事件(用于 UI 展示"下一步可以做什么"
*/
public Set<E> getAvailableEvents() {
Map<E, Transition<S, E, C>> stateTransitions = transitionTable.get(currentState);
if (stateTransitions == null) {
return Collections.emptySet();
}
return Collections.unmodifiableSet(stateTransitions.keySet());
}
/**
* 打印所有转换规则(调试用)
*/
public void printTransitionTable() {
System.out.println("========== 状态转换表 ==========");
for (Map.Entry<S, Map<E, Transition<S, E, C>>> entry : transitionTable.entrySet()) {
for (Transition<S, E, C> t : entry.getValue().values()) {
System.out.println(" " + t);
}
}
System.out.println("================================");
}
// =========================================================================
// 状态变更监听器接口
// =========================================================================
/**
* 状态变更监听器 —— 当状态发生变化时收到通知
*
* 典型用途:记录日志、发送通知、更新数据库等
*/
@FunctionalInterface
public interface StateChangeListener<S, E, C> {
void onStateChanged(S fromState, E event, S toState, C context);
}
// =========================================================================
// Builder 构建器 —— 用于流畅地创建状态机
// =========================================================================
/**
* 状态机构建器
*
* 使用方式:
* StateMachine<MyState, MyEvent, MyContext> fsm = StateMachine
* .<MyState, MyEvent, MyContext>builder()
* .initialState(MyState.INIT)
* .addTransition(...)
* .addTransition(...)
* .build();
*/
public static <S extends Enum<S>, E extends Enum<E>, C> Builder<S, E, C> builder() {
return new Builder<>();
}
public static class Builder<S extends Enum<S>, E extends Enum<E>, C> {
private S initialState;
private final Map<S, Map<E, Transition<S, E, C>>> transitionTable = new HashMap<>();
/**
* 设置初始状态
*/
public Builder<S, E, C> initialState(S state) {
this.initialState = state;
return this;
}
/**
* 添加一条转换规则(完整版:含守卫和动作)
*/
public Builder<S, E, C> addTransition(S source, E event, S target, Guard<C> guard, Action<C> action) {
transitionTable
.computeIfAbsent(source, k -> new HashMap<>())
.put(event, new Transition<>(source, event, target, guard, action));
return this;
}
/**
* 添加一条转换规则(简化版:只有动作,没有守卫)
*/
public Builder<S, E, C> addTransition(S source, E event, S target, Action<C> action) {
return addTransition(source, event, target, null, action);
}
/**
* 添加一条转换规则(最简版:纯状态跳转)
*/
public Builder<S, E, C> addTransition(S source, E event, S target) {
return addTransition(source, event, target, null, null);
}
/**
* 构建状态机实例
*/
public StateMachine<S, E, C> build() {
if (initialState == null) {
throw new IllegalStateException("必须设置初始状态!请调用 initialState()");
}
return new StateMachine<>(initialState, transitionTable);
}
}
}

View File

@@ -0,0 +1,74 @@
package com.example.fsm.core;
/**
* 状态转换定义 —— 描述"从哪个状态,经过什么事件,到达哪个状态"
*
* 这是状态机最核心的数据结构。每一条 Transition 就是状态图里的一条"箭头"。
*
* 一条完整的转换包含五个要素:
* 1. source —— 源状态(箭头的起点)
* 2. event —— 触发事件(箭头上的标签)
* 3. target —— 目标状态(箭头的终点)
* 4. guard —— 守卫条件(可选,转换前的检查)
* 5. action —— 转换动作(可选,转换时执行的业务逻辑)
*
* 示例:
* source=待支付, event=支付, target=已支付, guard=余额充足?, action=扣款
*
* @param <S> 状态类型(通常是枚举)
* @param <E> 事件类型(通常是枚举)
* @param <C> 上下文类型(业务数据)
*/
public class Transition<S extends Enum<S>, E extends Enum<E>, C> {
private final S source; // 源状态
private final E event; // 触发事件
private final S target; // 目标状态
private final Guard<C> guard; // 守卫条件(可为 null表示无条件放行
private final Action<C> action; // 转换动作(可为 null表示无副作用
/**
* 完整构造:指定所有要素
*/
public Transition(S source, E event, S target, Guard<C> guard, Action<C> action) {
this.source = source;
this.event = event;
this.target = target;
this.guard = guard;
this.action = action;
}
/**
* 简化构造:没有守卫条件和动作(纯粹的状态跳转)
*/
public Transition(S source, E event, S target) {
this(source, event, target, null, null);
}
// ===== Getter 方法 =====
public S getSource() {
return source;
}
public E getEvent() {
return event;
}
public S getTarget() {
return target;
}
public Guard<C> getGuard() {
return guard;
}
public Action<C> getAction() {
return action;
}
@Override
public String toString() {
return source + " --[" + event + "]--> " + target;
}
}

View File

@@ -0,0 +1,103 @@
package com.example.fsm.order;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* 订单上下文 —— 承载订单的业务数据
*
* 在状态机模式中Context上下文用于在状态转换过程中传递业务数据。
* Guard守卫可以读取它来判断条件Action动作可以修改它来更新业务状态。
*
* 这里模拟了一个简化版的电商订单,包含核心字段。
*/
public class OrderContext {
private String orderId; // 订单号
private String productName; // 商品名称
private BigDecimal amount; // 订单金额
private BigDecimal userBalance; // 用户余额(用于演示守卫条件)
private String refundReason; // 退款原因
private LocalDateTime createTime; // 创建时间
private LocalDateTime payTime; // 支付时间
private LocalDateTime shipTime; // 发货时间
/**
* 创建一个模拟订单
*/
public OrderContext(String orderId, String productName, BigDecimal amount, BigDecimal userBalance) {
this.orderId = orderId;
this.productName = productName;
this.amount = amount;
this.userBalance = userBalance;
this.createTime = LocalDateTime.now();
}
// ===== 业务方法 =====
/**
* 扣减用户余额(支付时调用)
*/
public void deductBalance() {
this.userBalance = this.userBalance.subtract(this.amount);
this.payTime = LocalDateTime.now();
}
/**
* 退还用户余额(退款时调用)
*/
public void refundBalance() {
this.userBalance = this.userBalance.add(this.amount);
}
/**
* 记录发货时间
*/
public void markShipped() {
this.shipTime = LocalDateTime.now();
}
/**
* 判断余额是否充足
*/
public boolean isBalanceSufficient() {
return userBalance.compareTo(amount) >= 0;
}
// ===== Getter / Setter =====
public String getOrderId() {
return orderId;
}
public String getProductName() {
return productName;
}
public BigDecimal getAmount() {
return amount;
}
public BigDecimal getUserBalance() {
return userBalance;
}
public String getRefundReason() {
return refundReason;
}
public void setRefundReason(String refundReason) {
this.refundReason = refundReason;
}
@Override
public String toString() {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return String.format(
"订单[%s] 商品=%s 金额=%.2f 用户余额=%.2f 创建时间=%s",
orderId, productName, amount, userBalance,
createTime != null ? createTime.format(fmt) : ""
);
}
}

View File

@@ -0,0 +1,48 @@
package com.example.fsm.order;
/**
* 订单事件枚举
*
* 事件 = 触发状态转换的"动作"或"信号"
*
* 在状态机中,状态不会自己变化,必须由事件来驱动。
* 比如:用户点击"付款"按钮 → 系统发出 PAY 事件 → 状态机将订单从"待支付"转为"已支付"
*
* 每个事件都有明确的触发者(买家/卖家/系统)和触发场景。
*/
public enum OrderEvent {
// ===== 正向流程事件 =====
PAY("支付", "买家完成付款"),
SHIP("发货", "卖家确认发货"),
RECEIVE("确认收货", "买家确认收到商品"),
COMPLETE("完成", "买家完成评价,或系统自动完成"),
// ===== 逆向流程事件 =====
CANCEL("取消订单", "买家主动取消未支付的订单"),
TIMEOUT("支付超时", "系统检测到订单超过支付时限,自动取消"),
REQUEST_REFUND("申请退款", "买家发起退款申请"),
REFUND_APPROVE("退款通过", "卖家审核通过退款申请"),
REFUND_REJECT("退款拒绝", "卖家驳回退款申请");
private final String displayName; // 中文显示名
private final String description; // 事件说明
OrderEvent(String displayName, String description) {
this.displayName = displayName;
this.description = description;
}
public String getDisplayName() {
return displayName;
}
public String getDescription() {
return description;
}
@Override
public String toString() {
return displayName + "(" + name() + ")";
}
}

View File

@@ -0,0 +1,61 @@
package com.example.fsm.order;
/**
* 订单状态枚举
*
* 对应电商系统中一个订单从创建到结束的所有可能状态。
*
* 状态流转全景图:
*
* ┌──────────┐
* ┌───→│ 已取消 │
* 取消/超时 │ └──────────┘
* │
* ┌──────────┐ 支付 ┌──────────┐ 发货 ┌──────────┐ 确认收货 ┌──────────┐ 完成 ┌──────────┐
* │ 待支付 │──────→│ 已支付 │──────→│ 已发货 │─────────→│ 已收货 │──────→│ 已完成 │
* └──────────┘ └──────────┘ └──────────┘ └──────────┘ └──────────┘
* │ │
* 申请退款│ 申请退款│
* ▼ ▼
* ┌──────────┐
* │ 退款中 │
* └──────────┘
* │ │
* 退款通过│ 退款拒绝│
* ▼ ▼
* ┌──────────┐ 回到之前的状态
* │ 已退款 │ (已支付/已发货)
* └──────────┘
*/
public enum OrderState {
WAIT_PAY("待支付", "订单已创建,等待买家付款"),
PAID("已支付", "买家已完成支付,等待卖家发货"),
SHIPPED("已发货", "卖家已发货,等待买家确认收货"),
RECEIVED("已收货", "买家已确认收货,等待评价"),
COMPLETED("已完成", "订单已完成(已评价或超时自动完成)"),
CANCELLED("已取消", "订单已取消(买家主动取消或支付超时)"),
REFUNDING("退款中", "买家申请退款,等待卖家审核"),
REFUNDED("已退款", "退款已完成,资金已退回买家账户");
private final String displayName; // 中文显示名
private final String description; // 状态说明
OrderState(String displayName, String description) {
this.displayName = displayName;
this.description = description;
}
public String getDisplayName() {
return displayName;
}
public String getDescription() {
return description;
}
@Override
public String toString() {
return displayName + "(" + name() + ")";
}
}

View File

@@ -0,0 +1,141 @@
package com.example.fsm.order;
import com.example.fsm.core.StateMachine;
/**
* 订单状态机工厂 —— 负责组装订单状态机的所有转换规则
*
* 为什么要单独一个工厂类?
* 状态机的"骨架"StateMachine是通用的不关心具体业务。
* 而"转换规则"是订单业务特有的。把规则的组装抽到工厂类里,
* 实现了 "状态机引擎" 和 "业务规则" 的分离。
*
* 如果将来要做"物流状态机"或"审批状态机",只需要写新的工厂类,
* 核心引擎完全复用。
*/
public class OrderStateMachineFactory {
/**
* 创建并返回一个配置好所有转换规则的订单状态机
*
* 下面每一条 addTransition 都对应状态图上的一条箭头。
* 阅读时建议对照 OrderState 中的状态流转图。
*/
public static StateMachine<OrderState, OrderEvent, OrderContext> create() {
return StateMachine.<OrderState, OrderEvent, OrderContext>builder()
// ==============================
// 初始状态:待支付
// ==============================
.initialState(OrderState.WAIT_PAY)
// ------------------------------
// 正向流程
// ------------------------------
// 待支付 --[支付]--> 已支付
// 守卫条件:用户余额 >= 订单金额
// 动作:扣减余额
.addTransition(
OrderState.WAIT_PAY, // 源状态
OrderEvent.PAY, // 触发事件
OrderState.PAID, // 目标状态
ctx -> ctx.isBalanceSufficient(), // 守卫:余额充足才能支付
ctx -> { // 动作:扣款
ctx.deductBalance();
System.out.println(" [动作] 扣款成功!扣除 " + ctx.getAmount() + " 元,剩余余额 " + ctx.getUserBalance() + "");
}
)
// 已支付 --[发货]--> 已发货
// 动作:记录发货时间
.addTransition(
OrderState.PAID,
OrderEvent.SHIP,
OrderState.SHIPPED,
ctx -> {
ctx.markShipped();
System.out.println(" [动作] 商品已发出!请注意查收。");
}
)
// 已发货 --[确认收货]--> 已收货
.addTransition(
OrderState.SHIPPED,
OrderEvent.RECEIVE,
OrderState.RECEIVED,
ctx -> System.out.println(" [动作] 买家已确认收货。")
)
// 已收货 --[完成]--> 已完成
.addTransition(
OrderState.RECEIVED,
OrderEvent.COMPLETE,
OrderState.COMPLETED,
ctx -> System.out.println(" [动作] 订单完成!感谢您的购买。")
)
// ------------------------------
// 取消/超时流程
// ------------------------------
// 待支付 --[取消]--> 已取消
.addTransition(
OrderState.WAIT_PAY,
OrderEvent.CANCEL,
OrderState.CANCELLED,
ctx -> System.out.println(" [动作] 订单已取消。")
)
// 待支付 --[超时]--> 已取消
.addTransition(
OrderState.WAIT_PAY,
OrderEvent.TIMEOUT,
OrderState.CANCELLED,
ctx -> System.out.println(" [动作] 支付超时,订单自动取消。")
)
// ------------------------------
// 退款流程
// ------------------------------
// 已支付 --[申请退款]--> 退款中
.addTransition(
OrderState.PAID,
OrderEvent.REQUEST_REFUND,
OrderState.REFUNDING,
ctx -> System.out.println(" [动作] 退款申请已提交,等待卖家审核。原因:" + ctx.getRefundReason())
)
// 已发货 --[申请退款]--> 退款中
.addTransition(
OrderState.SHIPPED,
OrderEvent.REQUEST_REFUND,
OrderState.REFUNDING,
ctx -> System.out.println(" [动作] 退款申请已提交(商品在途),等待卖家审核。原因:" + ctx.getRefundReason())
)
// 退款中 --[退款通过]--> 已退款
// 动作:退还用户余额
.addTransition(
OrderState.REFUNDING,
OrderEvent.REFUND_APPROVE,
OrderState.REFUNDED,
ctx -> {
ctx.refundBalance();
System.out.println(" [动作] 退款成功!已退回 " + ctx.getAmount() + " 元,当前余额 " + ctx.getUserBalance() + "");
}
)
// 退款中 --[退款拒绝]--> 已支付(退回到支付后的状态,简化处理)
.addTransition(
OrderState.REFUNDING,
OrderEvent.REFUND_REJECT,
OrderState.PAID,
ctx -> System.out.println(" [动作] 退款申请被驳回,订单恢复为已支付状态。")
)
.build();
}
}