feat(ops): 重构派单队列评分逻辑,支持楼层差与等待老化综合排序

- 新增 QueueScoreCalculator/QueueScoreContext/QueueScoreResult,统一按优先级分 + 楼层差分 - 等待老化分计算队列总分,并将 PRIORITY_WEIGHT 调整为 1500
- OrderQueueService 新增 rebuildWaitingTasksByUserId 接口,OrderQueueServiceEnhanced 支持按执行人重算 WAITING 队列、以当前执行工单楼层为基准动态重排,并在事务提交后同步刷新 Redis
- RedisOrderQueueServiceImpl 支持持久化 baseFloorNo、targetFloorNo、floorDiff、waitMinutes、scoreUpdateTime 等评分明细,清队列时同时清理关联 Hash,避免脏数据残留
- DispatchEngineImpl、CleanerPriorityScheduleStrategy、BadgeDeviceScheduleStrategy 调整为非抢占式派单:P0 忙碌时仅入队等待,空闲时直接派发,自动派单前按总分重排并派发下一单
- CleanOrderServiceImpl 取消 P0 自动打断链路,升级到 P0 后仅重算等待队列并发送通知;补充 QueueScoreCalculatorTest、OrderQueueServiceEnhancedTest、CleanerPriorityScheduleStrategyTest、CleanOrderEndToEndTest 覆盖新行为
This commit is contained in:
lzh
2026-03-07 21:12:48 +08:00
parent 26c4ce07eb
commit a9fd9313cc
16 changed files with 674 additions and 261 deletions

View File

@@ -9,10 +9,13 @@ import com.viewsh.module.ops.core.event.OrderEventPublisher;
import com.viewsh.module.ops.core.lifecycle.OrderLifecycleManager;
import com.viewsh.module.ops.core.lifecycle.model.OrderTransitionRequest;
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
import com.viewsh.module.ops.dal.dataobject.area.OpsBusAreaDO;
import com.viewsh.module.ops.dal.mysql.area.OpsBusAreaMapper;
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
import com.viewsh.module.ops.enums.PriorityEnum;
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
import com.viewsh.module.ops.environment.dal.dataobject.workorder.OpsOrderCleanExtDO;
import com.viewsh.module.ops.environment.dal.redis.TrafficActiveOrderRedisDAO;
import com.viewsh.module.ops.environment.dal.mysql.workorder.OpsOrderCleanExtMapper;
import com.viewsh.module.ops.environment.integration.consumer.*;
import com.viewsh.framework.common.pojo.CommonResult;
@@ -73,6 +76,8 @@ public class CleanOrderEndToEndTest {
@Mock
private OpsOrderMapper opsOrderMapper;
@Mock
private OpsBusAreaMapper opsBusAreaMapper;
@Mock
private OpsOrderCleanExtMapper cleanExtMapper;
@Mock
private OrderIdGenerator orderIdGenerator;
@@ -94,6 +99,8 @@ public class CleanOrderEndToEndTest {
private ValueOperations<String, String> valueOperations;
@Mock
private VoiceBroadcastService voiceBroadcastService;
@Mock
private TrafficActiveOrderRedisDAO trafficActiveOrderRedisDAO;
@Mock
private BadgeDeviceStatusService badgeDeviceStatusService;
@@ -149,6 +156,7 @@ public class CleanOrderEndToEndTest {
// 注入 CleanOrderEventListener
injectField(cleanOrderService, "cleanOrderEventListener", cleanOrderEventListener);
injectField(cleanOrderService, "opsBusAreaMapper", opsBusAreaMapper);
// 注入 CleanOrderAuditEventHandler 依赖
injectField(auditEventHandler, "eventLogRecorder", eventLogRecorder);
@@ -156,10 +164,18 @@ public class CleanOrderEndToEndTest {
injectField(auditEventHandler, "opsOrderMapper", opsOrderMapper);
injectField(auditEventHandler, "stringRedisTemplate", stringRedisTemplate);
injectField(auditEventHandler, "objectMapper", objectMapper);
injectField(createEventHandler, "trafficActiveOrderRedisDAO", trafficActiveOrderRedisDAO);
// Stub IotDeviceControlApi for resetTrafficCounter
lenient().when(iotDeviceControlApi.resetTrafficCounter(any()))
.thenReturn(CommonResult.success(true));
lenient().when(opsBusAreaMapper.selectById(anyLong()))
.thenAnswer(i -> OpsBusAreaDO.builder()
.id(i.getArgument(0))
.areaName("测试区域")
.parentPath(null)
.floorNo(1)
.build());
}
// ==========================================
@@ -338,7 +354,7 @@ public class CleanOrderEndToEndTest {
verify(eventLogRecorder).record(any());
// 2. TTS sent (orderId can be null for TTS_REQUEST events)
verify(voiceBroadcastService).broadcastInOrder(eq(5001L), contains("请回到作业区域"), eq((Long) null));
verify(voiceBroadcastService).broadcastDirect(eq(5001L), contains("请回到作业区域"), eq(9), eq((Long) null));
}
// ==========================================
@@ -378,9 +394,6 @@ public class CleanOrderEndToEndTest {
when(opsOrderMapper.selectById(orderId)).thenReturn(order);
when(orderQueueService.getByOpsOrderId(orderId)).thenReturn(queueDTO);
when(dispatchEngine.urgentInterrupt(eq(orderId), eq(2001L)))
.thenReturn(DispatchResult.success("Success", 2001L));
// Execute
boolean result = cleanOrderService.upgradePriorityToP0(orderId, "Manual Upgrade");
@@ -392,7 +405,7 @@ public class CleanOrderEndToEndTest {
assertEquals(PriorityEnum.P0.getPriority(), orderCaptor.getValue().getPriority());
verify(orderQueueService).adjustPriority(eq(500L), eq(PriorityEnum.P0), anyString());
verify(dispatchEngine).urgentInterrupt(orderId, 2001L);
verify(orderQueueService).rebuildWaitingTasksByUserId(2001L, order.getAreaId());
verify(cleanOrderEventListener).sendPriorityUpgradeNotification(eq(2001L), eq("WO-P2"), eq(orderId));
}

View File

@@ -36,7 +36,7 @@ class CleanerPriorityScheduleStrategyTest {
private com.viewsh.module.ops.core.dispatch.DispatchEngine dispatchEngine;
@Test
void testDecide_P0_Interrupt() {
void testDecide_P0_EnqueueOnlyWhenBusy() {
// Setup
OpsCleanerStatusDO c1 = new OpsCleanerStatusDO();
c1.setUserId(1L);
@@ -54,8 +54,7 @@ class CleanerPriorityScheduleStrategyTest {
DispatchDecision decision = strategy.decide(context);
// Verify
assertEquals(DispatchPath.INTERRUPT_AND_DISPATCH, decision.getPath());
assertEquals(500L, decision.getInterruptedOrderId());
assertEquals(DispatchPath.ENQUEUE_ONLY, decision.getPath());
}
@Test