chore: 【ops】FSM轻量级状态机实现

This commit is contained in:
lzh
2026-01-06 10:48:39 +08:00
parent 35564578b6
commit 9ef2730fd0
6 changed files with 536 additions and 0 deletions

View File

@@ -0,0 +1,79 @@
package com.viewsh.module.ops.environment.service.fsm.listener;
import com.viewsh.module.ops.service.fsm.event.OrderStateChangedEvent;
import com.viewsh.module.ops.service.fsm.listener.OrderStateChangeListener;
import com.viewsh.module.ops.environment.service.cleanorder.CleanOrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
/**
* 保洁工单状态变更监听器
* 职责:监听工单状态变更,执行保洁特定业务逻辑
*
* @author lzh
*/
@Slf4j
@Component
public class CleanOrderStateChangeListener implements OrderStateChangeListener {
@Resource
private CleanOrderService cleanOrderService;
@Override
public void onStateChanged(OrderStateChangedEvent event) {
// 只处理保洁类型的工单
if (!"CLEAN".equals(event.getOrder().getOrderType())) {
return;
}
log.info("保洁工单状态变更: orderId={}, {} -> {}, operatorId={}",
event.getOrder().getId(), event.getOldStatus(), event.getNewStatus(), event.getOperatorId());
// 根据新状态执行相应逻辑
switch (event.getNewStatus()) {
case QUEUED:
// 入队:无需特殊处理
break;
case DISPATCHED:
// 已推送:推送到工牌,由业务层处理
break;
case CONFIRMED:
// 确认:保洁员按键确认,由业务层处理保洁员状态
log.info("保洁工单已确认: orderId={}, cleanerId={}",
event.getOrder().getId(), event.getOperatorId());
break;
case ARRIVED:
// 到岗:记录到岗时间到扩展表
cleanOrderService.recordArrivedTime(event.getOrder().getId());
break;
case PAUSED:
// 暂停已在CleanOrderService.pauseCleanOrder()中处理
break;
case COMPLETED:
// 完成:计算作业时长,更新扩展表
cleanOrderService.calculateDuration(event.getOrder().getId());
// 触发自动推送下一个任务
if (event.getOperatorId() != null) {
cleanOrderService.autoDispatchNextOrder(event.getOrder().getId(), event.getOperatorId());
}
break;
case CANCELLED:
// 取消:清理相关状态
break;
default:
// 其他状态无需特殊处理
break;
}
}
}

View File

@@ -0,0 +1,59 @@
package com.viewsh.module.ops.service.event;
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderEventDO;
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderEventMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
/**
* 工单事件服务
* 职责:记录工单状态变更事件流
*
* @author lzh
*/
@Slf4j
@Service
public class OpsOrderEventService {
@Resource
private OpsOrderEventMapper eventMapper;
/**
* 记录事件
*
* @param opsOrderId 工单ID
* @param fromStatus 原状态
* @param toStatus 新状态
* @param eventType 事件类型
* @param operatorType 操作人类型
* @param operatorId 操作人ID
* @param remark 说明
*/
public void recordEvent(Long opsOrderId,
String fromStatus,
String toStatus,
String eventType,
String operatorType,
Long operatorId,
String remark) {
OpsOrderEventDO event = OpsOrderEventDO.builder()
.opsOrderId(opsOrderId)
.fromStatus(fromStatus)
.toStatus(toStatus)
.eventType(eventType)
.operatorType(operatorType)
.operatorId(operatorId)
.eventTime(LocalDateTime.now())
.remark(remark)
.build();
eventMapper.insert(event);
log.debug("记录工单事件: orderId={}, {} -> {}, eventType={}",
opsOrderId, fromStatus, toStatus, eventType);
}
}

View File

