refactor(ops): 简化AreaDevice与BadgeDevice服务实现
- AreaDeviceService: 移���设备索引缓存逻辑(由IoT模块管理) - AreaDeviceServiceImpl: 简化实现,直接查询数据库 - BadgeDeviceStatusService: 更新接口方法签名 - BadgeDeviceStatusServiceImpl: 修复语法错误,简化实现 - BadgeDeviceStatusEventListener: 适配事件处理逻辑 - CleanOrderEventListener: 更新事件处理 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 统一处理
|
||||
* <p>
|
||||
* 状态处理:
|
||||
* <table>
|
||||
@@ -41,12 +39,12 @@ import org.springframework.stereotype.Component;
|
||||
* <tr>
|
||||
* <td>CONFIRMED</td>
|
||||
* <td>保持BUSY</td>
|
||||
* <td>设置</td>
|
||||
* <td>更新状态</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>ARRIVED</td>
|
||||
* <td>保持BUSY</td>
|
||||
* <td>设置完整信息</td>
|
||||
* <td>更新状态+信标</td>
|
||||
* </tr>
|
||||
* <tr>
|
||||
* <td>PAUSED</td>
|
||||
@@ -80,9 +78,6 @@ public class BadgeDeviceStatusEventListener {
|
||||
@Resource
|
||||
private OrderQueueService orderQueueService;
|
||||
|
||||
@Resource
|
||||
private EventLogRecorder eventLogRecorder;
|
||||
|
||||
/**
|
||||
* 监听工单状态变更事件,同步更新设备工单关联
|
||||
* <p>
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
* 根据工单状态更新设备工牌关联
|
||||
* <p>
|
||||
* 设计原则:每个状态只更新变化的部分,避免重复设置相同数据
|
||||
* <p>
|
||||
* 状态处理逻辑:
|
||||
* <ul>
|
||||
* <li>DISPATCHED: 首次设置工单关联(orderId + areaId)</li>
|
||||
* <li>CONFIRMED: 只更新状态,orderId/areaId 已在 DISPATCHED 设置</li>
|
||||
* <li>ARRIVED: 更新状态 + 信标MAC</li>
|
||||
* <li>PAUSED: 只更新设备状态,工单关联保持</li>
|
||||
* <li>COMPLETED: 清除工单关联,根据等待任务决定设备状态</li>
|
||||
* <li>CANCELLED: 清除工单关联(如果是当前工单),根据等待任务决定设备状态</li>
|
||||
* </ul>
|
||||
*/
|
||||
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 状态专用)
|
||||
* <p>
|
||||
* 设置完整的工单信息:工单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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录工单完成业务日志
|
||||
*/
|
||||
|
||||
@@ -153,6 +153,18 @@ public interface BadgeDeviceStatusService {
|
||||
*/
|
||||
void updateOrderStatus(Long deviceId, String orderStatus);
|
||||
|
||||
/**
|
||||
* 更新工单状态和信标MAC
|
||||
* <p>
|
||||
* 用于到岗场景,同时更新工单状态和信标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);
|
||||
|
||||
/**
|
||||
* 初始化区域设备索引
|
||||
* <p>
|
||||
* 从 ops_area_device_relation 表加载 BADGE 类型的设备,
|
||||
* 建立区域到设备的索引关系
|
||||
*/
|
||||
void initAreaDeviceIndex();
|
||||
|
||||
/**
|
||||
* 刷新区域设备索引
|
||||
* <p>
|
||||
* 重新从数据库加载区域设备关系
|
||||
*/
|
||||
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);
|
||||
|
||||
// ==================== 设备管理 ====================
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<Long> deviceIds = areaDeviceService.getDeviceIdsByArea(areaId);
|
||||
// 直接从DB查询该区域的工牌设备ID列表
|
||||
List<Long> 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<Long> deviceIds = areaDeviceService.getDeviceIdsByArea(areaId);
|
||||
// 直接从DB查询该区域的工牌设备ID列表
|
||||
List<Long> 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
|
||||
|
||||
@@ -8,7 +8,6 @@ import java.util.List;
|
||||
* 区域设备关联服务
|
||||
* <p>
|
||||
* 职责:管理运营区域与 IoT 设备的关联关系
|
||||
* 提供缓存能力,支持快速查询
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@@ -24,9 +23,17 @@ public interface AreaDeviceService {
|
||||
List<OpsAreaDeviceRelationDO> listByAreaIdAndType(Long areaId, String relationType);
|
||||
|
||||
/**
|
||||
* 根据区域ID和关联类型查询单个启用的关联关系
|
||||
* 根据区域ID查询所有关联关系
|
||||
*
|
||||
* @param areaId 区域ID
|
||||
* @return 关联关系列表
|
||||
*/
|
||||
List<OpsAreaDeviceRelationDO> listByAreaId(Long areaId);
|
||||
|
||||
/**
|
||||
* 根据区域ID和关联类型查询单个启用的关联关系(带缓存)
|
||||
* <p>
|
||||
* 带缓存,返回第一个启用的配置
|
||||
* 缓存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<OpsAreaDeviceRelationDO> listByAreaId(Long areaId);
|
||||
|
||||
/**
|
||||
* 根据设备ID查询关联关系
|
||||
* <p>
|
||||
* 注意:如果设备关联多个区域,只返回第一个
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @return 关联关系,不存在返回 null
|
||||
@@ -51,54 +52,53 @@ public interface AreaDeviceService {
|
||||
OpsAreaDeviceRelationDO getByDeviceId(Long deviceId);
|
||||
|
||||
/**
|
||||
* 初始化区域设备索引
|
||||
* 获取设备关联的所有区域ID
|
||||
* <p>
|
||||
* 从数据库加载所有区域设备关系,建立 Redis 索引
|
||||
* 从数据库直接查询(无缓存)
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @return 区域ID集合
|
||||
*/
|
||||
void initAreaDeviceIndex();
|
||||
List<Long> getAreaIdsByDevice(Long deviceId);
|
||||
|
||||
/**
|
||||
* 刷新区域设备索引
|
||||
* 获取区域关联的所有设备ID
|
||||
* <p>
|
||||
* 重新从数据库加载并建立索引
|
||||
*/
|
||||
void refreshAreaDeviceIndex();
|
||||
|
||||
/**
|
||||
* 获取区域下的设备ID列表(带读穿透缓存)
|
||||
* 从数据库直接查询(无缓存)
|
||||
*
|
||||
* @param areaId 区域ID
|
||||
* @return 设备ID集合
|
||||
* @return 设备ID列表
|
||||
*/
|
||||
java.util.Set<Long> getDeviceIdsByArea(Long areaId);
|
||||
List<Long> getDeviceIdsByArea(Long areaId);
|
||||
|
||||
/**
|
||||
* 添加设备到区域索引
|
||||
* 获取区域关联的指定类型设备ID
|
||||
* <p>
|
||||
* 从数据库直接查询(无缓存)
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
* @param areaId 区域ID
|
||||
* @param relationType 关联类型
|
||||
* @return 设备ID列表
|
||||
*/
|
||||
void addToAreaIndex(Long deviceId, Long areaId);
|
||||
List<Long> getDeviceIdsByAreaAndType(Long areaId, String relationType);
|
||||
|
||||
/**
|
||||
* 从区域索引移除设备
|
||||
*
|
||||
* @param deviceId 设备ID
|
||||
* @param areaId 区域ID
|
||||
* 初始化区域设备配置缓存
|
||||
* <p>
|
||||
* 预热 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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
* 区域设备关联服务实现
|
||||
* <p>
|
||||
* 职责:
|
||||
* 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<String, Object> 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}
|
||||
* <p>
|
||||
* 格式: ops:area:device:{deviceId}
|
||||
* 唯一的区域设备配置缓存,适用于所有设备类型
|
||||
*/
|
||||
private static final String DEVICE_CACHE_KEY_PREFIX = "ops:area:device:";
|
||||
|
||||
/**
|
||||
* 区域+类型配置缓存 Key 前缀(JSON 格式,IoT 可读)
|
||||
* <p>
|
||||
* 格式: 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<OpsAreaDeviceRelationDO> listByAreaIdAndType(Long areaId, String relationType) {
|
||||
log.debug("[AreaDevice] 查询区域设备关联:areaId={}, relationType={}", areaId, relationType);
|
||||
return relationMapper.selectListByAreaIdAndRelationType(areaId, relationType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OpsAreaDeviceRelationDO> 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<OpsAreaDeviceRelationDO> relations = relationMapper.selectListByAreaIdAndRelationType(areaId,
|
||||
relationType);
|
||||
|
||||
// 返回第一个启用的
|
||||
// 2. 缓存未命中,查数据库
|
||||
List<OpsAreaDeviceRelationDO> 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<OpsAreaDeviceRelationDO> 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<OpsAreaDeviceRelationDO> 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<Long> getDeviceIdsByArea(Long areaId) {
|
||||
if (areaId == null) {
|
||||
return Set.of();
|
||||
}
|
||||
|
||||
String areaKey = AREA_BADGES_KEY_PREFIX + areaId;
|
||||
try {
|
||||
Set<Object> 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<OpsAreaDeviceRelationDO> relations = relationMapper.selectListByAreaIdAndRelationType(areaId, "BADGE");
|
||||
|
||||
Set<Long> 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<Long> getAreaIdsByDevice(Long deviceId) {
|
||||
if (deviceId == null) {
|
||||
return;
|
||||
return List.of();
|
||||
}
|
||||
return relationMapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<OpsAreaDeviceRelationDO>()
|
||||
.eq(OpsAreaDeviceRelationDO::getDeviceId, deviceId)
|
||||
.select(OpsAreaDeviceRelationDO::getAreaId))
|
||||
.stream()
|
||||
.map(OpsAreaDeviceRelationDO::getAreaId)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> getDeviceIdsByArea(Long areaId) {
|
||||
if (areaId == null) {
|
||||
return List.of();
|
||||
}
|
||||
return relationMapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<OpsAreaDeviceRelationDO>()
|
||||
.eq(OpsAreaDeviceRelationDO::getAreaId, areaId)
|
||||
.select(OpsAreaDeviceRelationDO::getDeviceId))
|
||||
.stream()
|
||||
.map(OpsAreaDeviceRelationDO::getDeviceId)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> 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<OpsAreaDeviceRelationDO> allRelations = relationMapper.selectList(
|
||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<OpsAreaDeviceRelationDO>()
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user