From 3291371dd7a3a53685f886345c25665c48dfd698 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Wed, 7 Jan 2026 15:03:34 +0800 Subject: [PATCH] =?UTF-8?q?feat(puzzle):=20=E6=B7=BB=E5=8A=A0=E6=99=AF?= =?UTF-8?q?=E5=8C=BA=E6=A8=A1=E6=9D=BF=E5=88=97=E8=A1=A8=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增景区模板列表缓存KEY常量PUZZLE_TEMPLATES_BY_SCENIC_KEY - 在清除模板缓存时同步清除对应景区的模板列表缓存 - 实现listTemplateByScenic方法根据景区ID获取启用模板列表并缓存 - 实现clearTemplateByScenicCache方法清除景区模板列表缓存 - 重构人脸匹配编排器使用新的缓存方法替代原有数据库查询 - 移除过期的redisTemplate依赖 --- .../puzzle/repository/PuzzleRepository.java | 81 +++++++++++++++++++ .../FaceMatchingOrchestrator.java | 17 ++-- 2 files changed, 86 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/ycwl/basic/puzzle/repository/PuzzleRepository.java b/src/main/java/com/ycwl/basic/puzzle/repository/PuzzleRepository.java index f4113ad3..311a4876 100644 --- a/src/main/java/com/ycwl/basic/puzzle/repository/PuzzleRepository.java +++ b/src/main/java/com/ycwl/basic/puzzle/repository/PuzzleRepository.java @@ -51,6 +51,11 @@ public class PuzzleRepository { */ private static final String PUZZLE_RECORDS_BY_FACE_KEY = "puzzle:records:faceId:%s"; + /** + * 景区模板列表缓存KEY(根据scenicId) + */ + private static final String PUZZLE_TEMPLATES_BY_SCENIC_KEY = "puzzle:templates:scenicId:%s"; + /** * 单条生成记录缓存KEY(根据recordId) */ @@ -167,6 +172,8 @@ public class PuzzleRepository { * @param code 模板编码(可为null,此时需要先查询获取) */ public void clearTemplateCache(Long id, String code) { + Long scenicId = null; + // 如果没有传code,尝试从缓存或数据库获取 if (code == null && id != null) { String idKey = String.format(PUZZLE_TEMPLATE_BY_ID_KEY, id); @@ -174,10 +181,25 @@ public class PuzzleRepository { if (cacheValue != null) { PuzzleTemplateEntity template = JacksonUtil.parseObject(cacheValue, PuzzleTemplateEntity.class); code = template.getCode(); + scenicId = template.getScenicId(); } else { PuzzleTemplateEntity template = templateMapper.getById(id); if (template != null) { code = template.getCode(); + scenicId = template.getScenicId(); + } + } + } else if (id != null) { + // 有 code 但需要获取 scenicId + String idKey = String.format(PUZZLE_TEMPLATE_BY_ID_KEY, id); + String cacheValue = redisTemplate.opsForValue().get(idKey); + if (cacheValue != null) { + PuzzleTemplateEntity template = JacksonUtil.parseObject(cacheValue, PuzzleTemplateEntity.class); + scenicId = template.getScenicId(); + } else { + PuzzleTemplateEntity template = templateMapper.getById(id); + if (template != null) { + scenicId = template.getScenicId(); } } } @@ -200,6 +222,11 @@ public class PuzzleRepository { if (id != null) { clearElementsCache(id); } + + // 清除景区模板列表缓存(确保列表数据一致性) + if (scenicId != null) { + clearTemplateByScenicCache(scenicId); + } } // ==================== 元素缓存 ==================== @@ -245,6 +272,58 @@ public class PuzzleRepository { log.debug("清除元素缓存: templateId={}", templateId); } + // ==================== 景区模板列表缓存 ==================== + + /** + * 根据景区ID获取启用的模板列表(优先从缓存读取) + * 用于人脸匹配后的批量拼图生成场景 + * + * @param scenicId 景区ID + * @return 启用状态的模板列表 + */ + public List listTemplateByScenic(Long scenicId) { + if (scenicId == null) { + log.warn("景区ID为空,跳过缓存查询"); + return templateMapper.list(null, null, 1); + } + + String cacheKey = String.format(PUZZLE_TEMPLATES_BY_SCENIC_KEY, scenicId); + + // 1. 尝试从缓存读取 + Boolean hasKey = redisTemplate.hasKey(cacheKey); + if (Boolean.TRUE.equals(hasKey)) { + String cacheValue = redisTemplate.opsForValue().get(cacheKey); + if (cacheValue != null) { + log.debug("从缓存读取景区模板列表: scenicId={}", scenicId); + return JacksonUtil.parseObject(cacheValue, new TypeReference>() {}); + } + } + + // 2. 从数据库查询(只查启用状态 status=1) + List templates = templateMapper.list(scenicId, null, 1); + + // 3. 写入缓存(即使是空列表也缓存,避免缓存穿透) + String json = JacksonUtil.toJSONString(templates); + redisTemplate.opsForValue().set(cacheKey, json, CACHE_EXPIRE_HOURS, TimeUnit.HOURS); + log.debug("景区模板列表缓存写入: scenicId={}, count={}", scenicId, templates.size()); + + return templates; + } + + /** + * 清除景区模板列表缓存 + * + * @param scenicId 景区ID + */ + public void clearTemplateByScenicCache(Long scenicId) { + if (scenicId == null) { + return; + } + String cacheKey = String.format(PUZZLE_TEMPLATES_BY_SCENIC_KEY, scenicId); + redisTemplate.delete(cacheKey); + log.debug("清除景区模板列表缓存: scenicId={}", scenicId); + } + // ==================== 批量清除 ==================== /** @@ -258,6 +337,8 @@ public class PuzzleRepository { deleteByPattern("puzzle:template:*"); // 使用 SCAN 删除元素缓存 deleteByPattern("puzzle:elements:*"); + // 使用 SCAN 删除景区模板列表缓存 + deleteByPattern("puzzle:templates:*"); log.warn("拼图缓存清除完成"); } diff --git a/src/main/java/com/ycwl/basic/service/pc/orchestrator/FaceMatchingOrchestrator.java b/src/main/java/com/ycwl/basic/service/pc/orchestrator/FaceMatchingOrchestrator.java index 4c138f2c..f1aa8812 100644 --- a/src/main/java/com/ycwl/basic/service/pc/orchestrator/FaceMatchingOrchestrator.java +++ b/src/main/java/com/ycwl/basic/service/pc/orchestrator/FaceMatchingOrchestrator.java @@ -6,6 +6,8 @@ import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO; import com.ycwl.basic.puzzle.dto.PuzzleGenerateRequest; import com.ycwl.basic.puzzle.dto.PuzzleGenerateResponse; import com.ycwl.basic.puzzle.dto.PuzzleTemplateDTO; +import com.ycwl.basic.puzzle.entity.PuzzleTemplateEntity; +import com.ycwl.basic.puzzle.repository.PuzzleRepository; import com.ycwl.basic.puzzle.service.IPuzzleGenerateService; import com.ycwl.basic.puzzle.service.IPuzzleTemplateService; @@ -102,9 +104,9 @@ public class FaceMatchingOrchestrator { @Autowired private IPuzzleGenerateService puzzleGenerateService; @Autowired - private RedisTemplate redisTemplate; - @Autowired private FaceStatusManager faceStatusManager; + @Autowired + private PuzzleRepository puzzleRepository; /** * 编排人脸匹配的完整流程 @@ -364,17 +366,8 @@ public class FaceMatchingOrchestrator { * @return */ private void asyncGeneratePuzzleTemplate(Long scenicId, Long faceId, Long memberId, String scene) { - if (redisTemplate.hasKey("puzzle_generated:face:" + faceId)) { - return; - } - redisTemplate.opsForValue().set( - "puzzle_generated:face:" + faceId, - "1", - 60 * 10, TimeUnit.SECONDS); - // 查询该景区所有启用状态的拼图模板 - List templateList = puzzleTemplateService.listTemplates( - scenicId, null, 1); // 查询启用状态的模板 + List templateList = puzzleRepository.listTemplateByScenic(scenicId); // 查询启用状态的模板 if (templateList == null || templateList.isEmpty()) { log.debug("景区不存在启用的拼图模板,跳过生成: scenicId={}", scenicId);