diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/core/dispatch/DispatchEngineImpl.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/core/dispatch/DispatchEngineImpl.java index 23cf034..48ab374 100644 --- a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/core/dispatch/DispatchEngineImpl.java +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/core/dispatch/DispatchEngineImpl.java @@ -14,9 +14,6 @@ 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; @@ -76,17 +73,6 @@ public class DispatchEngineImpl implements DispatchEngine { // ==================== 核心调度方法 ==================== @Override - @BusinessLog( - type = LogType.ORDER_DISPATCHED, - scope = LogScope.ORDER, - description = "工单自动派发", - includeParams = true, - includeResult = true, - result = "#result.success", - params = {"#context.orderId", "#context.businessType", "#context.priority"}, - targetId = "#context.orderId", - targetType = "order" - ) @Transactional(rollbackFor = Exception.class) public DispatchResult dispatch(OrderDispatchContext context) { log.info("开始调度工单: orderId={}, businessType={}, areaId={}, priority={}", @@ -202,33 +188,60 @@ public class DispatchEngineImpl implements DispatchEngine { if (waitingTasks.isEmpty()) { log.info("无等待任务,执行人变为空闲: assigneeId={}", assigneeId); - // 发布事件,由业务层更新执行人状态 return DispatchResult.success("无等待工单,执行人保持空闲", assigneeId); } - // 动态总分重排后,派发得分最低的等待工单 - OrderQueueDTO nextTask = waitingTasks.get(0); - log.info("派发下一单: queueId={}, orderId={}, score={}, floorDiff={}, waitMinutes={}", - nextTask.getId(), nextTask.getOpsOrderId(), nextTask.getQueueScore(), - nextTask.getFloorDiff(), nextTask.getWaitMinutes()); + // 遍历等待任务列表,跳过不可派发的脏数据 + int maxSkip = 50; + int skipped = 0; + for (OrderQueueDTO nextTask : waitingTasks) { + // 防御性校验:检查工单实际状态是否仍为 QUEUED + OpsOrderDO order = orderMapper.selectById(nextTask.getOpsOrderId()); + if (order == null || !WorkOrderStatusEnum.QUEUED.getStatus().equals(order.getStatus())) { + // 工单已不在 QUEUED 状态(可能被 forceTransition 直接完成),清理脏队列记录 + log.warn("跳过不可派发的队列记录: queueId={}, orderId={}, orderStatus={}", + nextTask.getId(), nextTask.getOpsOrderId(), + order != null ? order.getStatus() : "NOT_FOUND"); + try { + orderQueueService.updateStatus(nextTask.getId(), OrderQueueStatusEnum.REMOVED); + } catch (Exception e) { + log.error("清理脏队列记录失败: queueId={}", nextTask.getId(), e); + } + skipped++; + if (skipped >= maxSkip) { + log.error("跳过脏数据超过上限,终止: assigneeId={}, skipped={}", assigneeId, skipped); + break; + } + continue; + } - OrderTransitionRequest request = OrderTransitionRequest.builder() - .orderId(nextTask.getOpsOrderId()) - .targetStatus(WorkOrderStatusEnum.DISPATCHED) - .queueId(nextTask.getId()) - .assigneeId(assigneeId) - .operatorType(OperatorTypeEnum.SYSTEM) - .operatorId(assigneeId) - .reason("等待队列动态重排后自动派发") - .build(); + log.info("派发下一单: queueId={}, orderId={}, score={}, floorDiff={}, waitMinutes={}", + nextTask.getId(), nextTask.getOpsOrderId(), nextTask.getQueueScore(), + nextTask.getFloorDiff(), nextTask.getWaitMinutes()); - OrderTransitionResult result = orderLifecycleManager.transition(request); + OrderTransitionRequest request = OrderTransitionRequest.builder() + .orderId(nextTask.getOpsOrderId()) + .targetStatus(WorkOrderStatusEnum.DISPATCHED) + .queueId(nextTask.getId()) + .assigneeId(assigneeId) + .operatorType(OperatorTypeEnum.SYSTEM) + .operatorId(assigneeId) + .reason("等待队列动态重排后自动派发") + .build(); - if (result.isSuccess()) { - return DispatchResult.success("已按队列总分派发下一单", assigneeId); - } else { - return DispatchResult.fail("按队列总分派发下一单失败: " + result.getMessage()); + OrderTransitionResult result = orderLifecycleManager.transition(request); + + if (result.isSuccess()) { + return DispatchResult.success("已按队列总分派发下一单", assigneeId); + } else { + log.warn("派发下一单失败,尝试下一条: orderId={}, error={}", + nextTask.getOpsOrderId(), result.getMessage()); + } } + + // 所有等待任务都不可派发 + log.info("所有等待任务均不可派发,执行人变为空闲: assigneeId={}", assigneeId); + return DispatchResult.success("无可派发工单,执行人保持空闲", assigneeId); } // ==================== 策略注册 ==================== diff --git a/viewsh-module-ops/viewsh-module-security-biz/src/main/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceImpl.java b/viewsh-module-ops/viewsh-module-security-biz/src/main/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceImpl.java index f6564f4..079f048 100644 --- a/viewsh-module-ops/viewsh-module-security-biz/src/main/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceImpl.java +++ b/viewsh-module-ops/viewsh-module-security-biz/src/main/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceImpl.java @@ -22,7 +22,9 @@ import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecord; import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder; import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityExtDO; import com.viewsh.module.ops.security.dal.mysql.workorder.OpsOrderSecurityExtMapper; -import com.viewsh.module.ops.service.fsm.OrderStateMachine; +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 jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -62,7 +64,7 @@ public class SecurityOrderServiceImpl implements SecurityOrderService { private AreaPathBuilder areaPathBuilder; @Resource - private OrderStateMachine orderStateMachine; + private OrderLifecycleManager orderLifecycleManager; @Resource private EventLogRecorder eventLogRecorder; @@ -151,9 +153,15 @@ public class SecurityOrderServiceImpl implements SecurityOrderService { // 如果 userId 为 null(open-api 调用),取已分配人员 Long effectiveUserId = resolveOperatorId(orderId, userId); - // 状态转换:DISPATCHED → CONFIRMED(扩展表时间 + 业务日志由 EventListener 统一记录) - orderStateMachine.transition(order, WorkOrderStatusEnum.CONFIRMED, - OperatorTypeEnum.SECURITY_GUARD, effectiveUserId, "安保人员确认接单"); + // 状态转换:DISPATCHED → CONFIRMED(走完整责任链,确保队列同步 + 事件发布) + OrderTransitionRequest request = OrderTransitionRequest.builder() + .orderId(orderId) + .targetStatus(WorkOrderStatusEnum.CONFIRMED) + .operatorType(OperatorTypeEnum.SECURITY_GUARD) + .operatorId(effectiveUserId) + .reason("安保人员确认接单") + .build(); + orderLifecycleManager.transition(request); log.info("安保工单确认: orderId={}, userId={}", orderId, effectiveUserId); } @@ -172,15 +180,19 @@ public class SecurityOrderServiceImpl implements SecurityOrderService { log.info("安保工单已处于终态,跳过自动完单: orderId={}, status={}", orderId, currentStatus); return; } - // 2. PENDING:未派单,直接取消(PENDING → CANCELLED 规则已支持) + // 2. PENDING:未派单,直接取消(PENDING → CANCELLED 是合法转换) if (currentStatus == WorkOrderStatusEnum.PENDING) { - orderStateMachine.transition(order, WorkOrderStatusEnum.CANCELLED, - OperatorTypeEnum.SYSTEM, null, effectiveRemark + "(工单未派单)"); + orderLifecycleManager.cancelOrder(orderId, null, OperatorTypeEnum.SYSTEM, + effectiveRemark + "(工单未派单)"); } - // 3. 其他非终态(DISPATCHED / CONFIRMED / ARRIVED / PAUSED)→ 强制跳转 COMPLETED + // 3. 其他非终态(QUEUED / DISPATCHED / CONFIRMED / ARRIVED / PAUSED) + // 通过 forceComplete 走完整责任链(队列清理 + 事件发布),避免脏数据残留 else { - orderStateMachine.forceTransition(order, WorkOrderStatusEnum.COMPLETED, - OperatorTypeEnum.SYSTEM, null, effectiveRemark); + OrderTransitionResult result = orderLifecycleManager.forceComplete( + orderId, null, OperatorTypeEnum.SYSTEM, effectiveRemark); + if (!result.isSuccess()) { + throw new IllegalStateException("强制完成工单失败: " + result.getMessage()); + } } log.info("安保工单自动结单: orderId={}, 原始状态={}", orderId, currentStatus); @@ -211,9 +223,9 @@ public class SecurityOrderServiceImpl implements SecurityOrderService { // 如果 operatorId 为 null(open-api 调用),取已分配人员 Long effectiveOperatorId = resolveOperatorId(req.getOrderId(), req.getOperatorId()); - // 状态转换 → COMPLETED(扩展表 completedTime + 业务日志由 EventListener 统一记录,主表 endTime 由状态机统一设置) - orderStateMachine.transition(order, WorkOrderStatusEnum.COMPLETED, - OperatorTypeEnum.SECURITY_GUARD, effectiveOperatorId, "安保人员提交处理结果"); + // 状态转换 → COMPLETED(走完整责任链,确保队列同步 + 事件发布) + orderLifecycleManager.completeOrder(req.getOrderId(), effectiveOperatorId, + OperatorTypeEnum.SECURITY_GUARD, "安保人员提交处理结果"); // 更新扩展表:结果 + 图片 OpsOrderSecurityExtDO extUpdate = new OpsOrderSecurityExtDO(); diff --git a/viewsh-module-ops/viewsh-module-security-biz/src/test/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceTest.java b/viewsh-module-ops/viewsh-module-security-biz/src/test/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceTest.java index 14d9676..f0a0220 100644 --- a/viewsh-module-ops/viewsh-module-security-biz/src/test/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceTest.java +++ b/viewsh-module-ops/viewsh-module-security-biz/src/test/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceTest.java @@ -13,7 +13,9 @@ import com.viewsh.module.ops.infrastructure.code.OrderCodeGenerator; import com.viewsh.module.ops.infrastructure.id.OrderIdGenerator; import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityExtDO; import com.viewsh.module.ops.security.dal.mysql.workorder.OpsOrderSecurityExtMapper; -import com.viewsh.module.ops.service.fsm.OrderStateMachine; +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 org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -52,7 +54,7 @@ public class SecurityOrderServiceTest { @Mock private OrderEventPublisher orderEventPublisher; @Mock - private OrderStateMachine orderStateMachine; + private OrderLifecycleManager orderLifecycleManager; // 模拟数据库 private Map orderDB; @@ -199,14 +201,14 @@ public class SecurityOrderServiceTest { // 执行 securityOrderService.confirmOrder(TEST_ORDER_ID, userId); - // 验证状态机调用 - verify(orderStateMachine).transition( - eq(order), - eq(WorkOrderStatusEnum.CONFIRMED), - eq(OperatorTypeEnum.SECURITY_GUARD), - eq(userId), - eq("安保人员确认接单") - ); + // 验证走 orderLifecycleManager.transition() + ArgumentCaptor reqCaptor = ArgumentCaptor.forClass(OrderTransitionRequest.class); + verify(orderLifecycleManager).transition(reqCaptor.capture()); + OrderTransitionRequest capturedReq = reqCaptor.getValue(); + assertEquals(TEST_ORDER_ID, capturedReq.getOrderId()); + assertEquals(WorkOrderStatusEnum.CONFIRMED, capturedReq.getTargetStatus()); + assertEquals(OperatorTypeEnum.SECURITY_GUARD, capturedReq.getOperatorType()); + assertEquals(userId, capturedReq.getOperatorId()); // 验证不再直接写扩展表时间(由 EventListener 统一处理) verify(securityExtMapper, never()).insertOrUpdateSelective(any()); @@ -235,9 +237,8 @@ public class SecurityOrderServiceTest { () -> securityOrderService.confirmOrder(TEST_ORDER_ID, 2001L)); assertTrue(exception.getMessage().contains("工单类型不匹配")); - // 验证状态机未被调用 - verify(orderStateMachine, never()).transition( - any(), any(), any(), anyLong(), anyString()); + // 验证 lifecycleManager 未被调用 + verify(orderLifecycleManager, never()).transition(any(OrderTransitionRequest.class)); } // ==================== 自动完单测试 ==================== @@ -247,21 +248,21 @@ public class SecurityOrderServiceTest { // 准备 OpsOrderDO order = buildSecurityOrder(TEST_ORDER_ID, WorkOrderStatusEnum.ARRIVED); orderDB.put(TEST_ORDER_ID, order); + when(orderLifecycleManager.forceComplete(anyLong(), any(), any(), anyString())) + .thenReturn(OrderTransitionResult.builder().success(true).orderId(TEST_ORDER_ID).build()); // 执行 securityOrderService.autoCompleteOrder(TEST_ORDER_ID, "告警自动解除"); - // 验证状态机调用(ARRIVED 非终态,走 forceTransition) - verify(orderStateMachine).forceTransition( - eq(order), - eq(WorkOrderStatusEnum.COMPLETED), - eq(OperatorTypeEnum.SYSTEM), + // 验证走 forceComplete(完整责任链) + verify(orderLifecycleManager).forceComplete( + eq(TEST_ORDER_ID), isNull(), + eq(OperatorTypeEnum.SYSTEM), eq("告警自动解除") ); - // 验证不直接写扩展表 - verify(securityExtMapper, never()).insertOrUpdateSelective(any()); + // 验证不直接调用 orderStateMachine(已完全移除依赖) } @Test @@ -269,14 +270,16 @@ public class SecurityOrderServiceTest { // 准备 OpsOrderDO order = buildSecurityOrder(TEST_ORDER_ID, WorkOrderStatusEnum.ARRIVED); orderDB.put(TEST_ORDER_ID, order); + when(orderLifecycleManager.forceComplete(anyLong(), any(), any(), anyString())) + .thenReturn(OrderTransitionResult.builder().success(true).orderId(TEST_ORDER_ID).build()); // 执行:remark 为空 securityOrderService.autoCompleteOrder(TEST_ORDER_ID, null); // 验证使用默认备注 - verify(orderStateMachine).forceTransition( - any(), eq(WorkOrderStatusEnum.COMPLETED), - eq(OperatorTypeEnum.SYSTEM), isNull(), + verify(orderLifecycleManager).forceComplete( + eq(TEST_ORDER_ID), isNull(), + eq(OperatorTypeEnum.SYSTEM), eq("系统自动完单") ); } @@ -286,14 +289,16 @@ public class SecurityOrderServiceTest { // 准备 OpsOrderDO order = buildSecurityOrder(TEST_ORDER_ID, WorkOrderStatusEnum.ARRIVED); orderDB.put(TEST_ORDER_ID, order); + when(orderLifecycleManager.forceComplete(anyLong(), any(), any(), anyString())) + .thenReturn(OrderTransitionResult.builder().success(true).orderId(TEST_ORDER_ID).build()); // 执行:remark 为空白字符串 securityOrderService.autoCompleteOrder(TEST_ORDER_ID, " "); // 验证使用默认备注 - verify(orderStateMachine).forceTransition( - any(), eq(WorkOrderStatusEnum.COMPLETED), - eq(OperatorTypeEnum.SYSTEM), isNull(), + verify(orderLifecycleManager).forceComplete( + eq(TEST_ORDER_ID), isNull(), + eq(OperatorTypeEnum.SYSTEM), eq("系统自动完单") ); } @@ -313,16 +318,15 @@ public class SecurityOrderServiceTest { // 执行 securityOrderService.autoCompleteOrder(TEST_ORDER_ID, "告警自动解除"); - // 验证:走正常 transition → CANCELLED - verify(orderStateMachine).transition( - eq(order), - eq(WorkOrderStatusEnum.CANCELLED), - eq(OperatorTypeEnum.SYSTEM), + // 验证:走 cancelOrder(完整责任链) + verify(orderLifecycleManager).cancelOrder( + eq(TEST_ORDER_ID), isNull(), + eq(OperatorTypeEnum.SYSTEM), eq("告警自动解除(工单未派单)") ); - // 不应调用 forceTransition - verify(orderStateMachine, never()).forceTransition(any(), any(), any(), any(), any()); + // 不应调用 forceComplete + verify(orderLifecycleManager, never()).forceComplete(anyLong(), any(), any(), anyString()); } @Test @@ -330,16 +334,17 @@ public class SecurityOrderServiceTest { // 准备:DISPATCHED 状态工单 OpsOrderDO order = buildSecurityOrder(TEST_ORDER_ID, WorkOrderStatusEnum.DISPATCHED); orderDB.put(TEST_ORDER_ID, order); + when(orderLifecycleManager.forceComplete(anyLong(), any(), any(), anyString())) + .thenReturn(OrderTransitionResult.builder().success(true).orderId(TEST_ORDER_ID).build()); // 执行 securityOrderService.autoCompleteOrder(TEST_ORDER_ID, "告警自动解除"); - // 验证:走 forceTransition → COMPLETED - verify(orderStateMachine).forceTransition( - eq(order), - eq(WorkOrderStatusEnum.COMPLETED), - eq(OperatorTypeEnum.SYSTEM), + // 验证:走 forceComplete → COMPLETED(通过完整责任链) + verify(orderLifecycleManager).forceComplete( + eq(TEST_ORDER_ID), isNull(), + eq(OperatorTypeEnum.SYSTEM), eq("告警自动解除") ); } @@ -353,9 +358,9 @@ public class SecurityOrderServiceTest { // 执行:不应抛异常 securityOrderService.autoCompleteOrder(TEST_ORDER_ID, "告警自动解除"); - // 验证:状态机未被调用 - verify(orderStateMachine, never()).transition(any(), any(), any(), any(), any()); - verify(orderStateMachine, never()).forceTransition(any(), any(), any(), any(), any()); + // 验证:生命周期管理器未被调用 + verify(orderLifecycleManager, never()).forceComplete(anyLong(), any(), any(), anyString()); + verify(orderLifecycleManager, never()).cancelOrder(anyLong(), any(), any(), anyString()); } @Test @@ -363,16 +368,17 @@ public class SecurityOrderServiceTest { // 准备:DISPATCHED 状态工单 OpsOrderDO order = buildSecurityOrder(TEST_ORDER_ID, WorkOrderStatusEnum.DISPATCHED); orderDB.put(TEST_ORDER_ID, order); + when(orderLifecycleManager.forceComplete(anyLong(), any(), any(), anyString())) + .thenReturn(OrderTransitionResult.builder().success(true).orderId(TEST_ORDER_ID).build()); // 执行 securityOrderService.falseAlarmOrder(TEST_ORDER_ID); - // 验证:forceTransition 被调用(通过 autoCompleteOrder 委托) - verify(orderStateMachine).forceTransition( - eq(order), - eq(WorkOrderStatusEnum.COMPLETED), - eq(OperatorTypeEnum.SYSTEM), + // 验证:forceComplete 被调用(通过 autoCompleteOrder 委托) + verify(orderLifecycleManager).forceComplete( + eq(TEST_ORDER_ID), isNull(), + eq(OperatorTypeEnum.SYSTEM), eq("误报") ); @@ -403,12 +409,11 @@ public class SecurityOrderServiceTest { // 执行 securityOrderService.manualCompleteOrder(req); - // 验证状态机调用 - verify(orderStateMachine).transition( - eq(order), - eq(WorkOrderStatusEnum.COMPLETED), - eq(OperatorTypeEnum.SECURITY_GUARD), + // 验证走 orderLifecycleManager.completeOrder() + verify(orderLifecycleManager).completeOrder( + eq(TEST_ORDER_ID), eq(2001L), + eq(OperatorTypeEnum.SECURITY_GUARD), eq("安保人员提交处理结果") ); @@ -421,11 +426,6 @@ public class SecurityOrderServiceTest { assertNotNull(extUpdate.getResultImgUrls()); assertTrue(extUpdate.getResultImgUrls().contains("result1.jpg")); assertNull(extUpdate.getCompletedTime()); // 时间由 EventListener 写入 - - // 验证主表 endTime 更新 - ArgumentCaptor orderUpdateCaptor = ArgumentCaptor.forClass(OpsOrderDO.class); - verify(opsOrderMapper).updateById(orderUpdateCaptor.capture()); - assertNotNull(orderUpdateCaptor.getValue().getEndTime()); } @Test