You've already forked FrameTour-BE
refactor(face):重构人脸识别服务逻辑
- 将人脸识别补救逻辑提取到FaceRecoveryStrategy类中 - 将源文件关联处理逻辑提取到SourceRelationProcessor类中 - 将购买状态处理逻辑提取到BuyStatusProcessor类中 - 将视频重切处理逻辑提取到VideoRecreationHandler类中 - 在FaceServiceImpl中引入四个新的处理器组件 - 删除原有的冗长方法实现,改为调用对应处理器 - 更新方法调用方式以使用新的处理器实例 - 保留核心业务流程但解耦具体实现细节
This commit is contained in:
@@ -60,6 +60,10 @@ import com.ycwl.basic.service.pc.FaceService;
|
||||
import com.ycwl.basic.service.pc.ScenicService;
|
||||
import com.ycwl.basic.service.pc.helper.FaceMetricsRecorder;
|
||||
import com.ycwl.basic.service.pc.helper.SearchResultMerger;
|
||||
import com.ycwl.basic.service.pc.processor.BuyStatusProcessor;
|
||||
import com.ycwl.basic.service.pc.processor.FaceRecoveryStrategy;
|
||||
import com.ycwl.basic.service.pc.processor.SourceRelationProcessor;
|
||||
import com.ycwl.basic.service.pc.processor.VideoRecreationHandler;
|
||||
import com.ycwl.basic.service.pc.strategy.RematchContext;
|
||||
import com.ycwl.basic.service.pc.strategy.RematchModeStrategy;
|
||||
import com.ycwl.basic.service.pc.strategy.RematchStrategyFactory;
|
||||
@@ -158,7 +162,7 @@ public class FaceServiceImpl implements FaceService {
|
||||
@Autowired
|
||||
private TemplateRepository templateRepository;
|
||||
|
||||
// 新增的辅助类
|
||||
// 第一阶段的辅助类
|
||||
@Autowired
|
||||
private FaceMetricsRecorder metricsRecorder;
|
||||
@Autowired
|
||||
@@ -166,6 +170,16 @@ public class FaceServiceImpl implements FaceService {
|
||||
@Autowired
|
||||
private RematchStrategyFactory rematchStrategyFactory;
|
||||
|
||||
// 第二阶段的处理器
|
||||
@Autowired
|
||||
private SourceRelationProcessor sourceRelationProcessor;
|
||||
@Autowired
|
||||
private BuyStatusProcessor buyStatusProcessor;
|
||||
@Autowired
|
||||
private VideoRecreationHandler videoRecreationHandler;
|
||||
@Autowired
|
||||
private FaceRecoveryStrategy faceRecoveryStrategy;
|
||||
|
||||
@Override
|
||||
public ApiResponse<PageInfo<FaceRespVO>> pageQuery(FaceReqQuery faceReqQuery) {
|
||||
PageHelper.startPage(faceReqQuery.getPageNum(),faceReqQuery.getPageSize());
|
||||
@@ -341,7 +355,7 @@ public class FaceServiceImpl implements FaceService {
|
||||
}
|
||||
|
||||
// 执行补救逻辑(如需要)
|
||||
scenicDbSearchResult = executeFaceRecoveryLogic(scenicDbSearchResult, scenicConfig,
|
||||
scenicDbSearchResult = faceRecoveryStrategy.executeFaceRecoveryLogic(scenicDbSearchResult, scenicConfig,
|
||||
faceBodyAdapter, face.getScenicId());
|
||||
|
||||
// 3. 结果处理:更新人脸实体信息
|
||||
@@ -356,16 +370,16 @@ public class FaceServiceImpl implements FaceService {
|
||||
if (sampleListIds != null && !sampleListIds.isEmpty()) {
|
||||
try {
|
||||
// 4. 源文件关联:处理匹配到的源文件
|
||||
List<MemberSourceEntity> memberSourceEntityList = processMemberSources(sampleListIds, face);
|
||||
|
||||
List<MemberSourceEntity> memberSourceEntityList = sourceRelationProcessor.processMemberSources(sampleListIds, face);
|
||||
|
||||
if (!memberSourceEntityList.isEmpty()) {
|
||||
// 5. 业务逻辑处理:免费逻辑、购买状态、任务创建
|
||||
List<Long> freeSourceIds = processFreeSourceLogic(memberSourceEntityList, scenicConfig, isNew);
|
||||
processBuyStatus(memberSourceEntityList, freeSourceIds, face.getMemberId(),
|
||||
List<Long> freeSourceIds = sourceRelationProcessor.processFreeSourceLogic(memberSourceEntityList, scenicConfig, isNew);
|
||||
buyStatusProcessor.processBuyStatus(memberSourceEntityList, freeSourceIds, face.getMemberId(),
|
||||
face.getScenicId(), faceId);
|
||||
|
||||
|
||||
// 处理视频重切逻辑
|
||||
handleVideoRecreation(scenicConfig, memberSourceEntityList, faceId,
|
||||
videoRecreationHandler.handleVideoRecreation(scenicConfig, memberSourceEntityList, faceId,
|
||||
face.getMemberId(), sampleListIds, isNew);
|
||||
|
||||
// 过滤已存在的关联关系和无效的source引用,防止数据不一致
|
||||
@@ -448,269 +462,6 @@ public class FaceServiceImpl implements FaceService {
|
||||
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();
|
||||
}
|
||||
|
||||
List<SourceEntity> filteredSourceEntities = sourceEntities.stream()
|
||||
.sorted(Comparator.comparing(SourceEntity::getCreateTime).reversed())
|
||||
.collect(Collectors.groupingBy(SourceEntity::getDeviceId))
|
||||
.entrySet()
|
||||
.stream().flatMap(entry -> {
|
||||
DeviceConfigManager configManager = deviceRepository.getDeviceConfigManager(entry.getKey());
|
||||
if (configManager.getInteger("limit_video", 0) > 0) {
|
||||
return Stream.concat(
|
||||
entry.getValue().stream().filter(item -> item.getType() == 2),
|
||||
entry.getValue().stream().filter(item -> item.getType() == 1).limit(Math.min(entry.getValue().size(), configManager.getInteger("limit_video", 0)))
|
||||
);
|
||||
}
|
||||
return entry.getValue().stream();
|
||||
}).toList();
|
||||
return filteredSourceEntities.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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理免费源文件逻辑
|
||||
* 根据景区配置和是否新用户决定哪些照片可以免费
|
||||
*/
|
||||
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 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();
|
||||
|
||||
List<FaceSampleEntity> faceSampleList = faceRepository.getFaceSampleList(faceId);
|
||||
if (faceSampleList.isEmpty()) {
|
||||
log.info("faceId:{} sample list not exist", faceId);
|
||||
return;
|
||||
}
|
||||
List<Long> faceSampleIds = faceSampleList.stream()
|
||||
.sorted(Comparator.comparing(FaceSampleEntity::getCreateAt).reversed())
|
||||
.collect(Collectors.groupingBy(FaceSampleEntity::getDeviceId))
|
||||
.entrySet()
|
||||
.stream().flatMap(entry -> {
|
||||
DeviceConfigManager configManager = deviceRepository.getDeviceConfigManager(entry.getKey());
|
||||
if (configManager.getInteger("limit_video", 0) > 0) {
|
||||
return entry.getValue().subList(0, Math.min(entry.getValue().size(), configManager.getInteger("limit_video", 0))).stream();
|
||||
}
|
||||
return entry.getValue().stream();
|
||||
}).toList()
|
||||
.stream().map(FaceSampleEntity::getId).toList();
|
||||
log.info("视频切分任务: faceId={}, 原始数量={}, 筛选后数量={}", faceId, faceSampleList.size(), faceSampleIds.size());
|
||||
log.debug("视频重切逻辑:视频数量 {}, 照片数量 {}", videoCount, photoCount);
|
||||
|
||||
// 只有照片数量大于视频数量时才创建重切任务
|
||||
if (photoCount > videoCount) {
|
||||
VideoPieceGetter.Task task = new VideoPieceGetter.Task();
|
||||
task.faceId = faceId;
|
||||
task.faceSampleIds = faceSampleIds;
|
||||
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
|
||||
public ApiResponse<String> deleteFace(Long faceId) {
|
||||
@@ -1204,14 +955,14 @@ public class FaceServiceImpl implements FaceService {
|
||||
memberRelationRepository.clearSCacheByFace(faceId);
|
||||
log.debug("人脸旧关系数据删除完成:faceId={}", faceId);
|
||||
|
||||
List<MemberSourceEntity> memberSourceEntityList = processMemberSources(sampleListIds, face);
|
||||
List<MemberSourceEntity> memberSourceEntityList = sourceRelationProcessor.processMemberSources(sampleListIds, face);
|
||||
|
||||
if (!memberSourceEntityList.isEmpty()) {
|
||||
List<Long> freeSourceIds = processFreeSourceLogic(memberSourceEntityList, scenicConfig, false);
|
||||
processBuyStatus(memberSourceEntityList, freeSourceIds, face.getMemberId(),
|
||||
List<Long> freeSourceIds = sourceRelationProcessor.processFreeSourceLogic(memberSourceEntityList, scenicConfig, false);
|
||||
buyStatusProcessor.processBuyStatus(memberSourceEntityList, freeSourceIds, face.getMemberId(),
|
||||
face.getScenicId(), faceId);
|
||||
|
||||
handleVideoRecreation(scenicConfig, memberSourceEntityList, faceId,
|
||||
videoRecreationHandler.handleVideoRecreation(scenicConfig, memberSourceEntityList, faceId,
|
||||
face.getMemberId(), sampleListIds, false);
|
||||
|
||||
List<MemberSourceEntity> existingFiltered = sourceMapper.filterExistingRelations(memberSourceEntityList);
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.ycwl.basic.service.pc.processor;
|
||||
|
||||
import com.ycwl.basic.biz.OrderBiz;
|
||||
import com.ycwl.basic.model.mobile.order.IsBuyRespVO;
|
||||
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 购买状态处理器
|
||||
* 负责处理源文件的购买状态和免费状态
|
||||
*
|
||||
* @author longbinbin
|
||||
* @date 2025-01-31
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class BuyStatusProcessor {
|
||||
|
||||
@Autowired
|
||||
private OrderBiz orderBiz;
|
||||
|
||||
/**
|
||||
* 处理购买状态逻辑
|
||||
* 设置每个源文件的购买状态和免费状态
|
||||
*
|
||||
* @param memberSourceEntityList 源文件关联列表
|
||||
* @param freeSourceIds 免费的源文件ID列表
|
||||
* @param memberId 会员ID
|
||||
* @param scenicId 景区ID
|
||||
* @param faceId 人脸ID
|
||||
*/
|
||||
public 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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package com.ycwl.basic.service.pc.processor;
|
||||
|
||||
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 com.ycwl.basic.repository.FaceRepository;
|
||||
import com.ycwl.basic.service.task.TaskFaceService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 人脸识别补救策略
|
||||
* 负责执行人脸识别的补救逻辑
|
||||
*
|
||||
* @author longbinbin
|
||||
* @date 2025-01-31
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class FaceRecoveryStrategy {
|
||||
|
||||
@Autowired
|
||||
private FaceRepository faceRepository;
|
||||
|
||||
@Autowired
|
||||
private TaskFaceService faceService;
|
||||
|
||||
/**
|
||||
* 执行人脸识别补救逻辑
|
||||
* 当匹配结果数量少于阈值时,使用第一个匹配结果重新进行人脸搜索
|
||||
*
|
||||
* @param originalResult 原始搜索结果
|
||||
* @param scenicConfig 景区配置
|
||||
* @param faceBodyAdapter 人脸识别适配器
|
||||
* @param scenicId 景区ID
|
||||
* @return 补救后的搜索结果(如果不需要补救或补救失败则返回原始结果)
|
||||
*/
|
||||
public 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
package com.ycwl.basic.service.pc.processor;
|
||||
|
||||
import com.ycwl.basic.integration.common.manager.DeviceConfigManager;
|
||||
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
|
||||
import com.ycwl.basic.mapper.SourceMapper;
|
||||
import com.ycwl.basic.model.pc.face.entity.FaceEntity;
|
||||
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
|
||||
import com.ycwl.basic.model.pc.source.entity.SourceEntity;
|
||||
import com.ycwl.basic.repository.DeviceRepository;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* 源文件关联处理器
|
||||
* 负责处理人脸匹配后的源文件关联逻辑
|
||||
*
|
||||
* @author longbinbin
|
||||
* @date 2025-01-31
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SourceRelationProcessor {
|
||||
|
||||
@Autowired
|
||||
private SourceMapper sourceMapper;
|
||||
|
||||
@Autowired
|
||||
private DeviceRepository deviceRepository;
|
||||
|
||||
/**
|
||||
* 处理源文件关联逻辑
|
||||
* 根据匹配的样本ID创建MemberSourceEntity列表
|
||||
*
|
||||
* @param sampleListIds 匹配的样本ID列表
|
||||
* @param face 人脸实体
|
||||
* @return MemberSourceEntity列表
|
||||
*/
|
||||
public 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();
|
||||
}
|
||||
|
||||
// 按设备分组并应用限制
|
||||
List<SourceEntity> filteredSourceEntities = sourceEntities.stream()
|
||||
.sorted(Comparator.comparing(SourceEntity::getCreateTime).reversed())
|
||||
.collect(Collectors.groupingBy(SourceEntity::getDeviceId))
|
||||
.entrySet()
|
||||
.stream().flatMap(entry -> {
|
||||
DeviceConfigManager configManager = deviceRepository.getDeviceConfigManager(entry.getKey());
|
||||
if (configManager.getInteger("limit_video", 0) > 0) {
|
||||
// 优先保留所有图片,然后限制视频数量
|
||||
return Stream.concat(
|
||||
entry.getValue().stream().filter(item -> item.getType() == 2),
|
||||
entry.getValue().stream().filter(item -> item.getType() == 1)
|
||||
.limit(Math.min(entry.getValue().size(), configManager.getInteger("limit_video", 0)))
|
||||
);
|
||||
}
|
||||
return entry.getValue().stream();
|
||||
}).toList();
|
||||
|
||||
// 创建MemberSourceEntity列表
|
||||
return filteredSourceEntities.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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理免费源文件逻辑
|
||||
* 根据景区配置和是否新用户决定哪些照片可以免费
|
||||
*
|
||||
* @param memberSourceEntityList 源文件关联列表
|
||||
* @param scenicConfig 景区配置
|
||||
* @param isNew 是否新用户
|
||||
* @return 免费的源文件ID列表
|
||||
*/
|
||||
public 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 freeSourceIds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.ycwl.basic.service.pc.processor;
|
||||
|
||||
import com.ycwl.basic.integration.common.manager.DeviceConfigManager;
|
||||
import com.ycwl.basic.integration.common.manager.ScenicConfigManager;
|
||||
import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity;
|
||||
import com.ycwl.basic.model.pc.source.entity.MemberSourceEntity;
|
||||
import com.ycwl.basic.repository.DeviceRepository;
|
||||
import com.ycwl.basic.repository.FaceRepository;
|
||||
import com.ycwl.basic.task.VideoPieceGetter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 视频重切处理器
|
||||
* 负责处理视频重切任务的创建逻辑
|
||||
*
|
||||
* @author longbinbin
|
||||
* @date 2025-01-31
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class VideoRecreationHandler {
|
||||
|
||||
@Autowired
|
||||
private FaceRepository faceRepository;
|
||||
|
||||
@Autowired
|
||||
private DeviceRepository deviceRepository;
|
||||
|
||||
/**
|
||||
* 处理视频重切逻辑
|
||||
* 当非新用户且照片数量大于视频数量时,创建视频重切任务
|
||||
*
|
||||
* @param scenicConfig 景区配置
|
||||
* @param memberSourceEntityList 源文件关联列表
|
||||
* @param faceId 人脸ID
|
||||
* @param memberId 会员ID
|
||||
* @param sampleListIds 样本ID列表(用于日志)
|
||||
* @param isNew 是否新用户
|
||||
*/
|
||||
public 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();
|
||||
|
||||
List<FaceSampleEntity> faceSampleList = faceRepository.getFaceSampleList(faceId);
|
||||
if (faceSampleList.isEmpty()) {
|
||||
log.info("faceId:{} sample list not exist", faceId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 筛选样本ID
|
||||
List<Long> faceSampleIds = faceSampleList.stream()
|
||||
.sorted(Comparator.comparing(FaceSampleEntity::getCreateAt).reversed())
|
||||
.collect(Collectors.groupingBy(FaceSampleEntity::getDeviceId))
|
||||
.entrySet()
|
||||
.stream().flatMap(entry -> {
|
||||
DeviceConfigManager configManager = deviceRepository.getDeviceConfigManager(entry.getKey());
|
||||
if (configManager.getInteger("limit_video", 0) > 0) {
|
||||
return entry.getValue().subList(0, Math.min(entry.getValue().size(), configManager.getInteger("limit_video", 0))).stream();
|
||||
}
|
||||
return entry.getValue().stream();
|
||||
}).toList()
|
||||
.stream().map(FaceSampleEntity::getId).toList();
|
||||
|
||||
log.info("视频切分任务: faceId={}, 原始数量={}, 筛选后数量={}", faceId, faceSampleList.size(), faceSampleIds.size());
|
||||
log.debug("视频重切逻辑:视频数量 {}, 照片数量 {}", videoCount, photoCount);
|
||||
|
||||
// 只有照片数量大于视频数量时才创建重切任务
|
||||
if (photoCount > videoCount) {
|
||||
VideoPieceGetter.Task task = new VideoPieceGetter.Task();
|
||||
task.faceId = faceId;
|
||||
task.faceSampleIds = faceSampleIds;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user