feat(puzzle): 实现人脸匹配后异步生成拼图模板功能

- 移除查询规则时的景区ID参数,简化规则加载逻辑
- 为人脸匹配编排器添加拼图模板服务依赖
- 新增异步生成拼图模板方法,在人脸识别成功后触发
- 优化Mapper接口,添加@Mapper注解并移除冗余查询方法
- 更新文档说明,同步修改规则查询方式描述
- 清理SourceMapper中重复的deleted条件过滤逻辑
This commit is contained in:
2025-11-19 22:48:01 +08:00
parent b6cbb18a7f
commit 6d18a770b8
9 changed files with 104 additions and 25 deletions

View File

@@ -236,7 +236,7 @@ Map<String, String> execute(Long templateId, Long faceId, Long scenicId)
**执行流程**
1. **加载规则列表**
- 查询指定模板和景区的所有启用规则(`PuzzleFillRuleMapper.listByTemplateAndScenic`
- 查询指定模板的所有启用规则(`PuzzleFillRuleMapper.listByTemplateId`
-`priority`降序排序(优先级高的先执行)
2. **构建上下文**

View File

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

View File

@@ -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 {
/**

View File

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

View File

@@ -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 {
/**

View File

@@ -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 {
/**

View File

@@ -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();
}
/**
* 匹配上下文
* 封装匹配过程中需要的所有上下文信息

View File

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

View File

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