From 7dd3c9a5c4b64a3034db037becab7f1310862acd Mon Sep 17 00:00:00 2001 From: lzh Date: Wed, 25 Feb 2026 17:59:54 +0800 Subject: [PATCH] =?UTF-8?q?fix(ops):=20=E4=BF=AE=E5=A4=8D=E5=B7=A5?= =?UTF-8?q?=E7=89=8C=E8=AE=BE=E5=A4=87=E7=8A=B6=E6=80=81=E6=AE=8B=E7=95=99?= =?UTF-8?q?=20BUSY=20=E5=AF=BC=E8=87=B4=E4=B8=8B=E4=B8=80=E5=B7=A5?= =?UTF-8?q?=E5=8D=95=E6=97=A0=E6=B3=95=E6=B4=BE=E5=8F=91=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 repairDeviceOrderConsistency 方法,检测设备关联的工单是否已终态, 若是则清除 currentOpsOrderId 并将设备状态恢复为 IDLE - 定时对账 Job 增加工单一致性检查,自动修复历史残留 - 新增管理员手动修复 API:POST /ops/clean/order/repair-device-status - 修复预存 bug:valueOf("busy") 改为 fromCode("busy") 避免 IllegalArgumentException Co-Authored-By: Claude Opus 4.6 --- .../job/BadgeDeviceStatusSyncJob.java | 22 +++++--- .../badge/BadgeDeviceStatusService.java | 17 +++++++ .../badge/BadgeDeviceStatusServiceImpl.java | 50 ++++++++++++++++++- .../admin/clean/CleanWorkOrderController.java | 13 +++++ 4 files changed, 94 insertions(+), 8 deletions(-) diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/job/BadgeDeviceStatusSyncJob.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/job/BadgeDeviceStatusSyncJob.java index a803910..2005e13 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/job/BadgeDeviceStatusSyncJob.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/job/BadgeDeviceStatusSyncJob.java @@ -64,8 +64,8 @@ public class BadgeDeviceStatusSyncJob { public String execute() { try { SyncResult result = syncAllBadgeDeviceStatus(); - return StrUtil.format("工牌状态对账完成: 处理 {} 台,修正 {} 台,耗时 {} ms", - result.syncCount, result.correctedCount, result.durationMs); + return StrUtil.format("工牌状态对账完成: 处理 {} 台,修正 {} 台,工单修复 {} 台,耗时 {} ms", + result.syncCount, result.correctedCount, result.orderRepairedCount, result.durationMs); } catch (Exception e) { log.error("[SyncJob] 全量对账失败", e); return StrUtil.format("工牌状态对账失败: {}", e.getMessage()); @@ -84,6 +84,7 @@ public class BadgeDeviceStatusSyncJob { long startTime = System.currentTimeMillis(); int syncCount = 0; int correctedCount = 0; + int orderRepairedCount = 0; // 1. 获取所有工牌设备关联关系 List badgeRelations = areaDeviceRelationMapper @@ -91,7 +92,7 @@ public class BadgeDeviceStatusSyncJob { if (CollUtil.isEmpty(badgeRelations)) { log.info("[SyncJob] 无工牌设备,跳过对账"); - return new SyncResult(0, 0, System.currentTimeMillis() - startTime); + return new SyncResult(0, 0, 0, System.currentTimeMillis() - startTime); } List deviceIds = badgeRelations.stream() @@ -117,6 +118,13 @@ public class BadgeDeviceStatusSyncJob { // 4. 逐一对账并修正 for (DeviceStatusRespDTO iotStatus : iotResult.getData()) { + // 4a. 工单一致性检查(修复残留的已终态工单关联) + boolean orderRepaired = badgeDeviceStatusService.repairDeviceOrderConsistency(iotStatus.getDeviceId()); + if (orderRepaired) { + orderRepairedCount++; + } + + // 4b. IoT 在线/离线状态对账 boolean corrected = syncSingleDevice(iotStatus, deviceAreaMap.get(iotStatus.getDeviceId())); syncCount++; if (corrected) { @@ -125,10 +133,10 @@ public class BadgeDeviceStatusSyncJob { } long duration = System.currentTimeMillis() - startTime; - log.info("[SyncJob] 全量对账完成,共处理 {} 台设备,修正 {} 台,耗时 {} ms", - syncCount, correctedCount, duration); + log.info("[SyncJob] 全量对账完成,共处理 {} 台设备,修正 {} 台,工单修复 {} 台,耗时 {} ms", + syncCount, correctedCount, orderRepairedCount, duration); - return new SyncResult(syncCount, correctedCount, duration); + return new SyncResult(syncCount, correctedCount, orderRepairedCount, duration); } /** @@ -189,6 +197,6 @@ public class BadgeDeviceStatusSyncJob { /** * 同步结果 */ - public record SyncResult(int syncCount, int correctedCount, long durationMs) { + public record SyncResult(int syncCount, int correctedCount, int orderRepairedCount, long durationMs) { } } diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/BadgeDeviceStatusService.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/BadgeDeviceStatusService.java index 4b506f5..0abe7d2 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/BadgeDeviceStatusService.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/BadgeDeviceStatusService.java @@ -180,6 +180,23 @@ public interface BadgeDeviceStatusService { */ List listBadgesWithCurrentOrder(); + // ==================== 工单一致性修复 ==================== + + /** + * 修复设备工单一致性 + *

+ * 检查设备关联的 currentOpsOrderId 对应的工单是否已终态(COMPLETED/CANCELLED), + * 如果是,则清除工单关联并将设备状态设为 IDLE。 + *

+ * 适用场景: + * - 历史手动完单未触发事件导致的状态残留 + * - 异常情况下工单已终态但设备状态未同步 + * + * @param deviceId 设备ID + * @return true=进行了修复,false=无需修复 + */ + boolean repairDeviceOrderConsistency(Long deviceId); + // ==================== 区域管理 ==================== /** diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/BadgeDeviceStatusServiceImpl.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/BadgeDeviceStatusServiceImpl.java index da9b54e..da35701 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/BadgeDeviceStatusServiceImpl.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/badge/BadgeDeviceStatusServiceImpl.java @@ -1,7 +1,10 @@ package com.viewsh.module.ops.environment.service.badge; import com.viewsh.module.ops.api.badge.BadgeDeviceStatusDTO; +import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; +import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper; import com.viewsh.module.ops.enums.BadgeDeviceStatusEnum; +import com.viewsh.module.ops.enums.WorkOrderStatusEnum; import com.viewsh.module.ops.service.area.AreaDeviceService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; @@ -42,6 +45,9 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I @Resource private AreaDeviceService areaDeviceService; + @Resource + private OpsOrderMapper opsOrderMapper; + @Resource private ApplicationEventPublisher eventPublisher; @@ -277,7 +283,7 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I log.info("[updateBadgeOnlineStatus] 设备 {} 重新上线,但有进行中工单 {},保持状态 {}", deviceId, currentOrderIdObj, currentStatusStr); // 强制修正传入的状态为当前实际状态 - status = BadgeDeviceStatusEnum.valueOf(currentStatusStr); + status = BadgeDeviceStatusEnum.fromCode(currentStatusStr); } } } @@ -464,6 +470,48 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I } } + // ==================== 工单一致性修复 ==================== + + @Override + public boolean repairDeviceOrderConsistency(Long deviceId) { + if (deviceId == null) { + return false; + } + + try { + BadgeDeviceStatusDTO deviceStatus = getBadgeStatus(deviceId); + if (deviceStatus == null || deviceStatus.getCurrentOpsOrderId() == null) { + return false; + } + + Long orderId = deviceStatus.getCurrentOpsOrderId(); + OpsOrderDO order = opsOrderMapper.selectById(orderId); + + // 工单仍在进行中,无需修复 + if (order != null && !WorkOrderStatusEnum.fromStatus(order.getStatus()).isTerminal()) { + return false; + } + + // 工单不存在或已终态 → 清除关联并恢复设备为 IDLE + log.warn("[repairDeviceOrderConsistency] 清除残留工单关联: deviceId={}, orderId={}, orderStatus={}", + deviceId, orderId, order != null ? order.getStatus() : "NOT_FOUND"); + + clearCurrentOrder(deviceId); + + // 直接写 status 字段,避免 updateBadgeStatus 内部回写已清除的 currentOpsOrderId + if (deviceStatus.getStatus() != null && deviceStatus.getStatus() != BadgeDeviceStatusEnum.IDLE) { + String key = BADGE_STATUS_KEY_PREFIX + deviceId; + redisTemplate.opsForHash().put(key, "status", BadgeDeviceStatusEnum.IDLE.getCode()); + redisTemplate.opsForHash().put(key, "statusChangeTime", LocalDateTime.now().toString()); + } + return true; + + } catch (Exception e) { + log.error("[repairDeviceOrderConsistency] 修复失败: deviceId={}", deviceId, e); + return false; + } + } + @Override public void updateBadgeArea(Long deviceId, Long areaId, String areaName) { if (deviceId == null) { diff --git a/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/clean/CleanWorkOrderController.java b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/clean/CleanWorkOrderController.java index 0948acc..2842d81 100644 --- a/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/clean/CleanWorkOrderController.java +++ b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/clean/CleanWorkOrderController.java @@ -5,6 +5,7 @@ import com.viewsh.framework.security.core.util.SecurityFrameworkUtils; import com.viewsh.module.ops.api.clean.OrderTimelineRespDTO; import com.viewsh.module.ops.environment.dal.dataobject.ManualCompleteOrderReqDTO; import com.viewsh.module.ops.environment.dal.dataobject.UpgradePriorityReqDTO; +import com.viewsh.module.ops.environment.service.badge.BadgeDeviceStatusService; import com.viewsh.module.ops.environment.service.cleanorder.CleanWorkOrderService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -33,6 +34,9 @@ public class CleanWorkOrderController { @Resource private CleanWorkOrderService cleanWorkOrderService; + @Resource + private BadgeDeviceStatusService badgeDeviceStatusService; + @GetMapping("/timeline/{orderId}") @Operation(summary = "工单时间轴") @Parameter(name = "orderId", description = "工单ID", required = true) @@ -65,4 +69,13 @@ public class CleanWorkOrderController { cleanWorkOrderService.upgradePriority(req); return success(true); } + + @PostMapping("/repair-device-status") + @Operation(summary = "修复工牌设备状态", description = "当设备状态残留为BUSY但关联工单已完成/取消时,修复设备状态为IDLE") + @Parameter(name = "deviceId", description = "设备ID", required = true) + @PreAuthorize("@ss.hasPermission('ops:clean:order:complete')") + public CommonResult repairDeviceStatus(@RequestParam("deviceId") Long deviceId) { + boolean repaired = badgeDeviceStatusService.repairDeviceOrderConsistency(deviceId); + return success(repaired); + } }