chore: 【ops】FSM轻量级状态机实现
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.viewsh.module.ops.service.fsm.listener;
|
||||
|
||||
import com.viewsh.module.ops.service.fsm.event.OrderStateChangedEvent;
|
||||
|
||||
/**
|
||||
* 工单状态变更监听器接口
|
||||
* 用于扩展业务逻辑,各业务线可注册自己的监听器
|
||||
*
|
||||
* 使用示例:
|
||||
* <pre>
|
||||
* @Component
|
||||
* public class CleanOrderStateChangeListener implements OrderStateChangeListener {
|
||||
* @Override
|
||||
* public void onStateChanged(OrderStateChangedEvent event) {
|
||||
* // 处理保洁工单状态变更
|
||||
* if ("CLEAN".equals(event.getOrder().getOrderType())) {
|
||||
* // 自定义逻辑
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
public interface OrderStateChangeListener {
|
||||
|
||||
/**
|
||||
* 状态变更时触发
|
||||
*
|
||||
* @param event 状态变更事件
|
||||
*/
|
||||
void onStateChanged(OrderStateChangedEvent event);
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user