package com.ycwl.basic.biz; import com.ycwl.basic.enums.FaceCutStatus; import com.ycwl.basic.enums.FacePieceUpdateStatus; import com.ycwl.basic.enums.TemplateRenderStatus; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.concurrent.TimeUnit; /** * 人脸状态缓存管理器 * 统一管理人脸相关的Redis缓存状态 */ @Slf4j @Component public class FaceStatusManager { /** * 人脸切片状态缓存Key * 格式: face:status:cut:{faceId} * 过期时间: 1天 */ private static final String FACE_CUT_STATUS_KEY = "face:status:cut:%s"; /** * 人脸片段更新状态缓存Key(全局) * 格式: face:status:piece:update:{faceId} * 过期时间: 1天 * 键存在 = 无新片段,键不存在 = 有新片段 */ private static final String FACE_PIECE_UPDATE_KEY = "face:status:piece:update:%s"; /** * 人脸模板片段更新状态缓存Key(按模板) * 格式: face:status:piece:update:{faceId}:{templateId} * 过期时间: 1天 * 键存在 = 无新片段,键不存在 = 有新片段 */ private static final String FACE_TEMPLATE_PIECE_UPDATE_KEY = "face:status:piece:update:%s:%s"; /** * 人脸模板渲染状态缓存Key * 格式: face:status:render:{faceId}:{templateId} * 过期时间: 永久(或根据业务需要设置) */ private static final String FACE_TEMPLATE_RENDER_KEY = "face:status:render:%s:%s"; /** * 默认过期时间:1天 */ private static final long DEFAULT_EXPIRE_SECONDS = 86400L; @Autowired private RedisTemplate redisTemplate; // ==================== 切片状态相关方法 ==================== /** * 设置人脸切片状态 * @param faceId 人脸ID * @param status 切片状态 */ public void setFaceCutStatus(Long faceId, FaceCutStatus status) { if (faceId == null || status == null) { log.warn("设置切片状态参数为空: faceId={}, status={}", faceId, status); return; } String key = String.format(FACE_CUT_STATUS_KEY, faceId); redisTemplate.opsForValue().set(key, String.valueOf(status.getCode()), DEFAULT_EXPIRE_SECONDS, TimeUnit.SECONDS); log.debug("设置切片状态: faceId={}, status={}", faceId, status.getDescription()); } /** * 获取人脸切片状态 * @param faceId 人脸ID * @return 切片状态,缓存不存在时返回 COMPLETED(已完成) */ public FaceCutStatus getFaceCutStatus(Long faceId) { if (faceId == null) { log.warn("获取切片状态参数为空: faceId={}", faceId); return FaceCutStatus.COMPLETED; } String key = String.format(FACE_CUT_STATUS_KEY, faceId); String value = redisTemplate.opsForValue().get(key); if (value == null) { log.debug("切片状态缓存不存在,返回默认值COMPLETED: faceId={}", faceId); return FaceCutStatus.COMPLETED; } try { int code = Integer.parseInt(value); return FaceCutStatus.fromCodeOrDefault(code, FaceCutStatus.COMPLETED); } catch (NumberFormatException e) { log.error("切片状态值解析失败: faceId={}, value={}", faceId, value, e); return FaceCutStatus.COMPLETED; } } /** * 删除人脸切片状态缓存 * @param faceId 人脸ID */ public void deleteFaceCutStatus(Long faceId) { if (faceId == null) { return; } String key = String.format(FACE_CUT_STATUS_KEY, faceId); redisTemplate.delete(key); log.debug("删除切片状态缓存: faceId={}", faceId); } // ==================== 片段更新状态相关方法 ==================== /** * 标记无新片段(设置Redis键) * @param faceId 人脸ID * @param templateId 模板ID(可选,为null时标记全局状态) */ public void markNoNewPieces(Long faceId, Long templateId) { if (faceId == null) { log.warn("标记无新片段参数为空: faceId={}", faceId); return; } String key; if (templateId == null) { // 全局标记:该人脸的所有模板都无新片段 key = String.format(FACE_PIECE_UPDATE_KEY, faceId); log.debug("标记无新片段(全局): faceId={}", faceId); } else { // 模板级标记:该人脸在该模板下无新片段 key = String.format(FACE_TEMPLATE_PIECE_UPDATE_KEY, faceId, templateId); log.debug("标记无新片段(模板): faceId={}, templateId={}", faceId, templateId); } redisTemplate.opsForValue().set(key, "1", DEFAULT_EXPIRE_SECONDS, TimeUnit.SECONDS); } /** * 标记有新片段(删除Redis键) * @param faceId 人脸ID * @param templateId 模板ID(可选,为null时标记全局状态) */ public void markHasNewPieces(Long faceId, Long templateId) { if (faceId == null) { log.warn("标记有新片段参数为空: faceId={}", faceId); return; } String key; if (templateId == null) { // 全局标记:该人脸有新片段 key = String.format(FACE_PIECE_UPDATE_KEY, faceId); log.debug("标记有新片段(全局): faceId={}", faceId); } else { // 模板级标记:该人脸在该模板下有新片段 key = String.format(FACE_TEMPLATE_PIECE_UPDATE_KEY, faceId, templateId); log.debug("标记有新片段(模板): faceId={}, templateId={}", faceId, templateId); } redisTemplate.delete(key); } /** * 获取人脸片段更新状态 * @param faceId 人脸ID * @param templateId 模板ID(可选,为null时查询全局状态) * @return 片段更新状态,键存在=无新片段,键不存在=有新片段 */ public FacePieceUpdateStatus getFacePieceUpdateStatus(Long faceId, Long templateId) { if (faceId == null) { log.warn("获取片段更新状态参数为空: faceId={}", faceId); return FacePieceUpdateStatus.HAS_NEW_PIECES; } String key; if (templateId == null) { // 查询全局状态 key = String.format(FACE_PIECE_UPDATE_KEY, faceId); } else { // 查询模板级状态 key = String.format(FACE_TEMPLATE_PIECE_UPDATE_KEY, faceId, templateId); } Boolean exists = redisTemplate.hasKey(key); FacePieceUpdateStatus status = FacePieceUpdateStatus.fromKeyExists(Boolean.TRUE.equals(exists)); if (templateId == null) { log.debug("获取片段更新状态(全局): faceId={}, status={}", faceId, status.getDescription()); } else { log.debug("获取片段更新状态(模板): faceId={}, templateId={}, status={}", faceId, templateId, status.getDescription()); } return status; } /** * 获取人脸片段更新状态 - 全局版本 * @param faceId 人脸ID * @return 片段更新状态,键存在=无新片段,键不存在=有新片段 */ public FacePieceUpdateStatus getFacePieceUpdateStatus(Long faceId) { return getFacePieceUpdateStatus(faceId, null); } /** * 判断是否有新片段 * @param faceId 人脸ID * @param templateId 模板ID(可选,为null时查询全局状态) * @return true=有新片段,false=无新片段;如果templateId为null则默认返回true(有新片段) */ public boolean hasNewPieces(Long faceId, Long templateId) { if (templateId == null) { // 如果没有指定templateId,默认认为有新片段 log.debug("未指定templateId,默认返回有新片段: faceId={}", faceId); return true; } return getFacePieceUpdateStatus(faceId, templateId).hasNewPieces(); } /** * 判断是否有新片段 - 全局版本 * @param faceId 人脸ID * @return true=有新片段,false=无新片段 */ public boolean hasNewPieces(Long faceId) { return getFacePieceUpdateStatus(faceId, null).hasNewPieces(); } // ==================== 模板渲染状态相关方法 ==================== /** * 设置人脸模板渲染状态 * @param faceId 人脸ID * @param templateId 模板ID * @param status 渲染状态 */ public void setTemplateRenderStatus(Long faceId, Long templateId, TemplateRenderStatus status) { if (faceId == null || templateId == null || status == null) { log.warn("设置模板渲染状态参数为空: faceId={}, templateId={}, status={}", faceId, templateId, status); return; } String key = String.format(FACE_TEMPLATE_RENDER_KEY, faceId, templateId); // 渲染状态不设置过期时间,永久保存 redisTemplate.opsForValue().set(key, String.valueOf(status.getCode())); log.debug("设置模板渲染状态: faceId={}, templateId={}, status={}", faceId, templateId, status.getDescription()); } /** * 设置人脸模板渲染状态(带过期时间) * @param faceId 人脸ID * @param templateId 模板ID * @param status 渲染状态 * @param expireSeconds 过期时间(秒) */ public void setTemplateRenderStatus(Long faceId, Long templateId, TemplateRenderStatus status, long expireSeconds) { if (faceId == null || templateId == null || status == null) { log.warn("设置模板渲染状态参数为空: faceId={}, templateId={}, status={}", faceId, templateId, status); return; } String key = String.format(FACE_TEMPLATE_RENDER_KEY, faceId, templateId); redisTemplate.opsForValue().set(key, String.valueOf(status.getCode()), expireSeconds, TimeUnit.SECONDS); log.debug("设置模板渲染状态(带过期): faceId={}, templateId={}, status={}, expireSeconds={}", faceId, templateId, status.getDescription(), expireSeconds); } /** * 获取人脸模板渲染状态 * @param faceId 人脸ID * @param templateId 模板ID * @return 渲染状态,缓存不存在时返回 null */ public TemplateRenderStatus getTemplateRenderStatus(Long faceId, Long templateId) { if (faceId == null || templateId == null) { log.warn("获取模板渲染状态参数为空: faceId={}, templateId={}", faceId, templateId); return null; } String key = String.format(FACE_TEMPLATE_RENDER_KEY, faceId, templateId); String value = redisTemplate.opsForValue().get(key); if (value == null) { log.debug("模板渲染状态缓存不存在: faceId={}, templateId={}", faceId, templateId); return null; } try { int code = Integer.parseInt(value); return TemplateRenderStatus.fromCode(code); } catch (Exception e) { log.error("模板渲染状态值解析失败: faceId={}, templateId={}, value={}", faceId, templateId, value, e); return null; } } /** * 判断模板是否已渲染完成 * @param faceId 人脸ID * @param templateId 模板ID * @return true=已渲染,false=未渲染或正在渲染 */ public boolean isTemplateRendered(Long faceId, Long templateId) { TemplateRenderStatus status = getTemplateRenderStatus(faceId, templateId); return status != null && status.isRendered(); } /** * 判断模板是否正在渲染 * @param faceId 人脸ID * @param templateId 模板ID * @return true=正在渲染,false=未渲染或已完成 */ public boolean isTemplateRendering(Long faceId, Long templateId) { TemplateRenderStatus status = getTemplateRenderStatus(faceId, templateId); return status != null && status.isRendering(); } /** * 删除人脸模板渲染状态缓存 * @param faceId 人脸ID * @param templateId 模板ID */ public void deleteTemplateRenderStatus(Long faceId, Long templateId) { if (faceId == null || templateId == null) { return; } String key = String.format(FACE_TEMPLATE_RENDER_KEY, faceId, templateId); redisTemplate.delete(key); log.debug("删除模板渲染状态缓存: faceId={}, templateId={}", faceId, templateId); } /** * 删除人脸的所有模板渲染状态(使用模式匹配) * 注意:此操作可能影响性能,谨慎使用 * @param faceId 人脸ID */ public void deleteAllTemplateRenderStatus(Long faceId) { if (faceId == null) { return; } String pattern = String.format(FACE_TEMPLATE_RENDER_KEY, faceId, "*"); var keys = redisTemplate.keys(pattern); if (keys != null && !keys.isEmpty()) { redisTemplate.delete(keys); log.debug("批量删除模板渲染状态缓存: faceId={}, count={}", faceId, keys.size()); } } }