You've already forked FrameTour-BE
refactor(face): 重构人脸识别服务逻辑
- 优化了 faceId 参数校验和日志记录 - 重构了人脸识别主流程,增加了异常处理和日志记录 - 新增了人脸识别补救逻辑方法 - 优化了源文件关联、免费逻辑、购买状态处理等方法 - 重构了视频重切逻辑,使其更加清晰- 优化了时间范围筛选逻辑
This commit is contained in:
@@ -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());
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user