fix(ops): 修复区域设备索引缓存问题并优化查询性能
Some checks failed
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled

1. 修复 listAvailableBadges() 读穿透 bug
   - 改用 areaDeviceService.getDeviceIdsByArea() 获取设备列表
   - 缓存未命中时自动从数据库重建

2. 优化 N+1 查询问题
   - listBadgesByArea() 和 listAvailableBadges() 使用 batchGetBadgeStatus() 批量查询

3. 简化 BadgeDeviceStatusServiceImpl
   - 移除重复的 AREA_BADGES_KEY_PREFIX 常量
   - 区域索引操作委托给 AreaDeviceService 处理

4. 增强缓存可靠性
   - getDeviceIdsByArea() 支持读穿透缓存
   - 缓存 TTL 从 30 分钟延长到 24 小时

Co-Authored-By: Claude (MiniMax-M2.1) <noreply@anthropic.com>
This commit is contained in:
lzh
2026-01-29 18:32:34 +08:00
parent 6234709e19
commit 569ca2c0da
3 changed files with 75 additions and 62 deletions

View File

@@ -7,7 +7,6 @@ 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.stereotype.Service;
import java.time.LocalDateTime;
@@ -23,13 +22,11 @@ import java.util.stream.Collectors;
* 职责:
* 1. 设备状态管理IDLE/BUSY/PAUSED/OFFLINE
* 2. 设备与工单关联管理
* 3. 区域设备索引管理
* 4. 心跳超时检查
* 3. 区域设备索引查询转发
* <p>
* 设计说明:
* - 状态变更事件
* {@link com.viewsh.module.ops.environment.integration.listener.BadgeDeviceStatusEventListener}
* 处理
* - 状态变更由 IoT 事件驱动或定时对账任务触发
* - 区域索引维护委托给 {@link AreaDeviceService}
* - 本类只提供基础的服务方法
*
* @author lzh
@@ -48,12 +45,6 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I
* Redis Key 前缀
*/
private static final String BADGE_STATUS_KEY_PREFIX = "ops:badge:status:";
private static final String AREA_BADGES_KEY_PREFIX = "ops:area:badges:";
/**
* 心跳超时时间(分钟)
*/
private static final int HEARTBEAT_TIMEOUT_MINUTES = 30;
/**
* 状态过期时间(小时)
@@ -172,16 +163,13 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I
}
try {
String areaKey = AREA_BADGES_KEY_PREFIX + areaId;
Set<Object> deviceIds = redisTemplate.opsForSet().members(areaKey);
if (deviceIds == null || deviceIds.isEmpty()) {
Set<Long> deviceIds = areaDeviceService.getDeviceIdsByArea(areaId);
if (deviceIds.isEmpty()) {
return Collections.emptyList();
}
return deviceIds.stream()
.map(id -> Long.parseLong(id.toString()))
.map(this::getBadgeStatus)
// 使用批量查询,避免 N+1
return batchGetBadgeStatus(new ArrayList<>(deviceIds)).stream()
.filter(Objects::nonNull)
.filter(dto -> dto.getStatus() != null && dto.getStatus().isActive())
.sorted(Comparator.comparing(BadgeDeviceStatusDTO::getStatusChangeTime))
@@ -200,16 +188,14 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I
}
try {
String areaKey = AREA_BADGES_KEY_PREFIX + areaId;
Set<Object> deviceIds = redisTemplate.opsForSet().members(areaKey);
if (deviceIds == null || deviceIds.isEmpty()) {
// 使用读穿透方式获取设备ID列表缓存未命中时会从数据库重建
Set<Long> deviceIds = areaDeviceService.getDeviceIdsByArea(areaId);
if (deviceIds.isEmpty()) {
return Collections.emptyList();
}
return deviceIds.stream()
.map(id -> Long.parseLong(id.toString()))
.map(this::getBadgeStatus)
// 使用批量查询,避免 N+1
return batchGetBadgeStatus(new ArrayList<>(deviceIds)).stream()
.filter(Objects::nonNull)
.filter(dto -> dto.getStatus() == BadgeDeviceStatusEnum.IDLE)
.sorted(Comparator.comparing(BadgeDeviceStatusDTO::getLastHeartbeatTime).reversed())
@@ -274,13 +260,11 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I
statusMap.put("lastHeartbeatTime", now);
}
// 更新区域信息
// 更新实时物理区域信息 (Key2)
if (areaId != null) {
statusMap.put("currentAreaId", areaId);
// 更新区域索引
addToAreaIndex(deviceId, areaId);
} else {
// 保持现有区域信息
// 保持现有实时物理区域信息
Object existingAreaId = currentMap.get("currentAreaId");
if (existingAreaId != null) {
statusMap.put("currentAreaId", existingAreaId);
@@ -474,32 +458,12 @@ public class BadgeDeviceStatusServiceImpl implements BadgeDeviceStatusService, I
@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());
log.debug("添加设备到区域索引: deviceId={}, areaId={}", deviceId, areaId);
} catch (Exception e) {
log.error("添加设备到区域索引失败: deviceId={}, areaId={}", deviceId, areaId, e);
}
areaDeviceService.addToAreaIndex(deviceId, areaId);
}
@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("从区域索引移除设备: deviceId={}, areaId={}", deviceId, areaId);
} catch (Exception e) {
log.error("从区域索引移除设备失败: deviceId={}, areaId={}", deviceId, areaId, e);
}
areaDeviceService.removeFromAreaIndex(deviceId, areaId);
}
// ==================== 设备管理 ====================