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

状态机的基本使用

状态机

1. 什么是状态机

1.1 场景

在业务代码中对一些业务状态进行硬编码,如果有一天更改了业务逻辑就需要更改代码,不方便进行系统扩展和维护。

if (status == 状态1) {
  // TODO
} else if(status == 状态2) {
  // TODO
} ...

另外对订单状态的管理是散落在很多地方不方便对订单状态进行统一管理和维护。

1.2 使用状态机解决问题

使用状态机对业务状态进行统一管理

理解状态机设计模式需要理解四个要素:

  1. 现态:是指当前所处的状态;
  2. 事件:触发状态变更的事件;
  3. 动作:当事件被触发时,执行的操作;
  4. 次态:条件满足后要迁往的新状态。

例如:拿待支付状态到派单中状态举例:

  1. 现态:订单当前处于待支付状态那么现态为待支付。
  2. 事件:用户支付成功为事件,支付成功是条件,当条件满足进行状态迁移。
  3. 动作:将订单状态由待支付更改为派单中。
  4. 次态:派单中。

2. 实现状态机

基于设计模式开发状态机组件,参考代码:statemachine.zip

在需要使用的模块中添加状态机依赖

<dependency>
  <groupId>com.jzo2o</groupId>
  <artifactId>jzo2o-statemachine</artifactId>
  <version>1.0-SNAPSHOT</version>
</dependency>

下面以订单业务进行举例

2.1 状态枚举类

阅读订单状态枚举类,实现了 StatusDefine 状态接口,不论是现态还是次态都需要实现状态接口。

@Getter
@AllArgsConstructor
public enum OrderStatusEnum implements StatusDefine {
  NO_PAY(0, "待支付", "NO_PAY"),
  DISPATCHING(100, "派单中", "DISPATCHING"),
  NO_SERVE(200, "待服务", "NO_SERVE"),
  SERVING(300, "服务中", "SERVING"),
  FINISHED(500, "已完成", "FINISHED"),
  CANCELED(600, "已取消", "CANCELED"),
  CLOSED(700, "已关闭", "CLOSED");

  private final Integer status;
  private final String desc;
  private final String code;

  // 根据状态值获得对应枚举
  public static OrderStatusEnum codeOf(Integer status) {
    for (OrderStatusEnum orderStatusEnum : values()) {
      if (orderStatusEnum.status.equals(status)) { return orderStatusEnum; }
    }
    return null;
  }
}

2.2 状态变更事件枚举类

所有状态之间存在的变更都需要定义状态变更事件,它实现了 StatusChangeEvent 状态变更事件接口,事件对应状态机四要素的事件

@Getter
@AllArgsConstructor
public enum OrderStatusChangeEventEnum implements StatusChangeEvent {
  PAYED(OrderStatusEnum.NO_PAY, OrderStatusEnum.DISPATCHING, "支付成功", "payed"),
  DISPATCH(OrderStatusEnum.DISPATCHING, OrderStatusEnum.NO_SERVE, "接单/抢单成功", "dispatch"),
  START_SERVE(OrderStatusEnum.NO_SERVE, OrderStatusEnum.SERVING, "开始服务", "start_serve"),
  COMPLETE_SERVE(OrderStatusEnum.SERVING, OrderStatusEnum.FINISHED, "完成服务", "complete_serve"),
  EVALUATE(OrderStatusEnum.NO_EVALUATION, OrderStatusEnum.FINISHED, "评价完成", "evaluate"),
  CANCEL(OrderStatusEnum.NO_PAY, OrderStatusEnum.CANCELED, "取消订单", "cancel"),
  SERVE_PROVIDER_CANCEL(OrderStatusEnum.NO_SERVE, OrderStatusEnum.DISPATCHING, "服务人员/机构取消订单", "serve_provider_cancel"),
  CLOSE_DISPATCHING_ORDER(OrderStatusEnum.DISPATCHING, OrderStatusEnum.CLOSED, "派单中订单关闭", "close_dispatching_order"),
  CLOSE_NO_SERVE_ORDER(OrderStatusEnum.NO_SERVE, OrderStatusEnum.CLOSED, "待服务订单关闭", "close_no_serve_order"),
  CLOSE_SERVING_ORDER(OrderStatusEnum.SERVING, OrderStatusEnum.CLOSED, "服务中订单关闭", "close_serving_order"),
  CLOSE_NO_EVALUATION_ORDER(OrderStatusEnum.NO_EVALUATION, OrderStatusEnum.CLOSED, "待评价订单关闭", "close_no_evaluation_order"),
  CLOSE_FINISHED_ORDER(OrderStatusEnum.FINISHED, OrderStatusEnum.CLOSED, "已完成订单关闭", "close_finished_order");

