chore: 【ops】调度引擎重新调整
This commit is contained in:
@@ -1,62 +0,0 @@
|
||||
package com.viewsh.module.ops.core.dispatch;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 派单推荐结果
|
||||
* 包含推荐的执行人员及推荐理由
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AssigneeRecommendation {
|
||||
|
||||
/**
|
||||
* 推荐的执行人员ID
|
||||
*/
|
||||
private Long assigneeId;
|
||||
|
||||
/**
|
||||
* 执行人员姓名
|
||||
*/
|
||||
private String assigneeName;
|
||||
|
||||
/**
|
||||
* 匹配分数(0-100)
|
||||
* 分数越高表示越匹配
|
||||
*/
|
||||
private Integer score;
|
||||
|
||||
/**
|
||||
* 推荐理由
|
||||
* 例如:"同区域、电量充足、当前空闲"
|
||||
*/
|
||||
private String reason;
|
||||
|
||||
/**
|
||||
* 创建空推荐结果(表示没有合适的人员)
|
||||
*/
|
||||
public static AssigneeRecommendation none() {
|
||||
return new AssigneeRecommendation(null, null, 0, "无可用人员");
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建推荐结果
|
||||
*/
|
||||
public static AssigneeRecommendation of(Long assigneeId, String assigneeName, Integer score, String reason) {
|
||||
return new AssigneeRecommendation(assigneeId, assigneeName, score, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否有推荐结果
|
||||
*/
|
||||
public boolean hasRecommendation() {
|
||||
return assigneeId != null;
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
package com.viewsh.module.ops.core.dispatch;
|
||||
|
||||
import com.viewsh.module.ops.enums.PriorityEnum;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 派单上下文
|
||||
* 封装派单决策所需的所有信息
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
public class DispatchContext {
|
||||
|
||||
/**
|
||||
* 业务类型(CLEAN、REPAIR、SECURITY等)
|
||||
*/
|
||||
private String businessType;
|
||||
|
||||
/**
|
||||
* 工单ID
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 区域ID
|
||||
*/
|
||||
private Long areaId;
|
||||
|
||||
/**
|
||||
* 优先级
|
||||
*/
|
||||
private PriorityEnum priority;
|
||||
|
||||
/**
|
||||
* 技能要求(可选)
|
||||
* Key: 技能类型, Value: 技能等级
|
||||
*/
|
||||
private Map<String, Integer> skillRequirements;
|
||||
|
||||
/**
|
||||
* 排除的执行人员ID列表(可选)
|
||||
* 例如:已分配过该工单的人员
|
||||
*/
|
||||
private Set<Long> excludedAssigneeIds;
|
||||
|
||||
/**
|
||||
* 首选执行人员ID(可选)
|
||||
* 例如:指定的保洁员
|
||||
*/
|
||||
private Long preferredAssigneeId;
|
||||
|
||||
/**
|
||||
* 扩展参数(可选)
|
||||
* 用于传递业务特定的参数
|
||||
*/
|
||||
private Map<String, Object> extraParams;
|
||||
|
||||
/**
|
||||
* 创建保洁派单上下文
|
||||
*/
|
||||
public static DispatchContext forCleaner(Long orderId, Long areaId, PriorityEnum priority) {
|
||||
return DispatchContext.builder()
|
||||
.businessType("CLEAN")
|
||||
.orderId(orderId)
|
||||
.areaId(areaId)
|
||||
.priority(priority)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建安保派单上下文
|
||||
*/
|
||||
public static DispatchContext forSecurity(Long orderId, Long areaId, PriorityEnum priority) {
|
||||
return DispatchContext.builder()
|
||||
.businessType("SECURITY")
|
||||
.orderId(orderId)
|
||||
.areaId(areaId)
|
||||
.priority(priority)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建维修派单上下文
|
||||
*/
|
||||
public static DispatchContext forRepair(Long orderId, Long areaId, PriorityEnum priority) {
|
||||
return DispatchContext.builder()
|
||||
.businessType("REPAIR")
|
||||
.orderId(orderId)
|
||||
.areaId(areaId)
|
||||
.priority(priority)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扩展参数
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getExtraParam(String key, Class<T> type) {
|
||||
if (extraParams == null) {
|
||||
return null;
|
||||
}
|
||||
Object value = extraParams.get(key);
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (type.isInstance(value)) {
|
||||
return (T) value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加扩展参数
|
||||
*/
|
||||
public void addExtraParam(String key, Object value) {
|
||||
if (this.extraParams == null) {
|
||||
this.extraParams = new java.util.HashMap<>();
|
||||
}
|
||||
this.extraParams.put(key, value);
|
||||
}
|
||||
}
|
||||
@@ -1,80 +1,131 @@
|
||||
package com.viewsh.module.ops.core.dispatch;
|
||||
|
||||
import com.viewsh.module.ops.core.dispatch.model.AssigneeRecommendation;
|
||||
import com.viewsh.module.ops.core.dispatch.model.DispatchDecision;
|
||||
import com.viewsh.module.ops.core.dispatch.model.DispatchPath;
|
||||
import com.viewsh.module.ops.core.dispatch.model.DispatchResult;
|
||||
import com.viewsh.module.ops.core.dispatch.model.OrderDispatchContext;
|
||||
import com.viewsh.module.ops.core.dispatch.strategy.AssignStrategy;
|
||||
import com.viewsh.module.ops.core.dispatch.strategy.InterruptDecision;
|
||||
import com.viewsh.module.ops.core.dispatch.strategy.ScheduleStrategy;
|
||||
import com.viewsh.module.ops.core.lifecycle.model.OrderTransitionResult;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 派单引擎 - 纯决策层
|
||||
* 调度引擎 - 统一派单入口
|
||||
* <p>
|
||||
* 职责:
|
||||
* 1. 根据策略推荐最合适的执行人员
|
||||
* 2. 不涉及状态管理(队列状态、工单状态由各自服务管理)
|
||||
* 3. 不涉及设备通知(由通知服务处理)
|
||||
* 1. 分配决策:谁来接单(通过 AssignStrategy)
|
||||
* 2. 调度决策:怎么派单(通过 ScheduleStrategy)
|
||||
* 3. 执行编排:协调生命周期管理器完成派单
|
||||
* <p>
|
||||
* 设计原则:
|
||||
* - 单一职责:只负责派单决策
|
||||
* - 开闭原则:通过策略模式支持扩展
|
||||
* - 依赖倒置:业务层依赖接口而非实现
|
||||
* - 纯通用逻辑:不包含任何业务特定逻辑(如保洁员状态)
|
||||
* - 事件驱动:业务特定状态通过事件监听实现
|
||||
* - 可扩展:通过策略模式支持不同业务线
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
public interface DispatchEngine {
|
||||
|
||||
/**
|
||||
* 推荐执行人员(核心方法)
|
||||
* <p>
|
||||
* 根据派单上下文(区域、优先级、技能要求等)推荐最合适的执行人员
|
||||
*
|
||||
* @param context 派单上下文
|
||||
* @return 推荐结果,如果没有合适的返回 AssigneeRecommendation.none()
|
||||
*/
|
||||
AssigneeRecommendation recommendAssignee(DispatchContext context);
|
||||
// ==================== 核心调度方法 ====================
|
||||
|
||||
/**
|
||||
* 批量推荐执行人员
|
||||
* 调度工单(核心方法)
|
||||
* <p>
|
||||
* 用于场景:需要从多个候选人中选择最优人员,或需要备用人员
|
||||
* 完整的调度流程:
|
||||
* <ol>
|
||||
* <li>分配决策:通过 AssignStrategy 推荐执行人</li>
|
||||
* <li>调度决策:通过 ScheduleStrategy 决定调度路径</li>
|
||||
* <li>执行编排:协调生命周期管理器完成状态转换</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param context 派单上下文
|
||||
* @param limit 返回结果数量限制
|
||||
* @return 推荐结果列表,按匹配分数降序排序
|
||||
* @param context 调度上下文
|
||||
* @return 调度结果
|
||||
*/
|
||||
List<AssigneeRecommendation> recommendAssignees(DispatchContext context, int limit);
|
||||
DispatchResult dispatch(OrderDispatchContext context);
|
||||
|
||||
/**
|
||||
* 评估是否可以打断当前任务
|
||||
* P0紧急任务插队
|
||||
* <p>
|
||||
* 用于P0紧急任务插队场景:判断是否可以打断当前执行人员正在执行的任务
|
||||
* 当P0紧急任务需要派单时:
|
||||
* <ol>
|
||||
* <li>分配决策:推荐执行人</li>
|
||||
* <li>评估打断:判断是否可以打断当前任务</li>
|
||||
* <li>执行编排:打断当前任务 + 派发紧急任务</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param currentAssigneeId 当前执行任务的执行人员ID
|
||||
* @param urgentContext 紧急任务的派单上下文
|
||||
* @return 打断决策结果
|
||||
* @param urgentOrderId 紧急工单ID
|
||||
* @param assigneeId 执行人ID
|
||||
* @return 调度结果
|
||||
*/
|
||||
InterruptDecision evaluateInterrupt(Long currentAssigneeId, DispatchContext urgentContext);
|
||||
DispatchResult urgentInterrupt(Long urgentOrderId, Long assigneeId);
|
||||
|
||||
/**
|
||||
* 注册派单策略
|
||||
* 任务完成后自动调度下一个
|
||||
* <p>
|
||||
* 各业务线(保洁、安保、维修等)实现自己的派单策略,注册到引擎中
|
||||
* 优先级:
|
||||
* <ol>
|
||||
* <li>检查是否有被中断的任务,优先恢复</li>
|
||||
* <li>如果没有中断任务,推送队列中的下一个</li>
|
||||
* <li>如果没有等待任务,通知业务层更新执行人状态</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param strategy 派单策略实现
|
||||
* @param completedOrderId 已完成的工单ID
|
||||
* @param assigneeId 执行人ID
|
||||
* @return 调度结果
|
||||
*/
|
||||
void registerStrategy(DispatchStrategy strategy);
|
||||
DispatchResult autoDispatchNext(Long completedOrderId, Long assigneeId);
|
||||
|
||||
// ==================== 策略注册 ====================
|
||||
|
||||
/**
|
||||
* 注册业务类型与策略的映射
|
||||
* <p>
|
||||
* 建立业务类型(CLEAN/REPAIR/SECURITY)与策略名称的映射关系
|
||||
* 注册分配策略
|
||||
*
|
||||
* @param businessType 业务类型
|
||||
* @param strategyName 策略名称
|
||||
* @param strategy 分配策略实现
|
||||
*/
|
||||
void registerBusinessTypeStrategy(String businessType, String strategyName);
|
||||
void registerAssignStrategy(String businessType, AssignStrategy strategy);
|
||||
|
||||
/**
|
||||
* 根据业务类型获取策略
|
||||
* 注册调度策略
|
||||
*
|
||||
* @param businessType 业务类型
|
||||
* @return 派单策略,如果没有找到返回null
|
||||
* @param strategy 调度策略实现
|
||||
*/
|
||||
DispatchStrategy getStrategyByBusinessType(String businessType);
|
||||
void registerScheduleStrategy(String businessType, ScheduleStrategy strategy);
|
||||
|
||||
// ==================== 决策方法(供外部调用) ====================
|
||||
|
||||
/**
|
||||
* 推荐执行人
|
||||
* <p>
|
||||
* 纯决策方法,不涉及状态变更
|
||||
*
|
||||
* @param context 调度上下文
|
||||
* @return 推荐结果
|
||||
*/
|
||||
AssigneeRecommendation recommendAssignee(OrderDispatchContext context);
|
||||
|
||||
/**
|
||||
* 决策调度路径
|
||||
* <p>
|
||||
* 纯决策方法,不涉及状态变更
|
||||
*
|
||||
* @param context 调度上下文
|
||||
* @return 调度决策
|
||||
*/
|
||||
DispatchDecision decideSchedulePath(OrderDispatchContext context);
|
||||
|
||||
/**
|
||||
* 评估是否可以打断
|
||||
* <p>
|
||||
* 纯决策方法,不涉及状态变更
|
||||
*
|
||||
* @param currentAssigneeId 当前执行人ID
|
||||
* @param currentOrderId 当前工单ID
|
||||
* @param urgentContext 紧急任务上下文
|
||||
* @return 打断决策
|
||||
*/
|
||||
InterruptDecision evaluateInterrupt(Long currentAssigneeId, Long currentOrderId,
|
||||
OrderDispatchContext urgentContext);
|
||||
}
|
||||
|
||||
@@ -1,28 +1,43 @@
|
||||
package com.viewsh.module.ops.core.dispatch;
|
||||
|
||||
import com.viewsh.module.ops.enums.PriorityEnum;
|
||||
import com.viewsh.module.ops.api.queue.OrderQueueDTO;
|
||||
import com.viewsh.module.ops.api.queue.OrderQueueService;
|
||||
import com.viewsh.module.ops.core.dispatch.model.*;
|
||||
import com.viewsh.module.ops.core.dispatch.strategy.AssignStrategy;
|
||||
import com.viewsh.module.ops.core.dispatch.strategy.InterruptDecision;
|
||||
import com.viewsh.module.ops.core.dispatch.strategy.ScheduleStrategy;
|
||||
import com.viewsh.module.ops.core.lifecycle.OrderLifecycleManager;
|
||||
import com.viewsh.module.ops.core.lifecycle.model.OrderTransitionRequest;
|
||||
import com.viewsh.module.ops.core.lifecycle.model.OrderTransitionResult;
|
||||
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.OrderQueueStatusEnum;
|
||||
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
|
||||
import com.viewsh.module.ops.infrastructure.log.annotation.BusinessLog;
|
||||
import com.viewsh.module.ops.infrastructure.log.enumeration.LogScope;
|
||||
import com.viewsh.module.ops.infrastructure.log.enumeration.LogType;
|
||||
import jakarta.annotation.PostConstruct;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 派单引擎实现
|
||||
* 调度引擎实现
|
||||
* <p>
|
||||
* 职责:
|
||||
* 1. 管理派单策略的注册和查找
|
||||
* 2. 根据业务类型路由到对应的策略
|
||||
* 3. 提供统一的派单决策接口
|
||||
* 1. 管理策略的注册和查找
|
||||
* 2. 执行调度流程:分配决策 → 调度决策 → 执行编排
|
||||
* 3. 协调生命周期管理器完成状态转换
|
||||
* <p>
|
||||
* 注意:
|
||||
* - 这是纯决策层,不涉及任何状态管理
|
||||
* - 队列状态、工单状态由各自服务管理
|
||||
* - 设备通知由通知服务处理
|
||||
* 设计原则:
|
||||
* - 纯通用逻辑:不包含任何业务特定逻辑
|
||||
* - 事件驱动:业务特定状态通过事件监听实现
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@@ -30,191 +45,295 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
@Service
|
||||
public class DispatchEngineImpl implements DispatchEngine {
|
||||
|
||||
/**
|
||||
* 派单策略注册表
|
||||
* Key: 策略名称
|
||||
* Value: 策略实现
|
||||
*/
|
||||
private final Map<String, DispatchStrategy> strategyRegistry = new ConcurrentHashMap<>();
|
||||
@Resource
|
||||
private OrderLifecycleManager orderLifecycleManager;
|
||||
|
||||
@Resource
|
||||
private OrderQueueService orderQueueService;
|
||||
|
||||
@Resource
|
||||
private OpsOrderMapper orderMapper;
|
||||
|
||||
/**
|
||||
* 业务类型与策略的映射
|
||||
* Key: 业务类型(CLEAN、REPAIR、SECURITY)
|
||||
* Value: 策略名称
|
||||
* 分配策略注册表
|
||||
* Key: 业务类型(CLEAN、SECURITY、FACILITIES)
|
||||
* Value: 分配策略实现
|
||||
*/
|
||||
private final Map<String, String> businessTypeStrategyMap = new ConcurrentHashMap<>();
|
||||
private final Map<String, AssignStrategy> assignStrategyRegistry = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* 调度策略注册表
|
||||
* Key: 业务类型(CLEAN、SECURITY、FACILITIES)
|
||||
* Value: 调度策略实现
|
||||
*/
|
||||
private final Map<String, ScheduleStrategy> scheduleStrategyRegistry = new ConcurrentHashMap<>();
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
log.info("派单引擎已初始化,等待策略注册...");
|
||||
log.info("调度引擎已初始化,等待策略注册...");
|
||||
}
|
||||
|
||||
// ========== 策略管理 ==========
|
||||
// ==================== 核心调度方法 ====================
|
||||
|
||||
@Override
|
||||
public void registerStrategy(DispatchStrategy strategy) {
|
||||
if (strategy == null) {
|
||||
log.warn("尝试注册空策略,已忽略");
|
||||
return;
|
||||
@BusinessLog(
|
||||
type = LogType.DISPATCH,
|
||||
scope = LogScope.ORDER,
|
||||
description = "工单调度",
|
||||
includeParams = true,
|
||||
includeResult = true,
|
||||
result = "#result.success",
|
||||
params = {"#context.orderId", "#context.businessType", "#context.priority"}
|
||||
)
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DispatchResult dispatch(OrderDispatchContext context) {
|
||||
log.info("开始调度工单: orderId={}, businessType={}, areaId={}, priority={}",
|
||||
context.getOrderId(), context.getBusinessType(), context.getAreaId(), context.getPriority());
|
||||
|
||||
// ========== 步骤1:分配决策 - 谁来接单 ==========
|
||||
AssignStrategy assignStrategy = assignStrategyRegistry.get(context.getBusinessType());
|
||||
if (assignStrategy == null) {
|
||||
log.warn("未找到分配策略: businessType={}", context.getBusinessType());
|
||||
return DispatchResult.fail("未找到分配策略: " + context.getBusinessType());
|
||||
}
|
||||
|
||||
String strategyName = strategy.getName();
|
||||
String businessType = strategy.getSupportedBusinessType();
|
||||
|
||||
strategyRegistry.put(strategyName, strategy);
|
||||
|
||||
// 自动注册业务类型映射
|
||||
if (businessType != null && !businessType.isEmpty()) {
|
||||
businessTypeStrategyMap.put(businessType, strategyName);
|
||||
AssigneeRecommendation recommendation = assignStrategy.recommend(context);
|
||||
if (recommendation == null || !recommendation.hasRecommendation()) {
|
||||
log.warn("无可用执行人: orderId={}, businessType={}", context.getOrderId(), context.getBusinessType());
|
||||
return DispatchResult.fail("无可用执行人");
|
||||
}
|
||||
|
||||
log.info("派单策略已注册: strategyName={}, businessType={}",
|
||||
strategyName, businessType);
|
||||
Long assigneeId = recommendation.getAssigneeId();
|
||||
context.setRecommendedAssigneeId(assigneeId);
|
||||
|
||||
log.info("分配决策完成: orderId={}, assigneeId={}, assigneeName={}, reason={}",
|
||||
context.getOrderId(), assigneeId, recommendation.getAssigneeName(), recommendation.getReason());
|
||||
|
||||
// ========== 步骤2:调度决策 - 怎么派单 ==========
|
||||
ScheduleStrategy scheduleStrategy = scheduleStrategyRegistry.get(context.getBusinessType());
|
||||
if (scheduleStrategy == null) {
|
||||
log.warn("未找到调度策略: businessType={}", context.getBusinessType());
|
||||
return DispatchResult.fail("未找到调度策略: " + context.getBusinessType());
|
||||
}
|
||||
|
||||
// 查询执行人当前状态(由业务层提供 AssigneeStatus 实现)
|
||||
// 这里通过队列服务获取当前任务信息来判断状态
|
||||
List<OrderQueueDTO> currentTasks = orderQueueService.getTasksByUserId(assigneeId);
|
||||
context.setCurrentTasks(null); // 由调度策略使用
|
||||
|
||||
DispatchDecision decision = scheduleStrategy.decide(context);
|
||||
log.info("调度决策完成: path={}, reason={}", decision.getPath(), decision.getReason());
|
||||
|
||||
// ========== 步骤3:执行编排 ==========
|
||||
return executeDispatch(context, decision);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerBusinessTypeStrategy(String businessType, String strategyName) {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DispatchResult urgentInterrupt(Long urgentOrderId, Long assigneeId) {
|
||||
log.warn("开始P0紧急插队: urgentOrderId={}, assigneeId={}", urgentOrderId, assigneeId);
|
||||
|
||||
// 查询紧急工单
|
||||
OpsOrderDO urgentOrder = orderMapper.selectById(urgentOrderId);
|
||||
if (urgentOrder == null) {
|
||||
return DispatchResult.fail("紧急工单不存在: " + urgentOrderId);
|
||||
}
|
||||
|
||||
// 构建调度上下文
|
||||
OrderDispatchContext context = OrderDispatchContext.builder()
|
||||
.orderId(urgentOrderId)
|
||||
.orderCode(urgentOrder.getOrderCode())
|
||||
.orderTitle(urgentOrder.getTitle())
|
||||
.businessType(urgentOrder.getOrderType())
|
||||
.areaId(urgentOrder.getAreaId())
|
||||
.priority(urgentOrder.getPriorityEnum())
|
||||
.recommendedAssigneeId(assigneeId)
|
||||
.build();
|
||||
|
||||
// 查询执行人当前任务
|
||||
List<OrderQueueDTO> currentTasks = orderQueueService.getTasksByUserId(assigneeId);
|
||||
|
||||
// 如果有正在执行的任务,需要打断
|
||||
OrderQueueDTO currentTask = currentTasks.stream()
|
||||
.filter(t -> OrderQueueStatusEnum.PROCESSING.getStatus().equals(t.getQueueStatus()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (currentTask != null && !currentTask.getOpsOrderId().equals(urgentOrderId)) {
|
||||
// 需要打断当前任务
|
||||
log.info("打断当前任务: currentOrderId={}, urgentOrderId={}",
|
||||
currentTask.getOpsOrderId(), urgentOrderId);
|
||||
|
||||
try {
|
||||
orderLifecycleManager.interruptOrder(
|
||||
currentTask.getOpsOrderId(), urgentOrderId, assigneeId);
|
||||
} catch (Exception e) {
|
||||
log.warn("打断任务失败: currentOrderId={}", currentTask.getOpsOrderId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// 派发紧急任务
|
||||
OrderTransitionRequest request = OrderTransitionRequest.builder()
|
||||
.orderId(urgentOrderId)
|
||||
.targetStatus(WorkOrderStatusEnum.DISPATCHED)
|
||||
.assigneeId(assigneeId)
|
||||
.operatorType(OperatorTypeEnum.SYSTEM)
|
||||
.operatorId(assigneeId)
|
||||
.reason("P0紧急任务派单")
|
||||
.build();
|
||||
|
||||
OrderTransitionResult result = orderLifecycleManager.dispatch(request);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
return DispatchResult.success("P0紧急任务已派单", assigneeId);
|
||||
} else {
|
||||
return DispatchResult.fail("P0紧急任务派单失败: " + result.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public DispatchResult autoDispatchNext(Long completedOrderId, Long assigneeId) {
|
||||
log.info("任务完成后自动调度下一个: completedOrderId={}, assigneeId={}", completedOrderId, assigneeId);
|
||||
|
||||
// 1. 优先检查是否有被中断的任务
|
||||
List<OrderQueueDTO> interruptedTasks = orderQueueService.getInterruptedTasksByUserId(assigneeId);
|
||||
|
||||
if (!interruptedTasks.isEmpty()) {
|
||||
// 恢复第一个中断的任务
|
||||
OrderQueueDTO interruptedTask = interruptedTasks.get(0);
|
||||
log.info("恢复中断任务: orderId={}", interruptedTask.getOpsOrderId());
|
||||
|
||||
OrderTransitionRequest request = OrderTransitionRequest.builder()
|
||||
.orderId(interruptedTask.getOpsOrderId())
|
||||
.targetStatus(WorkOrderStatusEnum.DISPATCHED)
|
||||
.queueId(interruptedTask.getId())
|
||||
.assigneeId(assigneeId)
|
||||
.operatorType(OperatorTypeEnum.SYSTEM)
|
||||
.operatorId(assigneeId)
|
||||
.reason("恢复中断任务")
|
||||
.build();
|
||||
|
||||
OrderTransitionResult result = orderLifecycleManager.transition(request);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
return DispatchResult.success("已恢复中断任务", assigneeId);
|
||||
} else {
|
||||
return DispatchResult.fail("恢复中断任务失败: " + result.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 如果没有中断任务,推送队列中的下一个任务
|
||||
List<OrderQueueDTO> waitingTasks = orderQueueService.getWaitingTasksByUserId(assigneeId);
|
||||
|
||||
if (waitingTasks.isEmpty()) {
|
||||
log.info("无等待任务,执行人变为空闲: assigneeId={}", assigneeId);
|
||||
// 发布事件,由业务层更新执行人状态
|
||||
return DispatchResult.success("无等待任务,任务完成", assigneeId);
|
||||
}
|
||||
|
||||
// <20><>送第一个等待任务
|
||||
OrderQueueDTO nextTask = waitingTasks.get(0);
|
||||
log.info("推送下一个等待任务: taskId={}, orderId={}", nextTask.getId(), nextTask.getOpsOrderId());
|
||||
|
||||
OrderTransitionRequest request = OrderTransitionRequest.builder()
|
||||
.orderId(nextTask.getOpsOrderId())
|
||||
.targetStatus(WorkOrderStatusEnum.DISPATCHED)
|
||||
.queueId(nextTask.getId())
|
||||
.assigneeId(assigneeId)
|
||||
.operatorType(OperatorTypeEnum.SYSTEM)
|
||||
.operatorId(assigneeId)
|
||||
.reason("自动推送下一个任务")
|
||||
.build();
|
||||
|
||||
OrderTransitionResult result = orderLifecycleManager.transition(request);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
return DispatchResult.success("已推送下一个任务", assigneeId);
|
||||
} else {
|
||||
return DispatchResult.fail("推送下一个任务失败: " + result.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 策略注册 ====================
|
||||
|
||||
@Override
|
||||
public void registerAssignStrategy(String businessType, AssignStrategy strategy) {
|
||||
if (businessType == null || businessType.isEmpty()) {
|
||||
log.warn("业务类型为空,忽略注册");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!strategyRegistry.containsKey(strategyName)) {
|
||||
log.warn("策略不存在,无法注册映射: businessType={}, strategyName={}",
|
||||
businessType, strategyName);
|
||||
if (strategy == null) {
|
||||
log.warn("策略为空,忽略注册");
|
||||
return;
|
||||
}
|
||||
|
||||
businessTypeStrategyMap.put(businessType, strategyName);
|
||||
log.info("业务类型策略映射已注册: businessType={}, strategyName={}",
|
||||
businessType, strategyName);
|
||||
assignStrategyRegistry.put(businessType, strategy);
|
||||
log.info("分配策略已注册: businessType={}, strategyName={}", businessType, strategy.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public DispatchStrategy getStrategyByBusinessType(String businessType) {
|
||||
public void registerScheduleStrategy(String businessType, ScheduleStrategy strategy) {
|
||||
if (businessType == null || businessType.isEmpty()) {
|
||||
return null;
|
||||
log.warn("业务类型为空,忽略注册");
|
||||
return;
|
||||
}
|
||||
|
||||
String strategyName = businessTypeStrategyMap.get(businessType);
|
||||
if (strategyName == null) {
|
||||
log.debug("未找到业务类型对应的策略: businessType={}", businessType);
|
||||
return null;
|
||||
}
|
||||
|
||||
DispatchStrategy strategy = strategyRegistry.get(strategyName);
|
||||
if (strategy == null) {
|
||||
log.warn("策略不存在: strategyName={}", strategyName);
|
||||
log.warn("策略为空,忽略注册");
|
||||
return;
|
||||
}
|
||||
|
||||
return strategy;
|
||||
scheduleStrategyRegistry.put(businessType, strategy);
|
||||
log.info("调度策略已注册: businessType={}, strategyName={}", businessType, strategy.getName());
|
||||
}
|
||||
|
||||
// ========== 派单决策方法 ==========
|
||||
// ==================== 决策方法(供外部调用) ====================
|
||||
|
||||
@Override
|
||||
public AssigneeRecommendation recommendAssignee(DispatchContext context) {
|
||||
if (context == null) {
|
||||
log.warn("派单上下文为空,无法推荐人员");
|
||||
return AssigneeRecommendation.none();
|
||||
}
|
||||
|
||||
String businessType = context.getBusinessType();
|
||||
DispatchStrategy strategy = getStrategyByBusinessType(businessType);
|
||||
|
||||
public AssigneeRecommendation recommendAssignee(OrderDispatchContext context) {
|
||||
AssignStrategy strategy = assignStrategyRegistry.get(context.getBusinessType());
|
||||
if (strategy == null) {
|
||||
log.warn("未找到业务类型对应的派单策略: businessType={}, orderId={}",
|
||||
businessType, context.getOrderId());
|
||||
log.warn("未找到分配策略: businessType={}", context.getBusinessType());
|
||||
return AssigneeRecommendation.none();
|
||||
}
|
||||
|
||||
try {
|
||||
AssigneeRecommendation recommendation = strategy.recommendAssignee(context);
|
||||
if (recommendation != null && recommendation.hasRecommendation()) {
|
||||
log.info("派单推荐成功: orderId={}, businessType={}, assigneeId={}, score={}, reason={}",
|
||||
context.getOrderId(), businessType,
|
||||
recommendation.getAssigneeId(),
|
||||
recommendation.getScore(),
|
||||
recommendation.getReason());
|
||||
} else {
|
||||
log.info("派单推荐无合适人员: orderId={}, businessType={}",
|
||||
context.getOrderId(), businessType);
|
||||
}
|
||||
return recommendation;
|
||||
return strategy.recommend(context);
|
||||
} catch (Exception e) {
|
||||
log.error("派单推荐异常: orderId={}, businessType={}",
|
||||
context.getOrderId(), businessType, e);
|
||||
log.error("推荐执行人失败: businessType={}", context.getBusinessType(), e);
|
||||
return AssigneeRecommendation.none();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<AssigneeRecommendation> recommendAssignees(DispatchContext context, int limit) {
|
||||
if (context == null) {
|
||||
log.warn("派单上下文为空,无法推荐人员");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String businessType = context.getBusinessType();
|
||||
DispatchStrategy strategy = getStrategyByBusinessType(businessType);
|
||||
|
||||
public DispatchDecision decideSchedulePath(OrderDispatchContext context) {
|
||||
ScheduleStrategy strategy = scheduleStrategyRegistry.get(context.getBusinessType());
|
||||
if (strategy == null) {
|
||||
log.warn("未找到业务类型对应的派单策略: businessType={}, orderId={}",
|
||||
businessType, context.getOrderId());
|
||||
return Collections.emptyList();
|
||||
log.warn("未找到调度策略: businessType={}", context.getBusinessType());
|
||||
return DispatchDecision.unavailable("未找到调度策略");
|
||||
}
|
||||
|
||||
try {
|
||||
List<AssigneeRecommendation> recommendations = strategy.recommendAssignees(context, limit);
|
||||
log.info("批量派单推荐完成: orderId={}, businessType={}, count={}",
|
||||
context.getOrderId(), businessType,
|
||||
recommendations != null ? recommendations.size() : 0);
|
||||
return recommendations != null ? recommendations : Collections.emptyList();
|
||||
return strategy.decide(context);
|
||||
} catch (Exception e) {
|
||||
log.error("批量派单推荐异常: orderId={}, businessType={}",
|
||||
context.getOrderId(), businessType, e);
|
||||
return Collections.emptyList();
|
||||
log.error("调度决策失败: businessType={}", context.getBusinessType(), e);
|
||||
return DispatchDecision.unavailable("调度决策异常: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterruptDecision evaluateInterrupt(Long currentAssigneeId, DispatchContext urgentContext) {
|
||||
if (currentAssigneeId == null) {
|
||||
log.warn("当前执行人员ID为空,无法评估打断");
|
||||
return InterruptDecision.deny("当前执行人员ID为空", "请检查参数");
|
||||
}
|
||||
|
||||
if (urgentContext == null) {
|
||||
log.warn("紧急任务上下文为空,无法评估打断");
|
||||
return InterruptDecision.deny("紧急任务上下文为空", "请检查参数");
|
||||
}
|
||||
|
||||
String businessType = urgentContext.getBusinessType();
|
||||
DispatchStrategy strategy = getStrategyByBusinessType(businessType);
|
||||
|
||||
public InterruptDecision evaluateInterrupt(Long currentAssigneeId, Long currentOrderId,
|
||||
OrderDispatchContext urgentContext) {
|
||||
ScheduleStrategy strategy = scheduleStrategyRegistry.get(urgentContext.getBusinessType());
|
||||
if (strategy == null) {
|
||||
// 使用默认打断规则
|
||||
InterruptDecision decision = defaultInterruptDecision(urgentContext);
|
||||
log.info("使用默认打断规则: currentAssigneeId={}, urgentOrderId={}, canInterrupt={}",
|
||||
currentAssigneeId, urgentContext.getOrderId(), decision.canInterrupt());
|
||||
return decision;
|
||||
log.warn("未找到调度策略,使用默认打断规则: businessType={}", urgentContext.getBusinessType());
|
||||
return defaultInterruptDecision(urgentContext);
|
||||
}
|
||||
|
||||
try {
|
||||
InterruptDecision decision = strategy.evaluateInterrupt(
|
||||
currentAssigneeId,
|
||||
null, // currentOrderId 可选
|
||||
urgentContext
|
||||
);
|
||||
|
||||
log.info("打断评估完成: currentAssigneeId={}, urgentOrderId={}, canInterrupt={}, reason={}",
|
||||
currentAssigneeId, urgentContext.getOrderId(),
|
||||
decision.canInterrupt(), decision.getReason());
|
||||
|
||||
return decision;
|
||||
return strategy.evaluateInterrupt(currentAssigneeId, currentOrderId, urgentContext);
|
||||
} catch (Exception e) {
|
||||
log.error("打断评估异常: currentAssigneeId={}, urgentOrderId={}",
|
||||
currentAssigneeId, urgentContext.getOrderId(), e);
|
||||
log.error("打断评估失败: businessType={}", urgentContext.getBusinessType(), e);
|
||||
return InterruptDecision.deny("评估异常", "使用默认处理");
|
||||
}
|
||||
}
|
||||
@@ -223,29 +342,193 @@ public class DispatchEngineImpl implements DispatchEngine {
|
||||
* 默认打断决策
|
||||
* P0任务可以打断任何非P0任务
|
||||
*/
|
||||
private InterruptDecision defaultInterruptDecision(DispatchContext urgentContext) {
|
||||
if (urgentContext.getPriority() != null && urgentContext.getPriority().isUrgent()) {
|
||||
private InterruptDecision defaultInterruptDecision(OrderDispatchContext urgentContext) {
|
||||
if (urgentContext.isUrgent()) {
|
||||
return InterruptDecision.allowByDefault();
|
||||
}
|
||||
return InterruptDecision.deny(
|
||||
"紧急任务优先级不足",
|
||||
"建议等待当前任务完成"
|
||||
);
|
||||
return InterruptDecision.deny("紧急任务优先级不足", "建议等待当前任务完成");
|
||||
}
|
||||
|
||||
// ========== 查询方法 ==========
|
||||
// ==================== 私有方法 ====================
|
||||
|
||||
/**
|
||||
* 获取所有已注册的策略
|
||||
* 执行调度编排
|
||||
*/
|
||||
public List<DispatchStrategy> getAllStrategies() {
|
||||
return new ArrayList<>(strategyRegistry.values());
|
||||
private DispatchResult executeDispatch(OrderDispatchContext context, DispatchDecision decision) {
|
||||
Long orderId = context.getOrderId();
|
||||
Long assigneeId = context.getRecommendedAssigneeId();
|
||||
|
||||
switch (decision.getPath()) {
|
||||
case DIRECT_DISPATCH:
|
||||
return executeDirectDispatch(context, assigneeId);
|
||||
|
||||
case PUSH_AND_ENQUEUE:
|
||||
return executePushAndEnqueue(context, assigneeId);
|
||||
|
||||
case ENQUEUE_ONLY:
|
||||
return executeEnqueueOnly(context, assigneeId);
|
||||
|
||||
case INTERRUPT_AND_DISPATCH:
|
||||
return executeInterruptAndDispatch(context, assigneeId, decision.getInterruptedOrderId());
|
||||
|
||||
case UNAVAILABLE:
|
||||
return DispatchResult.fail(decision.getReason());
|
||||
|
||||
default:
|
||||
return DispatchResult.fail("未知的调度路径: " + decision.getPath());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有业务类型与策略的映射
|
||||
* 直接派单
|
||||
*/
|
||||
public Map<String, String> getAllBusinessTypeMappings() {
|
||||
return new java.util.HashMap<>(businessTypeStrategyMap);
|
||||
private DispatchResult executeDirectDispatch(OrderDispatchContext context, Long assigneeId) {
|
||||
log.info("执行直接派单: orderId={}, assigneeId={}", context.getOrderId(), assigneeId);
|
||||
|
||||
// 查询是否有队列记录(如果有,说明是自动推送等待任务)
|
||||
List<OrderQueueDTO> existingQueues = orderQueueService.getTasksByUserId(assigneeId);
|
||||
OrderQueueDTO queueDTO = existingQueues.stream()
|
||||
.filter(q -> q.getOpsOrderId().equals(context.getOrderId()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
OrderTransitionRequest request = OrderTransitionRequest.builder()
|
||||
.orderId(context.getOrderId())
|
||||
.targetStatus(WorkOrderStatusEnum.DISPATCHED)
|
||||
.assigneeId(assigneeId)
|
||||
.queueId(queueDTO != null ? queueDTO.getId() : null)
|
||||
.operatorType(OperatorTypeEnum.SYSTEM)
|
||||
.operatorId(assigneeId)
|
||||
.reason("自动派单")
|
||||
.build();
|
||||
|
||||
OrderTransitionResult result = orderLifecycleManager.dispatch(request);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
return DispatchResult.success(
|
||||
"直接派单成功",
|
||||
assigneeId,
|
||||
null,
|
||||
DispatchPath.DIRECT_DISPATCH,
|
||||
result.getQueueId()
|
||||
);
|
||||
} else {
|
||||
return DispatchResult.fail("直接派单失败: " + result.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送等待+新任务入队
|
||||
*/
|
||||
private DispatchResult executePushAndEnqueue(OrderDispatchContext context, Long assigneeId) {
|
||||
log.info("执行推送等待+新任务入队: orderId={}, assigneeId={}", context.getOrderId(), assigneeId);
|
||||
|
||||
// 先推送第一个等待任务(如果有的话)
|
||||
List<OrderQueueDTO> waitingTasks = orderQueueService.getWaitingTasksByUserId(assigneeId);
|
||||
if (!waitingTasks.isEmpty()) {
|
||||
OrderQueueDTO firstWaiting = waitingTasks.get(0);
|
||||
OrderTransitionRequest dispatchRequest = OrderTransitionRequest.builder()
|
||||
.orderId(firstWaiting.getOpsOrderId())
|
||||
.targetStatus(WorkOrderStatusEnum.DISPATCHED)
|
||||
.assigneeId(assigneeId)
|
||||
.queueId(firstWaiting.getId())
|
||||
.operatorType(OperatorTypeEnum.SYSTEM)
|
||||
.operatorId(assigneeId)
|
||||
.reason("自动推送等待任务")
|
||||
.build();
|
||||
|
||||
orderLifecycleManager.dispatch(dispatchRequest);
|
||||
log.info("已推送等待任务: taskId={}", firstWaiting.getId());
|
||||
}
|
||||
|
||||
// 新任务入队
|
||||
OrderTransitionRequest enqueueRequest = OrderTransitionRequest.builder()
|
||||
.orderId(context.getOrderId())
|
||||
.assigneeId(assigneeId)
|
||||
.operatorType(OperatorTypeEnum.SYSTEM)
|
||||
.operatorId(assigneeId)
|
||||
.reason("执行人忙碌,任务入队")
|
||||
.build();
|
||||
|
||||
OrderTransitionResult result = orderLifecycleManager.enqueue(enqueueRequest);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
return DispatchResult.success(
|
||||
"已推送等待任务,新任务已入队",
|
||||
assigneeId,
|
||||
null,
|
||||
DispatchPath.PUSH_AND_ENQUEUE,
|
||||
result.getQueueId()
|
||||
);
|
||||
} else {
|
||||
return DispatchResult.fail("入队失败: " + result.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅入队
|
||||
*/
|
||||
private DispatchResult executeEnqueueOnly(OrderDispatchContext context, Long assigneeId) {
|
||||
log.info("执行仅入队: orderId={}, assigneeId={}", context.getOrderId(), assigneeId);
|
||||
|
||||
OrderTransitionRequest request = OrderTransitionRequest.builder()
|
||||
.orderId(context.getOrderId())
|
||||
.assigneeId(assigneeId)
|
||||
.operatorType(OperatorTypeEnum.SYSTEM)
|
||||
.operatorId(assigneeId)
|
||||
.reason("执行人忙碌,任务入队")
|
||||
.build();
|
||||
|
||||
OrderTransitionResult result = orderLifecycleManager.enqueue(request);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
return DispatchResult.success(
|
||||
"任务已入队",
|
||||
assigneeId,
|
||||
null,
|
||||
DispatchPath.ENQUEUE_ONLY,
|
||||
result.getQueueId()
|
||||
);
|
||||
} else {
|
||||
return DispatchResult.fail("入队失败: " + result.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打断并派单
|
||||
*/
|
||||
private DispatchResult executeInterruptAndDispatch(OrderDispatchContext context, Long assigneeId,
|
||||
Long interruptedOrderId) {
|
||||
log.warn("执行打断并派单: orderId={}, assigneeId={}, interruptedOrderId={}",
|
||||
context.getOrderId(), assigneeId, interruptedOrderId);
|
||||
|
||||
// 先打断当前任务
|
||||
if (interruptedOrderId != null) {
|
||||
orderLifecycleManager.interruptOrder(interruptedOrderId, context.getOrderId(), assigneeId);
|
||||
}
|
||||
|
||||
// 派发紧急任务
|
||||
OrderTransitionRequest request = OrderTransitionRequest.builder()
|
||||
.orderId(context.getOrderId())
|
||||
.targetStatus(WorkOrderStatusEnum.DISPATCHED)
|
||||
.assigneeId(assigneeId)
|
||||
.operatorType(OperatorTypeEnum.SYSTEM)
|
||||
.operatorId(assigneeId)
|
||||
.reason("P0紧急任务派单")
|
||||
.build();
|
||||
|
||||
OrderTransitionResult result = orderLifecycleManager.dispatch(request);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
return DispatchResult.success(
|
||||
"P0紧急任务已派单",
|
||||
assigneeId,
|
||||
null,
|
||||
DispatchPath.INTERRUPT_AND_DISPATCH,
|
||||
result.getQueueId()
|
||||
);
|
||||
} else {
|
||||
return DispatchResult.fail("P0紧急任务派单失败: " + result.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
package com.viewsh.module.ops.core.dispatch;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 派单策略接口
|
||||
* <p>
|
||||
* 各业务模块(保洁、安保、工程等)需要实现此接口,定义自己的派单逻辑
|
||||
* <p>
|
||||
* 职责:
|
||||
* 1. 根据派单上下文推荐合适的执行人员
|
||||
* 2. 判断是否可以打断当前任务
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
public interface DispatchStrategy {
|
||||
|
||||
/**
|
||||
* 策略名称
|
||||
* <p>
|
||||
* 如:cleaner_area_priority, security_skill_match
|
||||
* 命名规范:{业务类型}_{策略描述}
|
||||
*
|
||||
* @return 策略名称
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* 支持的业务类型
|
||||
* <p>
|
||||
* 如:CLEAN、REPAIR、SECURITY
|
||||
*
|
||||
* @return 业务类型
|
||||
*/
|
||||
String getSupportedBusinessType();
|
||||
|
||||
/**
|
||||
* 执行派单策略,推荐执行人员
|
||||
* <p>
|
||||
* 根据派单上下文(区域、优先级、技能要求等)推荐最合适的执行人员
|
||||
*
|
||||
* @param context 派单上下文
|
||||
* @return 推荐结果,如果没有合适的返回 AssigneeRecommendation.none()
|
||||
*/
|
||||
AssigneeRecommendation recommendAssignee(DispatchContext context);
|
||||
|
||||
/**
|
||||
* 批量推荐执行人员
|
||||
* <p>
|
||||
* 用于场景:需要从多个候选人中选择,或需要备用人员
|
||||
*
|
||||
* @param context 派单上下文
|
||||
* @param limit 返回结果数量限制
|
||||
* @return 推荐结果列表,按匹配分数降序排序
|
||||
*/
|
||||
List<AssigneeRecommendation> recommendAssignees(DispatchContext context, int limit);
|
||||
|
||||
/**
|
||||
* 评估是否可以打断当前任务
|
||||
* <p>
|
||||
* 当P0紧急任务需要插队时,判断是否可以打断当前执行的任务
|
||||
*
|
||||
* @param currentAssigneeId 当前执行任务的执行人员ID
|
||||
* @param currentOrderId 当前正在执行的工单ID(可选)
|
||||
* @param urgentContext 紧急任务的派单上下文
|
||||
* @return 打断决策结果
|
||||
*/
|
||||
InterruptDecision evaluateInterrupt(Long currentAssigneeId, Long currentOrderId, DispatchContext urgentContext);
|
||||
|
||||
/**
|
||||
* 默认实现:判断是否可以打断
|
||||
* <p>
|
||||
* 默认规则:P0任务可以打断任何非P0任务
|
||||
*/
|
||||
default InterruptDecision defaultEvaluateInterrupt(Long currentAssigneeId, Long currentOrderId,
|
||||
DispatchContext urgentContext) {
|
||||
if (urgentContext.getPriority() != null && urgentContext.getPriority().isUrgent()) {
|
||||
return InterruptDecision.allowByDefault();
|
||||
}
|
||||
return InterruptDecision.deny(
|
||||
"紧急任务优先级不足",
|
||||
"建议等待当前任务完成"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
package com.viewsh.module.ops.core.dispatch;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 打断决策结果
|
||||
* 用于判断P0紧急任务是否可以打断当前正在执行的任务
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class InterruptDecision {
|
||||
|
||||
/**
|
||||
* 是否可以打断
|
||||
*/
|
||||
@Builder.Default
|
||||
private boolean canInterrupt = false;
|
||||
|
||||
/**
|
||||
* 打断原因
|
||||
* 例如:"紧急任务优先级更高"
|
||||
*/
|
||||
private String reason;
|
||||
|
||||
/**
|
||||
* 建议操作
|
||||
* 例如:"暂停当前任务"、"等待当前任务完成"
|
||||
*/
|
||||
private String suggestion;
|
||||
|
||||
/**
|
||||
* 可以打断
|
||||
*/
|
||||
public static InterruptDecision allow(String reason, String suggestion) {
|
||||
return new InterruptDecision(true, reason, suggestion);
|
||||
}
|
||||
|
||||
/**
|
||||
* 不可以打断
|
||||
*/
|
||||
public static InterruptDecision deny(String reason, String suggestion) {
|
||||
return new InterruptDecision(false, reason, suggestion);
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认可以打断(P0任务)
|
||||
*/
|
||||
public static InterruptDecision allowByDefault() {
|
||||
return InterruptDecision.allow(
|
||||
"P0紧急任务优先级最高",
|
||||
"建议暂停当前任务,立即执行P0任务"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 别名方法,用于更流畅的调用
|
||||
* Lombok 会生成 isCanInterrupt(),这里提供 canInterrupt() 别名
|
||||
*/
|
||||
public boolean canInterrupt() {
|
||||
return isCanInterrupt();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.viewsh.module.ops.core.dispatch.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 执行人推荐结果
|
||||
* <p>
|
||||
* 分配策略推荐的执行人信息
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class AssigneeRecommendation {
|
||||
|
||||
/**
|
||||
* 推荐的执行人ID
|
||||
*/
|
||||
private Long assigneeId;
|
||||
|
||||
/**
|
||||
* 推荐的执行人姓名
|
||||
*/
|
||||
private String assigneeName;
|
||||
|
||||
/**
|
||||
* 推荐评分(0-100)
|
||||
* <p>
|
||||
* 评分越高表示越适合
|
||||
*/
|
||||
private Integer score;
|
||||
|
||||
/**
|
||||
* 推荐理由
|
||||
*/
|
||||
private String reason;
|
||||
|
||||
/**
|
||||
* 所属区域ID
|
||||
*/
|
||||
private Long areaId;
|
||||
|
||||
/**
|
||||
* 是否有推荐结果
|
||||
*/
|
||||
public boolean hasRecommendation() {
|
||||
return assigneeId != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 空推荐结果
|
||||
*/
|
||||
public static AssigneeRecommendation none() {
|
||||
return new AssigneeRecommendation();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建推荐结果
|
||||
*
|
||||
* @param assigneeId 执行人ID
|
||||
* @param assigneeName 执行人姓名
|
||||
* @param score 评分
|
||||
* @param reason 推荐理由
|
||||
*/
|
||||
public static AssigneeRecommendation of(Long assigneeId, String assigneeName,
|
||||
Integer score, String reason) {
|
||||
return AssigneeRecommendation.builder()
|
||||
.assigneeId(assigneeId)
|
||||
.assigneeName(assigneeName)
|
||||
.score(score)
|
||||
.reason(reason)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package com.viewsh.module.ops.core.dispatch.model;
|
||||
|
||||
/**
|
||||
* 执行人状态通用接口
|
||||
* <p>
|
||||
* 调度引擎通过此接口获取执行人状态,不依赖具体业务实现。
|
||||
* 不同业务线(保洁、安保、工程等)需要实现此接口。
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
public interface AssigneeStatus {
|
||||
|
||||
/**
|
||||
* 获取状态码
|
||||
* <p>
|
||||
* 常见值:IDLE(空闲)、BUSY(忙碌)、OFFLINE(离线)
|
||||
*
|
||||
* @return 状态码
|
||||
*/
|
||||
String getStatus();
|
||||
|
||||
/**
|
||||
* 是否空闲
|
||||
*
|
||||
* @return true=空闲,false=忙碌或离线
|
||||
*/
|
||||
boolean isIdle();
|
||||
|
||||
/**
|
||||
* 是否忙碌
|
||||
*
|
||||
* @return true=忙碌,false=空闲或离线
|
||||
*/
|
||||
boolean isBusy();
|
||||
|
||||
/**
|
||||
* 是否在线
|
||||
*
|
||||
* @return true=在线,false=离线
|
||||
*/
|
||||
boolean isOnline();
|
||||
|
||||
/**
|
||||
* 获取当前正在执行的任务数
|
||||
*
|
||||
* @return 当前任务数
|
||||
*/
|
||||
Long getCurrentTaskCount();
|
||||
|
||||
/**
|
||||
* 获取等待中的任务数
|
||||
*
|
||||
* @return 等待任务数
|
||||
*/
|
||||
Long getWaitingTaskCount();
|
||||
|
||||
/**
|
||||
* 获取执行人ID
|
||||
*
|
||||
* @return 执行人ID
|
||||
*/
|
||||
Long getAssigneeId();
|
||||
|
||||
/**
|
||||
* 获取执行人姓名
|
||||
*
|
||||
* @return 执行人姓名
|
||||
*/
|
||||
String getAssigneeName();
|
||||
|
||||
/**
|
||||
* 获取所属区域ID
|
||||
*
|
||||
* @return 区域ID
|
||||
*/
|
||||
Long getAreaId();
|
||||
|
||||
/**
|
||||
* 获取最后心跳时间
|
||||
*
|
||||
* @return 心跳时间
|
||||
*/
|
||||
java.time.LocalDateTime getLastHeartbeatTime();
|
||||
|
||||
/**
|
||||
* 获取电量(百分比)
|
||||
* <p>
|
||||
* 仅适用于使用设备的执行人
|
||||
*
|
||||
* @return 电量百分比,0-100
|
||||
*/
|
||||
default Integer getBatteryLevel() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扩展属性
|
||||
* <p>
|
||||
* 用于支持业务特定的属性
|
||||
*
|
||||
* @param key 属性键
|
||||
* @return 属性值
|
||||
*/
|
||||
default Object getExtension(String key) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.viewsh.module.ops.core.dispatch.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 调度决策
|
||||
* <p>
|
||||
* 调度策略根据执行人状态决策的调度路径
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class DispatchDecision {
|
||||
|
||||
/**
|
||||
* 调度路径
|
||||
*/
|
||||
private DispatchPath path;
|
||||
|
||||
/**
|
||||
* 决策理由
|
||||
*/
|
||||
private String reason;
|
||||
|
||||
/**
|
||||
* 需要打断的工单ID(仅在 INTERRUPT_AND_DISPATCH 路径时有值)
|
||||
*/
|
||||
private Long interruptedOrderId;
|
||||
|
||||
/**
|
||||
* 直接派单决策
|
||||
*/
|
||||
public static DispatchDecision directDispatch() {
|
||||
return DispatchDecision.builder()
|
||||
.path(DispatchPath.DIRECT_DISPATCH)
|
||||
.reason("执行人空闲,直接派单")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 推送等待+新任务入队决策
|
||||
*/
|
||||
public static DispatchDecision pushAndEnqueue() {
|
||||
return DispatchDecision.builder()
|
||||
.path(DispatchPath.PUSH_AND_ENQUEUE)
|
||||
.reason("执行人空闲但有等待任务,推送等待任务+新任务入队")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅入队决策
|
||||
*/
|
||||
public static DispatchDecision enqueueOnly() {
|
||||
return DispatchDecision.builder()
|
||||
.path(DispatchPath.ENQUEUE_ONLY)
|
||||
.reason("执行人忙碌,任务入队等待")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 打断并派单决策
|
||||
*
|
||||
* @param interruptedOrderId 需要打断的工单ID
|
||||
*/
|
||||
public static DispatchDecision interruptAndDispatch(Long interruptedOrderId) {
|
||||
return DispatchDecision.builder()
|
||||
.path(DispatchPath.INTERRUPT_AND_DISPATCH)
|
||||
.reason("P0紧急任务,打断当前任务")
|
||||
.interruptedOrderId(interruptedOrderId)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 无法派单决策
|
||||
*/
|
||||
public static DispatchDecision unavailable(String reason) {
|
||||
return DispatchDecision.builder()
|
||||
.path(DispatchPath.UNAVAILABLE)
|
||||
.reason(reason)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否可以派单
|
||||
*/
|
||||
public boolean canDispatch() {
|
||||
return path != null && path != DispatchPath.UNAVAILABLE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.viewsh.module.ops.core.dispatch.model;
|
||||
|
||||
/**
|
||||
* 调度路径枚举
|
||||
* <p>
|
||||
* 定义工单调度的可能路径
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
public enum DispatchPath {
|
||||
|
||||
/**
|
||||
* 直接派单
|
||||
* <p>
|
||||
* 场景:执行人空闲且无正在执行的任务
|
||||
* <p>
|
||||
* 流程:工单 QUEUED → DISPATCHED,队列 WAITING → PROCESSING
|
||||
*/
|
||||
DIRECT_DISPATCH("direct_dispatch", "直接派单"),
|
||||
|
||||
/**
|
||||
* 推送等待任务+新任务入队
|
||||
* <p>
|
||||
* 场景:执行人空闲但有等待中的任务
|
||||
* <p>
|
||||
* 流程:先推送第一个等待任务,新任务入队
|
||||
*/
|
||||
PUSH_AND_ENQUEUE("push_and_enqueue", "推送等待+新任务入队"),
|
||||
|
||||
/**
|
||||
* 仅入队
|
||||
* <p>
|
||||
* 场景:执行人忙碌且非P0优先级
|
||||
* <p>
|
||||
* 流程:工单 PENDING → QUEUED,队列创建 WAITING
|
||||
*/
|
||||
ENQUEUE_ONLY("enqueue_only", "仅入队"),
|
||||
|
||||
/**
|
||||
* 打断并派单
|
||||
* <p>
|
||||
* 场景:P0紧急任务,执行人正在执行其他任务且可以打断
|
||||
* <p>
|
||||
* 流程:原任务 → PAUSED,新任务 → DISPATCHED
|
||||
*/
|
||||
INTERRUPT_AND_DISPATCH("interrupt_and_dispatch", "打断并派单"),
|
||||
|
||||
/**
|
||||
* 无法派单
|
||||
* <p>
|
||||
* 场景:无可用执行人或其他无法派单的情况
|
||||
*/
|
||||
UNAVAILABLE("unavailable", "无法派单");
|
||||
|
||||
private final String code;
|
||||
private final String description;
|
||||
|
||||
DispatchPath(String code, String description) {
|
||||
this.code = code;
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要入队
|
||||
*/
|
||||
public boolean needEnqueue() {
|
||||
return this == ENQUEUE_ONLY || this == INTERRUPT_AND_DISPATCH || this == PUSH_AND_ENQUEUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要立即派单
|
||||
*/
|
||||
public boolean needDispatch() {
|
||||
return this == DIRECT_DISPATCH || this == INTERRUPT_AND_DISPATCH;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.viewsh.module.ops.core.dispatch.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 调度结果
|
||||
* <p>
|
||||
* 调度引擎执行后的返回结果
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class DispatchResult {
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
private boolean success;
|
||||
|
||||
/**
|
||||
* 结果消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 分配的执行人ID
|
||||
*/
|
||||
private Long assigneeId;
|
||||
|
||||
/**
|
||||
* 分配的执行人姓名
|
||||
*/
|
||||
private String assigneeName;
|
||||
|
||||
/**
|
||||
* 调度路径
|
||||
*/
|
||||
private DispatchPath path;
|
||||
|
||||
/**
|
||||
* 队列ID(如果已入队)
|
||||
*/
|
||||
private Long queueId;
|
||||
|
||||
/**
|
||||
* 成功结果
|
||||
*/
|
||||
public static DispatchResult success(String message, Long assigneeId) {
|
||||
return DispatchResult.builder()
|
||||
.success(true)
|
||||
.message(message)
|
||||
.assigneeId(assigneeId)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 成功结果(完整信息)
|
||||
*/
|
||||
public static DispatchResult success(String message, Long assigneeId, String assigneeName,
|
||||
DispatchPath path, Long queueId) {
|
||||
return DispatchResult.builder()
|
||||
.success(true)
|
||||
.message(message)
|
||||
.assigneeId(assigneeId)
|
||||
.assigneeName(assigneeName)
|
||||
.path(path)
|
||||
.queueId(queueId)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 失败结果
|
||||
*/
|
||||
public static DispatchResult fail(String message) {
|
||||
return DispatchResult.builder()
|
||||
.success(false)
|
||||
.message(message)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.viewsh.module.ops.core.dispatch.model;
|
||||
|
||||
import com.viewsh.module.ops.enums.PriorityEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 调度上下文
|
||||
* <p>
|
||||
* 调度引擎执行调度时需要的上下文信息
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class OrderDispatchContext {
|
||||
|
||||
/**
|
||||
* 工单ID
|
||||
*/
|
||||
private Long orderId;
|
||||
|
||||
/**
|
||||
* 工单编号
|
||||
*/
|
||||
private String orderCode;
|
||||
|
||||
/**
|
||||
* 工单标题
|
||||
*/
|
||||
private String orderTitle;
|
||||
|
||||
/**
|
||||
* 业务类型
|
||||
* <p>
|
||||
* 例如:CLEAN(保洁)、SECURITY(安保)、FACILITIES(工程)
|
||||
*/
|
||||
private String businessType;
|
||||
|
||||
/**
|
||||
* 区域ID
|
||||
*/
|
||||
private Long areaId;
|
||||
|
||||
/**
|
||||
* 工单优先级
|
||||
*/
|
||||
private PriorityEnum priority;
|
||||
|
||||
/**
|
||||
* 推荐的执行人ID
|
||||
* <p>
|
||||
* 由分配策略推荐后填充
|
||||
*/
|
||||
private Long recommendedAssigneeId;
|
||||
|
||||
/**
|
||||
* 执行人当前状态
|
||||
* <p>
|
||||
* 由调度引擎查询后填充
|
||||
*/
|
||||
private AssigneeStatus assigneeStatus;
|
||||
|
||||
/**
|
||||
* 当前任务列表
|
||||
* <p>
|
||||
* 执行人当前正在执行和等待的任务
|
||||
*/
|
||||
private Map<String, Object> currentTasks;
|
||||
|
||||
/**
|
||||
* 紧急工单ID
|
||||
* <p>
|
||||
* 当此工单是P0紧急任务,需要打断其他任务时,记录被打断的工单ID
|
||||
*/
|
||||
private Long interruptedOrderId;
|
||||
|
||||
/**
|
||||
* 扩展信息
|
||||
* <p>
|
||||
* 用于支持业务特定的扩展信息
|
||||
*/
|
||||
private Map<String, Object> payload;
|
||||
|
||||
/**
|
||||
* 是否为P0紧急任务
|
||||
*/
|
||||
public boolean isUrgent() {
|
||||
return priority != null && priority.isUrgent();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取扩展信息
|
||||
*
|
||||
* @param key 键
|
||||
* @return 值
|
||||
*/
|
||||
public Object getPayload(String key) {
|
||||
return payload != null ? payload.get(key) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置扩展信息
|
||||
*
|
||||
* @param key 键
|
||||
* @param value 值
|
||||
*/
|
||||
public void putPayload(String key, Object value) {
|
||||
if (payload == null) {
|
||||
payload = new java.util.HashMap<>();
|
||||
}
|
||||
payload.put(key, value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.viewsh.module.ops.core.dispatch.strategy;
|
||||
|
||||
import com.viewsh.module.ops.core.dispatch.model.AssigneeRecommendation;
|
||||
import com.viewsh.module.ops.core.dispatch.model.OrderDispatchContext;
|
||||
|
||||
/**
|
||||
* 分配策略接口
|
||||
* <p>
|
||||
* 负责决策:谁来接单
|
||||
* <p>
|
||||
* 不同业务线实现此接口,提供各自的分配逻辑。
|
||||
* 例如:保洁业务按区域优先分配,安保业务按技能匹配。
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
public interface AssignStrategy {
|
||||
|
||||
/**
|
||||
* 获取策略名称
|
||||
*
|
||||
* @return 策略名称
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* 获取支持的业务类型
|
||||
*
|
||||
* @return 业务类型,例如:CLEAN、SECURITY、FACILITIES
|
||||
*/
|
||||
String getSupportedBusinessType();
|
||||
|
||||
/**
|
||||
* 推荐执行人
|
||||
* <p>
|
||||
* 根据工单信息推荐最合适的执行人
|
||||
*
|
||||
* @param context 调度上下文
|
||||
* @return 推荐结果,无合适执行人时返回 null
|
||||
*/
|
||||
AssigneeRecommendation recommend(OrderDispatchContext context);
|
||||
|
||||
/**
|
||||
* 批量推荐执行人
|
||||
* <p>
|
||||
* 用于备用人员场景
|
||||
*
|
||||
* @param context 调度上下文
|
||||
* @param limit 最多返回数量
|
||||
* @return 推荐结果列表,按评分降序排列
|
||||
*/
|
||||
default java.util.List<AssigneeRecommendation> recommendBatch(OrderDispatchContext context, int limit) {
|
||||
AssigneeRecommendation recommendation = recommend(context);
|
||||
if (recommendation == null || !recommendation.hasRecommendation()) {
|
||||
return java.util.Collections.emptyList();
|
||||
}
|
||||
return java.util.Collections.singletonList(recommendation);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.viewsh.module.ops.core.dispatch.strategy;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 打断决策
|
||||
* <p>
|
||||
* P0紧急任务时,是否可以打断当前正在执行的任务
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class InterruptDecision {
|
||||
|
||||
/**
|
||||
* 是否允许打断
|
||||
*/
|
||||
private boolean allowed;
|
||||
|
||||
/**
|
||||
* 拒绝原因(当不允许时)
|
||||
*/
|
||||
private String denyReason;
|
||||
|
||||
/**
|
||||
* 建议(当不允许时)
|
||||
*/
|
||||
private String suggestion;
|
||||
|
||||
/**
|
||||
* 允许打断(使用默认理由)
|
||||
*/
|
||||
public static InterruptDecision allowByDefault() {
|
||||
return InterruptDecision.builder()
|
||||
.allowed(true)
|
||||
.denyReason(null)
|
||||
.suggestion(null)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 允许打断(自定义理由)
|
||||
*/
|
||||
public static InterruptDecision allow(String reason) {
|
||||
return InterruptDecision.builder()
|
||||
.allowed(true)
|
||||
.denyReason(reason)
|
||||
.suggestion(null)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 拒绝打断
|
||||
*/
|
||||
public static InterruptDecision deny(String reason, String suggestion) {
|
||||
return InterruptDecision.builder()
|
||||
.allowed(false)
|
||||
.denyReason(reason)
|
||||
.suggestion(suggestion)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.viewsh.module.ops.core.dispatch.strategy;
|
||||
|
||||
import com.viewsh.module.ops.core.dispatch.model.DispatchDecision;
|
||||
import com.viewsh.module.ops.core.dispatch.model.OrderDispatchContext;
|
||||
|
||||
/**
|
||||
* 调度策略接口
|
||||
* <p>
|
||||
* 负责决策:怎么派单
|
||||
* <p>
|
||||
* 根据执行人当前状态决定调度路径:
|
||||
* <ul>
|
||||
* <li>空闲无任务 → 直接派单 (DIRECT_DISPATCH)</li>
|
||||
* <li>空闲有等待 → 推送等待任务 + 新任务入队 (PUSH_AND_ENQUEUE)</li>
|
||||
* <li>忙碌且非P0 → 入队等待 (ENQUEUE_ONLY)</li>
|
||||
* <li>忙碌且P0 → 评估打断 (INTERRUPT_AND_DISPATCH)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
public interface ScheduleStrategy {
|
||||
|
||||
/**
|
||||
* 获取策略名称
|
||||
*
|
||||
* @return 策略名称
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* 获取支持的业务类型
|
||||
*
|
||||
* @return 业务类型,例如:CLEAN、SECURITY、FACILITIES
|
||||
*/
|
||||
String getSupportedBusinessType();
|
||||
|
||||
/**
|
||||
* 决策调度路径
|
||||
* <p>
|
||||
* 根据调度上下文(包含推荐执行人信息)决定调度路径
|
||||
*
|
||||
* @param context 调度上下文
|
||||
* @return 调度决策
|
||||
*/
|
||||
DispatchDecision decide(OrderDispatchContext context);
|
||||
|
||||
/**
|
||||
* 评估是否可以打断
|
||||
* <p>
|
||||
* P0紧急任务时,评估是否可以打断当前正在执行的任务
|
||||
*
|
||||
* @param currentAssigneeId 当前执行人ID
|
||||
* @param currentOrderId 当前工单ID
|
||||
* @param urgentContext 紧急任务上下文
|
||||
* @return 打断决策
|
||||
*/
|
||||
default InterruptDecision evaluateInterrupt(Long currentAssigneeId, Long currentOrderId,
|
||||
OrderDispatchContext urgentContext) {
|
||||
// 默认实现:P0任务可以打断,其他不能
|
||||
if (urgentContext.isUrgent()) {
|
||||
return InterruptDecision.allowByDefault();
|
||||
}
|
||||
return InterruptDecision.deny("紧急任务优先级不足", "建议等待当前任务完成");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user