肖哥弹架构 跟大家“弹弹” Spring Statemachine设计与实战应用,需要代码关注
欢迎 点赞,关注,转发。
关注公号Solomon肖哥弹架构获取更多精彩内容
历史热点文章
- MyCat应用实战:分布式数据库中间件的实践与优化(篇幅一)
- 图解深度剖析:MyCat 架构设计与组件协同 (篇幅二)
- 一个项目代码讲清楚DO/PO/BO/AO/E/DTO/DAO/ POJO/VO
- 写代码总被Dis:5个项目案例带你掌握SOLID技巧,代码有架构风格
- 里氏替换原则在金融交易系统中的实践,再不懂你咬我
详细解析订单从创建、支付到发货的完整状态流转机制,通过清晰的时序图和状态转换图展示核心业务流程。
- 全流程架构设计:从订单创建、支付到发货的完整状态流转机制,配有多张流程图解
- 深度技术整合:Spring Statemachine与Spring Boot、JPA的无缝集成方案
- 企业级数据库设计:包含5张核心业务表和2张状态机持久化表的详细定义,附SQL脚本
一、 项目启动说明
- 环境要求:
- JDK 17+
- MySQL 8.0+
- Maven 3.8+
- 数据库初始化:
- 执行提供的SQL脚本创建数据库和表
- 配置修改:
- 修改
application.yml
中的数据库连接信息 - 配置其他必要参数
- 修改
二、系统架构图
三、核心流程说明
1. 创建订单流程
客户端 -> OrderController.createOrder()
-> OrderService.createOrder()
-> OrderRepository.save()
-> 返回订单ID
2. 订单支付流程
客户端 -> OrderController.payOrder()
-> OrderService.payOrder()
-> StateMachine.sendEvent(PAY)
-> PaymentGuard检查
-> OrderPayAction执行
-> OrderStateMachineListener记录
-> OrderRepository更新状态
3. 订单发货流程
客户端 -> OrderController.shipOrder()
-> OrderService.shipOrder()
-> StateMachine.sendEvent(SHIP)
-> ShippingGuard检查
-> OrderShipAction执行
-> OrderStateMachineListener记录
-> OrderShippingRepository保存物流信息
-> OrderRepository更新状态
四、项目目录结构图
order-statemachine-system/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── ordersystem/
│ │ │ ├── config/ # 配置类
│ │ │ │ ├── StateMachineConfig.java
│ │ │ │ └── WebConfig.java
│ │ │ ├── controller/ # 控制器层
│ │ │ │ ├── OrderController.java
│ │ │ │ └── ApiExceptionHandler.java
│ │ │ ├── dto/ # 数据传输对象
│ │ │ │ ├── request/
│ │ │ │ │ ├── CreateOrderRequest.java
│ │ │ │ │ ├── PaymentRequest.java
│ │ │ │ │ └── ShippingRequest.java
│ │ │ │ ├── response/
│ │ │ │ │ ├── OrderDetailResponse.java
│ │ │ │ │ └── OrderSimpleResponse.java
│ │ │ │ └── enums/ # 枚举
│ │ │ │ ├── OrderEvent.java
│ │ │ │ └── OrderState.java
│ │ │ ├── exception/ # 异常处理
│ │ │ │ ├── BusinessException.java
│ │ │ │ └── GlobalExceptionHandler.java
│ │ │ ├── model/ # 数据模型
│ │ │ │ ├── entity/
│ │ │ │ │ ├── Order.java
│ │ │ │ │ ├── OrderPayment.java
│ │ │ │ │ └── OrderShipping.java
│ │ │ │ └── vo/
│ │ │ │ └── OrderStateLogVO.java
│ │ │ ├── repository/ # 数据访问层
│ │ │ │ ├── OrderRepository.java
│ │ │ │ └── OrderStateHistoryRepository.java
│ │ │ ├── service/ # 服务层
│ │ │ │ ├── impl/
│ │ │ │ │ └── OrderServiceImpl.java
│ │ │ │ ├── OrderService.java
│ │ │ │ └── statemachine/ # 状态机相关
│ │ │ │ ├── action/
│ │ │ │ │ ├── OrderPayAction.java
│ │ │ │ │ └── OrderShipAction.java
│ │ │ │ ├── guard/
│ │ │ │ │ ├── PaymentGuard.java
│ │ │ │ │ └── ShippingGuard.java
│ │ │ │ ├── listener/
│ │ │ │ │ └── OrderStateMachineListener.java
│ │ │ │ └── OrderStateMachineBuilder.java
│ │ │ └── OrderSystemApplication.java # 启动类
│ │ └── resources/
│ │ ├── application.yml # 应用配置
│ │ ├── static/ # 静态资源
│ │ └── templates/ # 模板文件
│ └── test/ # 测试代码
│ └── java/
│ └── com/
│ └── example/
│ └── ordersystem/
│ ├── OrderServiceTest.java
│ └── StateMachineTest.java
├── pom.xml # Maven配置
└── README.md # 项目说明
1.目录结构说明
1.1. 配置层 (config)
- StateMachineConfig.java: 状态机核心配置,定义状态、事件和转换规则
- WebConfig.java: Web相关配置(拦截器、消息转换器等)
1.2. 控制器层 (controller)
- OrderController.java: 订单相关REST接口
- ApiExceptionHandler.java: API异常统一处理
1.3. DTO层 (dto)
- request/ : 入参DTO
- CreateOrderRequest.java: 创建订单请求
- PaymentRequest.java: 支付请求
- ShippingRequest.java: 发货请求
- response/ : 出参DTO
- OrderDetailResponse.java: 订单详情响应
- OrderSimpleResponse.java: 订单简略信息响应
- enums/ : 枚举定义
- OrderEvent.java: 订单事件枚举
- OrderState.java: 订单状态枚举
1.4. 异常处理 (exception)
- BusinessException.java: 业务异常基类
- GlobalExceptionHandler.java: 全局异常处理器
1.5. 模型层 (model)
- entity/ : 数据库实体
- Order.java: 订单主实体
- OrderPayment.java: 支付信息实体
- OrderShipping.java: 物流信息实体
- vo/ : 视图对象
- OrderStateLogVO.java: 状态变更日志视图对象
1.6. 数据访问层 (repository)
- OrderRepository.java: 订单数据访问接口
- OrderStateHistoryRepository.java: 状态历史数据访问接口
1.7. 服务层 (service)
- impl/OrderServiceImpl.java: 订单服务实现
- OrderService.java: 订单服务接口
- statemachine/ : 状态机相关实现
- action/ : 状态转换动作
- OrderPayAction.java: 支付动作
- OrderShipAction.java: 发货动作
- guard/ : 转换守卫条件
- PaymentGuard.java: 支付守卫条件
- ShippingGuard.java: 发货守卫条件
- listener/ : 状态机监听器
- OrderStateMachineListener.java: 状态变更监听器
- OrderStateMachineBuilder.java: 状态机构建器
- action/ : 状态转换动作
1.8. 资源文件 (resources)
- application.yml: 应用配置文件
- static/ : 静态资源文件
- templates/ : 模板文件
五、项目核心代码
1. 完整的领域模型
Order.java (订单实体)
import lombok.Data;
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
/**
* 订单实体类
* 注解说明:
* @Entity - 标记为JPA实体
* @Table - 指定数据库表名
* @Enumerated - 枚举类型存储为字符串
*/
@Entity
@Table(name = "t_order")
@Data
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String orderNo;
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private OrderState state;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal amount;
@Column(nullable = false)
private Long userId;
@Column(nullable = false, updatable = false)
private LocalDateTime createTime;
@Column
private LocalDateTime updateTime;
@Version
private Integer version; // 乐观锁版本号
/**
* 订单预处理 - 在持久化前执行
*/
@PrePersist
protected void onCreate() {
this.createTime = LocalDateTime.now();
this.state = OrderState.UNPAID; // 确保新建订单状态正确
}
/**
* 更新前处理
*/
@PreUpdate
protected void onUpdate() {
this.updateTime = LocalDateTime.now();
}
}
2. 完整的状态机配置
OrderStateMachineConfig.java
import org.springframework.context.annotation.Configuration;
import org.springframework.statemachine.config.EnableStateMachineFactory;
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
import org.springframework.statemachine.config.builders.*;
/**
* 订单状态机配置
* 配置说明:
* 1. 使用@EnableStateMachineFactory创建状态机工厂
* 2. 定义所有订单状态和初始状态
* 3. 配置状态转换规则
*/
@Configuration
@EnableStateMachineFactory(name = "orderStateMachineFactory")
public class OrderStateMachineConfig
extends StateMachineConfigurerAdapter<OrderState, OrderEvent> {
@Override
public void configure(StateMachineStateConfigurer<OrderState, OrderEvent> states)
throws Exception {
states.withStates()
.initial(OrderState.UNPAID)
.state(OrderState.PAID, context -> {
// 进入PAID状态时的处理
Order order = context.getExtendedState().get("order", Order.class);
System.out.println("订单[" + order.getOrderNo() + "]已支付");
})
.state(OrderState.SHIPPED, context -> {
// 发货后的处理
Order order = context.getExtendedState().get("order", Order.class);
System.out.println("订单[" + order.getOrderNo() + "]已发货");
})
.state(OrderState.COMPLETED, context -> {
// 订单完成处理
Order order = context.getExtendedState().get("order", Order.class);
System.out.println("订单[" + order.getOrderNo() + "]已完成");
})
.state(OrderState.CANCELLED, context -> {
// 订单取消处理
Order order = context.getExtendedState().get("order", Order.class);
System.out.println("订单[" + order.getOrderNo() + "]已取消");
})
.state(OrderState.RETURNING, context -> {
// 退货处理
Order order = context.getExtendedState().get("order", Order.class);
System.out.println("订单[" + order.getOrderNo() + "]申请退货");
});
}
@Override
public void configure(StateMachineTransitionConfigurer<OrderState, OrderEvent> transitions)
throws Exception {
transitions
// 支付转换:UNPAID -> PAID
.withExternal()
.source(OrderState.UNPAID)
.target(OrderState.PAID)
.event(OrderEvent.PAY)
.action(payAction()) // 支付动作
.and()
// 发货转换:PAID -> SHIPPED
.withExternal()
.source(OrderState.PAID)
.target(OrderState.SHIPPED)
.event(OrderEvent.SHIP)
.guard(this::shipGuard) // 发货前检查
.and()
// 确认收货:SHIPPED -> COMPLETED
.withExternal()
.source(OrderState.SHIPPED)
.target(OrderState.COMPLETED)
.event(OrderEvent.RECEIVE)
.and()
// 取消订单:UNPAID -> CANCELLED
.withExternal()
.source(OrderState.UNPAID)
.target(OrderState.CANCELLED)
.event(OrderEvent.CANCEL)
.action(cancelAction()) // 取消动作
.and()
// 申请退货:SHIPPED -> RETURNING
.withExternal()
.source(OrderState.SHIPPED)
.target(OrderState.RETURNING)
.event(OrderEvent.RETURN)
.guard(this::returnGuard); // 退货条件检查
}
/**
* 支付动作处理
*/
private Action<OrderState, OrderEvent> payAction() {
return context -> {
Order order = context.getExtendedState().get("order", Order.class);
// 实际业务中这里会调用支付网关
System.out.println("处理支付逻辑,订单金额:" + order.getAmount());
// 记录支付时间
context.getExtendedState().getVariables().put("payTime", LocalDateTime.now());
};
}
/**
* 取消订单处理
*/
private Action<OrderState, OrderEvent> cancelAction() {
return context -> {
Order order = context.getExtendedState().get("order", Order.class);
System.out.println("订单取消原因:" +
context.getExtendedState().get("cancelReason", String.class));
};
}
/**
* 发货前检查守卫
*/
private boolean shipGuard(StateContext<OrderState, OrderEvent> context) {
Order order = context.getExtendedState().get("order", Order.class);
// 检查是否有库存等业务规则
boolean canShip = checkInventory(order);
if (!canShip) {
System.out.println("库存不足,无法发货");
}
return canShip;
}
/**
* 退货条件检查守卫
*/
private boolean returnGuard(StateContext<OrderState, OrderEvent> context) {
Order order = context.getExtendedState().get("order", Order.class);
LocalDateTime payTime = (LocalDateTime) context.getExtendedState()
.getVariables().get("payTime");
// 检查是否在退货期内(示例:支付后7天内可退货)
if (payTime.plusDays(7).isBefore(LocalDateTime.now())) {
System.out.println("已超过退货期限");
return false;
}
return true;
}
// 模拟库存检查
private boolean checkInventory(Order order) {
return true; // 实际业务中会查询库存系统
}
}
3. 完整的订单服务实现
OrderServiceImpl.java
import org.springframework.statemachine.StateMachine;
import org.springframework.statemachine.config.StateMachineFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 订单服务实现
* 关键点:
* 1. 使用@Transactional确保状态变更和订单保存的原子性
* 2. 处理状态机的启动和停止
* 3. 业务校验前置
*/
@Service
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final OrderRepository orderRepository;
private final StateMachineFactory<OrderState, OrderEvent> stateMachineFactory;
private final OrderStateListener orderStateListener;
@Override
@Transactional
public Order createOrder(CreateOrderCommand command) {
Order order = new Order();
order.setOrderNo(generateOrderNo());
order.setAmount(command.getAmount());
order.setUserId(command.getUserId());
return orderRepository.save(order);
}
@Override
@Transactional
public void payOrder(Long orderId, PaymentInfo paymentInfo) {
Order order = getOrder(orderId);
checkState(order, OrderState.UNPAID, "支付");
StateMachine<OrderState, OrderEvent> stateMachine = buildStateMachine(order);
// 将订单和支付信息放入扩展状态
stateMachine.getExtendedState()
.getVariables()
.put("order", order);
stateMachine.getExtendedState()
.getVariables()
.put("paymentInfo", paymentInfo);
// 发送支付事件
boolean accepted = stateMachine.sendEvent(OrderEvent.PAY);
if (!accepted) {
throw new BusinessException("订单当前状态不能支付");
}
// 保存状态变更
order.setState(stateMachine.getState().getId());
orderRepository.save(order);
}
@Override
@Transactional
public void shipOrder(Long orderId, ShippingInfo shippingInfo) {
Order order = getOrder(orderId);
checkState(order, OrderState.PAID, "发货");
StateMachine<OrderState, OrderEvent> stateMachine = buildStateMachine(order);
stateMachine.getExtendedState()
.getVariables()
.put("order", order);
stateMachine.getExtendedState()
.getVariables()
.put("shippingInfo", shippingInfo);
boolean accepted = stateMachine.sendEvent(OrderEvent.SHIP);
if (!accepted) {
throw new BusinessException("订单当前状态不能发货");
}
order.setState(stateMachine.getState().getId());
orderRepository.save(order);
}
@Override
@Transactional
public void cancelOrder(Long orderId, String cancelReason) {
Order order = getOrder(orderId);
checkState(order, OrderState.UNPAID, "取消");
StateMachine<OrderState, OrderEvent> stateMachine = buildStateMachine(order);
stateMachine.getExtendedState()
.getVariables()
.put("order", order);
stateMachine.getExtendedState()
.getVariables()
.put("cancelReason", cancelReason);
boolean accepted = stateMachine.sendEvent(OrderEvent.CANCEL);
if (!accepted) {
throw new BusinessException("订单当前状态不能取消");
}
order.setState(stateMachine.getState().getId());
orderRepository.save(order);
}
@Override
public OrderState getOrderState(Long orderId) {
return getOrder(orderId).getState();
}
/**
* 构建状态机并启动
*/
private StateMachine<OrderState, OrderEvent> buildStateMachine(Order order) {
StateMachine<OrderState, OrderEvent> stateMachine =
stateMachineFactory.getStateMachine(order.getId().toString());
stateMachine.addStateListener(orderStateListener);
stateMachine.start();
// 恢复状态机到订单当前状态
stateMachine.getStateMachineAccessor()
.doWithAllRegions(access ->
access.resetStateMachine(new DefaultStateMachineContext<>(
order.getState(), null, null, null)));
return stateMachine;
}
/**
* 检查订单状态是否允许操作
*/
private void checkState(Order order, OrderState expectedState, String operation) {
if (order.getState() != expectedState) {
throw new BusinessException(
String.format("订单[%s]当前状态为%s,不能执行%s操作",
order.getOrderNo(), order.getState(), operation));
}
}
/**
* 获取订单并检查是否存在
*/
private Order getOrder(Long orderId) {
return orderRepository.findById(orderId)
.orElseThrow(() -> new NotFoundException("订单不存在"));
}
private String generateOrderNo() {
return "ORD" + System.currentTimeMillis() +
ThreadLocalRandom.current().nextInt(1000, 9999);
}
}
4. 完整的异常处理
BusinessException.java
/**
* 业务异常
* 用于处理业务规则违反情况
*/
public class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
}
}
/**
* 资源未找到异常
*/
public class NotFoundException extends BusinessException {
public NotFoundException(String message) {
super(message);
}
}
5. 完整的DTO定义
OrderDTO.java
/**
* 订单DTO - 用于接口传输
*/
@Data
public class OrderDTO {
private Long id;
private String orderNo;
private String state;
private BigDecimal amount;
private String createTime;
private String updateTime;
public static OrderDTO fromEntity(Order order) {
OrderDTO dto = new OrderDTO();
BeanUtils.copyProperties(order, dto);
dto.setState(order.getState().name());
dto.setCreateTime(DateUtils.format(order.getCreateTime()));
if (order.getUpdateTime() != null) {
dto.setUpdateTime(DateUtils.format(order.getUpdateTime()));
}
return dto;
}
}
/**
* 创建订单命令
*/
@Data
public class CreateOrderCommand {
@NotNull
private Long userId;
@DecimalMin(value = "0.01")
private BigDecimal amount;
// 其他创建订单需要的参数...
}
/**
* 支付信息
*/
@Data
public class PaymentInfo {
@NotBlank
private String paymentMethod; // 支付方式: WECHAT/ALIPAY/UNIONPAY
@NotBlank
private String transactionId; // 支付流水号
private LocalDateTime paymentTime;
}
/**
* 发货信息
*/
@Data
public class ShippingInfo {
@NotBlank
private String shippingCompany; // 物流公司
@NotBlank
private String trackingNumber; // 运单号
private LocalDateTime shippingTime;
}
6. 完整的REST控制器
OrderController.java
/**
* 订单REST接口
* 关键点:
* 1. 使用@Valid进行参数校验
* 2. 统一异常处理
* 3. 清晰的API文档
*/
@RestController
@RequestMapping("/api/orders")
@RequiredArgsConstructor
public class OrderController {
private final OrderService orderService;
@PostMapping
public ResponseEntity<OrderDTO> createOrder(
@Valid @RequestBody CreateOrderCommand command) {
Order order = orderService.createOrder(command);
return ResponseEntity.created(URI.create("/orders/" + order.getId()))
.body(OrderDTO.fromEntity(order));
}
@PostMapping("/{orderId}/pay")
public ResponseEntity<Void> payOrder(
@PathVariable Long orderId,
@Valid @RequestBody PaymentInfo paymentInfo) {
orderService.payOrder(orderId, paymentInfo);
return ResponseEntity.ok().build();
}
@PostMapping("/{orderId}/ship")
public ResponseEntity<Void> shipOrder(
@PathVariable Long orderId,
@Valid @RequestBody ShippingInfo shippingInfo) {
orderService.shipOrder(orderId, shippingInfo);
return ResponseEntity.ok().build();
}
@PostMapping("/{orderId}/cancel")
public ResponseEntity<Void> cancelOrder(
@PathVariable Long orderId,
@RequestParam String cancelReason) {
orderService.cancelOrder(orderId, cancelReason);
return ResponseEntity.ok().build();
}
@GetMapping("/{orderId}")
public ResponseEntity<OrderDTO> getOrder(@PathVariable Long orderId) {
Order order = orderService.getOrder(orderId);
return ResponseEntity.ok(OrderDTO.fromEntity(order));
}
@ExceptionHandler(BusinessException.class)
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
return ResponseEntity.badRequest()
.body(new ErrorResponse(ex.getMessage()));
}
@Data
@AllArgsConstructor
static class ErrorResponse {
private String message;
}
}
7. 完整的测试用例
OrderServiceTest.java
@SpringBootTest
@Transactional
class OrderServiceTest {
@Autowired
private OrderService orderService;
@Autowired
private OrderRepository orderRepository;
@Test
void testCreateOrder() {
CreateOrderCommand command = new CreateOrderCommand();
command.setUserId(1L);
command.setAmount(new BigDecimal("100.00"));
Order order = orderService.createOrder(command);
assertNotNull(order.getId());
assertEquals(OrderState.UNPAID, order.getState());
assertEquals(new BigDecimal("100.00"), order.getAmount());
}
@Test
void testPayOrder() {
Order order = createTestOrder();
PaymentInfo paymentInfo = new PaymentInfo();
paymentInfo.setPaymentMethod("ALIPAY");
paymentInfo.setTransactionId("TX123456");
orderService.payOrder(order.getId(), paymentInfo);
Order updated = orderRepository.findById(order.getId()).get();
assertEquals(OrderState.PAID, updated.getState());
}
@Test
void testPayOrderWrongState() {
Order order = createTestOrder();
order.setState(OrderState.PAID);
orderRepository.save(order);
PaymentInfo paymentInfo = new PaymentInfo();
paymentInfo.setPaymentMethod("ALIPAY");
paymentInfo.setTransactionId("TX123456");
assertThrows(BusinessException.class, () -> {
orderService.payOrder(order.getId(), paymentInfo);
});
}
@Test
void testShipOrder() {
Order order = createTestOrder();
order.setState(OrderState.PAID);
orderRepository.save(order);
ShippingInfo shippingInfo = new ShippingInfo();
shippingInfo.setShippingCompany("SF");
shippingInfo.setTrackingNumber("SF123456");
orderService.shipOrder(order.getId(), shippingInfo);
Order updated = orderRepository.findById(order.getId()).get();
assertEquals(OrderState.SHIPPED, updated.getState());
}
private Order createTestOrder() {
Order order = new Order();
order.setOrderNo("TEST" + System.currentTimeMillis());
order.setState(OrderState.UNPAID);
order.setAmount(new BigDecimal("100.00"));
order.setUserId(1L);
return orderRepository.save(order);
}
}
8. application.yml
配置
# 应用基础配置
spring:
application:
name: order-statemachine-system
datasource:
url: jdbc:mysql://localhost:3306/order_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
username: root
password: root123
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
pool-name: OrderHikariCP
minimum-idle: 5
maximum-pool-size: 20
idle-timeout: 30000
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
jpa:
hibernate:
ddl-auto: update # 生产环境建议改为validate
show-sql: true
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.MySQL8Dialect
jdbc:
lob:
non_contextual_creation: true
# 状态机配置
statemachine:
enums:
states: com.example.ordersystem.dto.enums.OrderState
events: com.example.ordersystem.dto.enums.OrderEvent
persist:
enabled: true
type: jpa # 使用JPA持久化状态机
factory-bean: orderStateMachineFactory # 指定状态机工厂Bean
# MyBatis配置(如果使用)
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.ordersystem.model.entity
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 应用自定义配置
app:
order:
state-machine:
auto-start: true # 是否自动启动状态机
persist-interval: 5000 # 状态机持久化间隔(ms)
payment:
timeout: 1800 # 支付超时时间(秒)
shipping:
companies: SF,STO,YTO,ZTO # 支持的物流公司
# 日志配置
logging:
level:
root: info
org.springframework.statemachine: debug
com.example.ordersystem: debug
file:
name: logs/order-system.log
max-size: 10MB
max-history: 7
# 服务端口
server:
port: 8080
servlet:
context-path: /api
六、数据库设计
1. 核心业务表设计
1.1 订单主表 (t_order
-- 订单主表:存储订单核心信息
CREATE TABLE `t_order` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '订单ID,自增主键',
`order_no` varchar(32) NOT NULL COMMENT '订单编号,业务唯一标识',
`user_id` bigint(20) NOT NULL COMMENT '下单用户ID',
`amount` decimal(10,2) NOT NULL COMMENT '订单总金额(元)',
`state` varchar(20) NOT NULL COMMENT '订单状态:UNPAID/PAID/SHIPPED/COMPLETED/CANCELLED/RETURNING',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '订单创建时间',
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '最后更新时间',
`version` int(11) NOT NULL DEFAULT '0' COMMENT '乐观锁版本号,用于并发控制',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_order_no` (`order_no`) COMMENT '订单编号唯一索引',
KEY `idx_user_id` (`user_id`) COMMENT '用户ID普通索引',
KEY `idx_state` (`state`) COMMENT '状态查询索引',
KEY `idx_create_time` (`create_time`) COMMENT '创建时间索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单主表';
1.2 订单状态变更历史表 (t_order_state_history)
-- 订单状态变更历史表:记录所有状态变更轨迹
CREATE TABLE `t_order_state_history` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_id` bigint(20) NOT NULL COMMENT '关联的订单ID',
`from_state` varchar(20) DEFAULT NULL COMMENT '变更前状态',
`to_state` varchar(20) NOT NULL COMMENT '变更后状态',
`event` varchar(20) NOT NULL COMMENT '触发事件:PAY/SHIP/RECEIVE/CANCEL/RETURN',
`operator` varchar(64) DEFAULT NULL COMMENT '操作人:系统自动/SYSTEM,用户ID,管理员ID',
`operation_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '操作时间',
`remark` varchar(255) DEFAULT NULL COMMENT '变更备注说明',
PRIMARY KEY (`id`),
KEY `idx_order_id` (`order_id`) COMMENT '订单ID索引',
KEY `idx_operation_time` (`operation_time`) COMMENT '操作时间索引',
CONSTRAINT `fk_history_order` FOREIGN KEY (`order_id`)
REFERENCES `t_order` (`id`) ON DELETE CASCADE COMMENT '外键关联订单主表'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单状态变更历史表';
1.3 支付信息表 (t_order_payment)
-- 订单支付信息表:记录支付相关数据
CREATE TABLE `t_order_payment` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '支付记录ID',
`order_id` bigint(20) NOT NULL COMMENT '关联的订单ID',
`payment_method` varchar(20) NOT NULL COMMENT '支付方式:WECHAT/ALIPAY/UNIONPAY/OTHER',
`transaction_id` varchar(64) NOT NULL COMMENT '第三方支付流水号',
`payment_amount` decimal(10,2) NOT NULL COMMENT '实际支付金额(可能含优惠)',
`payment_time` datetime NOT NULL COMMENT '支付成功时间',
`payment_status` varchar(20) NOT NULL COMMENT '支付状态:SUCCESS/FAIL/REFUND/PART_REFUND',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_transaction_id` (`transaction_id`) COMMENT '支付流水号唯一索引',
KEY `idx_order_id` (`order_id`) COMMENT '订单ID索引',
CONSTRAINT `fk_payment_order` FOREIGN KEY (`order_id`)
REFERENCES `t_order` (`id`) ON DELETE CASCADE COMMENT '外键关联订单主表'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单支付信息表';
1.4 物流信息表 (t_order_shipping)
-- 订单物流信息表:记录发货和物流信息
CREATE TABLE `t_order_shipping` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '物流记录ID',
`order_id` bigint(20) NOT NULL COMMENT '关联的订单ID',
`shipping_company` varchar(50) NOT NULL COMMENT '物流公司编码:SF,STO,YTO等',
`tracking_number` varchar(50) NOT NULL COMMENT '物流运单号',
`shipping_time` datetime NOT NULL COMMENT '实际发货时间',
`receiver_name` varchar(50) NOT NULL COMMENT '收货人姓名',
`receiver_phone` varchar(20) NOT NULL COMMENT '收货人手机号',
`receiver_address` varchar(255) NOT NULL COMMENT '详细收货地址',
PRIMARY KEY (`id`),
KEY `idx_order_id` (`order_id`) COMMENT '订单ID索引',
KEY `idx_tracking_number` (`tracking_number`) COMMENT '运单号索引',
CONSTRAINT `fk_shipping_order` FOREIGN KEY (`order_id`)
REFERENCES `t_order` (`id`) ON DELETE CASCADE COMMENT '外键关联订单主表'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单物流信息表';
1.5 退货信息表 (t_order_return)
-- 订单退货信息表:记录退货/退款信息
CREATE TABLE `t_order_return` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '退货记录ID',
`order_id` bigint(20) NOT NULL COMMENT '关联的订单ID',
`return_reason` varchar(255) NOT NULL COMMENT '退货原因描述',
`return_amount` decimal(10,2) DEFAULT NULL COMMENT '实际退款金额',
`return_status` varchar(20) NOT NULL COMMENT '退货状态:PROCESSING/COMPLETED/REJECTED',
`apply_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '退货申请时间',
`complete_time` datetime DEFAULT NULL COMMENT '退货完成时间',
`operator` varchar(64) DEFAULT NULL COMMENT '处理人:系统/客服ID',
PRIMARY KEY (`id`),
KEY `idx_order_id` (`order_id`) COMMENT '订单ID索引',
CONSTRAINT `fk_return_order` FOREIGN KEY (`order_id`)
REFERENCES `t_order` (`id`) ON DELETE CASCADE COMMENT '外键关联订单主表'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单退货信息表';
2. 状态机持久化表设计
2.1 状态机实例表 (sm_machine_instance)
-- 状态机实例表:维护状态机运行时实例
CREATE TABLE `sm_machine_instance` (
`machine_id` varchar(36) NOT NULL COMMENT '状态机实例UUID',
`machine_type` varchar(50) NOT NULL COMMENT '状态机类型:ORDER/PAYMENT等',
`state` varchar(50) NOT NULL COMMENT '当前状态值',
`business_key` varchar(36) NOT NULL COMMENT '关联业务ID(如订单ID)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '实例创建时间',
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '最后状态更新时间',
PRIMARY KEY (`machine_id`),
UNIQUE KEY `uk_business` (`machine_type`, `business_key`) COMMENT '业务唯一性约束',
KEY `idx_state` (`state`) COMMENT '状态查询索引'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='状态机实例表';
2.2 状态机上下文表 (sm_machine_context)
-- 状态机上下文表:存储状态机上下文数据
CREATE TABLE `sm_machine_context` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`machine_id` varchar(36) NOT NULL COMMENT '关联的状态机实例ID',
`context_data` blob COMMENT '序列化的上下文数据(JSON/Protobuf格式)',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_machine_id` (`machine_id`) COMMENT '状态机实例ID索引',
CONSTRAINT `fk_context_machine` FOREIGN KEY (`machine_id`)
REFERENCES `sm_machine_instance` (`machine_id`) ON DELETE CASCADE COMMENT '外键关联实例表'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='状态机上下文表';
3. 数据库初始化脚本
-- 初始化状态机类型配置
INSERT INTO `sm_machine_type` (`type`, `description`) VALUES
('ORDER', '订单状态机'),
('PAYMENT', '支付状态机');
-- 初始化订单状态枚举
INSERT INTO `sys_enum` (`enum_type`, `enum_key`, `enum_value`, `sort_order`) VALUES
('ORDER_STATE', 'UNPAID', '待支付', 1),
('ORDER_STATE', 'PAID', '已支付', 2),
('ORDER_STATE', 'SHIPPED', '已发货', 3),
('ORDER_STATE', 'COMPLETED', '已完成', 4),
('ORDER_STATE', 'CANCELLED', '已取消', 5),
('ORDER_STATE', 'RETURNING', '退货中', 6);
-- 初始化订单事件枚举
INSERT INTO `sys_enum` (`enum_type`, `enum_key`, `enum_value`, `sort_order`) VALUES
('ORDER_EVENT', 'PAY', '支付', 1),
('ORDER_EVENT', 'SHIP', '发货', 2),
('ORDER_EVENT', 'RECEIVE', '确认收货', 3),
('ORDER_EVENT', 'CANCEL', '取消订单', 4),
('ORDER_EVENT', 'RETURN', '申请退货', 5);
4. 性能优化脚本
4.1 索引优化
-- 为订单表添加组合索引
ALTER TABLE `t_order`
ADD INDEX `idx_user_state` (`user_id`, `state`) COMMENT '用户+状态联合查询优化',
ADD INDEX `idx_user_create_time` (`user_id`, `create_time`) COMMENT '用户+时间范围查询优化';
-- 为状态历史表添加查询优化索引
ALTER TABLE `t_order_state_history`
ADD INDEX `idx_order_event` (`order_id`, `event`) COMMENT '订单+事件查询优化',
ADD INDEX `idx_order_time` (`order_id`, `operation_time`) COMMENT '订单+时间范围查询优化';
4.2 分区方案(海量数据)
-- 订单表按月分区(RANGE分区)
ALTER TABLE `t_order` PARTITION BY RANGE (TO_DAYS(create_time)) (
PARTITION p202301 VALUES LESS THAN (TO_DAYS('2023-02-01')),
PARTITION p202302 VALUES LESS THAN (TO_DAYS('2023-03-01')),
PARTITION p202303 VALUES LESS THAN (TO_DAYS('2023-04-01')),
PARTITION pmax VALUES LESS THAN MAXVALUE
);
-- 状态历史表按订单ID哈希分区
ALTER TABLE `t_order_state_history` PARTITION BY HASH(order_id) PARTITIONS 8;
5. 数据库维护脚本
5.1 状态一致性检查
-- 检查订单表与状态机实例表的状态一致性
SELECT
o.id AS order_id,
o.order_no,
o.state AS order_state,
m.state AS machine_state,
CASE WHEN o.state = m.state THEN '一致' ELSE '不一致' END AS status
FROM t_order o
LEFT JOIN sm_machine_instance m ON o.id = m.business_key AND m.machine_type = 'ORDER'
WHERE o.state != m.state OR m.machine_id IS NULL;
5.2 状态历史修复
-- 修复缺失的初始状态记录
INSERT INTO t_order_state_history (
order_id, from_state, to_state, event, operator, operation_time
)
SELECT
o.id,
NULL,
o.state,
'CREATE',
'SYSTEM',
o.create_time
FROM t_order o
WHERE NOT EXISTS (
SELECT 1 FROM t_order_state_history h
WHERE h.order_id = o.id AND h.event = 'CREATE'
);
6. 数据库关系图说明
+----------------+ +-------------------+ +------------------+
| t_order | | t_order_payment | | t_order_shipping |
+----------------+ +-------------------+ +------------------+
| PK id |<---+ | PK id | +-->| PK id |
| order_no | | | FK order_id | | | FK order_id |
| user_id | | | payment_method | | | shipping_company
| amount | | | transaction_id | | | tracking_number
| state | | | payment_amount | | | shipping_time |
| create_time | | | payment_time | | +------------------+
| update_time | | | payment_status | |
| version | | +-------------------+ |
+----------------+ | |
| |
+---------------------+ +-------------------+
| t_order_state_history | | t_order_return |
+---------------------+ +-------------------+
| PK id | | PK id |
| FK order_id | | FK order_id |
| from_state | | return_reason |
| to_state | | return_amount |
| event | | return_status |
| operator | | apply_time |
| operation_time | | complete_time |
| remark | | operator |
+---------------------+ +-------------------+