refactor(face):重构人脸识别服务逻辑

- 将人脸识别补救逻辑提取到FaceRecoveryStrategy类中
- 将源文件关联处理逻辑提取到SourceRelationProcessor类中
- 将购买状态处理逻辑提取到BuyStatusProcessor类中
- 将视频重切处理逻辑提取到VideoRecreationHandler类中
- 在FaceServiceImpl中引入四个新的处理器组件
- 删除原有的冗长方法实现,改为调用对应处理器
- 更新方法调用方式以使用新的处理器实例
- 保留核心业务流程但解耦具体实现细节
This commit is contained in:
2025-10-31 17:31:48 +08:00
parent bf014db7ff
commit 3000e18cb7
5 changed files with 448 additions and 275 deletions

View File

@@ -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);

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}