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

@@ -64,6 +64,14 @@ public interface AreaDeviceService {
*/
void refreshAreaDeviceIndex();
/**
* 获取区域下的设备ID列表带读穿透缓存
*
* @param areaId 区域ID
* @return 设备ID集合
*/
java.util.Set<Long> getDeviceIdsByArea(Long areaId);
/**
* 添加设备到区域索引
*

View File

@@ -14,6 +14,7 @@ import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 区域设备关联服务实现
@@ -63,9 +64,10 @@ public class AreaDeviceServiceImpl implements AreaDeviceService, InitializingBea
private static final String NULL_CACHE = "NULL";
/**
* 缓存 TTL30 分钟
* 缓存 TTL24 小时
* 区域设备关系相对静态,可以设置较长过期时间
*/
private static final int CACHE_TTL_MINUTES = 30;
private static final int CACHE_TTL_HOURS = 24;
@Override
public void afterPropertiesSet() {
@@ -108,7 +110,8 @@ public class AreaDeviceServiceImpl implements AreaDeviceService, InitializingBea
}
// 从数据库查询
List<OpsAreaDeviceRelationDO> relations = relationMapper.selectListByAreaIdAndRelationType(areaId, relationType);
List<OpsAreaDeviceRelationDO> relations = relationMapper.selectListByAreaIdAndRelationType(areaId,
relationType);
// 返回第一个启用的
OpsAreaDeviceRelationDO relation = relations.stream()
@@ -123,9 +126,8 @@ public class AreaDeviceServiceImpl implements AreaDeviceService, InitializingBea
stringRedisTemplate.opsForValue().set(
cacheKey,
JsonUtils.toJsonString(dto),
CACHE_TTL_MINUTES,
TimeUnit.MINUTES
);
CACHE_TTL_HOURS,
TimeUnit.HOURS);
} else {
// 空值缓存,防止穿透
stringRedisTemplate.opsForValue().set(cacheKey, NULL_CACHE, 1, TimeUnit.MINUTES);
@@ -169,9 +171,8 @@ public class AreaDeviceServiceImpl implements AreaDeviceService, InitializingBea
stringRedisTemplate.opsForValue().set(
cacheKey,
JsonUtils.toJsonString(dto),
CACHE_TTL_MINUTES,
TimeUnit.MINUTES
);
CACHE_TTL_HOURS,
TimeUnit.HOURS);
} else {
// 空值缓存,防止穿透
stringRedisTemplate.opsForValue().set(cacheKey, NULL_CACHE, 1, TimeUnit.MINUTES);
@@ -213,6 +214,45 @@ public class AreaDeviceServiceImpl implements AreaDeviceService, InitializingBea
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) {
@@ -222,7 +262,8 @@ public class AreaDeviceServiceImpl implements AreaDeviceService, InitializingBea
try {
String areaKey = AREA_BADGES_KEY_PREFIX + areaId;
redisTemplate.opsForSet().add(areaKey, deviceId.toString());
redisTemplate.expire(areaKey, CACHE_TTL_MINUTES, TimeUnit.MINUTES);
// 延长过期时间到 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);