refactor(face): 重构人脸识别服务逻辑

- 优化了 faceId 参数校验和日志记录
- 重构了人脸识别主流程,增加了异常处理和日志记录
- 新增了人脸识别补救逻辑方法
- 优化了源文件关联、免费逻辑、购买状态处理等方法
- 重构了视频重切逻辑,使其更加清晰- 优化了时间范围筛选逻辑
This commit is contained in:
2025-09-10 17:00:09 +08:00
parent 7839082352
commit eaf959e1b8
2 changed files with 450 additions and 147 deletions

View File

@@ -26,7 +26,7 @@ import com.ycwl.basic.model.pc.face.req.FaceReqQuery;
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
import com.ycwl.basic.model.pc.scenic.entity.ScenicConfigEntity;
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
import com.ycwl.basic.model.pc.source.req.SourceReqQuery;
@@ -245,121 +245,361 @@ public class FaceServiceImpl implements FaceService {
@Override
public SearchFaceRespVo matchFaceId(Long faceId, boolean isNew) {
FaceEntity face = faceRepository.getFace(faceId);
if (face == null) {
return null;
if (faceId == null) {
throw new IllegalArgumentException("faceId 不能为空");
}
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(face.getScenicId());
IFaceBodyAdapter faceBodyAdapter = scenicService.getScenicFaceBodyAdapter(face.getScenicId());
SearchFaceRespVo scenicDbSearchResult = faceService.searchFace(faceBodyAdapter, String.valueOf(face.getScenicId()), face.getFaceUrl(), "人脸识别");
if (scenicDbSearchResult == null) {
throw new BaseException("人脸识别失败,请换一张试试把~");
}
if (scenicDbSearchResult.getSampleListIds() != null && scenicDbSearchResult.getFirstMatchRate() != null && !scenicDbSearchResult.getSampleListIds().isEmpty()) {
if (scenicConfig != null && scenicConfig.getFaceDetectHelperThreshold() != null && scenicConfig.getFaceDetectHelperThreshold() > 0) {
if (scenicDbSearchResult.getSampleListIds().size() < scenicConfig.getFaceDetectHelperThreshold()) {
// 补救逻辑
Long resultItem = scenicDbSearchResult.getSampleListIds().getFirst();
FaceSampleEntity faceSample = faceRepository.getFaceSample(resultItem);
if (faceSample != null) {
// 以这个结果为人脸库的匹配结果
SearchFaceRespVo tmpResult = faceService.searchFace(faceBodyAdapter, String.valueOf(face.getScenicId()), faceSample.getFaceUrl(), "人脸补救措施1");
if (tmpResult != null && tmpResult.getSampleListIds() != null && !tmpResult.getSampleListIds().isEmpty()) {
scenicDbSearchResult = tmpResult;
}
}
}
log.debug("开始人脸匹配:faceId={}, isNew={}", faceId, isNew);
try {
// 1. 数据准备:获取人脸信息、景区配置、适配器等
FaceEntity face = faceRepository.getFace(faceId);
if (face == null) {
log.warn("人脸不存在,faceId: {}", faceId);
return null;
}
}
FaceEntity faceEntity = new FaceEntity();
faceEntity.setId(faceId);
faceEntity.setScore(scenicDbSearchResult.getScore());
faceEntity.setMatchResult(scenicDbSearchResult.getSearchResultJson());
if (scenicDbSearchResult.getFirstMatchRate() != null) {
faceEntity.setFirstMatchRate(BigDecimal.valueOf(scenicDbSearchResult.getFirstMatchRate()));
}
if (scenicDbSearchResult.getSampleListIds() != null) {
faceEntity.setMatchSampleIds(scenicDbSearchResult.getSampleListIds().stream().map(String::valueOf).collect(Collectors.joining(",")));
}
faceEntity.setCreateAt(new Date());
faceEntity.setScenicId(face.getScenicId());
faceEntity.setMemberId(face.getMemberId());
faceEntity.setFaceUrl(face.getFaceUrl());
List<Long> sampleListIds = scenicDbSearchResult.getSampleListIds();
faceMapper.update(faceEntity);
faceRepository.clearFaceCache(faceEntity.getId());
if (sampleListIds != null && !sampleListIds.isEmpty()) {// 匹配原片:照片
List<SourceEntity> sourceEntities = sourceMapper.listBySampleIds(sampleListIds);
List<MemberSourceEntity> memberSourceEntityList = sourceEntities.stream().map(sourceEntity -> {
DeviceConfigManager deviceConfig = deviceRepository.getDeviceConfigManager(sourceEntity.getDeviceId());
MemberSourceEntity memberSourceEntity = new MemberSourceEntity();
memberSourceEntity.setScenicId(face.getScenicId());
memberSourceEntity.setFaceId(face.getId());
memberSourceEntity.setMemberId(face.getMemberId());
memberSourceEntity.setSourceId(sourceEntity.getId());
memberSourceEntity.setType(sourceEntity.getType());
memberSourceEntity.setIsFree(0);
if (deviceConfig != null) {
if (sourceEntity.getType() == 1) {
if (Integer.valueOf(1).equals(deviceConfig.getInteger("video_free"))) {
memberSourceEntity.setIsFree(1);
}
} else if (sourceEntity.getType() == 2) {
if (Integer.valueOf(1).equals(deviceConfig.getInteger("image_free"))) {
memberSourceEntity.setIsFree(1);
}
}
}
return memberSourceEntity;
}).collect(Collectors.toList());
List<Long> freeSourceIds = new ArrayList<>();
List<MemberSourceEntity> photoSource = memberSourceEntityList.stream().filter(item -> item.getIsFree() == 0).filter(item -> Integer.valueOf(2).equals(item.getType())).toList();
if (isNew) {
// 送照片逻辑
if (scenicConfig != null && scenicConfig.getPhotoFreeNum() != null && scenicConfig.getPhotoFreeNum() > 0) {
if (scenicConfig.getPhotoFreeNum() > photoSource.size()) {
freeSourceIds.addAll(photoSource.stream().map(MemberSourceEntity::getSourceId).toList());
} else {
freeSourceIds.addAll(photoSource.stream().limit(scenicConfig.getPhotoFreeNum()).map(MemberSourceEntity::getSourceId).toList());
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId());
IFaceBodyAdapter faceBodyAdapter = scenicService.getScenicFaceBodyAdapter(face.getScenicId());
if (faceBodyAdapter == null) {
log.error("无法获取人脸识别适配器,scenicId: {}", face.getScenicId());
throw new BaseException("人脸识别服务不可用,请稍后再试");
}
// 2. 人脸识别:执行人脸搜索和补救逻辑
SearchFaceRespVo scenicDbSearchResult;
try {
scenicDbSearchResult = faceService.searchFace(faceBodyAdapter,
String.valueOf(face.getScenicId()),
face.getFaceUrl(),
"人脸识别");
} catch (Exception e) {
log.error("人脸识别服务调用失败,faceId={}, scenicId={}", faceId, face.getScenicId(), e);
throw new BaseException("人脸识别失败,请换一张试试把~");
}
if (scenicDbSearchResult == null) {
log.warn("人脸识别返回结果为空,faceId={}", faceId);
throw new BaseException("人脸识别失败,请换一张试试把~");
}
// 执行补救逻辑(如需要)
scenicDbSearchResult = executeFaceRecoveryLogic(scenicDbSearchResult, scenicConfig,
faceBodyAdapter, face.getScenicId());
// 3. 结果处理:更新人脸实体信息
try {
updateFaceEntityResult(face, scenicDbSearchResult, faceId);
} catch (Exception e) {
log.error("更新人脸结果失败,faceId={}", faceId, e);
throw new BaseException("保存人脸识别结果失败");
}
List<Long> sampleListIds = scenicDbSearchResult.getSampleListIds();
if (sampleListIds != null && !sampleListIds.isEmpty()) {
try {
// 4. 源文件关联:处理匹配到的源文件
List<MemberSourceEntity> memberSourceEntityList = processMemberSources(sampleListIds, face);
if (!memberSourceEntityList.isEmpty()) {
// 5. 业务逻辑处理:免费逻辑、购买状态、任务创建
List<Long> freeSourceIds = processFreeSourceLogic(memberSourceEntityList, scenicConfig, isNew);
processBuyStatus(memberSourceEntityList, freeSourceIds, face.getMemberId(),
face.getScenicId(), faceId);
// 处理视频重切逻辑
handleVideoRecreation(scenicConfig, memberSourceEntityList, faceId,
face.getMemberId(), sampleListIds, isNew);
// 保存关联关系并创建任务
sourceMapper.addRelations(memberSourceEntityList);
taskTaskService.autoCreateTaskByFaceId(face.getId());
log.info("人脸匹配完成:faceId={}, 匹配样本数={}, 关联源文件数={}, 免费数={}",
faceId, sampleListIds.size(), memberSourceEntityList.size(), freeSourceIds.size());
}
} catch (Exception e) {
log.error("处理源文件关联失败,faceId={}", faceId, e);
// 源文件关联失败不影响主流程,记录错误但不抛出异常
}
} else {
// 重新切视频逻辑
if (scenicConfig != null && !Boolean.TRUE.equals(scenicConfig.getDisableSourceVideo())) {
long videoCount = memberSourceEntityList.stream().filter(item -> item.getType().equals(1)).count();
long photoCount = memberSourceEntityList.stream().filter(item -> item.getType().equals(2)).count();
if (photoCount > videoCount) {
VideoPieceGetter.Task task = new VideoPieceGetter.Task();
task.faceId = faceId;
task.faceSampleIds = sampleListIds;
task.templateId = null;
task.memberId = face.getMemberId();
task.callback = () -> {
log.info("task callback: {}", task);
};
VideoPieceGetter.addTask(task);
}
}
log.warn("人脸匹配无结果:faceId={}", faceId);
}
if (!memberSourceEntityList.isEmpty()) {
IsBuyRespVO isBuy = orderBiz.isBuy(face.getMemberId(), face.getScenicId(), memberSourceEntityList.getFirst().getType(), faceEntity.getId());
for (MemberSourceEntity memberSourceEntity : memberSourceEntityList) {
if (isBuy.isBuy()) { // 如果用户买过
memberSourceEntity.setIsBuy(1);
} else if (isBuy.isFree()) { // 全免费逻辑
memberSourceEntity.setIsBuy(1);
} else {
memberSourceEntity.setIsBuy(0);
}
if (freeSourceIds.contains(memberSourceEntity.getSourceId())) {
return scenicDbSearchResult;
} catch (BaseException e) {
// 业务异常直接抛出
throw e;
} catch (Exception e) {
log.error("人脸匹配处理异常,faceId={}, isNew={}", faceId, isNew, e);
throw new BaseException("人脸匹配处理失败,请稍后重试");
}
}
/**
* 更新人脸实体结果信息
*/
private void updateFaceEntityResult(FaceEntity originalFace, SearchFaceRespVo searchResult, Long faceId) {
FaceEntity faceEntity = new FaceEntity();
faceEntity.setId(faceId);
faceEntity.setScore(searchResult.getScore());
faceEntity.setMatchResult(searchResult.getSearchResultJson());
if (searchResult.getFirstMatchRate() != null) {
faceEntity.setFirstMatchRate(BigDecimal.valueOf(searchResult.getFirstMatchRate()));
}
if (searchResult.getSampleListIds() != null) {
faceEntity.setMatchSampleIds(searchResult.getSampleListIds().stream()
.map(String::valueOf)
.collect(Collectors.joining(",")));
}
faceEntity.setCreateAt(new Date());
faceEntity.setScenicId(originalFace.getScenicId());
faceEntity.setMemberId(originalFace.getMemberId());
faceEntity.setFaceUrl(originalFace.getFaceUrl());
faceMapper.update(faceEntity);
faceRepository.clearFaceCache(faceEntity.getId());
log.debug("人脸结果更新完成:faceId={}, score={}, 匹配数={}",
faceId, searchResult.getScore(),
searchResult.getSampleListIds() != null ? searchResult.getSampleListIds().size() : 0);
}
/**
* 执行人脸识别补救逻辑
* 当匹配结果数量少于阈值时,使用第一个匹配结果重新进行人脸搜索
*/
private SearchFaceRespVo executeFaceRecoveryLogic(SearchFaceRespVo originalResult,
ScenicConfigManager scenicConfig,
IFaceBodyAdapter faceBodyAdapter,
Long scenicId) {
if (originalResult == null || originalResult.getSampleListIds() == null ||
originalResult.getFirstMatchRate() == null || originalResult.getSampleListIds().isEmpty()) {
return originalResult;
}
if (scenicConfig == null) {
return originalResult;
}
// 检查是否需要执行补救逻辑
Integer helperThreshold = scenicConfig.getInteger("face_detect_helper_threshold", 0);
if (helperThreshold == null || helperThreshold <= 0) {
return originalResult;
}
// 检查匹配结果数量是否少于阈值
if (originalResult.getSampleListIds().size() >= helperThreshold) {
return originalResult;
}
log.info("执行人脸识别补救逻辑,原匹配数量: {}, 阈值: {}",
originalResult.getSampleListIds().size(), helperThreshold);
// 获取第一个匹配结果
Long firstResultId = originalResult.getSampleListIds().getFirst();
FaceSampleEntity faceSample = faceRepository.getFaceSample(firstResultId);
if (faceSample == null) {
log.warn("补救逻辑失败:无法找到人脸样本, sampleId: {}", firstResultId);
return originalResult;
}
// 使用人脸样本重新进行搜索
try {
SearchFaceRespVo recoveryResult = faceService.searchFace(faceBodyAdapter,
String.valueOf(scenicId),
faceSample.getFaceUrl(),
"人脸补救措施1");
if (recoveryResult != null && recoveryResult.getSampleListIds() != null &&
!recoveryResult.getSampleListIds().isEmpty()) {
log.info("补救逻辑成功,新匹配数量: {}", recoveryResult.getSampleListIds().size());
return recoveryResult;
}
} catch (Exception e) {
log.warn("补救逻辑执行失败", e);
}
return originalResult;
}
/**
* 处理源文件关联逻辑
* 根据匹配的样本ID创建MemberSourceEntity列表
*/
private List<MemberSourceEntity> processMemberSources(List<Long> sampleListIds, FaceEntity face) {
if (sampleListIds == null || sampleListIds.isEmpty()) {
return Collections.emptyList();
}
List<SourceEntity> sourceEntities = sourceMapper.listBySampleIds(sampleListIds);
if (sourceEntities.isEmpty()) {
return Collections.emptyList();
}
return sourceEntities.stream().map(sourceEntity -> {
DeviceConfigManager deviceConfig = deviceRepository.getDeviceConfigManager(sourceEntity.getDeviceId());
MemberSourceEntity memberSourceEntity = new MemberSourceEntity();
memberSourceEntity.setScenicId(face.getScenicId());
memberSourceEntity.setFaceId(face.getId());
memberSourceEntity.setMemberId(face.getMemberId());
memberSourceEntity.setSourceId(sourceEntity.getId());
memberSourceEntity.setType(sourceEntity.getType());
// 设置免费状态 - 默认收费
memberSourceEntity.setIsFree(0);
if (deviceConfig != null) {
// 视频类型检查
if (sourceEntity.getType() == 1) {
if (Integer.valueOf(1).equals(deviceConfig.getInteger("video_free"))) {
memberSourceEntity.setIsFree(1);
}
}
sourceMapper.addRelations(memberSourceEntityList);
taskTaskService.autoCreateTaskByFaceId(face.getId());
// 图片类型检查
else if (sourceEntity.getType() == 2) {
if (Integer.valueOf(1).equals(deviceConfig.getInteger("image_free"))) {
memberSourceEntity.setIsFree(1);
}
}
}
return memberSourceEntity;
}).collect(Collectors.toList());
}
/**
* 处理免费源文件逻辑
* 根据景区配置和是否新用户决定哪些照片可以免费
*/
private List<Long> processFreeSourceLogic(List<MemberSourceEntity> memberSourceEntityList,
ScenicConfigManager scenicConfig,
boolean isNew) {
List<Long> freeSourceIds = new ArrayList<>();
if (memberSourceEntityList.isEmpty()) {
return freeSourceIds;
}
if (isNew) {
// 新用户送照片逻辑
List<MemberSourceEntity> photoSource = memberSourceEntityList.stream()
.filter(item -> item.getIsFree() == 0) // 只考虑收费的
.filter(item -> Integer.valueOf(2).equals(item.getType())) // 只考虑照片类型
.toList();
Integer photoFreeNum = scenicConfig != null ? scenicConfig.getInteger("photo_free_num") : null;
if (scenicConfig != null && photoFreeNum != null && photoFreeNum > 0) {
int freePhotoCount = Math.min(photoFreeNum, photoSource.size());
freeSourceIds.addAll(photoSource.stream()
.limit(freePhotoCount)
.map(MemberSourceEntity::getSourceId)
.toList());
log.debug("新用户免费照片逻辑:配置免费数量 {}, 实际可用 {}, 赠送 {} 张",
photoFreeNum, photoSource.size(), freePhotoCount);
}
}
return scenicDbSearchResult;
return freeSourceIds;
}
/**
* 处理购买状态逻辑
* 设置每个源文件的购买状态和免费状态
*/
private void processBuyStatus(List<MemberSourceEntity> memberSourceEntityList,
List<Long> freeSourceIds,
Long memberId,
Long scenicId,
Long faceId) {
if (memberSourceEntityList.isEmpty()) {
return;
}
// 获取用户购买状态
IsBuyRespVO isBuy = orderBiz.isBuy(memberId, scenicId,
memberSourceEntityList.getFirst().getType(),
faceId);
for (MemberSourceEntity memberSourceEntity : memberSourceEntityList) {
// 设置购买状态
if (isBuy.isBuy()) {
// 如果用户买过
memberSourceEntity.setIsBuy(1);
} else if (isBuy.isFree()) {
// 全免费逻辑
memberSourceEntity.setIsBuy(1);
} else {
memberSourceEntity.setIsBuy(0);
}
// 设置免费状态
if (freeSourceIds.contains(memberSourceEntity.getSourceId())) {
memberSourceEntity.setIsFree(1);
}
}
log.debug("购买状态处理完成:用户购买状态 isBuy={}, isFree={}, 免费源文件数量={}",
isBuy.isBuy(), isBuy.isFree(), freeSourceIds.size());
}
/**
* 处理视频重切逻辑
* 当非新用户且照片数量大于视频数量时,创建视频重切任务
*/
private void handleVideoRecreation(ScenicConfigManager scenicConfig,
List<MemberSourceEntity> memberSourceEntityList,
Long faceId,
Long memberId,
List<Long> sampleListIds,
boolean isNew) {
// 新用户不执行视频重切逻辑
if (isNew) {
return;
}
// 检查景区是否禁用源视频功能
Boolean disableSourceVideo = scenicConfig != null ? scenicConfig.getBoolean("disable_source_video") : null;
if (scenicConfig == null || Boolean.TRUE.equals(disableSourceVideo)) {
log.debug("视频重切逻辑跳过:景区禁用了源视频功能");
return;
}
// 统计视频和照片数量
long videoCount = memberSourceEntityList.stream()
.filter(item -> Integer.valueOf(1).equals(item.getType()))
.count();
long photoCount = memberSourceEntityList.stream()
.filter(item -> Integer.valueOf(2).equals(item.getType()))
.count();
log.debug("视频重切逻辑:视频数量 {}, 照片数量 {}", videoCount, photoCount);
// 只有照片数量大于视频数量时才创建重切任务
if (photoCount > videoCount) {
VideoPieceGetter.Task task = new VideoPieceGetter.Task();
task.faceId = faceId;
task.faceSampleIds = sampleListIds;
task.templateId = null;
task.memberId = memberId;
task.callback = () -> {
log.info("视频重切任务回调: {}", task);
};
VideoPieceGetter.addTask(task);
log.debug("视频重切任务已创建:faceId={}, memberId={}, sampleIds={}",
faceId, memberId, sampleListIds.size());
} else {
log.debug("视频重切逻辑跳过:照片数量({})未超过视频数量({})", photoCount, videoCount);
}
}
@Override
@@ -443,8 +683,8 @@ public class FaceServiceImpl implements FaceService {
sourceImageContent.setContentType(2);
sourceVideoContent.setLockType(-1);
sourceImageContent.setLockType(-1);
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(faceRespVO.getScenicId());
if (!Boolean.TRUE.equals(scenicConfig.getDisableSourceImage())) {
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(faceRespVO.getScenicId());
if (!Boolean.TRUE.equals(scenicConfig.getBoolean("disable_source_image"))) {
IsBuyRespVO isBuyRespVO = orderBiz.isBuy(userId, faceRespVO.getScenicId(), 2, faceId);
sourceImageContent.setSourceType(isBuyRespVO.getGoodsType());
sourceImageContent.setContentId(isBuyRespVO.getGoodsId());
@@ -463,7 +703,7 @@ public class FaceServiceImpl implements FaceService {
sourceImageContent.setFreeCount((int) freeCount);
contentList.add(sourceImageContent);
}
if (!Boolean.TRUE.equals(scenicConfig.getDisableSourceVideo())) {
if (!Boolean.TRUE.equals(scenicConfig.getBoolean("disable_source_video"))) {
IsBuyRespVO isBuyRespVO = orderBiz.isBuy(userId, faceRespVO.getScenicId(), 1, faceId);
sourceVideoContent.setSourceType(isBuyRespVO.getGoodsType());
sourceVideoContent.setContentId(isBuyRespVO.getGoodsId());

View File

@@ -2,6 +2,7 @@ package com.ycwl.basic.service.task.impl;
import cn.hutool.core.date.DateUtil;
import com.ycwl.basic.integration.common.manager.DeviceConfigManager;
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
import com.ycwl.basic.utils.JacksonUtil;
import com.aliyuncs.facebody.model.v20191230.SearchFaceRequest;
import com.ycwl.basic.biz.OrderBiz;
@@ -168,26 +169,22 @@ public class TaskFaceServiceImpl implements TaskFaceService {
request.setDbName(dbName);
request.setImageUrl(faceUrl);
request.setLimit(200);
// request.setQualityScoreThreshold(60f);
FaceDetectLog logEntity = FaceDetectLog.quickCreate(reason, request);
float threshold = 0;
int tourMinutes = -1;
ScenicConfigManager scenicConfig = null;
if (StringUtils.isNumeric(dbName)) {
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(Long.valueOf(dbName));
scenicConfig = scenicRepository.getScenicConfigManager(Long.valueOf(dbName));
if (scenicConfig != null) {
if (scenicConfig.getFaceScoreThreshold() != null) {
threshold = scenicConfig.getFaceScoreThreshold() / 100F;
}
if (scenicConfig.getTourTime() != null) {
tourMinutes = scenicConfig.getTourTime();
if (scenicConfig.getFloat("face_score_threshold") != null) {
threshold = scenicConfig.getFloat("face_score_threshold") / 100F;
}
}
} else if (StringUtils.isNumeric(dbName.replace(USER_FACE_DB_NAME, ""))) {
Long scenicId = Long.valueOf(dbName.replace(USER_FACE_DB_NAME, ""));
ScenicConfigEntity scenicConfig = scenicRepository.getScenicConfig(scenicId);
scenicConfig = scenicRepository.getScenicConfigManager(scenicId);
if (scenicConfig != null) {
if (scenicConfig.getFaceScoreThreshold() != null) {
threshold = scenicConfig.getFaceScoreThreshold() / 100F;
if (scenicConfig.getFloat("face_score_threshold") != null) {
threshold = scenicConfig.getFloat("face_score_threshold") / 100F;
}
}
}
@@ -219,35 +216,8 @@ public class TaskFaceServiceImpl implements TaskFaceService {
.filter(StringUtils::isNumeric)
.map(Long::valueOf)
.collect(Collectors.toList());
List<FaceSampleEntity> allFaceSampleList = new ArrayList<>();
if (StringUtils.isNumeric(dbName)) { // 景区
allFaceSampleList = faceSampleMapper.listByIds(allFaceSampleIds);
if (!acceptFaceSampleIds.isEmpty()) {
List<Long> finalAcceptFaceSampleIds = acceptFaceSampleIds;
Optional<FaceSampleEntity> firstFaceSample = allFaceSampleList.stream()
.filter(faceSample -> finalAcceptFaceSampleIds.contains(faceSample.getId()))
.sorted(Comparator.comparing(FaceSampleEntity::getCreateAt).reversed())
.findAny();
// Long firstFaceSampleId = acceptFaceSampleIds.getFirst();
// Optional<FaceSampleEntity> firstFaceSample = allFaceSampleList.stream().filter(faceSample -> faceSample.getId().equals(firstFaceSampleId)).findAny();
if (firstFaceSample.isPresent()) {
if (tourMinutes > 0) {
List<FaceSampleEntity> acceptFaceSampleList = faceSampleMapper.listByIds(acceptFaceSampleIds);
Date startDate = DateUtil.offsetMinute(firstFaceSample.get().getCreateAt(), -tourMinutes);
Date endDate = DateUtil.offsetMinute(firstFaceSample.get().getCreateAt(), 1);
acceptFaceSampleIds = acceptFaceSampleList.stream()
.filter(faceSample -> faceSample.getCreateAt().after(startDate) && faceSample.getCreateAt().before(endDate))
.map(FaceSampleEntity::getId)
.collect(Collectors.toList());
log.info("时间范围逻辑:最高匹配:{},时间范围需要在:{}~{}间", firstFaceSample, startDate, endDate);
} else {
log.info("时间范围逻辑:景区未限制");
}
} else {
log.info("时间范围逻辑:最高匹配ID:{},未找到", "--");
}
}
}
List<FaceSampleEntity> allFaceSampleList = faceSampleMapper.listByIds(allFaceSampleIds);
acceptFaceSampleIds = applySampleFilters(acceptFaceSampleIds, allFaceSampleList, scenicConfig);
List<MatchLocalRecord> collect = new ArrayList<>();
for (SearchFaceResultItem item : records) {
MatchLocalRecord record = new MatchLocalRecord();
@@ -360,4 +330,97 @@ public class TaskFaceServiceImpl implements TaskFaceService {
}
redisTemplate.opsForValue().set(FaceConstant.FACE_USER_URL_PFX + faceId, faceUrl, 3, TimeUnit.DAYS);
}
/**
* 应用样本筛选逻辑
* 包含时间范围筛选和未来可能的其他筛选策略
*
* @param acceptedSampleIds 已接受的样本ID列表
* @param allFaceSampleList 所有人脸样本实体列表
* @param scenicConfig 景区配置,包含各种筛选策略的参数
* @return 筛选后的样本ID列表
*/
private List<Long> applySampleFilters(List<Long> acceptedSampleIds,
List<FaceSampleEntity> allFaceSampleList,
ScenicConfigManager scenicConfig) {
if (acceptedSampleIds == null || acceptedSampleIds.isEmpty()) {
return acceptedSampleIds;
}
if (scenicConfig == null) {
// 没有配置,不管
return acceptedSampleIds;
}
// 1. 找到最高匹配的样本(按创建时间倒序排列的第一个)
Optional<FaceSampleEntity> firstFaceSample = allFaceSampleList.stream()
.filter(faceSample -> acceptedSampleIds.contains(faceSample.getId())).max(Comparator.comparing(FaceSampleEntity::getCreateAt));
if (firstFaceSample.isEmpty()) {
log.warn("样本筛选逻辑:未找到匹配的人脸样本,acceptedIds: {}", acceptedSampleIds);
return acceptedSampleIds;
}
FaceSampleEntity topMatchSample = firstFaceSample.get();
log.debug("样本筛选逻辑:找到最高匹配样本 ID={}, 创建时间={}",
topMatchSample.getId(), topMatchSample.getCreateAt());
List<Long> filteredIds = acceptedSampleIds;
// 2. 应用时间范围筛选(基于景区配置)
if (scenicConfig.getInteger("tour_time", 0) > 0) {
filteredIds = filterSampleIdsByTimeRange(filteredIds, topMatchSample, scenicConfig.getInteger("tour_time"));
log.debug("应用时间范围筛选:游览时间限制={}分钟", scenicConfig.getInteger("tour_time"));
} else {
log.debug("时间范围逻辑:景区未设置游览时间限制");
}
// 3. TODO: 基于景区配置的其他筛选策略
// 可以根据 scenicConfig 中的配置来决定是否启用特定筛选
// 示例:未来可能的筛选策略
// if (scenicConfig.getEnableLocationFilter() != null && scenicConfig.getEnableLocationFilter()) {
// filteredIds = applyLocationFilter(filteredIds, allFaceSampleList, scenicConfig);
// }
// if (scenicConfig.getEnableQualityFilter() != null && scenicConfig.getEnableQualityFilter()) {
// filteredIds = applyQualityFilter(filteredIds, allFaceSampleList, scenicConfig);
// }
// if (scenicConfig.getMaxSampleCount() != null) {
// filteredIds = applySampleCountLimit(filteredIds, scenicConfig.getMaxSampleCount());
// }
log.debug("样本筛选完成:原始数量={}, 最终数量={}",
acceptedSampleIds.size(), filteredIds.size());
return filteredIds;
}
/**
* 根据时间范围过滤人脸样本ID
* 基于最高匹配样本的时间,过滤出指定时间范围内的样本ID
*/
private List<Long> filterSampleIdsByTimeRange(List<Long> acceptedSampleIds,
FaceSampleEntity firstMatch,
int tourMinutes) {
if (acceptedSampleIds == null || acceptedSampleIds.isEmpty() ||
firstMatch == null || tourMinutes <= 0) {
return acceptedSampleIds;
}
List<FaceSampleEntity> acceptFaceSampleList = faceSampleMapper.listByIds(acceptedSampleIds);
if (acceptFaceSampleList.isEmpty()) {
return acceptedSampleIds;
}
Date startDate = DateUtil.offsetMinute(firstMatch.getCreateAt(), -tourMinutes);
Date endDate = DateUtil.offsetMinute(firstMatch.getCreateAt(), 1);
List<Long> filteredIds = acceptFaceSampleList.stream()
.filter(faceSample -> faceSample.getCreateAt().after(startDate) &&
faceSample.getCreateAt().before(endDate))
.map(FaceSampleEntity::getId)
.collect(Collectors.toList());
log.info("时间范围逻辑:最高匹配:{},时间范围:{}~{},原样本数:{},过滤后样本数:{}",
firstMatch.getId(), startDate, endDate, acceptedSampleIds.size(), filteredIds.size());
return filteredIds;
}
}