fix(ops): 修复工牌关机重启后工单状态不一致漏洞

问题场景:
1. 工牌有执行中工单(ARRIVED)后关机
2. 工牌重启,Redis状态丢失/过期,设备变为IDLE
3. 系统推送新工单
4. 信标检测仍在用旧工单配置,导致状态混乱

修复方案:
1. 派发新工单前检查并清理/取消旧工单残留
2. 设备离线时自动取消未完成的工单
3. 信标检测器增加工单切换检测,清理旧检测状态

涉及文件:
- BadgeDeviceStatusEventListener: 增加旧工单清理和离线事件监听
- BadgeDeviceStatusServiceImpl: 设备离线时发布事件
- BeaconDetectionRuleProcessor: 工单切换检测

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-02-03 22:59:06 +08:00
parent 3443d4dcd4
commit 5edbc9f287
3 changed files with 204 additions and 10 deletions

View File

@@ -19,6 +19,7 @@ import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 蓝牙信标检测规则处理器
@@ -53,6 +54,12 @@ public class BeaconDetectionRuleProcessor {
@Resource
private RocketMQTemplate rocketMQTemplate;
/**
* 上次检测的工单ID缓存设备ID -> 工单ID
* 用于检测工单切换,清理旧工单的检测状态
*/
private final Map<Long, Long> lastDetectedOrderCache = new ConcurrentHashMap<>();
/**
* 处理蓝牙属性上报
* <p>
@@ -75,12 +82,27 @@ public class BeaconDetectionRuleProcessor {
if (currentOrder == null || currentOrder.getAreaId() == null) {
log.debug("[BeaconDetection] 无当前工单跳过检测deviceId={}", deviceId);
// 无工单时清理本地缓存
lastDetectedOrderCache.remove(deviceId);
return;
}
Long areaId = currentOrder.getAreaId();
Long orderId = currentOrder.getOrderId();
// 3. 检测工单切换,清理旧工单的检测状态
Long lastOrderId = lastDetectedOrderCache.get(deviceId);
if (lastOrderId != null && !lastOrderId.equals(orderId)) {
log.warn("[BeaconDetection] 检测到工单切换,清理旧工单的检测状态: " +
"deviceId={}, oldOrderId={}, newOrderId={}", deviceId, lastOrderId, orderId);
// 清理旧的检测状态(清理当前设备的所有区域检测状态)
cleanupAllDetectionState(deviceId);
}
// 更新缓存
lastDetectedOrderCache.put(deviceId, orderId);
log.debug("[BeaconDetection] 从工单状态获取区域deviceId={}, areaId={}, orderId={}",
deviceId, areaId, currentOrder.getOrderId());
deviceId, areaId, orderId);
// 3. 获取该区域的信标配置(从 BEACON 类型的设备获取)
CleanOrderIntegrationConfigService.AreaDeviceConfigWrapper beaconConfigWrapper = configService
@@ -331,4 +353,26 @@ public class BeaconDetectionRuleProcessor {
BadgeDeviceStatusRedisDAO.OrderInfo currentOrder = badgeDeviceStatusRedisDAO.getCurrentOrder(deviceId);
return currentOrder != null && !currentOrder.getAreaId().equals(areaId);
}
/**
* 清理设备所有区域的检测状态
* <p>
* 用于工单切换场景,清理本地缓存。
* Redis 数据arrivedTime、signalLoss、rssiWindow由以下路径清理
* <ul>
* <li>工单完成时SignalLossRuleProcessor.cleanupRedisData()</li>
* <li>自然过期Redis TTL 自动清理</li>
* <li>新数据覆盖:每次检测都会更新滑动窗口</li>
* </ul>
*
* @param deviceId 设备ID
*/
private void cleanupAllDetectionState(Long deviceId) {
if (deviceId == null) {
return;
}
// 清理本地缓存
lastDetectedOrderCache.remove(deviceId);
log.info("[BeaconDetection] 已清理设备工单切换检测状态: deviceId={}", deviceId);
}
}