You've already forked FrameTour-BE
feat(face): 添加人脸识别防重复调用机制
- 引入 FaceMatchDedupService 用于防止短时间内重复调用 - 在匹配前检查是否应跳过本次调用 - 匹配完成后标记已处理,避免重复执行 - 增强系统稳定性与性能,减少无效计算
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
package com.ycwl.basic.service.pc.helper;
|
||||
|
||||
import com.ycwl.basic.util.TtlCacheMap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* 人脸匹配防重复服务
|
||||
* 使用本地缓存实现2秒内的防重复调用机制
|
||||
*
|
||||
* @author Claude
|
||||
* @since 2025-11-27
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class FaceMatchDedupService {
|
||||
|
||||
/**
|
||||
* 防重复TTL时间(毫秒)
|
||||
*/
|
||||
private static final long DEFAULT_TTL_MILLIS = 2000L;
|
||||
|
||||
/**
|
||||
* 缓存Key前缀
|
||||
*/
|
||||
private static final String KEY_PREFIX = "face:match:dedup:";
|
||||
|
||||
/**
|
||||
* 防重复缓存
|
||||
* Key: face:match:dedup:{scenicId}:{userId}
|
||||
* Value: 时间戳
|
||||
*/
|
||||
private final TtlCacheMap<String, Long> dedupCache;
|
||||
|
||||
public FaceMatchDedupService() {
|
||||
this.dedupCache = new TtlCacheMap<>(DEFAULT_TTL_MILLIS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否应该跳过本次匹配(防重复)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param scenicId 景区ID
|
||||
* @return true-应该跳过,false-可以执行
|
||||
*/
|
||||
public boolean shouldSkip(Long userId, Long scenicId) {
|
||||
if (userId == null || scenicId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String key = buildKey(userId, scenicId);
|
||||
Long timestamp = dedupCache.get(key);
|
||||
|
||||
if (timestamp != null) {
|
||||
long elapsedMs = System.currentTimeMillis() - timestamp;
|
||||
log.info("防重复检查:检测到{}秒内的重复调用(已过{}ms),跳过匹配。userId={}, scenicId={}, 上次调用时间={}",
|
||||
DEFAULT_TTL_MILLIS / 1000.0, elapsedMs, userId, scenicId, new Date(timestamp));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 标记本次匹配已执行(用于防重复)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param scenicId 景区ID
|
||||
*/
|
||||
public void markMatched(Long userId, Long scenicId) {
|
||||
if (userId == null || scenicId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = buildKey(userId, scenicId);
|
||||
long timestamp = System.currentTimeMillis();
|
||||
dedupCache.put(key, timestamp, DEFAULT_TTL_MILLIS);
|
||||
|
||||
log.debug("防重复标记:记录匹配调用。userId={}, scenicId={}, TTL={}ms, timestamp={}",
|
||||
userId, scenicId, DEFAULT_TTL_MILLIS, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除指定用户的防重标记(用于测试或特殊场景)
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param scenicId 景区ID
|
||||
*/
|
||||
public void clearMark(Long userId, Long scenicId) {
|
||||
if (userId == null || scenicId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String key = buildKey(userId, scenicId);
|
||||
dedupCache.remove(key);
|
||||
log.info("防重复标记清除:userId={}, scenicId={}", userId, scenicId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建缓存Key
|
||||
* 格式:face:match:dedup:{scenicId}:{userId}
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param scenicId 景区ID
|
||||
* @return 缓存Key
|
||||
*/
|
||||
private String buildKey(Long userId, Long scenicId) {
|
||||
return KEY_PREFIX + scenicId + ":" + userId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取缓存统计信息(用于监控)
|
||||
*
|
||||
* @return 统计信息字符串
|
||||
*/
|
||||
public String getStats() {
|
||||
return dedupCache.getStats();
|
||||
}
|
||||
}
|
||||
@@ -65,6 +65,7 @@ import com.ycwl.basic.service.mobile.GoodsService;
|
||||
import com.ycwl.basic.service.pc.FaceService;
|
||||
import com.ycwl.basic.service.pc.ScenicService;
|
||||
import com.ycwl.basic.constant.SourceType;
|
||||
import com.ycwl.basic.service.pc.helper.FaceMatchDedupService;
|
||||
import com.ycwl.basic.service.pc.helper.FaceMetricsRecorder;
|
||||
import com.ycwl.basic.service.pc.helper.SearchResultMerger;
|
||||
import com.ycwl.basic.service.pc.helper.ScenicConfigFacade;
|
||||
@@ -175,6 +176,10 @@ public class FaceServiceImpl implements FaceService {
|
||||
@Autowired
|
||||
private FaceMatchingOrchestrator faceMatchingOrchestrator;
|
||||
|
||||
// 防重复服务
|
||||
@Autowired
|
||||
private FaceMatchDedupService faceMatchDedupService;
|
||||
|
||||
// 第二阶段的处理器
|
||||
@Autowired
|
||||
private SourceRelationProcessor sourceRelationProcessor;
|
||||
@@ -326,7 +331,27 @@ public class FaceServiceImpl implements FaceService {
|
||||
|
||||
@Override
|
||||
public SearchFaceRespVo matchFaceId(Long faceId) {
|
||||
return faceMatchingOrchestrator.orchestrateMatching(faceId, false);
|
||||
// 获取人脸信息用于防重检查
|
||||
FaceEntity face = faceRepository.getFace(faceId);
|
||||
if (face == null) {
|
||||
log.warn("人脸不存在,无法执行匹配,faceId: {}", faceId);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 防重复检查:如果2秒内已调用过,则跳过
|
||||
if (faceMatchDedupService.shouldSkip(face.getMemberId(), face.getScenicId())) {
|
||||
log.info("防重复:跳过人脸匹配。faceId={}, userId={}, scenicId={}",
|
||||
faceId, face.getMemberId(), face.getScenicId());
|
||||
return null; // 静默忽略,返回null
|
||||
}
|
||||
|
||||
// 正常执行人脸匹配
|
||||
SearchFaceRespVo result = faceMatchingOrchestrator.orchestrateMatching(faceId, false);
|
||||
|
||||
// 执行完成后标记,防止2秒内重复调用
|
||||
faceMatchDedupService.markMatched(face.getMemberId(), face.getScenicId());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user