You've already forked FrameTour-BE
Merge branch 'refs/heads/result_edit_2'
# Conflicts: # src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java # src/main/java/com/ycwl/basic/service/task/impl/TaskFaceServiceImpl.java
This commit is contained in:
@@ -5,8 +5,10 @@ import com.ycwl.basic.model.mobile.face.FaceRecognizeResp;
|
||||
import com.ycwl.basic.model.mobile.face.FaceStatusResp;
|
||||
import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.model.pc.face.req.FaceRecognitionUpdateReq;
|
||||
import com.ycwl.basic.model.pc.face.req.FaceReqQuery;
|
||||
import com.ycwl.basic.model.pc.face.resp.FaceRespVO;
|
||||
import com.ycwl.basic.model.pc.face.resp.FaceRecognitionDetailVO;
|
||||
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
|
||||
import com.ycwl.basic.model.pc.faceSample.resp.FaceSampleRespVO;
|
||||
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
|
||||
@@ -51,4 +53,8 @@ public interface FaceService {
|
||||
List<FaceSampleEntity> getLowMatchedFaceSamples(Long faceId);
|
||||
|
||||
void matchCustomFaceId(Long faceId, List<Long> faceSampleIds);
|
||||
|
||||
FaceRecognitionDetailVO updateRecognition(FaceRecognitionUpdateReq req);
|
||||
|
||||
FaceRecognitionDetailVO getRecognitionDetail(Long faceId);
|
||||
}
|
||||
|
||||
@@ -25,8 +25,13 @@ import com.ycwl.basic.model.mobile.goods.VideoTaskStatusVO;
|
||||
import com.ycwl.basic.model.mobile.order.IsBuyRespVO;
|
||||
import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO;
|
||||
import com.ycwl.basic.model.mobile.statistic.req.StatisticsRecordAddReq;
|
||||
import com.ycwl.basic.model.pc.device.entity.DeviceEntity;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.model.pc.face.enums.FaceRecognitionFilterReason;
|
||||
import com.ycwl.basic.model.pc.face.req.FaceRecognitionUpdateReq;
|
||||
import com.ycwl.basic.model.pc.face.req.FaceReqQuery;
|
||||
import com.ycwl.basic.model.pc.face.resp.FaceRecognitionDetailVO;
|
||||
import com.ycwl.basic.model.pc.face.resp.FaceRecognitionSampleVO;
|
||||
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;
|
||||
@@ -42,6 +47,7 @@ import com.ycwl.basic.model.pc.template.resp.TemplateRespVO;
|
||||
import com.ycwl.basic.model.pc.video.entity.MemberVideoEntity;
|
||||
import com.ycwl.basic.model.pc.video.entity.VideoEntity;
|
||||
import com.ycwl.basic.model.repository.TaskUpdateResult;
|
||||
import com.ycwl.basic.model.task.resp.SampleFilterTrace;
|
||||
import com.ycwl.basic.model.task.resp.SearchFaceRespVo;
|
||||
import com.ycwl.basic.repository.DeviceRepository;
|
||||
import com.ycwl.basic.repository.FaceRepository;
|
||||
@@ -74,12 +80,19 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -1098,21 +1111,23 @@ public class FaceServiceImpl implements FaceService {
|
||||
|
||||
@Override
|
||||
public void matchCustomFaceId(Long faceId, List<Long> faceSampleIds) {
|
||||
// 参数验证
|
||||
handleCustomFaceMatching(faceId, faceSampleIds);
|
||||
}
|
||||
|
||||
private SearchFaceRespVo handleCustomFaceMatching(Long faceId, List<Long> faceSampleIds) {
|
||||
if (faceId == null) {
|
||||
throw new IllegalArgumentException("faceId 不能为空");
|
||||
}
|
||||
if (faceSampleIds == null || faceSampleIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("faceSampleIds 不能为空");
|
||||
}
|
||||
|
||||
|
||||
log.debug("开始自定义人脸匹配:faceId={}, faceSampleIds={}", faceId, faceSampleIds);
|
||||
|
||||
// 记录自定义匹配调用次数,便于监控调用频率
|
||||
recordCustomMatchCount(faceId);
|
||||
|
||||
|
||||
try {
|
||||
// 1. 获取基础数据
|
||||
FaceEntity face = faceRepository.getFace(faceId);
|
||||
if (face == null) {
|
||||
log.warn("人脸不存在,faceId: {}", faceId);
|
||||
@@ -1127,7 +1142,7 @@ public class FaceServiceImpl implements FaceService {
|
||||
|
||||
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId());
|
||||
IFaceBodyAdapter faceBodyAdapter = scenicService.getScenicFaceBodyAdapter(face.getScenicId());
|
||||
|
||||
|
||||
if (faceBodyAdapter == null) {
|
||||
log.error("无法获取人脸识别适配器,scenicId: {}", face.getScenicId());
|
||||
throw new BaseException("人脸识别服务不可用,请稍后再试");
|
||||
@@ -1138,7 +1153,7 @@ public class FaceServiceImpl implements FaceService {
|
||||
log.debug("face_select_post_mode配置值: {}", faceSelectPostMode);
|
||||
|
||||
SearchFaceRespVo mergedResult;
|
||||
|
||||
|
||||
// 2. 根据face_select_post_mode决定搜索策略
|
||||
if (Integer.valueOf(2).equals(faceSelectPostMode)) {
|
||||
// 模式2:不搜索,直接使用用户选择的faceSampleIds
|
||||
@@ -1150,15 +1165,15 @@ public class FaceServiceImpl implements FaceService {
|
||||
List<SearchFaceRespVo> searchResults = new ArrayList<>();
|
||||
for (FaceSampleEntity faceSample : faceSamples) {
|
||||
try {
|
||||
SearchFaceRespVo result = faceService.searchFace(faceBodyAdapter,
|
||||
String.valueOf(face.getScenicId()),
|
||||
faceSample.getFaceUrl(),
|
||||
SearchFaceRespVo result = faceService.searchFace(faceBodyAdapter,
|
||||
String.valueOf(face.getScenicId()),
|
||||
faceSample.getFaceUrl(),
|
||||
"自定义人脸匹配");
|
||||
if (result != null) {
|
||||
searchResults.add(result);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("人脸样本搜索失败,faceSampleId={}, faceUrl={}",
|
||||
log.warn("人脸样本搜索失败,faceSampleId={}, faceUrl={}",
|
||||
faceSample.getId(), faceSample.getFaceUrl(), e);
|
||||
// 继续处理其他样本,不中断整个流程
|
||||
}
|
||||
@@ -1172,56 +1187,60 @@ public class FaceServiceImpl implements FaceService {
|
||||
// 2.2 根据模式整合多个搜索结果
|
||||
mergedResult = mergeSearchResults(searchResults, faceSelectPostMode);
|
||||
}
|
||||
|
||||
|
||||
// 3. 应用后置筛选逻辑
|
||||
if (mergedResult.getSampleListIds() != null && !mergedResult.getSampleListIds().isEmpty()) {
|
||||
List<FaceSampleEntity> allFaceSampleList = faceSampleMapper.listByIds(mergedResult.getSampleListIds());
|
||||
List<Long> filteredSampleIds = faceService.applySampleFilters(mergedResult.getSampleListIds(), allFaceSampleList, scenicConfig);
|
||||
SampleFilterTrace filterTrace = faceService.applySampleFiltersWithTrace(
|
||||
mergedResult.getSampleListIds(), allFaceSampleList, scenicConfig);
|
||||
List<Long> filteredSampleIds = filterTrace.getAcceptedSampleIds() == null
|
||||
? Collections.emptyList()
|
||||
: filterTrace.getAcceptedSampleIds();
|
||||
mergedResult.setSampleListIds(filteredSampleIds);
|
||||
log.debug("应用后置筛选:原始样本数={}, 筛选后样本数={}", allFaceSampleList.size(), filteredSampleIds.size());
|
||||
mergedResult.setFilterTrace(filterTrace);
|
||||
log.debug("应用后置筛选:原始样本数={}, 筛选后样本数={}",
|
||||
allFaceSampleList.size(), filteredSampleIds.size());
|
||||
}
|
||||
|
||||
// 5. 更新人脸实体结果
|
||||
updateFaceEntityResult(face, mergedResult, faceId);
|
||||
|
||||
// 6. 执行后续业务逻辑
|
||||
|
||||
List<Long> sampleListIds = mergedResult.getSampleListIds();
|
||||
if (sampleListIds != null && !sampleListIds.isEmpty()) {
|
||||
try {
|
||||
List<MemberSourceEntity> memberSourceEntityList = processMemberSources(sampleListIds, face);
|
||||
|
||||
|
||||
if (!memberSourceEntityList.isEmpty()) {
|
||||
List<Long> freeSourceIds = processFreeSourceLogic(memberSourceEntityList, scenicConfig, false);
|
||||
processBuyStatus(memberSourceEntityList, freeSourceIds, face.getMemberId(),
|
||||
face.getScenicId(), faceId);
|
||||
|
||||
handleVideoRecreation(scenicConfig, memberSourceEntityList, faceId,
|
||||
face.getMemberId(), sampleListIds, false);
|
||||
|
||||
// 过滤已存在的关联关系和无效的source引用,防止数据不一致
|
||||
processBuyStatus(memberSourceEntityList, freeSourceIds, face.getMemberId(),
|
||||
face.getScenicId(), faceId);
|
||||
|
||||
handleVideoRecreation(scenicConfig, memberSourceEntityList, faceId,
|
||||
face.getMemberId(), sampleListIds, false);
|
||||
|
||||
List<MemberSourceEntity> existingFiltered = sourceMapper.filterExistingRelations(memberSourceEntityList);
|
||||
List<MemberSourceEntity> validFiltered = sourceMapper.filterValidSourceRelations(existingFiltered);
|
||||
if (!validFiltered.isEmpty()) {
|
||||
sourceMapper.addRelations(validFiltered);
|
||||
log.debug("创建关联关系: faceId={}, 原始数量={}, 过滤后数量={}",
|
||||
faceId, memberSourceEntityList.size(), validFiltered.size());
|
||||
log.debug("创建关联关系: faceId={}, 原始数量={}, 过滤后数量={}",
|
||||
faceId, memberSourceEntityList.size(), validFiltered.size());
|
||||
} else {
|
||||
log.warn("没有有效的关联关系可创建: faceId={}, 原始数量={}", faceId, memberSourceEntityList.size());
|
||||
}
|
||||
memberRelationRepository.clearSCacheByFace(faceId);
|
||||
taskTaskService.autoCreateTaskByFaceId(faceId);
|
||||
|
||||
|
||||
log.info("自定义人脸匹配完成:faceId={}, 匹配样本数={}, 关联源文件数={}, 免费数={}",
|
||||
faceId, sampleListIds.size(), memberSourceEntityList.size(), freeSourceIds.size());
|
||||
faceId, sampleListIds.size(), memberSourceEntityList.size(), freeSourceIds.size());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("处理源文件关联失败,faceId={}", faceId, e);
|
||||
// 源文件关联失败不影响主流程
|
||||
}
|
||||
} else {
|
||||
log.warn("自定义人脸匹配无结果:faceId={}, faceSampleIds={}", faceId, faceSampleIds);
|
||||
}
|
||||
|
||||
|
||||
return mergedResult;
|
||||
|
||||
} catch (BaseException e) {
|
||||
throw e;
|
||||
} catch (Exception e) {
|
||||
@@ -1237,9 +1256,323 @@ public class FaceServiceImpl implements FaceService {
|
||||
return mergeSearchResults(searchResults, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FaceRecognitionDetailVO updateRecognition(FaceRecognitionUpdateReq req) {
|
||||
if (req == null || req.getFaceId() == null) {
|
||||
throw new IllegalArgumentException("faceId 不能为空");
|
||||
}
|
||||
Long faceId = req.getFaceId();
|
||||
FaceEntity face = faceRepository.getFace(faceId);
|
||||
if (face == null) {
|
||||
throw new BaseException("人脸不存在");
|
||||
}
|
||||
|
||||
boolean forceRematch = Boolean.TRUE.equals(req.getForceRematch());
|
||||
if (forceRematch) {
|
||||
matchFaceId(faceId, false);
|
||||
face = faceRepository.getFace(faceId);
|
||||
}
|
||||
|
||||
List<Long> currentAccepted = parseMatchSampleIds(face.getMatchSampleIds());
|
||||
List<Long> manualAccepted = Optional.ofNullable(req.getManualAcceptedSampleIds()).orElse(Collections.emptyList());
|
||||
List<Long> manualRejected = Optional.ofNullable(req.getManualRejectedSampleIds()).orElse(Collections.emptyList());
|
||||
Set<Long> manualRejectedSet = new HashSet<>(manualRejected);
|
||||
|
||||
LinkedHashSet<Long> finalSampleSet = new LinkedHashSet<>();
|
||||
manualAccepted.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.forEach(finalSampleSet::add);
|
||||
currentAccepted.stream()
|
||||
.filter(Objects::nonNull)
|
||||
.filter(id -> !manualRejectedSet.contains(id))
|
||||
.forEach(finalSampleSet::add);
|
||||
|
||||
boolean hasManualChange = !manualAccepted.isEmpty() || !manualRejectedSet.isEmpty();
|
||||
List<Long> finalSampleList = new ArrayList<>(finalSampleSet);
|
||||
boolean needsUpdate = hasManualChange && !finalSampleList.equals(currentAccepted);
|
||||
|
||||
if (needsUpdate) {
|
||||
if (finalSampleList.isEmpty()) {
|
||||
throw new BaseException("至少需要保留一个样本");
|
||||
}
|
||||
handleCustomFaceMatching(faceId, finalSampleList);
|
||||
}
|
||||
|
||||
if (Strings.isNotBlank(req.getRemark())) {
|
||||
log.info("人脸识别人工调整备注:faceId={}, remark={}", faceId, req.getRemark());
|
||||
}
|
||||
|
||||
return getRecognitionDetail(faceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FaceRecognitionDetailVO getRecognitionDetail(Long faceId) {
|
||||
if (faceId == null) {
|
||||
throw new IllegalArgumentException("faceId 不能为空");
|
||||
}
|
||||
FaceEntity face = faceRepository.getFace(faceId);
|
||||
if (face == null) {
|
||||
throw new BaseException("人脸不存在");
|
||||
}
|
||||
|
||||
FaceRecognitionDetailVO detail = new FaceRecognitionDetailVO();
|
||||
detail.setFaceId(faceId);
|
||||
detail.setMemberId(face.getMemberId());
|
||||
detail.setScenicId(face.getScenicId());
|
||||
detail.setFaceUrl(face.getFaceUrl());
|
||||
detail.setScore(face.getScore());
|
||||
detail.setFirstMatchRate(face.getFirstMatchRate() != null ? face.getFirstMatchRate().floatValue() : null);
|
||||
detail.setLowThreshold(redisTemplate.hasKey(FACE_LOW_THRESHOLD_PFX + faceId));
|
||||
detail.setLastMatchedAt(face.getUpdateAt() != null ? face.getUpdateAt() : face.getCreateAt());
|
||||
|
||||
String matchResultJson = face.getMatchResult();
|
||||
if (Strings.isBlank(matchResultJson)) {
|
||||
detail.setAcceptedSamples(Collections.emptyList());
|
||||
detail.setFilteredSamples(Collections.emptyList());
|
||||
return detail;
|
||||
}
|
||||
|
||||
List<SearchFaceResultItem> resultItems = JacksonUtil.fromJsonToList(matchResultJson, SearchFaceResultItem.class);
|
||||
if (resultItems == null) {
|
||||
resultItems = Collections.emptyList();
|
||||
}
|
||||
|
||||
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) {
|
||||
sampleUniverse.add(sampleId);
|
||||
itemBySampleId.putIfAbsent(sampleId, item);
|
||||
}
|
||||
}
|
||||
sampleUniverse.addAll(persistedAcceptedIds);
|
||||
|
||||
List<Long> allSampleIds = new ArrayList<>(sampleUniverse);
|
||||
if (allSampleIds.isEmpty()) {
|
||||
detail.setAcceptedSamples(Collections.emptyList());
|
||||
detail.setFilteredSamples(Collections.emptyList());
|
||||
return detail;
|
||||
}
|
||||
|
||||
List<FaceSampleEntity> allSamples = faceSampleMapper.listByIds(allSampleIds);
|
||||
Map<Long, FaceSampleEntity> sampleEntityMap = allSamples.stream()
|
||||
.collect(Collectors.toMap(FaceSampleEntity::getId, Function.identity(), (a, b) -> a, LinkedHashMap::new));
|
||||
|
||||
List<SourceEntity> sourceEntities = sourceMapper.listBySampleIds(allSampleIds);
|
||||
Map<Long, SourceEntity> sourceBySampleId = sourceEntities.stream()
|
||||
.collect(Collectors.toMap(SourceEntity::getFaceSampleId, Function.identity(), (a, b) -> a, LinkedHashMap::new));
|
||||
Map<Long, SourceEntity> sourceById = sourceEntities.stream()
|
||||
.collect(Collectors.toMap(SourceEntity::getId, Function.identity(), (a, b) -> a));
|
||||
|
||||
ScenicConfigManager scenicConfig = scenicRepository.getScenicConfigManager(face.getScenicId());
|
||||
Float thresholdConfig = scenicConfig != null ? scenicConfig.getFloat("face_score_threshold") : null;
|
||||
float threshold = thresholdConfig != null ? thresholdConfig / 100F : 0F;
|
||||
|
||||
List<Long> initialAcceptedIds = allSampleIds.stream()
|
||||
.filter(id -> {
|
||||
SearchFaceResultItem item = itemBySampleId.get(id);
|
||||
return item != null && item.getScore() != null && item.getScore() > threshold;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
|
||||
List<FaceSampleEntity> orderedSampleList = allSampleIds.stream()
|
||||
.map(sampleEntityMap::get)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
SampleFilterTrace filterTrace = faceService.applySampleFiltersWithTrace(initialAcceptedIds, orderedSampleList, scenicConfig);
|
||||
List<Long> systemAcceptedIds = filterTrace.getAcceptedSampleIds() == null
|
||||
? Collections.emptyList()
|
||||
: filterTrace.getAcceptedSampleIds();
|
||||
|
||||
Set<Long> initialAcceptedSet = new HashSet<>(initialAcceptedIds);
|
||||
for (Long sampleId : allSampleIds) {
|
||||
if (!initialAcceptedSet.contains(sampleId) && !systemAcceptedIds.contains(sampleId)) {
|
||||
filterTrace.addReason(sampleId, FaceRecognitionFilterReason.SCORE_BELOW_THRESHOLD);
|
||||
}
|
||||
}
|
||||
|
||||
Set<Long> systemAcceptedSet = new HashSet<>(systemAcceptedIds);
|
||||
Set<Long> persistedAcceptedSet = new HashSet<>(persistedAcceptedIds);
|
||||
for (Long sampleId : systemAcceptedSet) {
|
||||
if (!persistedAcceptedSet.contains(sampleId)) {
|
||||
filterTrace.addReason(sampleId, FaceRecognitionFilterReason.MANUAL_REJECTED);
|
||||
}
|
||||
}
|
||||
|
||||
List<MemberSourceEntity> relations = new ArrayList<>();
|
||||
List<MemberSourceEntity> videoRelations = memberRelationRepository.listSourceByFaceRelation(faceId, 1);
|
||||
if (videoRelations != null) {
|
||||
relations.addAll(videoRelations);
|
||||
}
|
||||
List<MemberSourceEntity> imageRelations = memberRelationRepository.listSourceByFaceRelation(faceId, 2);
|
||||
if (imageRelations != null) {
|
||||
relations.addAll(imageRelations);
|
||||
}
|
||||
Map<Long, MemberSourceEntity> relationBySampleId = new HashMap<>();
|
||||
for (MemberSourceEntity relation : relations) {
|
||||
SourceEntity source = sourceById.get(relation.getSourceId());
|
||||
if (source != null && source.getFaceSampleId() != null) {
|
||||
relationBySampleId.putIfAbsent(source.getFaceSampleId(), relation);
|
||||
}
|
||||
}
|
||||
|
||||
Map<Long, EnumSet<FaceRecognitionFilterReason>> reasonMap = filterTrace.getFilteredReasonMap();
|
||||
Map<Long, DeviceEntity> deviceCache = new HashMap<>();
|
||||
|
||||
List<Long> acceptedOrdered = new ArrayList<>();
|
||||
for (Long sampleId : allSampleIds) {
|
||||
if (persistedAcceptedSet.contains(sampleId)) {
|
||||
acceptedOrdered.add(sampleId);
|
||||
}
|
||||
}
|
||||
for (Long sampleId : persistedAcceptedIds) {
|
||||
if (!acceptedOrdered.contains(sampleId)) {
|
||||
acceptedOrdered.add(sampleId);
|
||||
}
|
||||
}
|
||||
|
||||
List<FaceRecognitionSampleVO> acceptedSamples = acceptedOrdered.stream()
|
||||
.map(sampleId -> buildSampleVO(
|
||||
sampleId,
|
||||
true,
|
||||
itemBySampleId.get(sampleId),
|
||||
sampleEntityMap.get(sampleId),
|
||||
sourceBySampleId.get(sampleId),
|
||||
relationBySampleId.get(sampleId),
|
||||
deviceCache,
|
||||
Collections.emptyList()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
Set<Long> acceptedSet = new HashSet<>(acceptedOrdered);
|
||||
List<FaceRecognitionSampleVO> filteredSamples = new ArrayList<>();
|
||||
for (Long sampleId : allSampleIds) {
|
||||
if (acceptedSet.contains(sampleId)) {
|
||||
continue;
|
||||
}
|
||||
EnumSet<FaceRecognitionFilterReason> reasons = reasonMap.get(sampleId);
|
||||
List<FaceRecognitionFilterReason> reasonList = reasons == null
|
||||
? Collections.emptyList()
|
||||
: new ArrayList<>(reasons);
|
||||
filteredSamples.add(buildSampleVO(
|
||||
sampleId,
|
||||
false,
|
||||
itemBySampleId.get(sampleId),
|
||||
sampleEntityMap.get(sampleId),
|
||||
sourceBySampleId.get(sampleId),
|
||||
relationBySampleId.get(sampleId),
|
||||
deviceCache,
|
||||
reasonList));
|
||||
}
|
||||
|
||||
detail.setAcceptedSamples(acceptedSamples);
|
||||
detail.setFilteredSamples(filteredSamples);
|
||||
return detail;
|
||||
}
|
||||
|
||||
private List<Long> parseMatchSampleIds(String matchSampleIds) {
|
||||
if (Strings.isBlank(matchSampleIds)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String[] segments = matchSampleIds.split(",");
|
||||
List<Long> result = new ArrayList<>(segments.length);
|
||||
for (String segment : segments) {
|
||||
Long id = parseLongSilently(segment);
|
||||
if (id != null) {
|
||||
result.add(id);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Long parseLongSilently(String value) {
|
||||
if (Strings.isBlank(value)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Long.valueOf(value.trim());
|
||||
} catch (NumberFormatException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private FaceRecognitionSampleVO buildSampleVO(Long sampleId,
|
||||
boolean accepted,
|
||||
SearchFaceResultItem resultItem,
|
||||
FaceSampleEntity sampleEntity,
|
||||
SourceEntity sourceEntity,
|
||||
MemberSourceEntity relation,
|
||||
Map<Long, DeviceEntity> deviceCache,
|
||||
List<FaceRecognitionFilterReason> reasons) {
|
||||
FaceRecognitionSampleVO vo = new FaceRecognitionSampleVO();
|
||||
vo.setSampleId(sampleId);
|
||||
vo.setAccepted(accepted);
|
||||
if (resultItem != null) {
|
||||
vo.setScore(resultItem.getScore());
|
||||
}
|
||||
if (sampleEntity != null) {
|
||||
vo.setFaceUrl(sampleEntity.getFaceUrl());
|
||||
vo.setDeviceId(sampleEntity.getDeviceId());
|
||||
vo.setShotAt(sampleEntity.getCreateAt());
|
||||
DeviceEntity device = getDeviceCached(sampleEntity.getDeviceId(), deviceCache);
|
||||
if (device != null) {
|
||||
vo.setDeviceName(device.getName());
|
||||
}
|
||||
}
|
||||
if (sourceEntity != null) {
|
||||
vo.setSourceId(sourceEntity.getId());
|
||||
vo.setSourceType(sourceEntity.getType());
|
||||
vo.setSourceUrl(resolveSourceUrl(sourceEntity));
|
||||
}
|
||||
if (relation != null) {
|
||||
vo.setIsFree(relation.getIsFree());
|
||||
vo.setIsBuy(relation.getIsBuy());
|
||||
}
|
||||
if (reasons != null && !reasons.isEmpty()) {
|
||||
vo.setFilterReasons(new ArrayList<>(reasons));
|
||||
vo.setFilterReasonTexts(reasons.stream()
|
||||
.map(FaceRecognitionFilterReason::getDescription)
|
||||
.collect(Collectors.toList()));
|
||||
} else {
|
||||
vo.setFilterReasons(Collections.emptyList());
|
||||
vo.setFilterReasonTexts(Collections.emptyList());
|
||||
}
|
||||
return vo;
|
||||
}
|
||||
|
||||
private DeviceEntity getDeviceCached(Long deviceId, Map<Long, DeviceEntity> cache) {
|
||||
if (deviceId == null) {
|
||||
return null;
|
||||
}
|
||||
if (cache.containsKey(deviceId)) {
|
||||
return cache.get(deviceId);
|
||||
}
|
||||
DeviceEntity device = deviceRepository.getDevice(deviceId);
|
||||
cache.put(deviceId, device);
|
||||
return device;
|
||||
}
|
||||
|
||||
private String resolveSourceUrl(SourceEntity sourceEntity) {
|
||||
if (sourceEntity == null) {
|
||||
return null;
|
||||
}
|
||||
if (!Strings.isBlank(sourceEntity.getUrl())) {
|
||||
return sourceEntity.getUrl();
|
||||
}
|
||||
if (!Strings.isBlank(sourceEntity.getVideoUrl())) {
|
||||
return sourceEntity.getVideoUrl();
|
||||
}
|
||||
if (!Strings.isBlank(sourceEntity.getThumbUrl())) {
|
||||
return sourceEntity.getThumbUrl();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并多个搜索结果
|
||||
*
|
||||
*
|
||||
* @param searchResults 搜索结果列表
|
||||
* @param mergeMode 合并模式:0-并集,1-交集
|
||||
* @return 合并后的结果
|
||||
@@ -1250,7 +1583,7 @@ public class FaceServiceImpl implements FaceService {
|
||||
if (searchResults == null || searchResults.isEmpty()) {
|
||||
return mergedResult;
|
||||
}
|
||||
|
||||
|
||||
List<String> allSearchJsons = new ArrayList<>();
|
||||
float maxScore = 0f;
|
||||
float maxFirstMatchRate = 0f;
|
||||
@@ -1289,7 +1622,7 @@ public class FaceServiceImpl implements FaceService {
|
||||
finalSampleIds = new ArrayList<>(allSampleIds);
|
||||
log.debug("使用并集模式合并搜索结果,并集样本数: {}", finalSampleIds.size());
|
||||
}
|
||||
|
||||
|
||||
mergedResult.setSampleListIds(finalSampleIds);
|
||||
mergedResult.setSearchResultJson(String.join("|", allSearchJsons));
|
||||
mergedResult.setScore(maxScore);
|
||||
@@ -1309,52 +1642,52 @@ public class FaceServiceImpl implements FaceService {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user