From 0ee7611a7df41e7832c12d848c07817b4d0fb47a Mon Sep 17 00:00:00 2001 From: lzh Date: Sun, 25 Jan 2026 09:38:36 +0800 Subject: [PATCH] =?UTF-8?q?feat(iot):=20=E4=BC=98=E5=8C=96=E4=BF=9D?= =?UTF-8?q?=E6=B4=81=E5=B7=A5=E5=8D=95=E9=85=8D=E7=BD=AE=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E6=9C=8D=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增空值缓存机制,防止缓存穿透 - 新增区域+类型配置查询缓存 - 统一缓存 TTL 为 5 分钟,空值缓存 60 秒 - 新增缓存失效方法 evictAreaCache Co-Authored-By: Claude Opus 4.5 --- ...leanOrderIntegrationConfigServiceImpl.java | 95 +++++++++++++++++-- 1 file changed, 89 insertions(+), 6 deletions(-) diff --git a/viewsh-module-iot/viewsh-module-iot-server/src/main/java/com/viewsh/module/iot/service/integration/clean/CleanOrderIntegrationConfigServiceImpl.java b/viewsh-module-iot/viewsh-module-iot-server/src/main/java/com/viewsh/module/iot/service/integration/clean/CleanOrderIntegrationConfigServiceImpl.java index 4227203..4ce922e 100644 --- a/viewsh-module-iot/viewsh-module-iot-server/src/main/java/com/viewsh/module/iot/service/integration/clean/CleanOrderIntegrationConfigServiceImpl.java +++ b/viewsh-module-iot/viewsh-module-iot-server/src/main/java/com/viewsh/module/iot/service/integration/clean/CleanOrderIntegrationConfigServiceImpl.java @@ -27,6 +27,8 @@ public class CleanOrderIntegrationConfigServiceImpl implements CleanOrderIntegra */ private static final String CONFIG_DEVICE_KEY_PATTERN = "iot:clean:config:device:%s"; private static final String CONFIG_AREA_KEY_PATTERN = "iot:clean:config:area:%s"; + private static final String CONFIG_WRAPPER_KEY_PATTERN = "iot:clean:config:wrapper:%s"; + private static final String CONFIG_AREA_TYPE_KEY_PATTERN = "iot:clean:config:area:%s:type:%s"; /** * 配置缓存 TTL(秒) @@ -34,6 +36,11 @@ public class CleanOrderIntegrationConfigServiceImpl implements CleanOrderIntegra * 5 分钟自动过期 */ private static final int CONFIG_CACHE_TTL_SECONDS = 300; + + /** + * 空值缓存标记(防止缓存穿透) + */ + private static final String NULL_CACHE_VALUE = "NULL"; @Resource private OpsAreaDeviceRelationMapper relationMapper; @@ -111,44 +118,106 @@ public class CleanOrderIntegrationConfigServiceImpl implements CleanOrderIntegra public AreaDeviceConfigWrapper getConfigByAreaIdAndRelationType(Long areaId, String relationType) { log.debug("[CleanOrderConfig] 查询单个区域配置:areaId={}, relationType={}", areaId, relationType); + // 1. 尝试从缓存读取 + String cacheKey = formatAreaTypeKey(areaId, relationType); + String cachedValue = stringRedisTemplate.opsForValue().get(cacheKey); + + if (cachedValue != null) { + if (NULL_CACHE_VALUE.equals(cachedValue)) { + log.debug("[CleanOrderConfig] 命中空值缓存:areaId={}, relationType={}", areaId, relationType); + return null; + } + log.debug("[CleanOrderConfig] 命中区域类型缓存:areaId={}, relationType={}", areaId, relationType); + return JsonUtils.parseObject(cachedValue, AreaDeviceConfigWrapper.class); + } + + // 2. 从数据库查询 List relations = relationMapper.selectListByAreaIdAndRelationType(areaId, relationType); if (relations.isEmpty()) { + // 缓存空值,防止缓存穿透 + stringRedisTemplate.opsForValue().set(cacheKey, NULL_CACHE_VALUE, 60, TimeUnit.SECONDS); return null; } // 返回第一个启用的配置 - return relations.stream() + AreaDeviceConfigWrapper wrapper = relations.stream() .filter(r -> r.getEnabled()) .findFirst() .map(this::wrapConfig) .orElse(null); + + // 3. 写入缓存 + if (wrapper != null) { + stringRedisTemplate.opsForValue().set( + cacheKey, + JsonUtils.toJsonString(wrapper), + CONFIG_CACHE_TTL_SECONDS, + TimeUnit.SECONDS + ); + } else { + // 缓存空值 + stringRedisTemplate.opsForValue().set(cacheKey, NULL_CACHE_VALUE, 60, TimeUnit.SECONDS); + } + + return wrapper; } @Override public AreaDeviceConfigWrapper getConfigWrapperByDeviceId(Long deviceId) { log.debug("[CleanOrderConfig] 查询设备完整配置:deviceId={}", deviceId); + // 1. 尝试从缓存读取 + String cacheKey = formatWrapperKey(deviceId); + String cachedValue = stringRedisTemplate.opsForValue().get(cacheKey); + + if (cachedValue != null) { + if (NULL_CACHE_VALUE.equals(cachedValue)) { + log.debug("[CleanOrderConfig] 命中空值缓存:deviceId={}", deviceId); + return null; + } + log.debug("[CleanOrderConfig] 命中 Wrapper 缓存:deviceId={}", deviceId); + return JsonUtils.parseObject(cachedValue, AreaDeviceConfigWrapper.class); + } + + // 2. 从数据库查询 OpsAreaDeviceRelationDO relation = relationMapper.selectByDeviceId(deviceId); if (relation == null || !relation.getEnabled()) { + // 缓存空值,防止缓存穿透(TTL 较短:60秒) + stringRedisTemplate.opsForValue().set(cacheKey, NULL_CACHE_VALUE, 60, TimeUnit.SECONDS); return null; } - return wrapConfig(relation); + AreaDeviceConfigWrapper wrapper = wrapConfig(relation); + + // 3. 写入缓存 + stringRedisTemplate.opsForValue().set( + cacheKey, + JsonUtils.toJsonString(wrapper), + CONFIG_CACHE_TTL_SECONDS, + TimeUnit.SECONDS + ); + + return wrapper; } @Override public void evictCache(Long deviceId) { - String cacheKey = formatDeviceKey(deviceId); - stringRedisTemplate.delete(cacheKey); + // 清除设备相关的所有缓存 + stringRedisTemplate.delete(formatDeviceKey(deviceId)); + stringRedisTemplate.delete(formatWrapperKey(deviceId)); log.info("[CleanOrderConfig] 清除设备配置缓存:deviceId={}", deviceId); } @Override public void evictAreaCache(Long areaId) { - String cacheKey = formatAreaKey(areaId); - stringRedisTemplate.delete(cacheKey); + // 清除区域相关的所有缓存(包括各类型) + stringRedisTemplate.delete(formatAreaKey(areaId)); + // 清除常用类型缓存 + stringRedisTemplate.delete(formatAreaTypeKey(areaId, "TRAFFIC_COUNTER")); + stringRedisTemplate.delete(formatAreaTypeKey(areaId, "BEACON")); + stringRedisTemplate.delete(formatAreaTypeKey(areaId, "BADGE")); log.info("[CleanOrderConfig] 清除区域配置缓存:areaId={}", areaId); } @@ -174,10 +243,24 @@ public class CleanOrderIntegrationConfigServiceImpl implements CleanOrderIntegra return String.format(CONFIG_DEVICE_KEY_PATTERN, deviceId); } + /** + * 格式化设备配置包装器缓存 Key + */ + private static String formatWrapperKey(Long deviceId) { + return String.format(CONFIG_WRAPPER_KEY_PATTERN, deviceId); + } + /** * 格式化区域配置缓存 Key */ private static String formatAreaKey(Long areaId) { return String.format(CONFIG_AREA_KEY_PATTERN, areaId); } + + /** + * 格式化区域+类型配置缓存 Key + */ + private static String formatAreaTypeKey(Long areaId, String relationType) { + return String.format(CONFIG_AREA_TYPE_KEY_PATTERN, areaId, relationType); + } }