From ccddab37ea36d8a99d2338a330c1d2826f530a45 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Mon, 15 Sep 2025 16:04:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(service):=20=E5=AE=9E=E7=8E=B0=E8=87=AA?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E4=BA=BA=E8=84=B8=E5=8C=B9=E9=85=8D=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 matchCustomFaceId 方法,实现自定义人脸匹配逻辑 - 优化 mergeSearchResults 方法,合并多个搜索结果 - 在 TaskFaceService 接口中添加 applySampleFilters 方法 - 在 TaskFaceServiceImpl 中实现 applySampleFilters 方法 --- .../service/pc/impl/FaceServiceImpl.java | 146 ++++++++++++++++++ .../basic/service/task/TaskFaceService.java | 15 ++ .../task/impl/TaskFaceServiceImpl.java | 3 +- 3 files changed, 163 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java b/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java index 20dbfe1c..672f76a1 100644 --- a/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java @@ -69,9 +69,11 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.LinkedHashSet; import java.util.List; import java.util.Optional; import java.util.Random; +import java.util.Set; import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -948,7 +950,151 @@ public class FaceServiceImpl implements FaceService { @Override public void matchCustomFaceId(Long faceId, List faceSampleIds) { + // 参数验证 + if (faceId == null) { + throw new IllegalArgumentException("faceId 不能为空"); + } + if (faceSampleIds == null || faceSampleIds.isEmpty()) { + throw new IllegalArgumentException("faceSampleIds 不能为空"); + } + + log.debug("开始自定义人脸匹配:faceId={}, faceSampleIds={}", faceId, faceSampleIds); + + try { + // 1. 获取基础数据 + FaceEntity face = faceRepository.getFace(faceId); + if (face == null) { + log.warn("人脸不存在,faceId: {}", faceId); + throw new BaseException("人脸不存在"); + } + List faceSamples = faceSampleMapper.listByIds(faceSampleIds); + if (faceSamples.isEmpty()) { + log.warn("未找到指定的人脸样本,faceSampleIds: {}", faceSampleIds); + throw new BaseException("未找到指定的人脸样本"); + } + + ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId()); + IFaceBodyAdapter faceBodyAdapter = scenicService.getScenicFaceBodyAdapter(face.getScenicId()); + + if (faceBodyAdapter == null) { + log.error("无法获取人脸识别适配器,scenicId: {}", face.getScenicId()); + throw new BaseException("人脸识别服务不可用,请稍后再试"); + } + + // 2. 对每个faceSample进行人脸搜索 + List searchResults = new ArrayList<>(); + for (FaceSampleEntity faceSample : faceSamples) { + try { + SearchFaceRespVo result = faceService.searchFace(faceBodyAdapter, + String.valueOf(face.getScenicId()), + faceSample.getFaceUrl(), + "自定义人脸匹配"); + if (result != null) { + searchResults.add(result); + } + } catch (Exception e) { + log.warn("人脸样本搜索失败,faceSampleId={}, faceUrl={}", + faceSample.getId(), faceSample.getFaceUrl(), e); + // 继续处理其他样本,不中断整个流程 + } + } + + if (searchResults.isEmpty()) { + log.warn("所有人脸样本搜索都失败,faceId={}, faceSampleIds={}", faceId, faceSampleIds); + throw new BaseException("人脸识别失败,请重试"); + } + + // 3. 整合多个搜索结果 + SearchFaceRespVo mergedResult = mergeSearchResults(searchResults); + + // 4. 应用后置筛选逻辑 + if (mergedResult.getSampleListIds() != null && !mergedResult.getSampleListIds().isEmpty()) { + List allFaceSampleList = faceSampleMapper.listByIds(mergedResult.getSampleListIds()); + List filteredSampleIds = faceService.applySampleFilters(mergedResult.getSampleListIds(), allFaceSampleList, scenicConfig); + mergedResult.setSampleListIds(filteredSampleIds); + log.debug("应用后置筛选:原始样本数={}, 筛选后样本数={}", allFaceSampleList.size(), filteredSampleIds.size()); + } + + // 5. 更新人脸实体结果 + updateFaceEntityResult(face, mergedResult, faceId); + + // 6. 执行后续业务逻辑 + List sampleListIds = mergedResult.getSampleListIds(); + if (sampleListIds != null && !sampleListIds.isEmpty()) { + try { + List memberSourceEntityList = processMemberSources(sampleListIds, face); + + if (!memberSourceEntityList.isEmpty()) { + List freeSourceIds = processFreeSourceLogic(memberSourceEntityList, scenicConfig, false); + processBuyStatus(memberSourceEntityList, freeSourceIds, face.getMemberId(), + face.getScenicId(), faceId); + + handleVideoRecreation(scenicConfig, memberSourceEntityList, faceId, + face.getMemberId(), sampleListIds, false); + + 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 { + log.warn("自定义人脸匹配无结果:faceId={}, faceSampleIds={}", faceId, faceSampleIds); + } + + } catch (BaseException e) { + throw e; + } catch (Exception e) { + log.error("自定义人脸匹配处理异常,faceId={}, faceSampleIds={}", faceId, faceSampleIds, e); + throw new BaseException("自定义人脸匹配处理失败,请稍后重试"); + } + } + + /** + * 合并多个搜索结果 + */ + private SearchFaceRespVo mergeSearchResults(List searchResults) { + SearchFaceRespVo mergedResult = new SearchFaceRespVo(); + + // 收集所有样本ID并去重 + Set allSampleIds = new LinkedHashSet<>(); + List allSearchJsons = new ArrayList<>(); + float maxScore = 0f; + float maxFirstMatchRate = 0f; + boolean hasLowThreshold = false; + + for (SearchFaceRespVo result : searchResults) { + if (result.getSampleListIds() != null) { + allSampleIds.addAll(result.getSampleListIds()); + } + 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; + } + } + + mergedResult.setSampleListIds(new ArrayList<>(allSampleIds)); + mergedResult.setSearchResultJson(String.join("|", allSearchJsons)); + mergedResult.setScore(maxScore); + mergedResult.setFirstMatchRate(maxFirstMatchRate); + mergedResult.setLowThreshold(hasLowThreshold); + + log.debug("合并搜索结果完成,总样本数: {}", allSampleIds.size()); + + return mergedResult; } /** diff --git a/src/main/java/com/ycwl/basic/service/task/TaskFaceService.java b/src/main/java/com/ycwl/basic/service/task/TaskFaceService.java index b450ac12..76c220dd 100644 --- a/src/main/java/com/ycwl/basic/service/task/TaskFaceService.java +++ b/src/main/java/com/ycwl/basic/service/task/TaskFaceService.java @@ -1,9 +1,13 @@ package com.ycwl.basic.service.task; import com.ycwl.basic.facebody.adapter.IFaceBodyAdapter; +import com.ycwl.basic.integration.common.manager.ScenicConfigManager; +import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity; import com.ycwl.basic.model.task.resp.SearchFaceRespVo; import org.springframework.web.multipart.MultipartFile; +import java.util.List; + public interface TaskFaceService { SearchFaceRespVo searchFace(Long faceId); @@ -13,4 +17,15 @@ public interface TaskFaceService { boolean deleteFaceSample(Long scenicId, String dbName, String entityId); boolean assureFaceDb(IFaceBodyAdapter faceBodyAdapter, String dbName); + + /** + * 应用样本筛选逻辑 + * @param acceptedSampleIds 已接受的样本ID列表 + * @param allFaceSampleList 所有人脸样本实体列表 + * @param scenicConfig 景区配置管理器 + * @return 筛选后的样本ID列表 + */ + List applySampleFilters(List acceptedSampleIds, + List allFaceSampleList, + ScenicConfigManager scenicConfig); } diff --git a/src/main/java/com/ycwl/basic/service/task/impl/TaskFaceServiceImpl.java b/src/main/java/com/ycwl/basic/service/task/impl/TaskFaceServiceImpl.java index 5da5d8a4..07a48529 100644 --- a/src/main/java/com/ycwl/basic/service/task/impl/TaskFaceServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/task/impl/TaskFaceServiceImpl.java @@ -352,7 +352,8 @@ public class TaskFaceServiceImpl implements TaskFaceService { * @param scenicConfig 景区配置,包含各种筛选策略的参数 * @return 筛选后的样本ID列表 */ - private List applySampleFilters(List acceptedSampleIds, + @Override + public List applySampleFilters(List acceptedSampleIds, List allFaceSampleList, ScenicConfigManager scenicConfig) { if (acceptedSampleIds == null || acceptedSampleIds.isEmpty()) {