  // 源状态
  private final OrderStatusEnum sourceStatus;
  // 目标状态
  private final OrderStatusEnum targetStatus;
  // 描述
  private final String desc;
  // 代码
  private final String code;
}

2.3 定义订单快照类

快照是订单变化瞬间的状态及相关信息。

快照基础类型是 StateMachineSnapshot,如果我们要实现订单快照则需要定义一个订单快照类 OrderSnapshotDTO 去继承 StateMachineSnapshot 类型,代码如下:

// 订单快照@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderSnapshotDTO extends StateMachineSnapshot {
  // ...原来的内容保持不变,添加以下代码

  @Override
  public String getSnapshotId() { return String.valueOf(id); }

  @Override
  public Integer getSnapshotStatus() { return ordersStatus; }

  @Override
  public void setSnapshotId(String snapshotId) {
    this.id = Long.parseLong(snapshotId);
  }

  @Override
  public void setSnapshotStatus(Integer snapshotStatus) {
    this.ordersStatus = snapshotStatus;
  }
}

2.4 定义事件变更动作类

当执行状态变更事件会伴随着执行具体的动作,此部分对应状态机四要素中的动作。

// 订单支付成功处理器
@Slf4j
@Component("order_payed")
public class OrderPayedHandler implements StatusChangeHandler<OrderSnapshotDTO> {
  @Resource
  private IOrdersCommonService ordersService;

  /**
   * 订单支付处理逻辑
   *
   * @param bizId   业务id
   * @param bizSnapshot 快照
   */
  @Override
  public void handler(String bizId, StatusChangeEvent statusChangeEventEnum, OrderSnapshotDTO bizSnapshot) {
    log.info("支付成功事件处理逻辑开始,订单号:{}", bizId);
  }
}

2.5 定义订单状态机类

AbstractStateMachine 状态机抽象类是状态机的核心类,是具体的状态机要继承的抽象类,比如我们实现订单状态机就需要继承 AbstractStateMachine 抽象类。

// 订单状态机
@Component
public class OrderStateMachine extends AbstractStateMachine<OrderSnapshotDTO> {

  public OrderStateMachine(StateMachinePersister stateMachinePersister, BizSnapshotService bizSnapshotService, RedisTemplate redisTemplate) {
    super(stateMachinePersister, bizSnapshotService, redisTemplate);
  }

  /**
   * 设置状态机名称
   *
   * @return 状态机名称
   */
  @Override
  protected String getName() { return "order"; }

  @Override
  protected void postProcessor(OrderSnapshotDTO orderSnapshotDTO) { }

  /**
   * 设置状态机初始状态
   *
   * @return 状态机初始状态
   */
  @Override
  protected OrderStatusEnum getInitState() { return OrderStatusEnum.NO_PAY; }
}

