diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/listener/BadgeDeviceStatusEventListener.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/listener/BadgeDeviceStatusEventListener.java index 90792b7..f8e221b 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/listener/BadgeDeviceStatusEventListener.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/listener/BadgeDeviceStatusEventListener.java @@ -8,9 +8,6 @@ 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.environment.service.badge.BadgeDeviceStatusService; -import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain; -import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecord; -import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; @@ -25,6 +22,7 @@ import org.springframework.stereotype.Component; * - 使用 @EventListener 监听事件,在事务内同步执行 * - 确保 IoT 模块能实时查询到正确的设备工单信息 * - 只处理保洁类型的工单 + * - 不记录业务日志,由 CleanOrderEventListener 统一处理 *

* 状态处理: * @@ -41,12 +39,12 @@ import org.springframework.stereotype.Component; * * * - * + * * * * * - * + * * * * @@ -80,9 +78,6 @@ public class BadgeDeviceStatusEventListener { @Resource private OrderQueueService orderQueueService; - @Resource - private EventLogRecorder eventLogRecorder; - /** * 监听工单状态变更事件,同步更新设备工单关联 *

@@ -117,7 +112,7 @@ public class BadgeDeviceStatusEventListener { } // 根据状态更新设备工单关联 - handleOrderStatusTransition(deviceId, orderId, newStatus, event); + handleOrderStatusTransition(deviceId, orderId, newStatus, event, order); } catch (Exception e) { log.error("[BadgeDeviceStatusEventListener] 处理工单状态变更事件失败: orderId={}", event.getOrderId(), e); @@ -126,177 +121,154 @@ public class BadgeDeviceStatusEventListener { /** * 根据工单状态更新设备工牌关联 + *

+ * 设计原则:每个状态只更新变化的部分,避免重复设置相同数据 + *

+ * 状态处理逻辑: + *

*/ private void handleOrderStatusTransition(Long deviceId, Long orderId, WorkOrderStatusEnum newStatus, - OrderStateChangedEvent event) { - var waitingTasks = orderQueueService.getWaitingTasksByUserIdFromDb(deviceId); + OrderStateChangedEvent event, OpsOrderDO order) { switch (newStatus) { case DISPATCHED: - // 工单已推送到工牌,设置工单关联和区域信息 - // currentAreaId 应该设置为工单所属区域,而非设备物理位置 - badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.BUSY, null, "新工单已推送"); - - OpsOrderDO order = opsOrderMapper.selectById(orderId); - if (order != null && order.getAreaId() != null) { - badgeDeviceStatusService.setCurrentOrderInfo( - deviceId, orderId, - event.getNewStatus().getStatus(), - order.getAreaId(), - null - ); - log.info("[BadgeDeviceStatusEventListener] 工单已推送,设备状态转为 BUSY: deviceId={}, orderId={}, areaId={}", - deviceId, orderId, order.getAreaId()); - } else { - badgeDeviceStatusService.setCurrentOrder(deviceId, orderId); - log.info("[BadgeDeviceStatusEventListener] 工单已推送,设备状态转为 BUSY: deviceId={}, orderId={}", deviceId, - orderId); - } + handleDispatched(deviceId, orderId, order); break; case CONFIRMED: - // 设备按键确认,更新工单关联和区域信息 - // currentAreaId 应该设置为工单所属区域,而非设备物理位置 - OpsOrderDO order = opsOrderMapper.selectById(orderId); - if (order != null && order.getAreaId() != null) { - badgeDeviceStatusService.setCurrentOrderInfo( - deviceId, orderId, - event.getNewStatus().getStatus(), - order.getAreaId(), - null - ); - log.debug("[BadgeDeviceStatusEventListener] 工单已确认,更新区域信息: deviceId={}, orderId={}, areaId={}", - deviceId, orderId, order.getAreaId()); - } else { - badgeDeviceStatusService.setCurrentOrder(deviceId, orderId); - log.debug("[BadgeDeviceStatusEventListener] 工单已确认: deviceId={}, orderId={}", deviceId, orderId); - } + handleConfirmed(deviceId, orderId); break; case ARRIVED: - // 设备已到岗,设置完整的工单信息(areaId, beaconMac) - updateDeviceOrderInfo(deviceId, orderId, event); - recordOrderArrivedLog(orderId, deviceId, event); - log.info("[BadgeDeviceStatusEventListener] 工单已到岗,更新设备工单信息: deviceId={}, orderId={}", deviceId, orderId); + handleArrived(deviceId, orderId, event); break; case PAUSED: - // 检查是否是 P0 打断场景 - Long urgentOrderId = event.getPayloadLong("urgentOrderId"); - if (urgentOrderId != null) { - // P0 打断场景:不修改设备状态,保持当前状态 - // 紧接着会有 P0 工单的 DISPATCHED 事件,将当前工单更新为 P0 工单 - log.info( - "[BadgeDeviceStatusEventListener] P0打断场景,工单已暂停,等待P0工单派发: pausedOrderId={}, urgentOrderId={}, deviceId={}", - orderId, urgentOrderId, deviceId); - } else { - // 普通暂停场景:设备状态转为 PAUSED - badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.PAUSED, null, "任务暂停"); - log.info("[BadgeDeviceStatusEventListener] 任务暂停,设备状态转为 PAUSED: deviceId={}", deviceId); - } + handlePaused(deviceId, orderId, event); break; case COMPLETED: - // 任务完成,清除工单关联 - badgeDeviceStatusService.clearCurrentOrder(deviceId); - - // 检查是否有等待任务,决定设备状态 - if (waitingTasks != null && !waitingTasks.isEmpty()) { - // 有等待任务,设备状态保持 BUSY(由后续 DISPATCHED 事件更新) - log.info("[BadgeDeviceStatusEventListener] 任务完成,有{}个等待任务,设备保持 BUSY: deviceId={}", - waitingTasks.size(), deviceId); - } else { - // 无等待任务,设备状态转为 IDLE - badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.IDLE, null, "任务完成,无新任务"); - log.info("[BadgeDeviceStatusEventListener] 任务完成,无等待任务,设备转为 IDLE: deviceId={}", deviceId); - } + handleCompleted(deviceId); break; case CANCELLED: - // 检查被取消的工单是否是设备当前正在执行的工单 - BadgeDeviceStatusDTO deviceStatus = badgeDeviceStatusService.getBadgeStatus(deviceId); - Long currentOrderId = deviceStatus != null ? deviceStatus.getCurrentOpsOrderId() : null; - - if (orderId.equals(currentOrderId)) { - // 取消的是当前正在执行的工单,清除工单关联 - badgeDeviceStatusService.clearCurrentOrder(deviceId); - - // 检查是否有等待任务,决定设备状态 - if (waitingTasks != null && !waitingTasks.isEmpty()) { - // 有等待任务,设备状态保持 BUSY(由后续 DISPATCHED 事件更新) - log.info("[BadgeDeviceStatusEventListener] 当前工单已取消,有{}个等待任务,设备保持 BUSY: deviceId={}", - waitingTasks.size(), deviceId); - } else { - // 无等待任务,设备状态转为 IDLE - badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.IDLE, null, "工单已取消"); - log.info("[BadgeDeviceStatusEventListener] 当前工单已取消,无等待任务,设备转为 IDLE: deviceId={}", deviceId); - } - } else { - // 取消的不是当前工单(可能是队列中的等待任务),不需要修改设备状态 - log.debug( - "[BadgeDeviceStatusEventListener] 取消的工单非当前执行工单,跳过设备状态更新: cancelledOrderId={}, currentOrderId={}, deviceId={}", - orderId, currentOrderId, deviceId); - } + handleCancelled(deviceId, orderId); break; default: - // 其他状态不处理 break; } } /** - * 更新设备工单信息(ARRIVED 状态专用) - *

- * 设置完整的工单信息:工单ID、工单状态、区域ID、信标MAC + * 处理工单推送状态(首次设置工单关联) */ - private void updateDeviceOrderInfo(Long deviceId, Long orderId, OrderStateChangedEvent event) { - try { - // 从 payload 中提取信息 - Long areaId = event.getPayloadLong("areaId"); + private void handleDispatched(Long deviceId, Long orderId, OpsOrderDO order) { + // 设备状态转为 BUSY + badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.BUSY, null, "新工单已推送"); - String beaconMac = null; - Object beaconMacObj = event.getPayload().get("beaconMac"); - if (beaconMacObj != null) { - beaconMac = String.valueOf(beaconMacObj); - } - - // 使用 BadgeDeviceStatusService 设置完整工单信息 + // 首次设置工单关联:orderId + status + areaId + if (order != null && order.getAreaId() != null) { badgeDeviceStatusService.setCurrentOrderInfo( - deviceId, - orderId, - event.getNewStatus().getStatus(), - areaId, - beaconMac); - - log.debug("[BadgeDeviceStatusEventListener] 设备工单信息已更新: deviceId={}, orderId={}, areaId={}, beaconMac={}", - deviceId, orderId, areaId, beaconMac); - - } catch (Exception e) { - log.error("[BadgeDeviceStatusEventListener] 更新设备工单信息失败: deviceId={}, orderId={}", deviceId, orderId, e); + deviceId, orderId, + WorkOrderStatusEnum.DISPATCHED.getStatus(), + order.getAreaId(), + null + ); + log.info("[BadgeDeviceStatusEventListener] 工单已推送: deviceId={}, orderId={}, areaId={}", + deviceId, orderId, order.getAreaId()); + } else { + badgeDeviceStatusService.setCurrentOrder(deviceId, orderId); + log.info("[BadgeDeviceStatusEventListener] 工单已推送(无区域信息): deviceId={}, orderId={}", deviceId, orderId); } } /** - * 记录工单到岗业务日志 + * 处理工单确认状态(只更新状态,不重复设置 orderId/areaId) */ - private void recordOrderArrivedLog(Long orderId, Long deviceId, OrderStateChangedEvent event) { - try { - Long areaId = event.getPayloadLong("areaId"); - String deviceKey = (String) event.getPayload().get("deviceKey"); + private void handleConfirmed(Long deviceId, Long orderId) { + // 只更新工单状态,orderId/areaId 已在 DISPATCHED 设置 + badgeDeviceStatusService.updateOrderStatus(deviceId, WorkOrderStatusEnum.CONFIRMED.getStatus()); + log.debug("[BadgeDeviceStatusEventListener] 工单已确认: deviceId={}, orderId={}", deviceId, orderId); + } - eventLogRecorder.record(EventLogRecord.builder() - .module("clean") - .domain(EventDomain.BEACON) - .eventType("ORDER_ARRIVED") - .message(String.format("蓝牙信标自动到岗确认 [设备:%s, 区域:%d]", deviceKey, areaId)) - .targetId(orderId) - .targetType("order") - .deviceId(deviceId) - .personId(deviceId) - .build()); + /** + * 处理工单到岗状态(更新状态 + 信标MAC) + */ + private void handleArrived(Long deviceId, Long orderId, OrderStateChangedEvent event) { + // 从 payload 中提取信标信息 + String beaconMac = (String) event.getPayload().get("beaconMac"); - } catch (Exception e) { - log.warn("[BadgeDeviceStatusEventListener] 记录到岗业务日志失败: orderId={}", orderId, e); + // 更新状态和信标MAC(orderId/areaId 已设置) + badgeDeviceStatusService.updateOrderStatusAndBeacon(deviceId, orderId, + WorkOrderStatusEnum.ARRIVED.getStatus(), beaconMac); + + log.debug("[BadgeDeviceStatusEventListener] 工单已到岗,更新信标: deviceId={}, orderId={}, beaconMac={}", + deviceId, orderId, beaconMac); + } + + /** + * 处理工单暂停状态 + */ + private void handlePaused(Long deviceId, Long orderId, OrderStateChangedEvent event) { + Long urgentOrderId = event.getPayloadLong("urgentOrderId"); + if (urgentOrderId != null) { + // P0 打断场景:不修改设备状态,等待 P0 工单的 DISPATCHED 事件 + log.info("[BadgeDeviceStatusEventListener] P0打断场景,等待P0工单派发: pausedOrderId={}, urgentOrderId={}", + orderId, urgentOrderId); + } else { + // 普通暂停场景:设备状态转为 PAUSED + badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.PAUSED, null, "任务暂停"); + log.info("[BadgeDeviceStatusEventListener] 任务暂停: deviceId={}", deviceId); + } + } + + /** + * 处理工单完成状态(清除工单关联) + */ + private void handleCompleted(Long deviceId) { + badgeDeviceStatusService.clearCurrentOrder(deviceId); + + // 检查是否有等待任务,决定设备状态 + var waitingTasks = orderQueueService.getWaitingTasksByUserIdFromDb(deviceId); + if (waitingTasks != null && !waitingTasks.isEmpty()) { + log.info("[BadgeDeviceStatusEventListener] 任务完成,有{}个等待任务,设备保持 BUSY: deviceId={}", + waitingTasks.size(), deviceId); + } else { + badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.IDLE, null, "任务完成,无新任务"); + log.info("[BadgeDeviceStatusEventListener] 任务完成,无等待任务,设备转为 IDLE: deviceId={}", deviceId); + } + } + + /** + * 处理工单取消状态 + */ + private void handleCancelled(Long deviceId, Long orderId) { + // 检查被取消的工单是否是设备当前正在执行的工单 + BadgeDeviceStatusDTO deviceStatus = badgeDeviceStatusService.getBadgeStatus(deviceId); + Long currentOrderId = deviceStatus != null ? deviceStatus.getCurrentOpsOrderId() : null; + + if (orderId.equals(currentOrderId)) { + badgeDeviceStatusService.clearCurrentOrder(deviceId); + + // 检查是否有等待任务,决定设备状态 + var waitingTasks = orderQueueService.getWaitingTasksByUserIdFromDb(deviceId); + if (waitingTasks != null && !waitingTasks.isEmpty()) { + log.info("[BadgeDeviceStatusEventListener] 当前工单已取消,有{}个等待任务,设备保持 BUSY: deviceId={}", + waitingTasks.size(), deviceId); + } else { + badgeDeviceStatusService.updateBadgeStatus(deviceId, BadgeDeviceStatusEnum.IDLE, null, "工单已取消"); + log.info("[BadgeDeviceStatusEventListener] 当前工单已取消,无等待任务,设备转为 IDLE: deviceId={}", deviceId); + } + } else { + log.debug("[BadgeDeviceStatusEventListener] 取消的工单非当前执行工单,跳过: cancelledOrderId={}, currentOrderId={}", + orderId, currentOrderId); } } } diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/listener/CleanOrderEventListener.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/listener/CleanOrderEventListener.java index 62ea445..a6022ca 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/listener/CleanOrderEventListener.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/integration/listener/CleanOrderEventListener.java @@ -359,6 +359,9 @@ public class CleanOrderEventListener { playVoice(deviceId, arrivedMessage); } + // 4. 记录到岗业务日志 + recordOrderArrivedLog(orderId, deviceId, event); + log.info("[CleanOrderEventListener] 到岗时间已记录: orderId={}, deviceId={}", orderId, deviceId); } @@ -787,6 +790,32 @@ public class CleanOrderEventListener { } } + /** + * 记录工单到岗业务日志 + */ + private void recordOrderArrivedLog(Long orderId, Long deviceId, OrderStateChangedEvent event) { + try { + Long areaId = event.getPayloadLong("areaId"); + String deviceKey = (String) event.getPayload().get("deviceKey"); + String beaconMac = (String) event.getPayload().get("beaconMac"); + + eventLogRecorder.record(EventLogRecord.builder() + .module("clean") + .domain(EventDomain.BEACON) + .eventType("ORDER_ARRIVED") + .message(String.format("蓝牙信标自动到岗确认 [设备:%s, 区域:%d, 信标:%s]", + deviceKey, areaId, beaconMac)) + .targetId(orderId) + .targetType("order") + .deviceId(deviceId) + .personId(deviceId) + .build()); + + } catch (Exception e) { + log.warn("[CleanOrderEventListener] 记录到岗业务日志失败: orderId={}", orderId, e); + } + } + /** * 记录工单完成业务日志 */ 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 b3b3896..62c8544 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 @@ -153,6 +153,18 @@ public interface BadgeDeviceStatusService { */ void updateOrderStatus(Long deviceId, String orderStatus); + /** + * 更新工单状态和信标MAC + *

+ * 用于到岗场景,同时更新工单状态和信标MAC地址 + * + * @param deviceId 设备ID + * @param orderId 工单ID + * @param orderStatus 工单状态 + * @param beaconMac 信标MAC地址(可为null) + */ + void updateOrderStatusAndBeacon(Long deviceId, Long orderId, String orderStatus, String beaconMac); + /** * 清除当前工单(包括工单ID、工单状态、信标MAC) * @@ -178,37 +190,6 @@ public interface BadgeDeviceStatusService { */ void updateBadgeArea(Long deviceId, Long areaId, String areaName); - /** - * 初始化区域设备索引 - *

- * 从 ops_area_device_relation 表加载 BADGE 类型的设备, - * 建立区域到设备的索引关系 - */ - void initAreaDeviceIndex(); - - /** - * 刷新区域设备索引 - *

- * 重新从数据库加载区域设备关系 - */ - void refreshAreaDeviceIndex(); - - /** - * 将设备添加到区域索引 - * - * @param deviceId 设备ID - * @param areaId 区域ID - */ - void addToAreaIndex(Long deviceId, Long areaId); - - /** - * 从区域索引移除设备 - * - * @param deviceId 设备ID - * @param areaId 区域ID - */ - void removeFromAreaIndex(Long deviceId, Long areaId); - // ==================== 设备管理 ==================== /** 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 3c2ae2d..73e736b 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 @@ -53,8 +53,8 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I @Override public void afterPropertiesSet() { - // 启动时初始化区域设备索引 - initAreaDeviceIndex(); + // 启动时初始化区域设备配置缓存 + areaDeviceService.initConfigCache(); } // ==================== 状态管理 ==================== @@ -163,13 +163,14 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I } try { - Set deviceIds = areaDeviceService.getDeviceIdsByArea(areaId); + // 直接从DB查询该区域的工牌设备ID列表 + List deviceIds = areaDeviceService.getDeviceIdsByAreaAndType(areaId, "BADGE"); if (deviceIds.isEmpty()) { return Collections.emptyList(); } - // 使用批量查询,避免 N+1 - return batchGetBadgeStatus(new ArrayList<>(deviceIds)).stream() + // 批量查询设备状态,避免 N+1 + return batchGetBadgeStatus(deviceIds).stream() .filter(Objects::nonNull) .filter(dto -> dto.getStatus() != null && dto.getStatus().isActive()) .sorted(Comparator.comparing(BadgeDeviceStatusDTO::getStatusChangeTime)) @@ -188,14 +189,14 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I } try { - // 使用读穿透方式获取设备ID列表,缓存未命中时会从数据库重建 - Set deviceIds = areaDeviceService.getDeviceIdsByArea(areaId); + // 直接从DB查询该区域的工牌设备ID列表 + List deviceIds = areaDeviceService.getDeviceIdsByAreaAndType(areaId, "BADGE"); if (deviceIds.isEmpty()) { return Collections.emptyList(); } - // 使用批量查询,避免 N+1 - return batchGetBadgeStatus(new ArrayList<>(deviceIds)).stream() + // 批量查询设备状态,避免 N+1 + return batchGetBadgeStatus(deviceIds).stream() .filter(Objects::nonNull) .filter(dto -> dto.getStatus() == BadgeDeviceStatusEnum.IDLE) .sorted(Comparator.comparing(BadgeDeviceStatusDTO::getLastHeartbeatTime).reversed()) @@ -387,6 +388,26 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I } } + @Override + public void updateOrderStatusAndBeacon(Long deviceId, Long orderId, String orderStatus, String beaconMac) { + if (deviceId == null) { + return; + } + + try { + String key = BADGE_STATUS_KEY_PREFIX + deviceId; + // 更新工单状态和信标MAC(orderId 和 areaId 已在 DISPATCHED 时设置,不需要重复) + redisTemplate.opsForHash().put(key, "currentOrderStatus", orderStatus); + if (beaconMac != null) { + redisTemplate.opsForHash().put(key, "beaconMac", beaconMac); + } + log.debug("更新工单状态和信标: deviceId={}, orderId={}, orderStatus={}, beaconMac={}", + deviceId, orderId, orderStatus, beaconMac); + } catch (Exception e) { + log.error("更新工单状态和信标失败: deviceId={}, orderId={}", deviceId, orderId, e); + } + } + @Override public void clearCurrentOrder(Long deviceId) { if (deviceId == null) { @@ -448,44 +469,12 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I redisTemplate.opsForHash().putAll(key, statusMap); } - if (areaId != null) { - addToAreaIndex(deviceId, areaId); - } - log.debug("更新工牌设备区域: deviceId={}, areaId={}, areaName={}", deviceId, areaId, areaName); } catch (Exception e) { log.error("更新工牌设备区域失败: deviceId={}", deviceId, e); } } - // ==================== 区域管理 ==================== - - @Override - public void initAreaDeviceIndex() { - log.info("[BadgeDeviceStatusService] 开始初始化区域设备索引..."); - // 委托给 AreaDeviceService 处理 - areaDeviceService.initAreaDeviceIndex(); - log.info("[BadgeDeviceStatusService] 区域设备索引初始化完成"); - } - - @Override - public void refreshAreaDeviceIndex() { - log.info("[BadgeDeviceStatusService] 开始刷新区域设备索引..."); - // 委托给 AreaDeviceService 处理 - areaDeviceService.refreshAreaDeviceIndex(); - log.info("[BadgeDeviceStatusService] 区域设备索引刷新完成"); - } - - @Override - public void addToAreaIndex(Long deviceId, Long areaId) { - areaDeviceService.addToAreaIndex(deviceId, areaId); - } - - @Override - public void removeFromAreaIndex(Long deviceId, Long areaId) { - areaDeviceService.removeFromAreaIndex(deviceId, areaId); - } - // ==================== 设备管理 ==================== @Override diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/area/AreaDeviceService.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/area/AreaDeviceService.java index 3328580..04f646d 100644 --- a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/area/AreaDeviceService.java +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/area/AreaDeviceService.java @@ -8,7 +8,6 @@ import java.util.List; * 区域设备关联服务 *

* 职责:管理运营区域与 IoT 设备的关联关系 - * 提供缓存能力,支持快速查询 * * @author lzh */ @@ -24,9 +23,17 @@ public interface AreaDeviceService { List listByAreaIdAndType(Long areaId, String relationType); /** - * 根据区域ID和关联类型查询单个启用的关联关系 + * 根据区域ID查询所有关联关系 + * + * @param areaId 区域ID + * @return 关联关系列表 + */ + List listByAreaId(Long areaId); + + /** + * 根据区域ID和关联类型查询单个启用的关联关系(带缓存) *

- * 带缓存,返回第一个启用的配置 + * 缓存Key: ops:area:{areaId}:type:{relationType} * * @param areaId 区域ID * @param relationType 关联类型 @@ -34,16 +41,10 @@ public interface AreaDeviceService { */ OpsAreaDeviceRelationDO getByAreaIdAndType(Long areaId, String relationType); - /** - * 根据区域ID查询所有启用的关联关系 - * - * @param areaId 区域ID - * @return 关联关系列表 - */ - List listByAreaId(Long areaId); - /** * 根据设备ID查询关联关系 + *

+ * 注意:如果设备关联多个区域,只返回第一个 * * @param deviceId 设备ID * @return 关联关系,不存在返回 null @@ -51,54 +52,53 @@ public interface AreaDeviceService { OpsAreaDeviceRelationDO getByDeviceId(Long deviceId); /** - * 初始化区域设备索引 + * 获取设备关联的所有区域ID *

- * 从数据库加载所有区域设备关系,建立 Redis 索引 + * 从数据库直接查询(无缓存) + * + * @param deviceId 设备ID + * @return 区域ID集合 */ - void initAreaDeviceIndex(); + List getAreaIdsByDevice(Long deviceId); /** - * 刷新区域设备索引 + * 获取区域关联的所有设备ID *

- * 重新从数据库加载并建立索引 - */ - void refreshAreaDeviceIndex(); - - /** - * 获取区域下的设备ID列表(带读穿透缓存) + * 从数据库直接查询(无缓存) * * @param areaId 区域ID - * @return 设备ID集合 + * @return 设备ID列表 */ - java.util.Set getDeviceIdsByArea(Long areaId); + List getDeviceIdsByArea(Long areaId); /** - * 添加设备到区域索引 + * 获取区域关联的指定类型设备ID + *

+ * 从数据库直接查询(无缓存) * - * @param deviceId 设备ID - * @param areaId 区域ID + * @param areaId 区域ID + * @param relationType 关联类型 + * @return 设备ID列表 */ - void addToAreaIndex(Long deviceId, Long areaId); + List getDeviceIdsByAreaAndType(Long areaId, String relationType); /** - * 从区域索引移除设备 - * - * @param deviceId 设备ID - * @param areaId 区域ID + * 初始化区域设备配置缓存 + *

+ * 预热 ops:area:{areaId}:type:{relationType} 缓存 */ - void removeFromAreaIndex(Long deviceId, Long areaId); + void initConfigCache(); /** - * 清除区域缓存 - * - * @param areaId 区域ID + * 刷新区域设备配置缓存 */ - void evictAreaCache(Long areaId); + void refreshConfigCache(); /** - * 清除设备缓存 + * 清除区域配置缓存 * - * @param deviceId 设备ID + * @param areaId 区域ID + * @param relationType 关联类型 */ - void evictDeviceCache(Long deviceId); + void evictConfigCache(Long areaId, String relationType); } diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/area/AreaDeviceServiceImpl.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/area/AreaDeviceServiceImpl.java index 1ddbd80..8c3f2e4 100644 --- a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/area/AreaDeviceServiceImpl.java +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/service/area/AreaDeviceServiceImpl.java @@ -7,22 +7,20 @@ import com.viewsh.module.ops.dal.mysql.area.OpsAreaDeviceRelationMapper; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.InitializingBean; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import java.util.List; -import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * 区域设备关联服务实现 *

- * 职责: - * 1. 管理运营区域与 IoT 设备的关联关系 - * 2. 维护 Redis 缓存索引 - * 3. 提供快速查询能力 + * 缓存设计: + * - 唯一缓存Key: ops:area:{areaId}:type:{relationType} + * - 用途: 存储区域+类型的设备配置(1对1绑定设备) + * - N对N设备(如工牌): 先查DB获取关联区域,再用此Key获取配置 * * @author lzh */ @@ -33,30 +31,15 @@ public class AreaDeviceServiceImpl implements AreaDeviceService, InitializingBea @Resource private OpsAreaDeviceRelationMapper relationMapper; - @Resource - private RedisTemplate redisTemplate; - @Resource private StringRedisTemplate stringRedisTemplate; /** - * Redis Key 前缀 - */ - private static final String AREA_BADGES_KEY_PREFIX = "ops:area:badges:"; - - /** - * 设备配置缓存 Key 前缀(JSON 格式,IoT 可读) + * 缓存Key前缀: ops:area:{areaId}:type:{relationType} *

- * 格式: ops:area:device:{deviceId} + * 唯一的区域设备配置缓存,适用于所有设备类型 */ - private static final String DEVICE_CACHE_KEY_PREFIX = "ops:area:device:"; - - /** - * 区域+类型配置缓存 Key 前缀(JSON 格式,IoT 可读) - *

- * 格式: ops:area:{areaId}:type:{relationType} - */ - private static final String AREA_TYPE_CACHE_KEY_PREFIX = "ops:area:%s:type:%s"; + private static final String CONFIG_CACHE_KEY_PREFIX = "ops:area:%s:type:%s"; /** * 空值缓存标记 @@ -64,26 +47,22 @@ public class AreaDeviceServiceImpl implements AreaDeviceService, InitializingBea private static final String NULL_CACHE = "NULL"; /** - * 缓存 TTL(24 小时) - * 区域设备关系相对静态,可以设置较长过期时间 + * 缓存TTL(24小时) */ private static final int CACHE_TTL_HOURS = 24; @Override public void afterPropertiesSet() { - // 启动时初始化区域设备索引 - initAreaDeviceIndex(); + initConfigCache(); } @Override public List listByAreaIdAndType(Long areaId, String relationType) { - log.debug("[AreaDevice] 查询区域设备关联:areaId={}, relationType={}", areaId, relationType); return relationMapper.selectListByAreaIdAndRelationType(areaId, relationType); } @Override public List listByAreaId(Long areaId) { - log.debug("[AreaDevice] 查询区域所有设备:areaId={}", areaId); return relationMapper.selectListByAreaId(areaId); } @@ -93,33 +72,31 @@ public class AreaDeviceServiceImpl implements AreaDeviceService, InitializingBea return null; } - // 先从缓存读取(缓存存储的是 AreaDeviceDTO JSON) - String cacheKey = String.format(AREA_TYPE_CACHE_KEY_PREFIX, areaId, relationType); + String cacheKey = String.format(CONFIG_CACHE_KEY_PREFIX, areaId, relationType); + + // 1. 先读缓存 try { String cached = stringRedisTemplate.opsForValue().get(cacheKey); if (cached != null) { if (NULL_CACHE.equals(cached)) { return null; } - log.debug("[AreaDevice] 命中区域类型配置缓存:areaId={}, type={}", areaId, relationType); + log.debug("[AreaDevice] 命中配置缓存:areaId={}, type={}", areaId, relationType); AreaDeviceDTO dto = JsonUtils.parseObject(cached, AreaDeviceDTO.class); return toAreaDeviceRelationDO(dto); } } catch (Exception e) { - log.warn("[AreaDevice] 读取区域类型配置缓存失败:areaId={}, type={}", areaId, relationType, e); + log.warn("[AreaDevice] 读取配置缓存失败:areaId={}, type={}", areaId, relationType, e); } - // 从数据库查询 - List relations = relationMapper.selectListByAreaIdAndRelationType(areaId, - relationType); - - // 返回第一个启用的 + // 2. 缓存未命中,查数据库 + List relations = relationMapper.selectListByAreaIdAndRelationType(areaId, relationType); OpsAreaDeviceRelationDO relation = relations.stream() .filter(r -> r.getEnabled()) .findFirst() .orElse(null); - // 写入 JSON 缓存(IoT 可读,存储 AreaDeviceDTO) + // 3. 写入缓存 try { if (relation != null) { AreaDeviceDTO dto = toAreaDeviceDTO(relation); @@ -129,11 +106,10 @@ public class AreaDeviceServiceImpl implements AreaDeviceService, InitializingBea CACHE_TTL_HOURS, TimeUnit.HOURS); } else { - // 空值缓存,防止穿透 stringRedisTemplate.opsForValue().set(cacheKey, NULL_CACHE, 1, TimeUnit.MINUTES); } } catch (Exception e) { - log.warn("[AreaDevice] 写入区域类型配置缓存失败:areaId={}, type={}", areaId, relationType, e); + log.warn("[AreaDevice] 写入配置缓存失败:areaId={}, type={}", areaId, relationType, e); } return relation; @@ -144,219 +120,108 @@ public class AreaDeviceServiceImpl implements AreaDeviceService, InitializingBea if (deviceId == null) { return null; } - - // 先从缓存读取(缓存存储的是 AreaDeviceDTO JSON) - String cacheKey = DEVICE_CACHE_KEY_PREFIX + deviceId; - try { - String cached = stringRedisTemplate.opsForValue().get(cacheKey); - if (cached != null) { - if (NULL_CACHE.equals(cached)) { - return null; - } - log.debug("[AreaDevice] 命中设备配置缓存:deviceId={}", deviceId); - AreaDeviceDTO dto = JsonUtils.parseObject(cached, AreaDeviceDTO.class); - return toAreaDeviceRelationDO(dto); - } - } catch (Exception e) { - log.warn("[AreaDevice] 读取设备配置缓存失败:deviceId={}", deviceId, e); - } - - // 从数据库查询 - OpsAreaDeviceRelationDO relation = relationMapper.selectByDeviceId(deviceId); - - // 写入 JSON 缓存(IoT 可读,存储 AreaDeviceDTO) - try { - if (relation != null) { - AreaDeviceDTO dto = toAreaDeviceDTO(relation); - stringRedisTemplate.opsForValue().set( - cacheKey, - JsonUtils.toJsonString(dto), - CACHE_TTL_HOURS, - TimeUnit.HOURS); - } else { - // 空值缓存,防止穿透 - stringRedisTemplate.opsForValue().set(cacheKey, NULL_CACHE, 1, TimeUnit.MINUTES); - } - } catch (Exception e) { - log.warn("[AreaDevice] 写入设备配置缓存失败:deviceId={}", deviceId, e); - } - - return relation; + // 直接查数据库,返回第一个关联 + return relationMapper.selectByDeviceId(deviceId); } @Override - public void initAreaDeviceIndex() { - log.info("[AreaDevice] 开始初始化区域设备索引..."); - - try { - // 1. 查询所有工牌设备关联,建立区域索引 - List badgeRelations = relationMapper - .selectListByAreaIdAndRelationType(null, "BADGE"); - - int badgeCount = 0; - for (OpsAreaDeviceRelationDO relation : badgeRelations) { - if (relation.getAreaId() != null && relation.getDeviceId() != null) { - addToAreaIndex(relation.getDeviceId(), relation.getAreaId()); - badgeCount++; - } - } - - log.info("[AreaDevice] 工牌设备索引初始化完成:共加载 {} 个工牌设备关联", badgeCount); - - // 2. 查询所有信标设备关联,预热区域+类型配置缓存 - List beaconRelations = relationMapper - .selectListByAreaIdAndRelationType(null, "BEACON"); - - int beaconCount = 0; - for (OpsAreaDeviceRelationDO relation : beaconRelations) { - if (relation.getAreaId() != null && relation.getEnabled()) { - // 写入区域+类型配置缓存 - String cacheKey = String.format(AREA_TYPE_CACHE_KEY_PREFIX, relation.getAreaId(), "BEACON"); - try { - AreaDeviceDTO dto = toAreaDeviceDTO(relation); - stringRedisTemplate.opsForValue().set( - cacheKey, - JsonUtils.toJsonString(dto), - CACHE_TTL_HOURS, - TimeUnit.HOURS); - beaconCount++; - } catch (Exception e) { - log.warn("[AreaDevice] 写入信标配置缓存失败:areaId={}", relation.getAreaId(), e); - } - } - } - - log.info("[AreaDevice] 信标配置缓存初始化完成:共加载 {} 个信标配置", beaconCount); - - } catch (Exception e) { - log.error("[AreaDevice] 区域设备索引初始化失败", e); - } - } - - @Override - public void refreshAreaDeviceIndex() { - log.info("[AreaDevice] 开始刷新区域设备索引..."); - initAreaDeviceIndex(); - } - - @Override - public Set getDeviceIdsByArea(Long areaId) { - if (areaId == null) { - return Set.of(); - } - - String areaKey = AREA_BADGES_KEY_PREFIX + areaId; - try { - Set members = redisTemplate.opsForSet().members(areaKey); - if (members != null && !members.isEmpty()) { - return members.stream() - .map(m -> Long.parseLong(m.toString())) - .collect(Collectors.toSet()); - } - - // 缓存未命中,读穿透从数据库重构 - log.info("[AreaDevice] 区域索引缓存未命中,从数据库重建:areaId={}", areaId); - List relations = relationMapper.selectListByAreaIdAndRelationType(areaId, "BADGE"); - - Set deviceIds = relations.stream() - .filter(OpsAreaDeviceRelationDO::getEnabled) - .map(OpsAreaDeviceRelationDO::getDeviceId) - .collect(Collectors.toSet()); - - if (!deviceIds.isEmpty()) { - // 写入缓存 - String[] idStrings = deviceIds.stream().map(Object::toString).toArray(String[]::new); - redisTemplate.opsForSet().add(areaKey, (Object[]) idStrings); - redisTemplate.expire(areaKey, CACHE_TTL_HOURS, TimeUnit.HOURS); - } - - return deviceIds; - - } catch (Exception e) { - log.error("[AreaDevice] 获取区域设备索引失败:areaId={}", areaId, e); - return Set.of(); - } - } - - @Override - public void addToAreaIndex(Long deviceId, Long areaId) { - if (deviceId == null || areaId == null) { - return; - } - - try { - String areaKey = AREA_BADGES_KEY_PREFIX + areaId; - redisTemplate.opsForSet().add(areaKey, deviceId.toString()); - // 延长过期时间到 24 小时 - redisTemplate.expire(areaKey, CACHE_TTL_HOURS, TimeUnit.HOURS); - log.debug("[AreaDevice] 添加设备到区域索引:deviceId={}, areaId={}", deviceId, areaId); - } catch (Exception e) { - log.error("[AreaDevice] 添加设备到区域索引失败:deviceId={}, areaId={}", deviceId, areaId, e); - } - } - - @Override - public void removeFromAreaIndex(Long deviceId, Long areaId) { - if (deviceId == null || areaId == null) { - return; - } - - try { - String areaKey = AREA_BADGES_KEY_PREFIX + areaId; - redisTemplate.opsForSet().remove(areaKey, deviceId.toString()); - log.debug("[AreaDevice] 从区域索引移除设备:deviceId={}, areaId={}", deviceId, areaId); - } catch (Exception e) { - log.error("[AreaDevice] 从区域索引移除设备失败:deviceId={}, areaId={}", deviceId, areaId, e); - } - } - - @Override - public void evictAreaCache(Long areaId) { - if (areaId == null) { - return; - } - - try { - String areaKey = AREA_BADGES_KEY_PREFIX + areaId; - redisTemplate.delete(areaKey); - log.info("[AreaDevice] 清除区域缓存:areaId={}", areaId); - } catch (Exception e) { - log.error("[AreaDevice] 清除区域缓存失败:areaId={}", areaId, e); - } - } - - @Override - public void evictDeviceCache(Long deviceId) { + public List getAreaIdsByDevice(Long deviceId) { if (deviceId == null) { - return; + return List.of(); } + return relationMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(OpsAreaDeviceRelationDO::getDeviceId, deviceId) + .select(OpsAreaDeviceRelationDO::getAreaId)) + .stream() + .map(OpsAreaDeviceRelationDO::getAreaId) + .collect(Collectors.toList()); + } + + @Override + public List getDeviceIdsByArea(Long areaId) { + if (areaId == null) { + return List.of(); + } + return relationMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(OpsAreaDeviceRelationDO::getAreaId, areaId) + .select(OpsAreaDeviceRelationDO::getDeviceId)) + .stream() + .map(OpsAreaDeviceRelationDO::getDeviceId) + .collect(Collectors.toList()); + } + + @Override + public List getDeviceIdsByAreaAndType(Long areaId, String relationType) { + if (areaId == null || relationType == null) { + return List.of(); + } + return relationMapper.selectListByAreaIdAndRelationType(areaId, relationType).stream() + .filter(OpsAreaDeviceRelationDO::getEnabled) + .map(OpsAreaDeviceRelationDO::getDeviceId) + .collect(Collectors.toList()); + } + + @Override + public void initConfigCache() { + log.info("[AreaDevice] 开始初始化区域设备配置缓存..."); try { - String cacheKey = DEVICE_CACHE_KEY_PREFIX + deviceId; - stringRedisTemplate.delete(cacheKey); - log.info("[AreaDevice] 清除设备配置缓存:deviceId={}", deviceId); + // 查询所有启用的设备关联 + List allRelations = relationMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(OpsAreaDeviceRelationDO::getEnabled, true)); + + // 按区域+类型分组,预热缓存 + int cacheCount = 0; + for (OpsAreaDeviceRelationDO relation : allRelations) { + if (relation.getAreaId() == null || relation.getRelationType() == null) { + continue; + } + + String cacheKey = String.format(CONFIG_CACHE_KEY_PREFIX, + relation.getAreaId(), relation.getRelationType()); + + try { + AreaDeviceDTO dto = toAreaDeviceDTO(relation); + stringRedisTemplate.opsForValue().set( + cacheKey, + JsonUtils.toJsonString(dto), + CACHE_TTL_HOURS, + TimeUnit.HOURS); + cacheCount++; + } catch (Exception e) { + log.warn("[AreaDevice] 写入配置缓存失败:areaId={}, type={}", + relation.getAreaId(), relation.getRelationType(), e); + } + } + + log.info("[AreaDevice] 配置缓存初始化完成:共加载 {} 个配置", cacheCount); + } catch (Exception e) { - log.error("[AreaDevice] 清除设备配置缓存失败:deviceId={}", deviceId, e); + log.error("[AreaDevice] 配置缓存初始化失败", e); } } - /** - * 清除区域+类型配置缓存 - * - * @param areaId 区域ID - * @param relationType 关联类型 - */ - public void evictAreaTypeCache(Long areaId, String relationType) { + @Override + public void refreshConfigCache() { + log.info("[AreaDevice] 开始刷新区域设备配置缓存..."); + initConfigCache(); + } + + @Override + public void evictConfigCache(Long areaId, String relationType) { if (areaId == null || relationType == null) { return; } try { - String cacheKey = String.format(AREA_TYPE_CACHE_KEY_PREFIX, areaId, relationType); + String cacheKey = String.format(CONFIG_CACHE_KEY_PREFIX, areaId, relationType); stringRedisTemplate.delete(cacheKey); - log.info("[AreaDevice] 清除区域类型配置缓存:areaId={}, type={}", areaId, relationType); + log.info("[AreaDevice] 清除配置缓存:areaId={}, type={}", areaId, relationType); } catch (Exception e) { - log.error("[AreaDevice] 清除区域类型配置缓存失败:areaId={}, type={}", areaId, relationType, e); + log.error("[AreaDevice] 清除配置缓存失败:areaId={}, type={}", areaId, relationType, e); } }

CONFIRMED保持BUSY设置更新状态
ARRIVED保持BUSY设置完整信息更新状态+信标
PAUSED