diff --git a/src/main/java/com/ycwl/basic/puzzle/claude.md b/src/main/java/com/ycwl/basic/puzzle/claude.md index da3136d3..ee8bdef5 100644 --- a/src/main/java/com/ycwl/basic/puzzle/claude.md +++ b/src/main/java/com/ycwl/basic/puzzle/claude.md @@ -236,7 +236,7 @@ Map execute(Long templateId, Long faceId, Long scenicId) **执行流程**: 1. **加载规则列表**: - - 查询指定模板和景区的所有启用规则(`PuzzleFillRuleMapper.listByTemplateAndScenic`) + - 查询指定模板的所有启用规则(`PuzzleFillRuleMapper.listByTemplateId`) - 按`priority`降序排序(优先级高的先执行) 2. **构建上下文**: diff --git a/src/main/java/com/ycwl/basic/puzzle/fill/PuzzleElementFillEngine.java b/src/main/java/com/ycwl/basic/puzzle/fill/PuzzleElementFillEngine.java index 54169a7a..8af69d30 100644 --- a/src/main/java/com/ycwl/basic/puzzle/fill/PuzzleElementFillEngine.java +++ b/src/main/java/com/ycwl/basic/puzzle/fill/PuzzleElementFillEngine.java @@ -62,7 +62,7 @@ public class PuzzleElementFillEngine { try { // 1. 查询模板的所有启用规则(按priority DESC排序) - List rules = ruleMapper.listByTemplateAndScenic(templateId, scenicId); + List rules = ruleMapper.listByTemplateId(templateId); if (rules == null || rules.isEmpty()) { log.debug("模板[{}]没有配置自动填充规则", templateId); return dynamicData; diff --git a/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleElementMapper.java b/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleElementMapper.java index cd962218..2e4e21b7 100644 --- a/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleElementMapper.java +++ b/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleElementMapper.java @@ -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 { /** diff --git a/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleFillRuleMapper.java b/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleFillRuleMapper.java index 402af382..c3021815 100644 --- a/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleFillRuleMapper.java +++ b/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleFillRuleMapper.java @@ -20,13 +20,4 @@ public interface PuzzleFillRuleMapper extends BaseMapper { * @return 规则列表 */ List listByTemplateId(@Param("templateId") Long templateId); - - /** - * 根据模板ID和景区ID查询所有启用的规则(按优先级降序) - * - * @param templateId 模板ID - * @param scenicId 景区ID - * @return 规则列表 - */ - List listByTemplateAndScenic(@Param("templateId") Long templateId, @Param("scenicId") Long scenicId); } diff --git a/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleGenerationRecordMapper.java b/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleGenerationRecordMapper.java index 8b6839dc..3e045b3e 100644 --- a/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleGenerationRecordMapper.java +++ b/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleGenerationRecordMapper.java @@ -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 { /** diff --git a/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleTemplateMapper.java b/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleTemplateMapper.java index 889311a0..7e969f0a 100644 --- a/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleTemplateMapper.java +++ b/src/main/java/com/ycwl/basic/puzzle/mapper/PuzzleTemplateMapper.java @@ -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 { /** diff --git a/src/main/java/com/ycwl/basic/service/pc/orchestrator/FaceMatchingOrchestrator.java b/src/main/java/com/ycwl/basic/service/pc/orchestrator/FaceMatchingOrchestrator.java index f6a19337..5804ab37 100644 --- a/src/main/java/com/ycwl/basic/service/pc/orchestrator/FaceMatchingOrchestrator.java +++ b/src/main/java/com/ycwl/basic/service/pc/orchestrator/FaceMatchingOrchestrator.java @@ -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 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 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(); + } + /** * 匹配上下文 * 封装匹配过程中需要的所有上下文信息 diff --git a/src/main/resources/mapper/PuzzleFillRuleMapper.xml b/src/main/resources/mapper/PuzzleFillRuleMapper.xml index 1e7a0f1b..f5cac85c 100644 --- a/src/main/resources/mapper/PuzzleFillRuleMapper.xml +++ b/src/main/resources/mapper/PuzzleFillRuleMapper.xml @@ -26,14 +26,4 @@ ORDER BY priority DESC, id ASC - - diff --git a/src/main/resources/mapper/SourceMapper.xml b/src/main/resources/mapper/SourceMapper.xml index 3697dccc..f91f41dc 100644 --- a/src/main/resources/mapper/SourceMapper.xml +++ b/src/main/resources/mapper/SourceMapper.xml @@ -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