2.6 状态机表设计

  1. 状态机持久化表

    每个订单对应状态机表中的一条记录。

    CREATE TABLE `state_persister` (
      `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
      `state_machine_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '状态机名称',
      `biz_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '业务id',
      `state` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '状态',
      `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      PRIMARY KEY (`id`) USING BTREE,
      UNIQUE KEY `唯一索引` (`state_machine_name`,`biz_id`) USING BTREE
    ) ENGINE=InnoDB AUTO_INCREMENT=1908702574605910019 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='状态机持久化表';
    
  2. 状态机快照表

    一个订单在快照表有多条记录,每变一个状态会记录该状态下的快照信息(即订单相关的详细信息)便于查询订单变化的历史记录。

    CREATE TABLE `biz_snapshot` (
      `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键',
      `state_machine_name` varchar(50) DEFAULT NULL COMMENT '状态机名称',
      `biz_id` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '业务id',
      `db_shard_id` bigint DEFAULT NULL COMMENT '分库键',
      `state` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '状态代码',
      `biz_data` varchar(5000) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '业务数据',
      `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
      `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE=InnoDB AUTO_INCREMENT=1908702660589142017 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci ROW_FORMAT=DYNAMIC COMMENT='业务数据快照';
    

3. 使用

在配置文件中导入状态机

@Configuration
@ComponentScan({"com.jzo2o.orders.base.service","com.jzo2o.orders.base.handler"})
@MapperScan("com.jzo2o.orders.base.mapper")
@Import({OrderStateMachine.class}) // 导入状态机
@EnableConfigurationProperties({DispatchProperties.class, ExecutorProperties.class})
public class AutoImportConfiguration { }

启用订单状态机:

// 创建订单快照对象
OrderSnapshotDTO orderSnapshotDTO = BeanUtils.toBean(orders, OrderSnapshotDTO.class);
/*
 * 启动订单状态机
 *
 * Long dbShardId :分库ID
 * String bizId :订单ID
 * StatusDefine statusDefine :订单状态定义(默认 NO_PAY,可省略)
 * T bizSnapshot :订单快照
 */
orderStateMachine.start(ordersId.toString(), OrderStatusEnum.NO_PAY, orderSnapshotDTO);

调用状态机:

String bizId = orders.getId().toString();
// 创建快照对象,可配置需要的数据
OrderSnapshotDTO orderSnapshotDTO = new OrderSnapshotDTO();
// orderSnapshotDTO.setPayTime(DateUtils.now());
// ...
// 调用状态机,更新订单状态
orderStateMachine.changeStatus(bizId, OrderStatusChangeEventEnum.PAYED, orderSnapshotDTO);

说明:这里使用状态变更事件未 PAYED,参考 事件变更枚举类 可以看到运行的代码为 payed,于是就可以找到对于事件处理器 order_payed ,从而处理对应的事件。

相关文章:

  • 做公众号微网站2022年可以打开的网址
  • 做平面设计都关注哪些网站网站优化策略分析
  • 百度做网站推广多少钱如何联系百度推广
  • 给wordpress上锁seo关键词排名优化销售
  • vb .net网站开发短期培训就业学校
  • 轻淘客的轻网站怎么做线上线下推广方案
  • 天文学数据集记录 | 智能体知识库| AI大模型训练
  • 利用持久变量绕过长度限制 + unicode特性绕过waf-- xyctf 出题人已疯12 复现
  • VS Code下开发FPGA——FPGA开发体验提升__下
  • 5. 深度剖析:Spring AI项目架构与分层体系全解读
  • 3D珠宝渲染用什么软件比较好?渲染100邀请码1a12
  • 【学习笔记】MeshCat: 基于three.js的远程可控3D可视化工具
  • Python 字典和集合(子类化UserDict)
  • Java spring mybatis面试题(200道),八股文
  • 深入浅出卡尔曼滤波:从理论推导到C++实战
  • 前端面试题(六):HTTP和HTTPS的区别以及他们如何保障数据安全
  • FFMpeg视频编码实战和音频编码实战
  • 大模型是如何把向量解码成文字输出的
  • 伪代码的定义与应用场景
  • 大模型Agent | 构建智能体 AI-Agent的 5大挑战,及解决方案!
  • 20250408在荣品的PRO-RK3566开发板使用Rockchip原厂的buildroot系统时自动挂载eth0
  • Qt 自带的QSqlDatabase 模块中使用的 SQLite 和 SQLite 官方提供的 C 语言版本(sqlite.org)对比
  • 部门职责、工作内容
  • 【NLP 面经 6】
  • 如何判断一条连接是TCP连接还是UDP连接?
  • 2024年AIS SCI:多策略灰狼算法CBRGWO,深度解析+性能实测