feat(puzzle): 添加拼图生成异步处理能力

- 移除 @RequiredArgsConstructor 注解,改用手动构造函数注入
- 添加 ThreadPoolExecutor 实现拼图生成异步处理
- 新增 generateAsync 方法支持异步生成拼图
- 新增 generateSync 方法支持同步生成拼图
- 重构核心生成逻辑为 doGenerateInternal 方法供同步异步共用
- 在 FaceMatchingOrchestrator 中优化拼图模板生成逻辑
- 支持根据场景选择同步或异步生成模式
- 添加线程池队列大小监控和日志记录
This commit is contained in:
2026-01-01 21:26:34 +08:00
parent 44f5008fd1
commit f8374519c3
3 changed files with 225 additions and 118 deletions

View File

@@ -151,12 +151,7 @@ public class FaceMatchingOrchestrator {
processSourceRelations(context, searchResult, faceId, isNew);
// 步骤7: 异步生成拼图模板
Thread thread = asyncGeneratePuzzleTemplate(context.face.getScenicId(), faceId, context.face.getMemberId());
if (Strings.CI.equals(scene, "printer")) {
if (thread != null) {
thread.join();
}
}
asyncGeneratePuzzleTemplate(context.face.getScenicId(), faceId, context.face.getMemberId(), scene);
return searchResult;
@@ -367,101 +362,74 @@ public class FaceMatchingOrchestrator {
*
* @return
*/
private Thread asyncGeneratePuzzleTemplate(Long scenicId, Long faceId, Long memberId) {
private void asyncGeneratePuzzleTemplate(Long scenicId, Long faceId, Long memberId, String scene) {
if (redisTemplate.hasKey("puzzle_generated:face:" + faceId)) {
return null;
return;
}
redisTemplate.opsForValue().set(
"puzzle_generated:face:" + faceId,
"1",
60 * 10, TimeUnit.SECONDS);
Thread thread = new Thread(() -> {
try {
log.info("开始异步生成景区拼图模板: scenicId={}, faceId={}", scenicId, faceId);
try {
log.info("开始异步生成景区拼图模板: scenicId={}, faceId={}", scenicId, faceId);
// 查询该景区所有启用状态的拼图模板
List<PuzzleTemplateDTO> templateList = puzzleTemplateService.listTemplates(
scenicId, null, 1); // 查询启用状态的模板
// 查询该景区所有启用状态的拼图模板
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());
baseDynamicData.put("scenicText", scenicBasic.getName());
baseDynamicData.put("dateStr", DateUtil.format(new Date(), "yyyy.MM.dd"));
// 使用虚拟线程池并行生成所有模板
AtomicInteger successCount = new AtomicInteger(0);
AtomicInteger failCount = new AtomicInteger(0);
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
// 为每个模板创建一个异步任务
List<CompletableFuture<Void>> futures = templateList.stream()
.map(template -> CompletableFuture.runAsync(() -> {
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));
generateRequest.setRequireRuleMatch(true);
// 调用拼图生成服务
PuzzleGenerateResponse response = puzzleGenerateService.generate(generateRequest);
log.info("拼图生成成功: scenicId={}, templateCode={}, imageUrl={}",
scenicId, template.getCode(), response.getImageUrl());
successCount.incrementAndGet();
} catch (Exception e) {
log.error("拼图生成失败: scenicId={}, templateCode={}, templateName={}",
scenicId, template.getCode(), template.getName(), e);
failCount.incrementAndGet();
}
}, executor))
.toList();
// 等待所有任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
log.info("景区拼图模板批量生成完成: scenicId={}, 总数={}, 成功={}, 失败={}",
scenicId, templateList.size(), successCount.get(), failCount.get());
} catch (Exception e) {
// 异步任务失败不影响主流程,仅记录日志
log.error("异步生成拼图模板失败: scenicId={}, faceId={}", scenicId, faceId, e);
if (templateList == null || templateList.isEmpty()) {
log.info("景区不存在启用的拼图模板,跳过生成: scenicId={}", scenicId);
return;
}
}, "PuzzleTemplateGenerator-" + scenicId);
thread.start();
return thread;
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());
baseDynamicData.put("scenicText", scenicBasic.getName());
baseDynamicData.put("dateStr", DateUtil.format(new Date(), "yyyy.MM.dd"));
templateList
.forEach(template -> {
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));
generateRequest.setRequireRuleMatch(true);
if (template.getAutoAddPrint() > 0 && Strings.CI.equals(scene, "printer")) {
puzzleGenerateService.generateSync(generateRequest);
} else {
puzzleGenerateService.generateAsync(generateRequest);
}
});
} catch (Exception e) {
// 异步任务失败不影响主流程,仅记录日志
log.error("异步生成拼图模板失败: scenicId={}, faceId={}", scenicId, faceId, e);
}
return;
}
/**