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 cc32add..8e232db 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 @@ -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 sampleListIds = scenicDbSearchResult.getSampleListIds(); - faceMapper.update(faceEntity); - faceRepository.clearFaceCache(faceEntity.getId()); - if (sampleListIds != null && !sampleListIds.isEmpty()) {// 匹配原片:照片 - List sourceEntities = sourceMapper.listBySampleIds(sampleListIds); - List 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 freeSourceIds = new ArrayList<>(); - List 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 sampleListIds = scenicDbSearchResult.getSampleListIds(); + if (sampleListIds != null && !sampleListIds.isEmpty()) { + try { + // 4. 源文件关联:处理匹配到的源文件 + List memberSourceEntityList = processMemberSources(sampleListIds, face); + + if (!memberSourceEntityList.isEmpty()) { + // 5. 业务逻辑处理:免费逻辑、购买状态、任务创建 + List 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); + + 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 processMemberSources(List sampleListIds, FaceEntity face) { + if (sampleListIds == null || sampleListIds.isEmpty()) { + return Collections.emptyList(); + } + + List 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); } - if (freeSourceIds.contains(memberSourceEntity.getSourceId())) { + } + // 图片类型检查 + else if (sourceEntity.getType() == 2) { + if (Integer.valueOf(1).equals(deviceConfig.getInteger("image_free"))) { memberSourceEntity.setIsFree(1); } } - sourceMapper.addRelations(memberSourceEntityList); - taskTaskService.autoCreateTaskByFaceId(face.getId()); + } + + return memberSourceEntity; + }).collect(Collectors.toList()); + } + + /** + * 处理免费源文件逻辑 + * 根据景区配置和是否新用户决定哪些照片可以免费 + */ + private List processFreeSourceLogic(List memberSourceEntityList, + ScenicConfigManager scenicConfig, + boolean isNew) { + List freeSourceIds = new ArrayList<>(); + + if (memberSourceEntityList.isEmpty()) { + return freeSourceIds; + } + + if (isNew) { + // 新用户送照片逻辑 + List 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 memberSourceEntityList, + List 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 memberSourceEntityList, + Long faceId, + Long memberId, + List 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()); 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 44f4beb..910716e 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 @@ -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 allFaceSampleList = new ArrayList<>(); - if (StringUtils.isNumeric(dbName)) { // 景区 - allFaceSampleList = faceSampleMapper.listByIds(allFaceSampleIds); - if (!acceptFaceSampleIds.isEmpty()) { - List finalAcceptFaceSampleIds = acceptFaceSampleIds; - Optional firstFaceSample = allFaceSampleList.stream() - .filter(faceSample -> finalAcceptFaceSampleIds.contains(faceSample.getId())) - .sorted(Comparator.comparing(FaceSampleEntity::getCreateAt).reversed()) - .findAny(); -// Long firstFaceSampleId = acceptFaceSampleIds.getFirst(); -// Optional firstFaceSample = allFaceSampleList.stream().filter(faceSample -> faceSample.getId().equals(firstFaceSampleId)).findAny(); - if (firstFaceSample.isPresent()) { - if (tourMinutes > 0) { - List 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 allFaceSampleList = faceSampleMapper.listByIds(allFaceSampleIds); + acceptFaceSampleIds = applySampleFilters(acceptFaceSampleIds, allFaceSampleList, scenicConfig); List 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 applySampleFilters(List acceptedSampleIds, + List allFaceSampleList, + ScenicConfigManager scenicConfig) { + if (acceptedSampleIds == null || acceptedSampleIds.isEmpty()) { + return acceptedSampleIds; + } + if (scenicConfig == null) { + // 没有配置,不管 + return acceptedSampleIds; + } + + // 1. 找到最高匹配的样本(按创建时间倒序排列的第一个) + Optional 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 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 filterSampleIdsByTimeRange(List acceptedSampleIds, + FaceSampleEntity firstMatch, + int tourMinutes) { + if (acceptedSampleIds == null || acceptedSampleIds.isEmpty() || + firstMatch == null || tourMinutes <= 0) { + return acceptedSampleIds; + } + + List acceptFaceSampleList = faceSampleMapper.listByIds(acceptedSampleIds); + if (acceptFaceSampleList.isEmpty()) { + return acceptedSampleIds; + } + + Date startDate = DateUtil.offsetMinute(firstMatch.getCreateAt(), -tourMinutes); + Date endDate = DateUtil.offsetMinute(firstMatch.getCreateAt(), 1); + + List 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; + } }