@@ -0,0 +1,271 @@
package com.viewsh.module.ops.service.fsm;
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
import com.viewsh.module.ops.enums.OperatorTypeEnum;
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
import com.viewsh.module.ops.service.event.OpsOrderEventService;
import com.viewsh.module.ops.service.fsm.event.OrderStateChangedEvent;
import com.viewsh.module.ops.service.fsm.listener.OrderStateChangeListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import jakarta.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
/**
* 工单状态机核心
* 职责:
* 1. 管理工单状态转换规则
* 2. 执行状态转换并记录事件
* 3. 触发状态变更监听器
*
* @author lzh
*/
@Slf4j
@Component
public class OrderStateMachine {
@Resource
private OpsOrderMapper opsOrderMapper;
@Resource
private OpsOrderEventService eventService;
/**
* 状态转换规则(清晰可见)
* Key: 当前状态
* Value: 允许转换到的目标状态集合
*
* 通用流程PENDING → DISPATCHED → ARRIVED → COMPLETED
* 保洁流程PENDING → QUEUED → DISPATCHED → CONFIRMED → ARRIVED → COMPLETED
*/
private static final Map<WorkOrderStatusEnum, Set<WorkOrderStatusEnum>> TRANSITIONS = Map.of(
// 初始状态
WorkOrderStatusEnum.PENDING, Set.of(
WorkOrderStatusEnum.QUEUED, // 保洁业务:入队
WorkOrderStatusEnum.DISPATCHED, // 通用业务:直接派单
WorkOrderStatusEnum.CANCELLED
),
// 保洁业务特有状态
WorkOrderStatusEnum.QUEUED, Set.of(
WorkOrderStatusEnum.DISPATCHED, // 推送到工牌
WorkOrderStatusEnum.CANCELLED
),
WorkOrderStatusEnum.DISPATCHED, Set.of(
WorkOrderStatusEnum.CONFIRMED, // 保洁员确认
WorkOrderStatusEnum.ARRIVED, // 通用业务:直接到岗
WorkOrderStatusEnum.CANCELLED
),
WorkOrderStatusEnum.CONFIRMED, Set.of(
WorkOrderStatusEnum.ARRIVED, // 感知信标开始作业
WorkOrderStatusEnum.CANCELLED
),
// 作业中
WorkOrderStatusEnum.ARRIVED, Set.of(
WorkOrderStatusEnum.PAUSED, // 暂停作业
WorkOrderStatusEnum.COMPLETED // 完成作业
),
// 暂停状态
WorkOrderStatusEnum.PAUSED, Set.of(
WorkOrderStatusEnum.ARRIVED, // 恢复作业
WorkOrderStatusEnum.CANCELLED
),
// 终态
WorkOrderStatusEnum.COMPLETED, Collections.emptySet(),
WorkOrderStatusEnum.CANCELLED, Collections.emptySet()
);
/**
* 状态变更监听器列表(支持扩展)
*/
private final List<OrderStateChangeListener> listeners = new ArrayList<>();
/**
* 注册监听器
*
* @param listener 监听器实例
*/
public void registerListener(OrderStateChangeListener listener) {
if (listener != null) {
listeners.add(listener);
log.info("注册状态监听器: {}", listener.getClass().getSimpleName());
}
}
/**
* 执行状态转换(核心方法)
*
* @param order 工单对象
* @param newStatus 目标状态
* @param operatorType 操作人类型
* @param operatorId 操作人ID
* @param remark 说明
*/
@Transactional(rollbackFor = Exception.class)
public void transition(OpsOrderDO order,
WorkOrderStatusEnum newStatus,
OperatorTypeEnum operatorType,
Long operatorId,
String remark) {
// 1. 参数校验
if (order == null) {
throw new IllegalArgumentException("工单对象不能为空");
}
if (newStatus == null) {
throw new IllegalArgumentException("目标状态不能为空");
}
// 2. 校验状态转换合法性
WorkOrderStatusEnum currentStatus = WorkOrderStatusEnum.valueOf(order.getStatus());
validateTransition(currentStatus, newStatus);
// 3. 更新工单状态和相关字段
WorkOrderStatusEnum oldStatus = currentStatus;
order.setStatus(newStatus.name());
updateStatusFields(order, newStatus);
opsOrderMapper.updateById(order);
// 4. 记录事件流
eventService.recordEvent(
order.getId(),
oldStatus.name(),
newStatus.name(),
determineEventType(newStatus),
operatorType.getType(),
operatorId,
remark
);
// 5. 触发监听器(扩展点)
notifyListeners(order, oldStatus, newStatus, operatorType, operatorId, remark);
log.info("工单状态转换成功: orderId={}, {} -> {}, operatorType={}, operatorId={}",
order.getId(), oldStatus, newStatus, operatorType, operatorId);
}
/**
* 校验状态转换是否合法
*/
private void validateTransition(WorkOrderStatusEnum currentStatus, WorkOrderStatusEnum newStatus) {
Set<WorkOrderStatusEnum> allowedTargets = TRANSITIONS.get(currentStatus);
if (allowedTargets == null || !allowedTargets.contains(newStatus)) {
throw new IllegalStateException(String.format(
"非法状态转换: %s -> %s该转换不被允许",
currentStatus.name(), newStatus.name()
));
}
}
/**
* 根据状态更新相关字段
*/
private void updateStatusFields(OpsOrderDO order, WorkOrderStatusEnum newStatus) {
switch (newStatus) {
case QUEUED:
// 入队时无需特殊处理
break;
case DISPATCHED:
// 派单时记录派单时间(如有需要)
break;
case CONFIRMED:
// 确认时,保洁员状态由 CleanOrderService 处理
break;
case ARRIVED:
// 到岗时记录开始时间
order.setStartTime(LocalDateTime.now());
break;
case PAUSED:
// 暂停时,记录暂停时间由保洁业务线处理
break;
case COMPLETED:
// 完成时记录结束时间
order.setEndTime(LocalDateTime.now());
break;
case CANCELLED:
// 取消时记录结束时间
order.setEndTime(LocalDateTime.now());
break;
default:
break;
}
}
/**
* 根据目标状态确定事件类型
*/
private String determineEventType(WorkOrderStatusEnum newStatus) {
return switch (newStatus) {
case QUEUED -> "QUEUED";
case DISPATCHED -> "DISPATCHED";
case CONFIRMED -> "CONFIRMED";
case ARRIVED -> "ACCEPT";
case PAUSED -> "PAUSE";
case COMPLETED -> "COMPLETE";
case CANCELLED -> "CANCEL";
default -> "STATUS_CHANGE";
};
}
/**
* 触发所有监听器
*/
private void notifyListeners(OpsOrderDO order,
WorkOrderStatusEnum oldStatus,
WorkOrderStatusEnum newStatus,
OperatorTypeEnum operatorType,
Long operatorId,
String remark) {
if (listeners.isEmpty()) {
return;
}
OrderStateChangedEvent event = OrderStateChangedEvent.builder()
.order(order)
.oldStatus(oldStatus)
.newStatus(newStatus)
.operatorType(operatorType)
.operatorId(operatorId)
.eventTime(LocalDateTime.now())
.remark(remark)
.build();
listeners.forEach(listener -> {
try {
listener.onStateChanged(event);
} catch (Exception e) {
// 监听器异常不影响主流程
log.error("[状态监听器] 执行失败: listener={}, orderId={}, error={}",
listener.getClass().getSimpleName(), order.getId(), e.getMessage(), e);
}
});
}
/**
* 获取当前状态允许转换到的目标状态集合
*
* @param currentStatus 当前状态
* @return 允许的目标状态集合
*/
public Set<WorkOrderStatusEnum> getAllowedTransitions(WorkOrderStatusEnum currentStatus) {
return TRANSITIONS.getOrDefault(currentStatus, Collections.emptySet());
}
}

