You've already forked FrameTour-BE
feat(puzzle): 添加景区模板列表缓存功能
- 新增景区模板列表缓存KEY常量PUZZLE_TEMPLATES_BY_SCENIC_KEY - 在清除模板缓存时同步清除对应景区的模板列表缓存 - 实现listTemplateByScenic方法根据景区ID获取启用模板列表并缓存 - 实现clearTemplateByScenicCache方法清除景区模板列表缓存 - 重构人脸匹配编排器使用新的缓存方法替代原有数据库查询 - 移除过期的redisTemplate依赖
This commit is contained in:
@@ -51,6 +51,11 @@ public class PuzzleRepository {
|
|||||||
*/
|
*/
|
||||||
private static final String PUZZLE_RECORDS_BY_FACE_KEY = "puzzle:records:faceId:%s";
|
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)
|
* 单条生成记录缓存KEY(根据recordId)
|
||||||
*/
|
*/
|
||||||
@@ -167,6 +172,8 @@ public class PuzzleRepository {
|
|||||||
* @param code 模板编码(可为null,此时需要先查询获取)
|
* @param code 模板编码(可为null,此时需要先查询获取)
|
||||||
*/
|
*/
|
||||||
public void clearTemplateCache(Long id, String code) {
|
public void clearTemplateCache(Long id, String code) {
|
||||||
|
Long scenicId = null;
|
||||||
|
|
||||||
// 如果没有传code,尝试从缓存或数据库获取
|
// 如果没有传code,尝试从缓存或数据库获取
|
||||||
if (code == null && id != null) {
|
if (code == null && id != null) {
|
||||||
String idKey = String.format(PUZZLE_TEMPLATE_BY_ID_KEY, id);
|
String idKey = String.format(PUZZLE_TEMPLATE_BY_ID_KEY, id);
|
||||||
@@ -174,10 +181,25 @@ public class PuzzleRepository {
|
|||||||
if (cacheValue != null) {
|
if (cacheValue != null) {
|
||||||
PuzzleTemplateEntity template = JacksonUtil.parseObject(cacheValue, PuzzleTemplateEntity.class);
|
PuzzleTemplateEntity template = JacksonUtil.parseObject(cacheValue, PuzzleTemplateEntity.class);
|
||||||
code = template.getCode();
|
code = template.getCode();
|
||||||
|
scenicId = template.getScenicId();
|
||||||
} else {
|
} else {
|
||||||
PuzzleTemplateEntity template = templateMapper.getById(id);
|
PuzzleTemplateEntity template = templateMapper.getById(id);
|
||||||
if (template != null) {
|
if (template != null) {
|
||||||
code = template.getCode();
|
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) {
|
if (id != null) {
|
||||||
clearElementsCache(id);
|
clearElementsCache(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 清除景区模板列表缓存(确保列表数据一致性)
|
||||||
|
if (scenicId != null) {
|
||||||
|
clearTemplateByScenicCache(scenicId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== 元素缓存 ====================
|
// ==================== 元素缓存 ====================
|
||||||
@@ -245,6 +272,58 @@ public class PuzzleRepository {
|
|||||||
log.debug("清除元素缓存: templateId={}", templateId);
|
log.debug("清除元素缓存: templateId={}", templateId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== 景区模板列表缓存 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据景区ID获取启用的模板列表(优先从缓存读取)
|
||||||
|
* 用于人脸匹配后的批量拼图生成场景
|
||||||
|
*
|
||||||
|
* @param scenicId 景区ID
|
||||||
|
* @return 启用状态的模板列表
|
||||||
|
*/
|
||||||
|
public List<PuzzleTemplateEntity> 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<List<PuzzleTemplateEntity>>() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 从数据库查询(只查启用状态 status=1)
|
||||||
|
List<PuzzleTemplateEntity> 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:*");
|
deleteByPattern("puzzle:template:*");
|
||||||
// 使用 SCAN 删除元素缓存
|
// 使用 SCAN 删除元素缓存
|
||||||
deleteByPattern("puzzle:elements:*");
|
deleteByPattern("puzzle:elements:*");
|
||||||
|
// 使用 SCAN 删除景区模板列表缓存
|
||||||
|
deleteByPattern("puzzle:templates:*");
|
||||||
|
|
||||||
log.warn("拼图缓存清除完成");
|
log.warn("拼图缓存清除完成");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.PuzzleGenerateRequest;
|
||||||
import com.ycwl.basic.puzzle.dto.PuzzleGenerateResponse;
|
import com.ycwl.basic.puzzle.dto.PuzzleGenerateResponse;
|
||||||
import com.ycwl.basic.puzzle.dto.PuzzleTemplateDTO;
|
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.IPuzzleGenerateService;
|
||||||
import com.ycwl.basic.puzzle.service.IPuzzleTemplateService;
|
import com.ycwl.basic.puzzle.service.IPuzzleTemplateService;
|
||||||
|
|
||||||
@@ -102,9 +104,9 @@ public class FaceMatchingOrchestrator {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IPuzzleGenerateService puzzleGenerateService;
|
private IPuzzleGenerateService puzzleGenerateService;
|
||||||
@Autowired
|
@Autowired
|
||||||
private RedisTemplate<String, String> redisTemplate;
|
|
||||||
@Autowired
|
|
||||||
private FaceStatusManager faceStatusManager;
|
private FaceStatusManager faceStatusManager;
|
||||||
|
@Autowired
|
||||||
|
private PuzzleRepository puzzleRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编排人脸匹配的完整流程
|
* 编排人脸匹配的完整流程
|
||||||
@@ -364,17 +366,8 @@ public class FaceMatchingOrchestrator {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
private void asyncGeneratePuzzleTemplate(Long scenicId, Long faceId, Long memberId, String scene) {
|
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<PuzzleTemplateDTO> templateList = puzzleTemplateService.listTemplates(
|
List<PuzzleTemplateEntity> templateList = puzzleRepository.listTemplateByScenic(scenicId); // 查询启用状态的模板
|
||||||
scenicId, null, 1); // 查询启用状态的模板
|
|
||||||
|
|
||||||
if (templateList == null || templateList.isEmpty()) {
|
if (templateList == null || templateList.isEmpty()) {
|
||||||
log.debug("景区不存在启用的拼图模板,跳过生成: scenicId={}", scenicId);
|
log.debug("景区不存在启用的拼图模板,跳过生成: scenicId={}", scenicId);
|
||||||
|
|||||||
Reference in New Issue
Block a user