chore: 【ops】保洁员Dispatch初步搭建

This commit is contained in:
lzh
2026-01-06 10:51:44 +08:00
parent 46926e8127
commit 43a47a465c
3 changed files with 527 additions and 0 deletions

View File

@@ -0,0 +1,163 @@
package com.viewsh.module.ops.environment.service.dispatch;
import com.viewsh.module.ops.api.queue.OrderQueueDTO;
import com.viewsh.module.ops.dal.dataobject.cleaner.OpsCleanerStatusDO;
import com.viewsh.module.ops.enums.CleanerStatusEnum;
import com.viewsh.module.ops.enums.PriorityEnum;
import com.viewsh.module.ops.environment.service.cleaner.CleanerStatusService;
import com.viewsh.module.ops.service.dispatch.DispatchStrategy;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.Comparator;
import java.util.List;
/**
* 保洁区域优先派单策略
* 优先分配给该区域的空闲保洁员,按以下规则排序:
* 1. 状态必须是IDLE空闲
* 2. 优先选择同区域的保洁员
* 3. 考虑保洁员的工作量平衡
*
* @author lzh
*/
@Component
@Slf4j
public class CleanerAreaPriorityStrategy implements DispatchStrategy {
@Resource
private CleanerStatusService cleanerStatusService;
@Resource
private com.viewsh.module.ops.service.dispatch.DispatchEngineServiceImpl dispatchEngineService;
/**
* 策略名称
*/
private static final String STRATEGY_NAME = "cleaner_area_priority";
@PostConstruct
public void init() {
// 注册策略到派单引擎
dispatchEngineService.registerStrategy(this);
// 注册执行人员类型与策略的映射
dispatchEngineService.registerAssigneeTypeStrategy("CLEANER", STRATEGY_NAME);
log.info("保洁区域优先派单策略已注册: strategyName={}", STRATEGY_NAME);
}
@Override
public String getName() {
return STRATEGY_NAME;
}
@Override
public Long recommendAssignee(OrderQueueDTO queueDTO) {
log.info("执行保洁区域优先派单策略: opsOrderId={}", queueDTO.getOpsOrderId());
// 在新的队列模型中,推荐逻辑应该在工单创建时完成
// 这里主要用于手动派单或重新派单的场景
// TODO: 从 eventPayload 中解析区域ID然后查询可用保洁员
log.warn("在新队列模型中,推荐保洁员应该在工单创建时完成: opsOrderId={}",
queueDTO.getOpsOrderId());
return null;
}
@Override
public boolean canInterrupt(Long currentAssigneeId, Long currentOrderId, Long urgentOrderId) {
log.warn("判断是否可打断保洁员任务: assigneeId={}, currentOrderId={}, urgentOrderId={}",
currentAssigneeId, currentOrderId, urgentOrderId);
// P0任务可以打断P1/P2/P3任务
// 简化处理:默认允许打断
log.info("允许打断:紧急任务优先级更高: urgentOrderId={}, currentOrderId={}",
urgentOrderId, currentOrderId);
return true;
}
/**
* 选择最佳保洁员
* 综合考虑以下因素:
* 1. 是否在该区域(已过滤)
* 2. 当前工作量(已完成的工单数少优先)
*/
private OpsCleanerStatusDO selectBestCleaner(List<OpsCleanerStatusDO> cleaners,
OrderQueueDTO queueDTO) {
// 使用Comparator进行多条件排序
return cleaners.stream()
.max(Comparator
// 1. 电量充足优先(电量>20%
.comparing((OpsCleanerStatusDO cleaner) ->
cleaner.getBatteryLevel() != null && cleaner.getBatteryLevel() > 20 ? 1 : 0)
// 2. 心跳时间最新的优先(在线状态好)
.thenComparing(OpsCleanerStatusDO::getLastHeartbeatTime, Comparator.nullsLast(Comparator.naturalOrder()))
// 3. 没有当前工单的<E58D95><E79A84><EFBFBD>完全空闲
.thenComparing((OpsCleanerStatusDO cleaner) ->
cleaner.getCurrentOpsOrderId() == null ? 1 : 0)
// 4. 工作量少的优先(状态变更时间早的说明刚完成)
.thenComparing(OpsCleanerStatusDO::getUpdateTime, Comparator.nullsLast(Comparator.naturalOrder()))
)
.orElse(null);
}
/**
* 为新工单推荐保洁员(工单创建时调用)
* 这是主要的推荐方法,用于在工单创建时选择合适的保洁员
*
* @param areaId 区域ID
* @param priority 工单优先级
* @return 推荐的保洁员ID如果没有合适的返回null
*/
public Long recommendCleanerForNewOrder(Long areaId, PriorityEnum priority) {
log.info("为新工单推荐保洁员: areaId={}, priority={}", areaId, priority);
// TODO-lzh:目前场景是单个区域绑定单个保洁员(所以只需查出对应保洁返回即可)
// 查询该区域的可用保洁员IDLE状态
List<OpsCleanerStatusDO> availableCleaners =
cleanerStatusService.listAvailableCleaners(areaId);
if (availableCleaners.isEmpty()) {
log.warn("该区域没有可用保洁员: areaId={}", areaId);
return null;
}
log.info("找到 {} 个可用保洁员: areaId={}", availableCleaners.size(), areaId);
// 对于P0紧急任务优先选择电量充足、心跳最新的
if (priority.isUrgent()) {
log.warn("为P0紧急任务推荐保洁员优先选择最佳保洁员");
}
// 选择最佳保洁员
OpsCleanerStatusDO selectedCleaner = selectBestCleanerById(availableCleaners);
if (selectedCleaner != null) {
log.info("为新工单推荐保洁员: areaId={}, priority={}, cleanerId={}, cleanerName={}",
areaId, priority, selectedCleaner.getUserId(), selectedCleaner.getUserName());
return selectedCleaner.getUserId();
}
return null;
}
/**
* 从保洁员列表中选择最佳保洁员(简化版本,不需要 queueDTO
*/
private OpsCleanerStatusDO selectBestCleanerById(List<OpsCleanerStatusDO> cleaners) {
return cleaners.stream()
.max(Comparator
// 1. 电量充足优先(电量>20%
.comparing((OpsCleanerStatusDO cleaner) ->
cleaner.getBatteryLevel() != null && cleaner.getBatteryLevel() > 20 ? 1 : 0)
// 2. 心跳时间最新的优先(在线状态好)
.thenComparing(OpsCleanerStatusDO::getLastHeartbeatTime, Comparator.nullsLast(Comparator.naturalOrder()))
// 3. 没有当前工单的优先(完全空闲)
.thenComparing((OpsCleanerStatusDO cleaner) ->
cleaner.getCurrentOpsOrderId() == null ? 1 : 0)
)
.orElse(null);
}
}