View File

@@ -0,0 +1,55 @@
package com.viewsh.module.ops.service.fsm.event;
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
import com.viewsh.module.ops.enums.OperatorTypeEnum;
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 工单状态变更事件
*
* @author lzh
*/
@Data
@Builder
public class OrderStateChangedEvent {
/**
* 工单对象
*/
private OpsOrderDO order;
/**
* 原状态
*/
private WorkOrderStatusEnum oldStatus;
/**
* 新状态
*/
private WorkOrderStatusEnum newStatus;
/**
* 操作人类型
*/
private OperatorTypeEnum operatorType;
/**
* 操作人ID
*/
private Long operatorId;
/**
* 事件时间
*/
private LocalDateTime eventTime;
/**
* 说明
*/
private String remark;
}

View File

@@ -0,0 +1,34 @@
package com.viewsh.module.ops.service.fsm.listener;
import com.viewsh.module.ops.service.fsm.event.OrderStateChangedEvent;
/**
* 工单状态变更监听器接口
* 用于扩展业务逻辑,各业务线可注册自己的监听器
*
* 使用示例:
* <pre>
* &#64;Component
* public class CleanOrderStateChangeListener implements OrderStateChangeListener {
* &#64;Override
* public void onStateChanged(OrderStateChangedEvent event) {
* // 处理保洁工单状态变更
* if ("CLEAN".equals(event.getOrder().getOrderType())) {
* // 自定义逻辑
* }
* }
* }
* </pre>
*
* @author lzh
*/
public interface OrderStateChangeListener {
/**
* 状态变更时触发
*
* @param event 状态变更事件
*/
void onStateChanged(OrderStateChangedEvent event);
}

View File

@@ -0,0 +1,38 @@
package com.viewsh.module.ops.config;
import com.viewsh.module.ops.service.fsm.OrderStateMachine;
import com.viewsh.module.ops.service.fsm.listener.OrderStateChangeListener;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Bean;
import java.util.List;
/**
* 工单状态机配置
* 职责:自动注册所有状态监听器
*
* @author lzh
*/
@Slf4j
@Configuration
public class OrderStateMachineConfig {
@Resource
private OrderStateMachine orderStateMachine;
/**
* 注册所有状态监听器
* 使用 Bean 定义的方式Spring 会自动收集所有 OrderStateChangeListener 类型的 Bean
*/
@Bean
public Boolean registerStateChangeListeners(List<OrderStateChangeListener> listeners) {
if (listeners != null && !listeners.isEmpty()) {
listeners.forEach(orderStateMachine::registerListener);
log.info("工单状态机监听器注册完成,共{}个监听器", listeners.size());
}
return true;
}
}