feat(face): 添加人脸识别重试功能并优化得分筛选逻辑

- 在FaceSampleController中新增retryFaceRecognition接口用于手动重试失败的人脸识别任务- 集成人脸识别Kafka服务,支持异步处理重试请求- 在FaceServiceImpl中增加从景区配置读取人脸得分阈值的功能
- 根据配置的得分阈值对人脸识别结果进行筛选,过滤低分样本
- 添加详细的日志记录和异常处理机制- 优化线程池使用,确保重试任务能够正确提交和执行
This commit is contained in:
2025-11-01 19:58:09 +08:00
parent 96a4d3ffeb
commit 222f974ad5
3 changed files with 93 additions and 1 deletions

View File

@@ -1,4 +1,5 @@
package com.ycwl.basic.controller.pc;
import com.ycwl.basic.integration.kafka.service.FaceProcessingKafkaService;
import com.github.pagehelper.PageInfo;
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
@@ -15,13 +16,14 @@ import java.util.List;
* @Author:longbinbin
* @Date:2024/12/2 16:33
*/
@Deprecated
@RestController
@RequestMapping("/api/faceSample/v1")
// 人脸样本管理
public class FaceSampleController {
@Autowired
private FaceSampleService FaceSampleService;
@Autowired(required = false)
private FaceProcessingKafkaService faceProcessingKafkaService;
// 分页查询人脸样本
@PostMapping("/page")
@@ -39,4 +41,25 @@ public class FaceSampleController {
return FaceSampleService.getById(id);
}
/**
* 重试失败的人脸识别
* 用于手动重试状态为-1的人脸样本
*
* @param id 人脸样本ID
* @return 重试结果
*/
@PostMapping("/retry/{id}")
public ApiResponse<String> retryFaceRecognition(@PathVariable("id") Long id) {
if (faceProcessingKafkaService == null) {
return ApiResponse.fail("Kafka服务未启用,无法重试人脸识别");
}
boolean success = faceProcessingKafkaService.retryFaceRecognition(id);
if (success) {
return ApiResponse.success("人脸识别重试任务已提交");
} else {
return ApiResponse.fail("提交重试任务失败,请检查人脸样本状态");
}
}
}

View File

@@ -191,4 +191,53 @@ public class FaceProcessingKafkaService {
log.error("更新人脸样本状态失败, faceSampleId: {}", faceSampleId, e);
}
}
/**
* 重试失败的人脸识别
* 用于手动重试状态为-1的人脸样本
*
* @param faceSampleId 人脸样本ID
* @return 是否成功提交重试任务
*/
public boolean retryFaceRecognition(Long faceSampleId) {
try {
// 查询人脸样本信息
FaceSampleEntity faceSample = faceSampleMapper.getEntity(faceSampleId);
if (faceSample == null) {
log.error("人脸样本不存在, faceSampleId: {}", faceSampleId);
return false;
}
// 检查状态是否为失败状态(-1)
if (faceSample.getStatus() != -1) {
log.warn("人脸样本状态不是失败状态, 无需重试, faceSampleId: {}, status: {}",
faceSampleId, faceSample.getStatus());
return false;
}
// 构造人脸处理消息
FaceProcessingMessage message = FaceProcessingMessage.builder()
.faceSampleId(faceSample.getId())
.scenicId(faceSample.getScenicId())
.deviceId(faceSample.getDeviceId())
.faceUrl(faceSample.getFaceUrl())
.shotTime(faceSample.getCreateAt())
.createTime(new Date())
.source("retry-manual")
.build();
// 提交到线程池进行异步处理
faceRecognitionExecutor.execute(() -> processFaceRecognitionAsync(message));
log.info("人脸识别重试任务已提交, faceSampleId: {}, 活跃线程: {}, 队列大小: {}",
faceSampleId, faceRecognitionExecutor.getActiveCount(),
faceRecognitionExecutor.getQueue().size());
return true;
} catch (Exception e) {
log.error("提交人脸识别重试任务失败, faceSampleId: {}", faceSampleId, e);
return false;
}
}
}

View File

@@ -1341,12 +1341,32 @@ public class FaceServiceImpl implements FaceService {
resultItems = Collections.emptyList();
}
// 获取景区配置的得分阈值
Float scoreThreshold = null;
try {
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId());
if (scenicConfig != null) {
BigDecimal thresholdConfig = scenicConfig.getBigDecimal("face_select_score_threshold");
if (thresholdConfig != null) {
// 配置值是0~100的小数,需要转换为0~1的阈值
scoreThreshold = thresholdConfig.floatValue() / 100.0f;
}
}
} catch (Exception e) {
log.warn("获取景区人脸得分阈值配置失败, scenicId: {}, 将不进行得分筛选", face.getScenicId(), e);
}
List<Long> persistedAcceptedIds = parseMatchSampleIds(face.getMatchSampleIds());
LinkedHashSet<Long> sampleUniverse = new LinkedHashSet<>();
Map<Long, SearchFaceResultItem> itemBySampleId = new LinkedHashMap<>();
for (SearchFaceResultItem item : resultItems) {
Long sampleId = parseLongSilently(item.getExtData());
if (sampleId != null) {
// 根据得分阈值筛选样本
if (scoreThreshold != null && item.getScore() != null && item.getScore() < scoreThreshold) {
// 得分低于阈值,跳过此样本
continue;
}
sampleUniverse.add(sampleId);
itemBySampleId.putIfAbsent(sampleId, item);
}