在复杂业务系统开发中,状态机是一种非常实用的设计模式,特别适用于需要管理状态流转的场景,如订单系统、工作流引擎等。本文将深入解析状态机的设计思想和实现细节,帮助大家更好地理解和应用状态机模式。
一、状态机基础概念
1.1 什么是状态机?
状态机(State Machine),全称为有限状态机(Finite State Machine, FSM),是一种数学模型,描述了系统在不同状态之间的转移规则。它由以下几个部分组成:
- 状态(State):系统可能处于的某个具体情况。
- 事件(Event)或输入(Input):触发状态变化的外部或内部因素。
- 转换(Transition):状态如何根据事件变化。
- 初始状态(Initial State):系统的起始状态。
- 终止状态(Final State,可选):系统可能的结束状态。
1.2 状态机应用场景
- 订单状态管理:未支付→已支付→已发货→已完成
- 审批流程:待审批→审批中→已通过/已拒绝
- 任务调度:等待→执行中→成功/失败
- 设备状态:离线→在线→工作中→故障
二、实战
2.1 核心接口定义
- 状态定义接口
/**
* 状态抽象接口
*/
public interface StatusDefine {
/**
* 返回状态编号
*/
Integer getStatus();
/**
* 返回状态描述
*/
String getDesc();
/**
* 返回状态代码
*/
String getCode();
}
- 状态变更事件接口
/**
* 状态变量事件抽象接口
*/
public interface StatusChangeEvent {
/**
* 原始状态
*/
StatusDefine getSourceStatus();
/**
* 变更后的状态
*/
StatusDefine getTargetStatus();
/**
* @return 返回事件描述
*/
String getDesc();
/**
* @return 返回事件代码
*/
String getCode();
}
- 状态核心处理器接口
/**
* 状态变化处理器
* 说明:
* 1、功能说明:状态变更时执行的业务逻辑
* 2、接口实现类的bean名称规则为:状态机名称_状态变更事件名称
* 例如:在实现类上添加 @Component("order_close_dispatching_order") order为状态机名称,close_dispatching_order为取消正常派单订单的事件名称
*
*/
public interface StatusChangeHandler<T extends StateMachineSnapshot> {
/**
* 状态变化处理逻辑
*
* @param bizId 业务id
* @param statusChangeEventEnum 状态变更事件
* @param bizSnapshot 快照
*/
void handler(String bizId,StatusChangeEvent statusChangeEventEnum,T bizSnapshot);
}
- 状态快照基础类
/**
* 状态快照基础类
*/
public abstract class StateMachineSnapshot {
/**
* 返回快照id
*/
public abstract String getSnapshotId();
/**
* 返回快照状态
*/
public abstract Integer getSnapshotStatus();
/**
* 设置快照id
*/
public abstract void setSnapshotId(String snapshotId);
/**
* 设置快照状态
*/
public abstract void setSnapshotStatus(Integer snapshotStatus);
}
2.2 核心实现
@Slf4j
public abstract class AbstractStateMachine<T extends StateMachineSnapshot> {
/**
* 状态机持久化程序
*/
private final StateMachinePersister stateMachinePersister;
/**
* 业务快照服务层程序
*/
private final BizSnapshotService bizSnapshotService;
/**
* redis处理程序
*/
private RedisTemplate redisTemplate;
/**
* 初始化状态
*/
private final StatusDefine initState;
/**
* 状态机名称
*/
private final String name;
/**
* 构造方法
*
* @param stateMachinePersister 状态机持久化程序
* @param bizSnapshotService 业务快照服务层程序
* @param redisTemplate redis处理程序
*/
protected AbstractStateMachine(StateMachinePersister stateMachinePersister, BizSnapshotService bizSnapshotService, RedisTemplate redisTemplate) {
this.stateMachinePersister = stateMachinePersister;
this.bizSnapshotService = bizSnapshotService;
this.redisTemplate = redisTemplate;
this.initState = getInitState();
this.name = getName();
}
/**
* @return 返回状态机名称
*/
protected abstract String getName();
/**
* 后处理方法,在更改状态机执行逻辑
*/
protected abstract void postProcessor(T bizSnapshot);
/**
* @return 初始状态
*/
protected abstract StatusDefine getInitState();
/**
* 状态机初始化,不保存快照
*
* @param bizId 业务id
* @return 初始化状态代码
*/
public String start(String bizId) {
return start(null, bizId, initState, null);
}
/**
* 启动状态机,并设置当前状态,不保存快照
*
* @param bizId 业务id
* @param statusDefine 当前状态
* @return 当前状态代码
*/
public String start(String bizId, StatusDefine statusDefine) {
return start(null, bizId, statusDefine, null);
}
/**
* 启动状态机,并设置当前状态和保存业务快照,快照不分库
*
* @param bizId 业务id
* @param statusDefine 当前状态
* @param bizSnapshot 快照
* @return 当前状态代码
*/
public String start(String bizId, StatusDefine statusDefine, T bizSnapshot) {
return start(null, bizId, statusDefine, bizSnapshot);
}
/**
* 状态机初始化,并保存业务快照,快照分库分表
*
* @param dbShardId 分库键
* @param bizId 业务id
* @param bizSnapshot 业务快照
* @return 初始化状态代码
*/
public String start(Long dbShardId, String bizId, T bizSnapshot) {
return start(dbShardId, bizId, initState, bizSnapshot);
}
/**
* 启动状态机,并设置当前状态和保存业务快照,快照分库分表
*
* @param dbShardId 分库键
* @param bizId 业务id
* @param statusDefine 当前状态
* @param bizSnapshot 快照
* @return 当前状态代码
*/
public String start(Long dbShardId, String bizId, StatusDefine statusDefine, T bizSnapshot) {
//1.初始化状态机状态
String currentState = stateMachinePersister.getCurrentState(name, bizId);
if (ObjectUtil.isEmpty(currentState)) {
stateMachinePersister.init(name, bizId, statusDefine);
} else {
throw new IllegalStateException("已存在状态,不可初始化");
}
//2.保存业务快照
if (bizSnapshot == null) {
bizSnapshot = ReflectUtil.newInstance(getSnapshotClass());
}
//设置快照id
bizSnapshot.setSnapshotId(bizId);
//设置快照状态
bizSnapshot.setSnapshotStatus(statusDefine.getStatus());
//快照转json
String bizSnapshotString = JSONUtil.toJsonStr(bizSnapshot);
if (ObjectUtil.isNotEmpty(bizSnapshot)) {
bizSnapshotService.save(dbShardId, name, bizId, statusDefine, bizSnapshotString);
}
//执行后处理方法
postProcessor(bizSnapshot);
return statusDefine.getCode();
}
/**
* 持久化删除
*
* @param bizId 业务id
*/
public void clear(String bizId) {
stateMachinePersister.clear(name, bizId);
}
/**
* 获取当前状态
*
* @param bizId 业务id
* @return 当前状态代码
*/
public String getCurrentState(String bizId) {
return stateMachinePersister.getCurrentState(name, bizId);
}
/**
* 获取当前快照
*
* @param bizId 业务id
* @return 快照信息
*/
public String getCurrentSnapshot(String bizId) {
//当前状态code
String currentState = getCurrentState(bizId);
return bizSnapshotService.findLastSnapshotByBizIdAndState(name, bizId, currentState);
}
/**
* 根据状态查询业务快照
*
* @param bizId 业务id
* @param statusDefine 状态
* @return 业务快照
*/
public String getSnapshotByStatus(String bizId, StatusDefine statusDefine) {
String statusCode = statusDefine == null ? null : statusDefine.getCode();
return bizSnapshotService.findLastSnapshotByBizIdAndState(name, bizId, statusCode);
}
/**
* 获取当前状态的快照缓存
*
* @param bizId 业务id
* @return 快照信息
*/
public String getCurrentSnapshotCache(String bizId) {
//先查询缓存,如果缓存没有就查询数据库然后存缓存
String key = "STATE_MACHINE:" + name + ":" + bizId;
Object object = redisTemplate.opsForValue().get(key);
if (ObjectUtil.isNotEmpty(object)) {
return object.toString();
}
String bizSnapshot = getCurrentSnapshot(bizId);
redisTemplate.opsForValue().set(key, bizSnapshot, 30, TimeUnit.MINUTES);
return bizSnapshot;
}
/**
* 新增快照
*
* @param dbShardId 分库键
* @param bizId 业务id
* @param statusDefine 状态
* @param bizSnapshot 业务快照
*/
public void saveSnapshot(Long dbShardId, String bizId, StatusDefine statusDefine, T bizSnapshot) {
//快照转json
String jsonString = JSONUtil.toJsonStr(bizSnapshot);
//新增快照
bizSnapshotService.save(dbShardId, name, bizId, statusDefine, jsonString);
//清理缓存
String key = "STATE_MACHINE:" + name + ":" + bizId;
redisTemplate.delete(key);
}
/**
* 变更状态并保存快照,快照不进行分库
*
* @param bizId 业务id
* @param statusChangeEventEnum 状态变换事件
*/
public void changeStatus(String bizId, StatusChangeEvent statusChangeEventEnum) {
changeStatus(null, bizId, statusChangeEventEnum, null);
}
/**
* 变更状态并保存快照,快照不进行分库
*
* @param bizId 业务id
* @param statusChangeEventEnum 状态变换事件
* @param bizSnapshot 业务数据快照(json格式)
*/
public void changeStatus(String bizId, StatusChangeEvent statusChangeEventEnum, T bizSnapshot) {
changeStatus(null, bizId, statusChangeEventEnum, bizSnapshot);
}
/**
* 变更状态并保存快照,快照不进行分库
*
* @param dbShardId 分库键
* @param bizId 业务id
* @param statusChangeEventEnum 状态变换事件
*/
public void changeStatus(Long dbShardId, String bizId, StatusChangeEvent statusChangeEventEnum) {
changeStatus(dbShardId, bizId, statusChangeEventEnum, null);
}
/**
* 变更状态并保存快照,快照分库分表
*
* @param dbShardId 分库键
* @param bizId 业务id
* @param statusChangeEventEnum 状态变换事件
* @param bizSnapshot 业务数据快照(json格式)
*/
public void changeStatus(Long dbShardId, String bizId, StatusChangeEvent statusChangeEventEnum, T bizSnapshot) {
//1.查询当前状态
String statusCode = getCurrentState(bizId);
//2.校验起止状态是否与事件匹配
if (ObjectUtil.isNotEmpty(statusChangeEventEnum.getSourceStatus()) && ObjectUtil.notEqual(statusChangeEventEnum.getSourceStatus().getCode(), statusCode)) {
throw new CommonException(HTTP_INTERNAL_ERROR, "状态机起止状态与事件不匹配");
}
//3.获取状态处理程序bean
//事件代码
String eventCode = statusChangeEventEnum.getCode();
StatusChangeHandler bean = null;
try {
bean = SpringUtil.getBean(name + "_" + eventCode, StatusChangeHandler.class);
} catch (Exception e) {
log.info("不存在‘{}’StatusChangeHandler", name + "_" + eventCode);
}
if (bizSnapshot == null) {
bizSnapshot = ReflectUtil.newInstance(getSnapshotClass());
}
//设置快照id
bizSnapshot.setSnapshotId(bizId);
//设置目标状态
bizSnapshot.setSnapshotStatus(statusChangeEventEnum.getTargetStatus().getStatus());
if (ObjectUtil.isNotNull(bean)) {
//4.执行状态变更
bean.handler(bizId, statusChangeEventEnum, bizSnapshot);
}
//5.状态持久化
stateMachinePersister.persist(name, bizId, statusChangeEventEnum.getTargetStatus());
//6、存储快照
if (ObjectUtil.isNotEmpty(bizSnapshot)) {
//构建新的快照信息
bizSnapshot = buildNewSnapshot(bizId, bizSnapshot, statusChangeEventEnum.getSourceStatus());
String newBizSnapShotString = JSONUtil.toJsonStr(bizSnapshot);
bizSnapshotService.save(dbShardId, name, bizId, statusChangeEventEnum.getTargetStatus(), newBizSnapShotString);
}
//7.清理快照缓存
String key = "STATE_MACHINE:" + name + ":" + bizId;
redisTemplate.delete(key);
//执行后处理方法
postProcessor(bizSnapshot);
}
/**
* 构建新的快照数据
*
* @param bizId 业务id
* @param bizSnapshot 业务快照
* @param statusDefine 状态
* @return 业务快照(json格式)
*/
public T buildNewSnapshot(String bizId, T bizSnapshot, StatusDefine statusDefine) {
//1.获取上一个状态订单快照
String currentSnapshot = getSnapshotByStatus(bizId, statusDefine);
if (ObjectUtil.isEmpty(currentSnapshot)) {
return bizSnapshot;
}
//2.将当前状态订单快照转为bean
T oldOrderSnapshotDTO = JSONUtil.toBean(currentSnapshot, getSnapshotClass());
//3.将新的订单快照数据覆盖旧订单快照数据,忽略null
T orderSnapshotDTO = BeanUtils.copyIgnoreNull(bizSnapshot, oldOrderSnapshotDTO, getSnapshotClass());
return orderSnapshotDTO;
}
private Class<T> getSnapshotClass() {
ParameterizedType parameterizedType = (ParameterizedType) this.getClass().getGenericSuperclass();
return (Class<T>) parameterizedType.getActualTypeArguments()[0];
}
}
2.3 状态持久化接口
/**
* 业务数据快照
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("biz_snapshot")
public class BizSnapshot {
/**
* 主键
*/
private Long id;
/**
* 状态机名称
*/
private String stateMachineName;
/**
* 业务id
*/
private String bizId;
/**
* 分库键
*/
private Long dbShardId;
/**
* 状态,取StatusDefine中的 code
*/
private String state;
/**
* 业务数据(json格式化)
*/
private String bizData;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 创建时间
*/
private LocalDateTime updateTime;
}
/**
* 状态持久化
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("state_persister")
public class StatePersister {
/**
* 主键
*/
private Long id;
/**
* 状态机名称
*/
private String stateMachineName;
/**
* 业务id
*/
private String bizId;
/**
* 状态,取StatusDefine中的 code
*/
private String state;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
}
- 实现该接口将状态信息持久化到数据库
public interface StateMachinePersister {
/**
* 业务数据状态初始化
*
* @param stateMachineName 状态机名称
* @param bizId 业务id
* @param statusDefine 当前状态
*/
void init(String stateMachineName, String bizId, StatusDefine statusDefine);
/**
* 业务数据状态持久化
*
* @param stateMachineName 状态机名称
* @param bizId 业务id
* @param statusDefine 当前状态
*/
void persist(String stateMachineName, String bizId, StatusDefine statusDefine);
/**
* 查询业务数据当前持久化状态
*
* @param stateMachineName 状态机名称
* @param bizId 业务id
* @return 当前持久化状态代码
*/
String getCurrentState(String stateMachineName, String bizId);
/**
* 根据状态机名称和业务id清理持久化状态
*
* @param stateMachineName 状态机名称
* @param bizId 业务id
*/
void clear(String stateMachineName, String bizId);
}
2.4 业务数据实现类
public interface BizSnapshotService {
/**
* 新增业务快照
*
* @param dbShardId 分库键
* @param stateMachineName 状态机名称
* @param bizId 业务id
* @param statusDefine 状态
* @param bizSnapshot 业务快照
*/
void save(Long dbShardId, String stateMachineName, String bizId, StatusDefine statusDefine, String bizSnapshot);
/**
* 根据业务id和状态查询最新业务快照
*
* @param stateMachineName 状态机名称
* @param bizId 业务id
* @param state 状态代码
* @return 业务快照
*/
String findLastSnapshotByBizIdAndState(String stateMachineName, String bizId, String state);
}
@Component
public class BizSnapshotServiceImpl implements BizSnapshotService {
/**
* 业务快照数据层处理程序
*/
private final BizSnapshotMapper bizSnapshotMapper;
/**
* 默认分库键值,不进行分库时使用
*/
private static final Long DEFAULT_DB_SHARD_ID = 1L;
/**
* 构造器
*
* @param bizSnapshotMapper 业务快照数据层处理程序
*/
public BizSnapshotServiceImpl(BizSnapshotMapper bizSnapshotMapper) {
this.bizSnapshotMapper = bizSnapshotMapper;
}
/**
* 新增业务快照
*
* @param dbShardId 分库键
* @param stateMachineName 状态机名称
* @param bizId 业务id
* @param statusDefine 状态
* @param bizSnapshot 业务快照
*/
@Override
public void save(Long dbShardId, String stateMachineName, String bizId, StatusDefine statusDefine, String bizSnapshot) {
BizSnapshot model = BizSnapshot.builder()
.id(IdUtil.getSnowflakeNextId())
.stateMachineName(stateMachineName)
.bizId(bizId)
.dbShardId(dbShardId)
.state(statusDefine.getCode())
.bizData(bizSnapshot).build();
//如果分库键为空,即数据库不进行分库,使其默认值为1
if (null == dbShardId) {
model.setDbShardId(DEFAULT_DB_SHARD_ID);
}
bizSnapshotMapper.insert(model);
}
/**
* 根据业务id和状态查询最新业务快照
*
* @param stateMachineName 状态机名称
* @param bizId 业务id
* @param state 状态代码
* @return 业务快照
*/
@Override
public String findLastSnapshotByBizIdAndState(String stateMachineName, String bizId, String state) {
LambdaQueryWrapper<BizSnapshot> queryWrapper = Wrappers.<BizSnapshot>lambdaQuery()
.eq(ObjectUtil.isNotEmpty(stateMachineName), BizSnapshot::getStateMachineName, stateMachineName)
.eq(ObjectUtil.isNotEmpty(bizId), BizSnapshot::getBizId, bizId)
.eq(ObjectUtil.isNotEmpty(state), BizSnapshot::getState, state)
.gt(BizSnapshot::getDbShardId, 0)
.orderByDesc(BizSnapshot::getCreateTime)
.last("limit 1");
BizSnapshot bizSnapshot = bizSnapshotMapper.selectOne(queryWrapper);
return ObjectUtil.isNotNull(bizSnapshot)?bizSnapshot.getBizData():null;
}
}