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. **加载规则列表**:
|
1. **加载规则列表**:
|
||||||
- 查询指定模板和景区的所有启用规则(`PuzzleFillRuleMapper.listByTemplateAndScenic`)
|
- 查询指定模板的所有启用规则(`PuzzleFillRuleMapper.listByTemplateId`)
|
||||||
- 按`priority`降序排序(优先级高的先执行)
|
- 按`priority`降序排序(优先级高的先执行)
|
||||||
|
|
||||||
2. **构建上下文**:
|
2. **构建上下文**:
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ public class PuzzleElementFillEngine {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. 查询模板的所有启用规则(按priority DESC排序)
|
// 1. 查询模板的所有启用规则(按priority DESC排序)
|
||||||
List<PuzzleFillRuleEntity> rules = ruleMapper.listByTemplateAndScenic(templateId, scenicId);
|
List<PuzzleFillRuleEntity> rules = ruleMapper.listByTemplateId(templateId);
|
||||||
if (rules == null || rules.isEmpty()) {
|
if (rules == null || rules.isEmpty()) {
|
||||||
log.debug("模板[{}]没有配置自动填充规则", templateId);
|
log.debug("模板[{}]没有配置自动填充规则", templateId);
|
||||||
return dynamicData;
|
return dynamicData;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.ycwl.basic.puzzle.mapper;
|
package com.ycwl.basic.puzzle.mapper;
|
||||||
|
|
||||||
import com.ycwl.basic.puzzle.entity.PuzzleElementEntity;
|
import com.ycwl.basic.puzzle.entity.PuzzleElementEntity;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -11,6 +12,7 @@ import java.util.List;
|
|||||||
* @author Claude
|
* @author Claude
|
||||||
* @since 2025-01-17
|
* @since 2025-01-17
|
||||||
*/
|
*/
|
||||||
|
@Mapper
|
||||||
public interface PuzzleElementMapper {
|
public interface PuzzleElementMapper {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -20,13 +20,4 @@ public interface PuzzleFillRuleMapper extends BaseMapper<PuzzleFillRuleEntity> {
|
|||||||
* @return 规则列表
|
* @return 规则列表
|
||||||
*/
|
*/
|
||||||
List<PuzzleFillRuleEntity> listByTemplateId(@Param("templateId") Long templateId);
|
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;
|
package com.ycwl.basic.puzzle.mapper;
|
||||||
|
|
||||||
import com.ycwl.basic.puzzle.entity.PuzzleGenerationRecordEntity;
|
import com.ycwl.basic.puzzle.entity.PuzzleGenerationRecordEntity;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -11,6 +12,7 @@ import java.util.List;
|
|||||||
* @author Claude
|
* @author Claude
|
||||||
* @since 2025-01-17
|
* @since 2025-01-17
|
||||||
*/
|
*/
|
||||||
|
@Mapper
|
||||||
public interface PuzzleGenerationRecordMapper {
|
public interface PuzzleGenerationRecordMapper {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.ycwl.basic.puzzle.mapper;
|
package com.ycwl.basic.puzzle.mapper;
|
||||||
|
|
||||||
import com.ycwl.basic.puzzle.entity.PuzzleTemplateEntity;
|
import com.ycwl.basic.puzzle.entity.PuzzleTemplateEntity;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -11,6 +12,7 @@ import java.util.List;
|
|||||||
* @author Claude
|
* @author Claude
|
||||||
* @since 2025-01-17
|
* @since 2025-01-17
|
||||||
*/
|
*/
|
||||||
|
@Mapper
|
||||||
public interface PuzzleTemplateMapper {
|
public interface PuzzleTemplateMapper {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,4 +1,13 @@
|
|||||||
package com.ycwl.basic.service.pc.orchestrator;
|
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.biz.OrderBiz;
|
||||||
import com.ycwl.basic.exception.BaseException;
|
import com.ycwl.basic.exception.BaseException;
|
||||||
@@ -79,6 +88,10 @@ public class FaceMatchingOrchestrator {
|
|||||||
private BuyStatusProcessor buyStatusProcessor;
|
private BuyStatusProcessor buyStatusProcessor;
|
||||||
@Autowired
|
@Autowired
|
||||||
private VideoRecreationHandler videoRecreationHandler;
|
private VideoRecreationHandler videoRecreationHandler;
|
||||||
|
@Autowired
|
||||||
|
private IPuzzleTemplateService puzzleTemplateService;
|
||||||
|
@Autowired
|
||||||
|
private IPuzzleGenerateService puzzleGenerateService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编排人脸匹配的完整流程
|
* 编排人脸匹配的完整流程
|
||||||
@@ -106,7 +119,7 @@ public class FaceMatchingOrchestrator {
|
|||||||
SearchFaceRespVo searchResult = executeFaceRecognition(context);
|
SearchFaceRespVo searchResult = executeFaceRecognition(context);
|
||||||
if (searchResult == null) {
|
if (searchResult == null) {
|
||||||
log.warn("人脸识别返回结果为空,faceId={}", faceId);
|
log.warn("人脸识别返回结果为空,faceId={}", faceId);
|
||||||
throw new BaseException("人脸识别失败,请换一张试试把~");
|
throw new BaseException("人脸识别失败,请换一张试试把~");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行补救逻辑
|
// 执行补救逻辑
|
||||||
@@ -119,6 +132,9 @@ public class FaceMatchingOrchestrator {
|
|||||||
// 步骤4-6: 处理源文件关联和业务逻辑
|
// 步骤4-6: 处理源文件关联和业务逻辑
|
||||||
processSourceRelations(context, searchResult, faceId, isNew);
|
processSourceRelations(context, searchResult, faceId, isNew);
|
||||||
|
|
||||||
|
// 步骤7: 异步生成拼图模板
|
||||||
|
asyncGeneratePuzzleTemplate(context.face.getScenicId(), faceId, context.face.getMemberId());
|
||||||
|
|
||||||
return searchResult;
|
return searchResult;
|
||||||
|
|
||||||
} catch (BaseException e) {
|
} 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
|
ORDER BY priority DESC, id ASC
|
||||||
</select>
|
</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>
|
</mapper>
|
||||||
|
|||||||
@@ -367,7 +367,6 @@
|
|||||||
INNER JOIN source s ON ms.source_id = s.id
|
INNER JOIN source s ON ms.source_id = s.id
|
||||||
WHERE ms.face_id = #{faceId}
|
WHERE ms.face_id = #{faceId}
|
||||||
AND s.type = 2
|
AND s.type = 2
|
||||||
AND s.deleted = 0
|
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<select id="getSourceByFaceAndDeviceIndex" resultType="com.ycwl.basic.model.pc.source.entity.SourceEntity">
|
<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
|
INNER JOIN member_source ms ON s.id = ms.source_id
|
||||||
WHERE ms.face_id = #{faceId}
|
WHERE ms.face_id = #{faceId}
|
||||||
AND s.type = #{type}
|
AND s.type = #{type}
|
||||||
AND s.deleted = 0
|
|
||||||
)
|
)
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM ranked_sources
|
FROM ranked_sources
|
||||||
@@ -410,7 +408,6 @@
|
|||||||
FROM member_source ms
|
FROM member_source ms
|
||||||
INNER JOIN source s ON ms.source_id = s.id
|
INNER JOIN source s ON ms.source_id = s.id
|
||||||
WHERE ms.face_id = #{faceId}
|
WHERE ms.face_id = #{faceId}
|
||||||
AND s.deleted = 0
|
|
||||||
ORDER BY s.device_id ASC
|
ORDER BY s.device_id ASC
|
||||||
</select>
|
</select>
|
||||||
</mapper>
|
</mapper>
|
||||||
|
|||||||
Reference in New Issue
Block a user