You've already forked FrameTour-BE
feat(puzzle): 实现人脸匹配后异步生成拼图模板功能
- 移除查询规则时的景区ID参数,简化规则加载逻辑 - 为人脸匹配编排器添加拼图模板服务依赖 - 新增异步生成拼图模板方法,在人脸识别成功后触发 - 优化Mapper接口,添加@Mapper注解并移除冗余查询方法 - 更新文档说明,同步修改规则查询方式描述 - 清理SourceMapper中重复的deleted条件过滤逻辑
This commit is contained in:
@@ -236,7 +236,7 @@ Map<String, String> execute(Long templateId, Long faceId, Long scenicId)
|
||||
|
||||
**执行流程**:
|
||||
1. **加载规则列表**:
|
||||
- 查询指定模板和景区的所有启用规则(`PuzzleFillRuleMapper.listByTemplateAndScenic`)
|
||||
- 查询指定模板的所有启用规则(`PuzzleFillRuleMapper.listByTemplateId`)
|
||||
- 按`priority`降序排序(优先级高的先执行)
|
||||
|
||||
2. **构建上下文**:
|
||||
|
||||
@@ -62,7 +62,7 @@ public class PuzzleElementFillEngine {
|
||||
|
||||
try {
|
||||
// 1. 查询模板的所有启用规则(按priority DESC排序)
|
||||
List<PuzzleFillRuleEntity> rules = ruleMapper.listByTemplateAndScenic(templateId, scenicId);
|
||||
List<PuzzleFillRuleEntity> rules = ruleMapper.listByTemplateId(templateId);
|
||||
if (rules == null || rules.isEmpty()) {
|
||||
log.debug("模板[{}]没有配置自动填充规则", templateId);
|
||||
return dynamicData;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ycwl.basic.puzzle.mapper;
|
||||
|
||||
import com.ycwl.basic.puzzle.entity.PuzzleElementEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
@@ -11,6 +12,7 @@ import java.util.List;
|
||||
* @author Claude
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Mapper
|
||||
public interface PuzzleElementMapper {
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,13 +20,4 @@ public interface PuzzleFillRuleMapper extends BaseMapper<PuzzleFillRuleEntity> {
|
||||
* @return 规则列表
|
||||
*/
|
||||
List<PuzzleFillRuleEntity> listByTemplateId(@Param("templateId") Long templateId);
|
||||
|
||||
/**
|
||||
* 根据模板ID和景区ID查询所有启用的规则(按优先级降序)
|
||||
*
|
||||
* @param templateId 模板ID
|
||||
* @param scenicId 景区ID
|
||||
* @return 规则列表
|
||||
*/
|
||||
List<PuzzleFillRuleEntity> listByTemplateAndScenic(@Param("templateId") Long templateId, @Param("scenicId") Long scenicId);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ycwl.basic.puzzle.mapper;
|
||||
|
||||
import com.ycwl.basic.puzzle.entity.PuzzleGenerationRecordEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
@@ -11,6 +12,7 @@ import java.util.List;
|
||||
* @author Claude
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Mapper
|
||||
public interface PuzzleGenerationRecordMapper {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ycwl.basic.puzzle.mapper;
|
||||
|
||||
import com.ycwl.basic.puzzle.entity.PuzzleTemplateEntity;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
@@ -11,6 +12,7 @@ import java.util.List;
|
||||
* @author Claude
|
||||
* @since 2025-01-17
|
||||
*/
|
||||
@Mapper
|
||||
public interface PuzzleTemplateMapper {
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
package com.ycwl.basic.service.pc.orchestrator;
|
||||
import com.ycwl.basic.integration.scenic.dto.scenic.ScenicV2DTO;
|
||||
import com.ycwl.basic.puzzle.dto.PuzzleGenerateRequest;
|
||||
import com.ycwl.basic.puzzle.dto.PuzzleGenerateResponse;
|
||||
import com.ycwl.basic.puzzle.dto.PuzzleTemplateDTO;
|
||||
import com.ycwl.basic.puzzle.service.IPuzzleGenerateService;
|
||||
import com.ycwl.basic.puzzle.service.IPuzzleTemplateService;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.ycwl.basic.biz.OrderBiz;
|
||||
import com.ycwl.basic.exception.BaseException;
|
||||
@@ -79,6 +88,10 @@ public class FaceMatchingOrchestrator {
|
||||
private BuyStatusProcessor buyStatusProcessor;
|
||||
@Autowired
|
||||
private VideoRecreationHandler videoRecreationHandler;
|
||||
@Autowired
|
||||
private IPuzzleTemplateService puzzleTemplateService;
|
||||
@Autowired
|
||||
private IPuzzleGenerateService puzzleGenerateService;
|
||||
|
||||
/**
|
||||
* 编排人脸匹配的完整流程
|
||||
@@ -106,7 +119,7 @@ public class FaceMatchingOrchestrator {
|
||||
SearchFaceRespVo searchResult = executeFaceRecognition(context);
|
||||
if (searchResult == null) {
|
||||
log.warn("人脸识别返回结果为空,faceId={}", faceId);
|
||||
throw new BaseException("人脸识别失败,请换一张试试把~");
|
||||
throw new BaseException("人脸识别失败,请换一张试试把~");
|
||||
}
|
||||
|
||||
// 执行补救逻辑
|
||||
@@ -119,6 +132,9 @@ public class FaceMatchingOrchestrator {
|
||||
// 步骤4-6: 处理源文件关联和业务逻辑
|
||||
processSourceRelations(context, searchResult, faceId, isNew);
|
||||
|
||||
// 步骤7: 异步生成拼图模板
|
||||
asyncGeneratePuzzleTemplate(context.face.getScenicId(), faceId, context.face.getMemberId());
|
||||
|
||||
return searchResult;
|
||||
|
||||
} catch (BaseException e) {
|
||||
@@ -321,6 +337,85 @@ public class FaceMatchingOrchestrator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 步骤8: 异步生成拼图模板
|
||||
* 在人脸匹配完成后,异步为该景区的所有启用的拼图模板生成图片
|
||||
*/
|
||||
private void asyncGeneratePuzzleTemplate(Long scenicId, Long faceId, Long memberId) {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
log.info("开始异步生成景区拼图模板: scenicId={}, faceId={}", scenicId, faceId);
|
||||
|
||||
// 查询该景区所有启用状态的拼图模板
|
||||
List<PuzzleTemplateDTO> templateList = puzzleTemplateService.listTemplates(
|
||||
scenicId, null, 1); // 查询启用状态的模板
|
||||
|
||||
if (templateList == null || templateList.isEmpty()) {
|
||||
log.info("景区不存在启用的拼图模板,跳过生成: scenicId={}", scenicId);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("景区存在 {} 个启用的拼图模板,开始逐个生成: scenicId={}", templateList.size(), scenicId);
|
||||
|
||||
// 获取人脸信息用于动态数据
|
||||
FaceEntity face = faceRepository.getFace(faceId);
|
||||
if (face == null) {
|
||||
log.warn("人脸信息不存在,无法生成拼图: faceId={}", faceId);
|
||||
return;
|
||||
}
|
||||
ScenicV2DTO scenicBasic = scenicRepository.getScenicBasic(face.getScenicId());
|
||||
// 准备公共动态数据
|
||||
Map<String, String> baseDynamicData = new HashMap<>();
|
||||
if (face.getFaceUrl() != null) {
|
||||
baseDynamicData.put("faceImage", face.getFaceUrl());
|
||||
baseDynamicData.put("userAvatar", face.getFaceUrl());
|
||||
}
|
||||
baseDynamicData.put("faceId", String.valueOf(faceId));
|
||||
baseDynamicData.put("scenicName", scenicBasic.getName());
|
||||
|
||||
// 遍历所有模板,逐个生成
|
||||
int successCount = 0;
|
||||
int failCount = 0;
|
||||
for (PuzzleTemplateDTO template : templateList) {
|
||||
try {
|
||||
log.info("开始生成拼图: scenicId={}, templateCode={}, templateName={}",
|
||||
scenicId, template.getCode(), template.getName());
|
||||
|
||||
// 构建生成请求
|
||||
PuzzleGenerateRequest generateRequest = new PuzzleGenerateRequest();
|
||||
generateRequest.setScenicId(scenicId);
|
||||
generateRequest.setUserId(memberId);
|
||||
generateRequest.setFaceId(faceId);
|
||||
generateRequest.setBusinessType("face_matching");
|
||||
generateRequest.setTemplateCode(template.getCode());
|
||||
generateRequest.setOutputFormat("PNG");
|
||||
generateRequest.setQuality(90);
|
||||
generateRequest.setDynamicData(new HashMap<>(baseDynamicData));
|
||||
|
||||
// 调用拼图生成服务
|
||||
PuzzleGenerateResponse response = puzzleGenerateService.generate(generateRequest);
|
||||
|
||||
log.info("拼图生成成功: scenicId={}, templateCode={}, imageUrl={}",
|
||||
scenicId, template.getCode(), response.getImageUrl());
|
||||
successCount++;
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("拼图生成失败: scenicId={}, templateCode={}, templateName={}",
|
||||
scenicId, template.getCode(), template.getName(), e);
|
||||
failCount++;
|
||||
}
|
||||
}
|
||||
|
||||
log.info("景区拼图模板批量生成完成: scenicId={}, 总数={}, 成功={}, 失败={}",
|
||||
scenicId, templateList.size(), successCount, failCount);
|
||||
|
||||
} catch (Exception e) {
|
||||
// 异步任务失败不影响主流程,仅记录日志
|
||||
log.error("异步生成拼图模板失败: scenicId={}, faceId={}", scenicId, faceId, e);
|
||||
}
|
||||
}, "PuzzleTemplateGenerator-" + scenicId).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* 匹配上下文
|
||||
* 封装匹配过程中需要的所有上下文信息
|
||||
|
||||
@@ -26,14 +26,4 @@
|
||||
ORDER BY priority DESC, id ASC
|
||||
</select>
|
||||
|
||||
<select id="listByTemplateAndScenic" resultMap="BaseResultMap">
|
||||
SELECT *
|
||||
FROM puzzle_fill_rule
|
||||
WHERE template_id = #{templateId}
|
||||
AND scenic_id = #{scenicId}
|
||||
AND enabled = 1
|
||||
AND deleted = 0
|
||||
ORDER BY priority DESC, id ASC
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -367,7 +367,6 @@
|
||||
INNER JOIN source s ON ms.source_id = s.id
|
||||
WHERE ms.face_id = #{faceId}
|
||||
AND s.type = 2
|
||||
AND s.deleted = 0
|
||||
</select>
|
||||
|
||||
<select id="getSourceByFaceAndDeviceIndex" resultType="com.ycwl.basic.model.pc.source.entity.SourceEntity">
|
||||
@@ -397,7 +396,6 @@
|
||||
INNER JOIN member_source ms ON s.id = ms.source_id
|
||||
WHERE ms.face_id = #{faceId}
|
||||
AND s.type = #{type}
|
||||
AND s.deleted = 0
|
||||
)
|
||||
SELECT *
|
||||
FROM ranked_sources
|
||||
@@ -410,7 +408,6 @@
|
||||
FROM member_source ms
|
||||
INNER JOIN source s ON ms.source_id = s.id
|
||||
WHERE ms.face_id = #{faceId}
|
||||
AND s.deleted = 0
|
||||
ORDER BY s.device_id ASC
|
||||
</select>
|
||||
</mapper>
|
||||
|
||||
Reference in New Issue
Block a user