all
This commit is contained in:
8
.claude/settings.local.json
Normal file
8
.claude/settings.local.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(javac:*)",
|
||||
"Bash(java:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
323
README.md
Normal file
323
README.md
Normal 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 框架做企业级实现
|
||||
BIN
out/com/example/fsm/OrderFSMDemo.class
Normal file
BIN
out/com/example/fsm/OrderFSMDemo.class
Normal file
Binary file not shown.
BIN
out/com/example/fsm/core/Action.class
Normal file
BIN
out/com/example/fsm/core/Action.class
Normal file
Binary file not shown.
BIN
out/com/example/fsm/core/Guard.class
Normal file
BIN
out/com/example/fsm/core/Guard.class
Normal file
Binary file not shown.
BIN
out/com/example/fsm/core/StateMachine$Builder.class
Normal file
BIN
out/com/example/fsm/core/StateMachine$Builder.class
Normal file
Binary file not shown.
BIN
out/com/example/fsm/core/StateMachine$StateChangeListener.class
Normal file
BIN
out/com/example/fsm/core/StateMachine$StateChangeListener.class
Normal file
Binary file not shown.
BIN
out/com/example/fsm/core/StateMachine.class
Normal file
BIN
out/com/example/fsm/core/StateMachine.class
Normal file
Binary file not shown.
BIN
out/com/example/fsm/core/Transition.class
Normal file
BIN
out/com/example/fsm/core/Transition.class
Normal file
Binary file not shown.
BIN
out/com/example/fsm/order/OrderContext.class
Normal file
BIN
out/com/example/fsm/order/OrderContext.class
Normal file
Binary file not shown.
BIN
out/com/example/fsm/order/OrderEvent.class
Normal file
BIN
out/com/example/fsm/order/OrderEvent.class
Normal file
Binary file not shown.
BIN
out/com/example/fsm/order/OrderState.class
Normal file
BIN
out/com/example/fsm/order/OrderState.class
Normal file
Binary file not shown.
BIN
out/com/example/fsm/order/OrderStateMachineFactory.class
Normal file
BIN
out/com/example/fsm/order/OrderStateMachineFactory.class
Normal file
Binary file not shown.
25
run.bat
Normal file
25
run.bat
Normal 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
|
||||
214
src/com/example/fsm/OrderFSMDemo.java
Normal file
214
src/com/example/fsm/OrderFSMDemo.java
Normal 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;
|
||||
}
|
||||
}
|
||||
20
src/com/example/fsm/core/Action.java
Normal file
20
src/com/example/fsm/core/Action.java
Normal 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);
|
||||
}
|
||||
25
src/com/example/fsm/core/Guard.java
Normal file
25
src/com/example/fsm/core/Guard.java
Normal 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);
|
||||
}
|
||||
223
src/com/example/fsm/core/StateMachine.java
Normal file
223
src/com/example/fsm/core/StateMachine.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
74
src/com/example/fsm/core/Transition.java
Normal file
74
src/com/example/fsm/core/Transition.java
Normal 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;
|
||||
}
|
||||
}
|
||||
103
src/com/example/fsm/order/OrderContext.java
Normal file
103
src/com/example/fsm/order/OrderContext.java
Normal 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) : "无"
|
||||
);
|
||||
}
|
||||
}
|
||||
48
src/com/example/fsm/order/OrderEvent.java
Normal file
48
src/com/example/fsm/order/OrderEvent.java
Normal 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() + ")";
|
||||
}
|
||||
}
|
||||
61
src/com/example/fsm/order/OrderState.java
Normal file
61
src/com/example/fsm/order/OrderState.java
Normal 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() + ")";
|
||||
}
|
||||
}
|
||||
141
src/com/example/fsm/order/OrderStateMachineFactory.java
Normal file
141
src/com/example/fsm/order/OrderStateMachineFactory.java
Normal 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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user