You've already forked FrameTour-BE
feat(face): 引入人脸识别指标记录与搜索结果合并功能
- 新增 FaceMetricsRecorder 类用于记录人脸识别、自定义匹配及低阈值检测次数 - 新增 SearchResultMerger 类用于合并多个人脸搜索结果,支持并集与交集模式- 在 FaceServiceImpl 中引入 metricsRecorder 和 resultMerger 辅助类 - 替换原有的 Redis 操作代码为 FaceMetricsRecorder 的方法调用- 将搜索结果合并逻辑从 FaceServiceImpl 提取至 SearchResultMerger- 新增策略模式相关类:RematchContext、RematchModeStrategy 接口及四种实现 - 使用策略工厂 Rematch
This commit is contained in:
@@ -0,0 +1,169 @@
|
|||||||
|
package com.ycwl.basic.service.pc.helper;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
import static com.ycwl.basic.constant.FaceConstant.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 人脸识别指标记录器
|
||||||
|
* 负责记录人脸识别相关的计数指标到Redis
|
||||||
|
*
|
||||||
|
* @author longbinbin
|
||||||
|
* @date 2025-01-31
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class FaceMetricsRecorder {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisTemplate<String, String> redisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录人脸识别次数到Redis
|
||||||
|
* 设置2天过期时间
|
||||||
|
*
|
||||||
|
* @param faceId 人脸ID
|
||||||
|
*/
|
||||||
|
public void recordRecognitionCount(Long faceId) {
|
||||||
|
if (faceId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String redisKey = FACE_RECOGNITION_COUNT_PFX + faceId;
|
||||||
|
|
||||||
|
// 使用Redis原子操作INCR增加计数
|
||||||
|
Long count = redisTemplate.opsForValue().increment(redisKey);
|
||||||
|
|
||||||
|
// 设置2天过期时间(48小时)
|
||||||
|
redisTemplate.expire(redisKey, 2, TimeUnit.DAYS);
|
||||||
|
|
||||||
|
log.debug("人脸识别计数更新:faceId={}, count={}", faceId, count);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 计数失败不应影响主要业务逻辑,只记录错误日志
|
||||||
|
log.error("记录人脸识别次数失败:faceId={}", faceId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录自定义人脸匹配次数到Redis
|
||||||
|
* 设置2天过期时间
|
||||||
|
*
|
||||||
|
* @param faceId 人脸ID
|
||||||
|
*/
|
||||||
|
public void recordCustomMatchCount(Long faceId) {
|
||||||
|
if (faceId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String redisKey = FACE_CUSTOM_MATCH_COUNT_PFX + faceId;
|
||||||
|
|
||||||
|
Long count = redisTemplate.opsForValue().increment(redisKey);
|
||||||
|
redisTemplate.expire(redisKey, 2, TimeUnit.DAYS);
|
||||||
|
|
||||||
|
log.debug("自定义人脸匹配计数更新:faceId={}, count={}", faceId, count);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("记录自定义人脸匹配次数失败:faceId={}", faceId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录低阈值检测的人脸ID到Redis
|
||||||
|
* 设置2天过期时间
|
||||||
|
*
|
||||||
|
* @param faceId 人脸ID
|
||||||
|
*/
|
||||||
|
public void recordLowThreshold(Long faceId) {
|
||||||
|
if (faceId == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String redisKey = FACE_LOW_THRESHOLD_PFX + faceId;
|
||||||
|
|
||||||
|
// 设置标记,表示该人脸ID触发了低阈值检测
|
||||||
|
redisTemplate.opsForValue().set(redisKey, "1", 2, TimeUnit.DAYS);
|
||||||
|
|
||||||
|
log.debug("记录低阈值检测人脸:faceId={}", faceId);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 记录失败不应影响主要业务逻辑,只记录错误日志
|
||||||
|
log.error("记录低阈值检测人脸失败:faceId={}", faceId, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取人脸识别次数
|
||||||
|
*
|
||||||
|
* @param faceId 人脸ID
|
||||||
|
* @return 识别次数
|
||||||
|
*/
|
||||||
|
public long getRecognitionCount(Long faceId) {
|
||||||
|
if (faceId == null) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String countKey = FACE_RECOGNITION_COUNT_PFX + faceId;
|
||||||
|
String countStr = redisTemplate.opsForValue().get(countKey);
|
||||||
|
if (countStr != null) {
|
||||||
|
return Long.parseLong(countStr);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("获取识别次数失败:faceId={}", faceId, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取自定义匹配次数
|
||||||
|
*
|
||||||
|
* @param faceId 人脸ID
|
||||||
|
* @return 自定义匹配次数
|
||||||
|
*/
|
||||||
|
public long getCustomMatchCount(Long faceId) {
|
||||||
|
if (faceId == null) {
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String customMatchKey = FACE_CUSTOM_MATCH_COUNT_PFX + faceId;
|
||||||
|
String customMatchCountStr = redisTemplate.opsForValue().get(customMatchKey);
|
||||||
|
if (customMatchCountStr != null) {
|
||||||
|
return Long.parseLong(customMatchCountStr);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("获取自定义匹配次数失败:faceId={}", faceId, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0L;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查是否触发过低阈值检测
|
||||||
|
*
|
||||||
|
* @param faceId 人脸ID
|
||||||
|
* @return 是否触发过低阈值检测
|
||||||
|
*/
|
||||||
|
public boolean hasLowThreshold(Long faceId) {
|
||||||
|
if (faceId == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String lowThresholdKey = FACE_LOW_THRESHOLD_PFX + faceId;
|
||||||
|
return Boolean.TRUE.equals(redisTemplate.hasKey(lowThresholdKey));
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("检查低阈值状态失败:faceId={}", faceId, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
package com.ycwl.basic.service.pc.helper;
|
||||||
|
|
||||||
|
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 搜索结果合并器
|
||||||
|
* 负责合并多个人脸搜索结果
|
||||||
|
*
|
||||||
|
* @author longbinbin
|
||||||
|
* @date 2025-01-31
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class SearchResultMerger {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并多个搜索结果(默认使用并集模式)
|
||||||
|
*
|
||||||
|
* @param searchResults 搜索结果列表
|
||||||
|
* @return 合并后的结果
|
||||||
|
*/
|
||||||
|
public SearchFaceRespVo merge(List<SearchFaceRespVo> searchResults) {
|
||||||
|
return merge(searchResults, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并多个搜索结果
|
||||||
|
*
|
||||||
|
* @param searchResults 搜索结果列表
|
||||||
|
* @param mergeMode 合并模式:0-并集,1-交集
|
||||||
|
* @return 合并后的结果
|
||||||
|
*/
|
||||||
|
public SearchFaceRespVo merge(List<SearchFaceRespVo> searchResults, Integer mergeMode) {
|
||||||
|
SearchFaceRespVo mergedResult = new SearchFaceRespVo();
|
||||||
|
|
||||||
|
if (searchResults == null || searchResults.isEmpty()) {
|
||||||
|
return mergedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> allSearchJsons = new ArrayList<>();
|
||||||
|
float maxScore = 0f;
|
||||||
|
float maxFirstMatchRate = 0f;
|
||||||
|
boolean hasLowThreshold = false;
|
||||||
|
|
||||||
|
// 收集基础信息
|
||||||
|
for (SearchFaceRespVo result : searchResults) {
|
||||||
|
if (result.getSearchResultJson() != null) {
|
||||||
|
allSearchJsons.add(result.getSearchResultJson());
|
||||||
|
}
|
||||||
|
if (result.getScore() > maxScore) {
|
||||||
|
maxScore = result.getScore();
|
||||||
|
}
|
||||||
|
if (result.getFirstMatchRate() > maxFirstMatchRate) {
|
||||||
|
maxFirstMatchRate = result.getFirstMatchRate();
|
||||||
|
}
|
||||||
|
if (result.isLowThreshold()) {
|
||||||
|
hasLowThreshold = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据合并模式处理样本ID
|
||||||
|
List<Long> finalSampleIds;
|
||||||
|
if (Integer.valueOf(1).equals(mergeMode)) {
|
||||||
|
// 模式1:交集 - 只保留所有搜索结果中都出现的样本ID
|
||||||
|
finalSampleIds = computeIntersection(searchResults);
|
||||||
|
log.debug("使用交集模式合并搜索结果,交集样本数: {}", finalSampleIds.size());
|
||||||
|
} else {
|
||||||
|
// 模式0:并集(默认) - 收集所有样本ID并去重
|
||||||
|
Set<Long> allSampleIds = new LinkedHashSet<>();
|
||||||
|
for (SearchFaceRespVo result : searchResults) {
|
||||||
|
if (result.getSampleListIds() != null) {
|
||||||
|
allSampleIds.addAll(result.getSampleListIds());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finalSampleIds = new ArrayList<>(allSampleIds);
|
||||||
|
log.debug("使用并集模式合并搜索结果,并集样本数: {}", finalSampleIds.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
mergedResult.setSampleListIds(finalSampleIds);
|
||||||
|
mergedResult.setSearchResultJson(String.join("|", allSearchJsons));
|
||||||
|
mergedResult.setScore(maxScore);
|
||||||
|
mergedResult.setFirstMatchRate(maxFirstMatchRate);
|
||||||
|
mergedResult.setLowThreshold(hasLowThreshold);
|
||||||
|
|
||||||
|
log.debug("合并搜索结果完成,模式={}, 最终样本数: {}", mergeMode, finalSampleIds.size());
|
||||||
|
|
||||||
|
return mergedResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算多个搜索结果的交集
|
||||||
|
* 返回在所有搜索结果中都出现的样本ID
|
||||||
|
*
|
||||||
|
* @param searchResults 搜索结果列表
|
||||||
|
* @return 交集样本ID列表
|
||||||
|
*/
|
||||||
|
public List<Long> computeIntersection(List<SearchFaceRespVo> searchResults) {
|
||||||
|
if (searchResults == null || searchResults.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过滤掉空结果
|
||||||
|
List<List<Long>> validSampleLists = searchResults.stream()
|
||||||
|
.filter(result -> result.getSampleListIds() != null && !result.getSampleListIds().isEmpty())
|
||||||
|
.map(SearchFaceRespVo::getSampleListIds)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (validSampleLists.isEmpty()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果只有一个有效结果,直接返回
|
||||||
|
if (validSampleLists.size() == 1) {
|
||||||
|
return new ArrayList<>(validSampleLists.getFirst());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算交集:从第一个列表开始,保留在所有其他列表中都出现的ID
|
||||||
|
Set<Long> intersection = new LinkedHashSet<>(validSampleLists.getFirst());
|
||||||
|
|
||||||
|
for (int i = 1; i < validSampleLists.size(); i++) {
|
||||||
|
intersection.retainAll(validSampleLists.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ArrayList<>(intersection);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建直接结果(模式2:不搜索,直接使用用户选择的faceSampleIds)
|
||||||
|
*
|
||||||
|
* @param faceSampleIds 用户选择的人脸样本ID列表
|
||||||
|
* @return 搜索结果对象
|
||||||
|
*/
|
||||||
|
public SearchFaceRespVo createDirectResult(List<Long> faceSampleIds) {
|
||||||
|
SearchFaceRespVo result = new SearchFaceRespVo();
|
||||||
|
|
||||||
|
// 直接使用用户选择的faceSampleIds作为结果
|
||||||
|
result.setSampleListIds(new ArrayList<>(faceSampleIds));
|
||||||
|
|
||||||
|
// 设置默认值
|
||||||
|
result.setScore(1.0f);
|
||||||
|
result.setFirstMatchRate(1.0f);
|
||||||
|
result.setLowThreshold(false);
|
||||||
|
result.setSearchResultJson("");
|
||||||
|
|
||||||
|
log.debug("创建直接结果,样本数: {}", faceSampleIds.size());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -58,6 +58,11 @@ import com.ycwl.basic.repository.VideoTaskRepository;
|
|||||||
import com.ycwl.basic.service.mobile.GoodsService;
|
import com.ycwl.basic.service.mobile.GoodsService;
|
||||||
import com.ycwl.basic.service.pc.FaceService;
|
import com.ycwl.basic.service.pc.FaceService;
|
||||||
import com.ycwl.basic.service.pc.ScenicService;
|
import com.ycwl.basic.service.pc.ScenicService;
|
||||||
|
import com.ycwl.basic.service.pc.helper.FaceMetricsRecorder;
|
||||||
|
import com.ycwl.basic.service.pc.helper.SearchResultMerger;
|
||||||
|
import com.ycwl.basic.service.pc.strategy.RematchContext;
|
||||||
|
import com.ycwl.basic.service.pc.strategy.RematchModeStrategy;
|
||||||
|
import com.ycwl.basic.service.pc.strategy.RematchStrategyFactory;
|
||||||
import com.ycwl.basic.service.task.TaskFaceService;
|
import com.ycwl.basic.service.task.TaskFaceService;
|
||||||
import com.ycwl.basic.service.task.TaskService;
|
import com.ycwl.basic.service.task.TaskService;
|
||||||
import com.ycwl.basic.storage.StorageFactory;
|
import com.ycwl.basic.storage.StorageFactory;
|
||||||
@@ -153,6 +158,14 @@ public class FaceServiceImpl implements FaceService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private TemplateRepository templateRepository;
|
private TemplateRepository templateRepository;
|
||||||
|
|
||||||
|
// 新增的辅助类
|
||||||
|
@Autowired
|
||||||
|
private FaceMetricsRecorder metricsRecorder;
|
||||||
|
@Autowired
|
||||||
|
private SearchResultMerger resultMerger;
|
||||||
|
@Autowired
|
||||||
|
private RematchStrategyFactory rematchStrategyFactory;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ApiResponse<PageInfo<FaceRespVO>> pageQuery(FaceReqQuery faceReqQuery) {
|
public ApiResponse<PageInfo<FaceRespVO>> pageQuery(FaceReqQuery faceReqQuery) {
|
||||||
PageHelper.startPage(faceReqQuery.getPageNum(),faceReqQuery.getPageSize());
|
PageHelper.startPage(faceReqQuery.getPageNum(),faceReqQuery.getPageSize());
|
||||||
@@ -296,9 +309,9 @@ public class FaceServiceImpl implements FaceService {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
log.debug("开始人脸匹配:faceId={}, isNew={}", faceId, isNew);
|
log.debug("开始人脸匹配:faceId={}, isNew={}", faceId, isNew);
|
||||||
|
|
||||||
// 记录识别次数到Redis,设置2天过期时间
|
// 记录识别次数到Redis,设置2天过期时间
|
||||||
recordFaceRecognitionCount(faceId);
|
metricsRecorder.recordRecognitionCount(faceId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
@@ -387,7 +400,7 @@ public class FaceServiceImpl implements FaceService {
|
|||||||
|
|
||||||
// 检查低阈值检测结果,如果为true则记录该人脸ID到Redis
|
// 检查低阈值检测结果,如果为true则记录该人脸ID到Redis
|
||||||
if (scenicDbSearchResult != null && scenicDbSearchResult.isLowThreshold()) {
|
if (scenicDbSearchResult != null && scenicDbSearchResult.isLowThreshold()) {
|
||||||
recordLowThresholdFace(faceId);
|
metricsRecorder.recordLowThreshold(faceId);
|
||||||
log.debug("触发低阈值检测,记录faceId: {}", faceId);
|
log.debug("触发低阈值检测,记录faceId: {}", faceId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -947,23 +960,13 @@ public class FaceServiceImpl implements FaceService {
|
|||||||
statusResp.setStep1Status(true);
|
statusResp.setStep1Status(true);
|
||||||
|
|
||||||
statusResp.setFaceUrl(face.getFaceUrl());
|
statusResp.setFaceUrl(face.getFaceUrl());
|
||||||
|
|
||||||
// 查询识别次数
|
// 查询识别次数
|
||||||
String countKey = FACE_RECOGNITION_COUNT_PFX + faceId;
|
long recognitionCount = metricsRecorder.getRecognitionCount(faceId);
|
||||||
String countStr = redisTemplate.opsForValue().get(countKey);
|
|
||||||
long recognitionCount = 0L;
|
|
||||||
if (countStr != null) {
|
|
||||||
try {
|
|
||||||
recognitionCount = Long.parseLong(countStr);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
log.warn("识别次数解析失败,faceId={}, count={}", faceId, countStr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
statusResp.setRecognitionCount(recognitionCount);
|
statusResp.setRecognitionCount(recognitionCount);
|
||||||
|
|
||||||
// 查询是否触发过低阈值检测
|
// 查询是否触发过低阈值检测
|
||||||
String lowThresholdKey = FACE_LOW_THRESHOLD_PFX + faceId;
|
Boolean hasLowThreshold = metricsRecorder.hasLowThreshold(faceId);
|
||||||
Boolean hasLowThreshold = redisTemplate.hasKey(lowThresholdKey);
|
|
||||||
statusResp.setHasLowThreshold(hasLowThreshold);
|
statusResp.setHasLowThreshold(hasLowThreshold);
|
||||||
|
|
||||||
log.debug("查询人脸状态:faceId={}, recognitionCount={}, hasLowThreshold={}",
|
log.debug("查询人脸状态:faceId={}, recognitionCount={}, hasLowThreshold={}",
|
||||||
@@ -1005,27 +1008,10 @@ public class FaceServiceImpl implements FaceService {
|
|||||||
}
|
}
|
||||||
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId());
|
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId());
|
||||||
|
|
||||||
String recognitionKey = FACE_RECOGNITION_COUNT_PFX + faceId;
|
// 使用FaceMetricsRecorder获取计数信息
|
||||||
String recognitionCountStr = redisTemplate.opsForValue().get(recognitionKey);
|
long recognitionCount = metricsRecorder.getRecognitionCount(faceId);
|
||||||
long recognitionCount = 0L;
|
long customMatchCount = metricsRecorder.getCustomMatchCount(faceId);
|
||||||
if (recognitionCountStr != null) {
|
boolean hasLowThreshold = metricsRecorder.hasLowThreshold(faceId);
|
||||||
try {
|
|
||||||
recognitionCount = Long.parseLong(recognitionCountStr);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
log.warn("识别次数解析失败,faceId={}, count={}", faceId, recognitionCountStr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String customMatchKey = FACE_CUSTOM_MATCH_COUNT_PFX + faceId;
|
|
||||||
String customMatchCountStr = redisTemplate.opsForValue().get(customMatchKey);
|
|
||||||
long customMatchCount = 0L;
|
|
||||||
if (customMatchCountStr != null) {
|
|
||||||
try {
|
|
||||||
customMatchCount = Long.parseLong(customMatchCountStr);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
log.warn("自定义匹配次数解析失败,faceId={}, count={}", faceId, customMatchCountStr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Integer faceSelectMaxCount = scenicConfig.getInteger("face_select_max_count");
|
Integer faceSelectMaxCount = scenicConfig.getInteger("face_select_max_count");
|
||||||
if (faceSelectMaxCount != null && faceSelectMaxCount > 0 && customMatchCount > faceSelectMaxCount) {
|
if (faceSelectMaxCount != null && faceSelectMaxCount > 0 && customMatchCount > faceSelectMaxCount) {
|
||||||
@@ -1072,16 +1058,20 @@ public class FaceServiceImpl implements FaceService {
|
|||||||
if (projectMatch) {
|
if (projectMatch) {
|
||||||
ruleMatched++;
|
ruleMatched++;
|
||||||
}
|
}
|
||||||
// 查询是否触发过低阈值检测
|
|
||||||
String lowThresholdKey = FACE_LOW_THRESHOLD_PFX + faceId;
|
// 使用策略模式替换switch语句
|
||||||
boolean hasLowThreshold = redisTemplate.hasKey(lowThresholdKey);
|
|
||||||
Integer mode = scenicConfig.getInteger("re_match_mode", 0);
|
Integer mode = scenicConfig.getInteger("re_match_mode", 0);
|
||||||
return switch (mode) {
|
RematchContext context = RematchContext.builder()
|
||||||
case 1 -> tourMatch || recognitionCount > 1 || hasLowThreshold;
|
.recognitionCount(recognitionCount)
|
||||||
case 5 -> hasLowThreshold || (ruleMatched >= 2);
|
.hasLowThreshold(hasLowThreshold)
|
||||||
case 9 -> hasLowThreshold && ruleMatched >= 2;
|
.tourMatch(tourMatch)
|
||||||
default -> false;
|
.projectMatch(projectMatch)
|
||||||
};
|
.ruleMatched(ruleMatched)
|
||||||
|
.faceCreateAt(face.getCreateAt())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
RematchModeStrategy strategy = rematchStrategyFactory.getStrategy(mode);
|
||||||
|
return strategy.shouldRematch(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1128,7 +1118,7 @@ public class FaceServiceImpl implements FaceService {
|
|||||||
log.debug("开始自定义人脸匹配:faceId={}, faceSampleIds={}", faceId, faceSampleIds);
|
log.debug("开始自定义人脸匹配:faceId={}, faceSampleIds={}", faceId, faceSampleIds);
|
||||||
|
|
||||||
// 记录自定义匹配调用次数,便于监控调用频率
|
// 记录自定义匹配调用次数,便于监控调用频率
|
||||||
recordCustomMatchCount(faceId);
|
metricsRecorder.recordCustomMatchCount(faceId);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
FaceEntity face = faceRepository.getFace(faceId);
|
FaceEntity face = faceRepository.getFace(faceId);
|
||||||
@@ -1161,7 +1151,7 @@ public class FaceServiceImpl implements FaceService {
|
|||||||
if (Integer.valueOf(2).equals(faceSelectPostMode)) {
|
if (Integer.valueOf(2).equals(faceSelectPostMode)) {
|
||||||
// 模式2:不搜索,直接使用用户选择的faceSampleIds
|
// 模式2:不搜索,直接使用用户选择的faceSampleIds
|
||||||
log.debug("使用模式2:直接使用用户选择的人脸样本,不进行搜索");
|
log.debug("使用模式2:直接使用用户选择的人脸样本,不进行搜索");
|
||||||
mergedResult = createDirectResult(faceSampleIds);
|
mergedResult = resultMerger.createDirectResult(faceSampleIds);
|
||||||
mergedResult.setSearchResultJson(face.getMatchResult()); // 没有检索
|
mergedResult.setSearchResultJson(face.getMatchResult()); // 没有检索
|
||||||
} else {
|
} else {
|
||||||
// 模式0(并集)和模式1(交集):需要进行搜索
|
// 模式0(并集)和模式1(交集):需要进行搜索
|
||||||
@@ -1189,7 +1179,7 @@ public class FaceServiceImpl implements FaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2.2 根据模式整合多个搜索结果
|
// 2.2 根据模式整合多个搜索结果
|
||||||
mergedResult = mergeSearchResults(searchResults, faceSelectPostMode);
|
mergedResult = resultMerger.merge(searchResults, faceSelectPostMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 应用后置筛选逻辑
|
// 3. 应用后置筛选逻辑
|
||||||
@@ -1256,13 +1246,6 @@ public class FaceServiceImpl implements FaceService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 合并多个搜索结果(兼容老版本,默认使用并集模式)
|
|
||||||
*/
|
|
||||||
private SearchFaceRespVo mergeSearchResults(List<SearchFaceRespVo> searchResults) {
|
|
||||||
return mergeSearchResults(searchResults, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateRecognition(FaceRecognitionUpdateReq req) {
|
public void updateRecognition(FaceRecognitionUpdateReq req) {
|
||||||
if (req == null || req.getFaceId() == null) {
|
if (req == null || req.getFaceId() == null) {
|
||||||
@@ -1326,7 +1309,7 @@ public class FaceServiceImpl implements FaceService {
|
|||||||
detail.setFaceUrl(face.getFaceUrl());
|
detail.setFaceUrl(face.getFaceUrl());
|
||||||
detail.setScore(face.getScore());
|
detail.setScore(face.getScore());
|
||||||
detail.setFirstMatchRate(face.getFirstMatchRate() != null ? face.getFirstMatchRate().floatValue() : null);
|
detail.setFirstMatchRate(face.getFirstMatchRate() != null ? face.getFirstMatchRate().floatValue() : null);
|
||||||
detail.setLowThreshold(redisTemplate.hasKey(FACE_LOW_THRESHOLD_PFX + faceId));
|
detail.setLowThreshold(metricsRecorder.hasLowThreshold(faceId));
|
||||||
detail.setLastMatchedAt(face.getUpdateAt() != null ? face.getUpdateAt() : face.getCreateAt());
|
detail.setLastMatchedAt(face.getUpdateAt() != null ? face.getUpdateAt() : face.getCreateAt());
|
||||||
|
|
||||||
String matchResultJson = face.getMatchResult();
|
String matchResultJson = face.getMatchResult();
|
||||||
@@ -1505,198 +1488,4 @@ public class FaceServiceImpl implements FaceService {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 合并多个搜索结果
|
|
||||||
*
|
|
||||||
* @param searchResults 搜索结果列表
|
|
||||||
* @param mergeMode 合并模式:0-并集,1-交集
|
|
||||||
* @return 合并后的结果
|
|
||||||
*/
|
|
||||||
private SearchFaceRespVo mergeSearchResults(List<SearchFaceRespVo> searchResults, Integer mergeMode) {
|
|
||||||
SearchFaceRespVo mergedResult = new SearchFaceRespVo();
|
|
||||||
|
|
||||||
if (searchResults == null || searchResults.isEmpty()) {
|
|
||||||
return mergedResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> allSearchJsons = new ArrayList<>();
|
|
||||||
float maxScore = 0f;
|
|
||||||
float maxFirstMatchRate = 0f;
|
|
||||||
boolean hasLowThreshold = false;
|
|
||||||
|
|
||||||
// 收集基础信息
|
|
||||||
for (SearchFaceRespVo result : searchResults) {
|
|
||||||
if (result.getSearchResultJson() != null) {
|
|
||||||
allSearchJsons.add(result.getSearchResultJson());
|
|
||||||
}
|
|
||||||
if (result.getScore() > maxScore) {
|
|
||||||
maxScore = result.getScore();
|
|
||||||
}
|
|
||||||
if (result.getFirstMatchRate() > maxFirstMatchRate) {
|
|
||||||
maxFirstMatchRate = result.getFirstMatchRate();
|
|
||||||
}
|
|
||||||
if (result.isLowThreshold()) {
|
|
||||||
hasLowThreshold = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 根据合并模式处理样本ID
|
|
||||||
List<Long> finalSampleIds;
|
|
||||||
if (Integer.valueOf(1).equals(mergeMode)) {
|
|
||||||
// 模式1:交集 - 只保留所有搜索结果中都出现的样本ID
|
|
||||||
finalSampleIds = computeIntersection(searchResults);
|
|
||||||
log.debug("使用交集模式合并搜索结果,交集样本数: {}", finalSampleIds.size());
|
|
||||||
} else {
|
|
||||||
// 模式0:并集(默认) - 收集所有样本ID并去重
|
|
||||||
Set<Long> allSampleIds = new LinkedHashSet<>();
|
|
||||||
for (SearchFaceRespVo result : searchResults) {
|
|
||||||
if (result.getSampleListIds() != null) {
|
|
||||||
allSampleIds.addAll(result.getSampleListIds());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finalSampleIds = new ArrayList<>(allSampleIds);
|
|
||||||
log.debug("使用并集模式合并搜索结果,并集样本数: {}", finalSampleIds.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
mergedResult.setSampleListIds(finalSampleIds);
|
|
||||||
mergedResult.setSearchResultJson(String.join("|", allSearchJsons));
|
|
||||||
mergedResult.setScore(maxScore);
|
|
||||||
mergedResult.setFirstMatchRate(maxFirstMatchRate);
|
|
||||||
mergedResult.setLowThreshold(hasLowThreshold);
|
|
||||||
|
|
||||||
log.debug("合并搜索结果完成,模式={}, 最终样本数: {}", mergeMode, finalSampleIds.size());
|
|
||||||
|
|
||||||
return mergedResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算多个搜索结果的交集
|
|
||||||
* 返回在所有搜索结果中都出现的样本ID
|
|
||||||
*/
|
|
||||||
private List<Long> computeIntersection(List<SearchFaceRespVo> searchResults) {
|
|
||||||
if (searchResults == null || searchResults.isEmpty()) {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 过滤掉空结果
|
|
||||||
List<List<Long>> validSampleLists = searchResults.stream()
|
|
||||||
.filter(result -> result.getSampleListIds() != null && !result.getSampleListIds().isEmpty())
|
|
||||||
.map(SearchFaceRespVo::getSampleListIds)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
if (validSampleLists.isEmpty()) {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果只有一个有效结果,直接返回
|
|
||||||
if (validSampleLists.size() == 1) {
|
|
||||||
return new ArrayList<>(validSampleLists.getFirst());
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算交集:从第一个列表开始,保留在所有其他列表中都出现的ID
|
|
||||||
Set<Long> intersection = new LinkedHashSet<>(validSampleLists.getFirst());
|
|
||||||
|
|
||||||
for (int i = 1; i < validSampleLists.size(); i++) {
|
|
||||||
intersection.retainAll(validSampleLists.get(i));
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ArrayList<>(intersection);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建直接结果(模式2:不搜索,直接使用用户选择的faceSampleIds)
|
|
||||||
*
|
|
||||||
* @param faceSampleIds 用户选择的人脸样本ID列表
|
|
||||||
* @return 搜索结果对象
|
|
||||||
*/
|
|
||||||
private SearchFaceRespVo createDirectResult(List<Long> faceSampleIds) {
|
|
||||||
SearchFaceRespVo result = new SearchFaceRespVo();
|
|
||||||
|
|
||||||
// 直接使用用户选择的faceSampleIds作为结果
|
|
||||||
result.setSampleListIds(new ArrayList<>(faceSampleIds));
|
|
||||||
|
|
||||||
// 设置默认值
|
|
||||||
result.setScore(1.0f);
|
|
||||||
result.setFirstMatchRate(1.0f);
|
|
||||||
result.setLowThreshold(false);
|
|
||||||
result.setSearchResultJson("");
|
|
||||||
|
|
||||||
log.debug("创建直接结果,样本数: {}", faceSampleIds.size());
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录自定义人脸匹配次数到Redis
|
|
||||||
*
|
|
||||||
* @param faceId 人脸ID
|
|
||||||
*/
|
|
||||||
private void recordCustomMatchCount(Long faceId) {
|
|
||||||
if (faceId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
String redisKey = FACE_CUSTOM_MATCH_COUNT_PFX + faceId;
|
|
||||||
|
|
||||||
Long count = redisTemplate.opsForValue().increment(redisKey);
|
|
||||||
redisTemplate.expire(redisKey, 2, TimeUnit.DAYS);
|
|
||||||
|
|
||||||
log.debug("自定义人脸匹配计数更新:faceId={}, count={}", faceId, count);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("记录自定义人脸匹配次数失败:faceId={}", faceId, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录人脸识别次数到Redis
|
|
||||||
*
|
|
||||||
* @param faceId 人脸ID
|
|
||||||
*/
|
|
||||||
private void recordFaceRecognitionCount(Long faceId) {
|
|
||||||
if (faceId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
String redisKey = FACE_RECOGNITION_COUNT_PFX + faceId;
|
|
||||||
|
|
||||||
// 使用Redis原子操作INCR增加计数
|
|
||||||
Long count = redisTemplate.opsForValue().increment(redisKey);
|
|
||||||
|
|
||||||
// 设置2天过期时间(48小时)
|
|
||||||
redisTemplate.expire(redisKey, 2, TimeUnit.DAYS);
|
|
||||||
|
|
||||||
log.debug("人脸识别计数更新:faceId={}, count={}", faceId, count);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 计数失败不应影响主要业务逻辑,只记录错误日志
|
|
||||||
log.error("记录人脸识别次数失败:faceId={}", faceId, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记录低阈值检测的人脸ID到Redis
|
|
||||||
*
|
|
||||||
* @param faceId 人脸ID
|
|
||||||
*/
|
|
||||||
private void recordLowThresholdFace(Long faceId) {
|
|
||||||
if (faceId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
String redisKey = FACE_LOW_THRESHOLD_PFX + faceId;
|
|
||||||
|
|
||||||
// 设置标记,表示该人脸ID触发了低阈值检测
|
|
||||||
redisTemplate.opsForValue().set(redisKey, "1", 2, TimeUnit.DAYS);
|
|
||||||
|
|
||||||
log.debug("记录低阈值检测人脸:faceId={}", faceId);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 记录失败不应影响主要业务逻辑,只记录错误日志
|
|
||||||
log.error("记录低阈值检测人脸失败:faceId={}", faceId, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package com.ycwl.basic.service.pc.strategy;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重匹配上下文
|
||||||
|
* 包含判断是否需要重匹配的所有必要信息
|
||||||
|
*
|
||||||
|
* @author longbinbin
|
||||||
|
* @date 2025-01-31
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class RematchContext {
|
||||||
|
/**
|
||||||
|
* 人脸识别次数
|
||||||
|
*/
|
||||||
|
private long recognitionCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否触发低阈值检测
|
||||||
|
*/
|
||||||
|
private boolean hasLowThreshold;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否符合游览时间匹配
|
||||||
|
*/
|
||||||
|
private boolean tourMatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否符合项目时间匹配
|
||||||
|
*/
|
||||||
|
private boolean projectMatch;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 规则匹配数量
|
||||||
|
*/
|
||||||
|
private int ruleMatched;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 人脸创建时间
|
||||||
|
*/
|
||||||
|
private Date faceCreateAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.ycwl.basic.service.pc.strategy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重匹配模式策略接口
|
||||||
|
* 用于判断是否需要进行人脸重新匹配
|
||||||
|
*
|
||||||
|
* @author longbinbin
|
||||||
|
* @date 2025-01-31
|
||||||
|
*/
|
||||||
|
public interface RematchModeStrategy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断是否应该重新匹配
|
||||||
|
*
|
||||||
|
* @param context 重匹配上下文
|
||||||
|
* @return true-需要重匹配, false-不需要重匹配
|
||||||
|
*/
|
||||||
|
boolean shouldRematch(RematchContext context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取策略对应的模式值
|
||||||
|
*
|
||||||
|
* @return 模式值
|
||||||
|
*/
|
||||||
|
int getMode();
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.ycwl.basic.service.pc.strategy;
|
||||||
|
|
||||||
|
import com.ycwl.basic.service.pc.strategy.impl.DefaultRematchStrategy;
|
||||||
|
import com.ycwl.basic.service.pc.strategy.impl.RematchMode1Strategy;
|
||||||
|
import com.ycwl.basic.service.pc.strategy.impl.RematchMode5Strategy;
|
||||||
|
import com.ycwl.basic.service.pc.strategy.impl.RematchMode9Strategy;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import jakarta.annotation.PostConstruct;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重匹配策略工厂
|
||||||
|
* 根据模式值获取对应的策略实例
|
||||||
|
*
|
||||||
|
* @author longbinbin
|
||||||
|
* @date 2025-01-31
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class RematchStrategyFactory {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private List<RematchModeStrategy> strategies;
|
||||||
|
|
||||||
|
private final Map<Integer, RematchModeStrategy> strategyMap = new HashMap<>();
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void init() {
|
||||||
|
for (RematchModeStrategy strategy : strategies) {
|
||||||
|
strategyMap.put(strategy.getMode(), strategy);
|
||||||
|
log.debug("注册重匹配策略: mode={}, class={}",
|
||||||
|
strategy.getMode(), strategy.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据模式值获取对应的策略
|
||||||
|
*
|
||||||
|
* @param mode 模式值(0-默认, 1-模式1, 5-模式5, 9-模式9)
|
||||||
|
* @return 对应的策略实例,如果没有找到则返回默认策略
|
||||||
|
*/
|
||||||
|
public RematchModeStrategy getStrategy(Integer mode) {
|
||||||
|
if (mode == null) {
|
||||||
|
return strategyMap.getOrDefault(0, new DefaultRematchStrategy());
|
||||||
|
}
|
||||||
|
|
||||||
|
RematchModeStrategy strategy = strategyMap.get(mode);
|
||||||
|
if (strategy == null) {
|
||||||
|
log.warn("未找到重匹配模式{}对应的策略,使用默认策略", mode);
|
||||||
|
return strategyMap.getOrDefault(0, new DefaultRematchStrategy());
|
||||||
|
}
|
||||||
|
|
||||||
|
return strategy;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.ycwl.basic.service.pc.strategy.impl;
|
||||||
|
|
||||||
|
import com.ycwl.basic.service.pc.strategy.RematchContext;
|
||||||
|
import com.ycwl.basic.service.pc.strategy.RematchModeStrategy;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认重匹配策略(模式0或其他未定义模式)
|
||||||
|
* 条件: 不触发重匹配
|
||||||
|
*
|
||||||
|
* @author longbinbin
|
||||||
|
* @date 2025-01-31
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class DefaultRematchStrategy implements RematchModeStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldRematch(RematchContext context) {
|
||||||
|
log.debug("DefaultRematchStrategy判断: 默认不重匹配");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMode() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.ycwl.basic.service.pc.strategy.impl;
|
||||||
|
|
||||||
|
import com.ycwl.basic.service.pc.strategy.RematchContext;
|
||||||
|
import com.ycwl.basic.service.pc.strategy.RematchModeStrategy;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重匹配模式1策略
|
||||||
|
* 条件: tourMatch || recognitionCount > 1 || hasLowThreshold
|
||||||
|
* 满足任一条件即可重匹配
|
||||||
|
*
|
||||||
|
* @author longbinbin
|
||||||
|
* @date 2025-01-31
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class RematchMode1Strategy implements RematchModeStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldRematch(RematchContext context) {
|
||||||
|
boolean result = context.isTourMatch()
|
||||||
|
|| context.getRecognitionCount() > 1
|
||||||
|
|| context.isHasLowThreshold();
|
||||||
|
|
||||||
|
log.debug("RematchMode1Strategy判断: tourMatch={}, recognitionCount={}, hasLowThreshold={}, result={}",
|
||||||
|
context.isTourMatch(), context.getRecognitionCount(),
|
||||||
|
context.isHasLowThreshold(), result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMode() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.ycwl.basic.service.pc.strategy.impl;
|
||||||
|
|
||||||
|
import com.ycwl.basic.service.pc.strategy.RematchContext;
|
||||||
|
import com.ycwl.basic.service.pc.strategy.RematchModeStrategy;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重匹配模式5策略
|
||||||
|
* 条件: hasLowThreshold || (ruleMatched >= 2)
|
||||||
|
* 触发低阈值或匹配2个及以上规则即可重匹配
|
||||||
|
*
|
||||||
|
* @author longbinbin
|
||||||
|
* @date 2025-01-31
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class RematchMode5Strategy implements RematchModeStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldRematch(RematchContext context) {
|
||||||
|
boolean result = context.isHasLowThreshold()
|
||||||
|
|| context.getRuleMatched() >= 2;
|
||||||
|
|
||||||
|
log.debug("RematchMode5Strategy判断: hasLowThreshold={}, ruleMatched={}, result={}",
|
||||||
|
context.isHasLowThreshold(), context.getRuleMatched(), result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMode() {
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.ycwl.basic.service.pc.strategy.impl;
|
||||||
|
|
||||||
|
import com.ycwl.basic.service.pc.strategy.RematchContext;
|
||||||
|
import com.ycwl.basic.service.pc.strategy.RematchModeStrategy;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重匹配模式9策略
|
||||||
|
* 条件: hasLowThreshold && (ruleMatched >= 2)
|
||||||
|
* 必须同时触发低阈值且匹配2个及以上规则才可重匹配
|
||||||
|
*
|
||||||
|
* @author longbinbin
|
||||||
|
* @date 2025-01-31
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class RematchMode9Strategy implements RematchModeStrategy {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean shouldRematch(RematchContext context) {
|
||||||
|
boolean result = context.isHasLowThreshold()
|
||||||
|
&& context.getRuleMatched() >= 2;
|
||||||
|
|
||||||
|
log.debug("RematchMode9Strategy判断: hasLowThreshold={}, ruleMatched={}, result={}",
|
||||||
|
context.isHasLowThreshold(), context.getRuleMatched(), result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMode() {
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user