You've already forked FrameTour-BE
feat(puzzle): 添加拼图生成异步处理能力
- 移除 @RequiredArgsConstructor 注解,改用手动构造函数注入 - 添加 ThreadPoolExecutor 实现拼图生成异步处理 - 新增 generateAsync 方法支持异步生成拼图 - 新增 generateSync 方法支持同步生成拼图 - 重构核心生成逻辑为 doGenerateInternal 方法供同步异步共用 - 在 FaceMatchingOrchestrator 中优化拼图模板生成逻辑 - 支持根据场景选择同步或异步生成模式 - 添加线程池队列大小监控和日志记录
This commit is contained in:
@@ -12,10 +12,29 @@ import com.ycwl.basic.puzzle.dto.PuzzleGenerateResponse;
|
|||||||
public interface IPuzzleGenerateService {
|
public interface IPuzzleGenerateService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成拼图图片
|
* 生成拼图图片(默认同步模式)
|
||||||
*
|
*
|
||||||
* @param request 生成请求
|
* @param request 生成请求
|
||||||
* @return 生成结果(包含图片URL等信息)
|
* @return 生成结果(包含图片URL等信息)
|
||||||
*/
|
*/
|
||||||
PuzzleGenerateResponse generate(PuzzleGenerateRequest request);
|
PuzzleGenerateResponse generate(PuzzleGenerateRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步生成拼图图片
|
||||||
|
* <p>立即执行并阻塞等待结果返回</p>
|
||||||
|
*
|
||||||
|
* @param request 生成请求
|
||||||
|
* @return 生成结果(包含图片URL等信息)
|
||||||
|
*/
|
||||||
|
PuzzleGenerateResponse generateSync(PuzzleGenerateRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步生成拼图图片
|
||||||
|
* <p>提交到队列,由固定大小的线程池异步处理,不等待结果</p>
|
||||||
|
* <p>队列满时会降级为同步执行(CallerRunsPolicy)</p>
|
||||||
|
*
|
||||||
|
* @param request 生成请求
|
||||||
|
* @return 生成记录ID(可用于后续追踪状态)
|
||||||
|
*/
|
||||||
|
Long generateAsync(PuzzleGenerateRequest request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import com.ycwl.basic.repository.ScenicRepository;
|
|||||||
import com.ycwl.basic.service.printer.PrinterService;
|
import com.ycwl.basic.service.printer.PrinterService;
|
||||||
import com.ycwl.basic.storage.StorageFactory;
|
import com.ycwl.basic.storage.StorageFactory;
|
||||||
import com.ycwl.basic.utils.WxMpUtil;
|
import com.ycwl.basic.utils.WxMpUtil;
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -38,6 +37,9 @@ import java.util.HashMap;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 拼图图片生成服务实现
|
* 拼图图片生成服务实现
|
||||||
@@ -47,33 +49,104 @@ import java.util.UUID;
|
|||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Service
|
@Service
|
||||||
@RequiredArgsConstructor
|
|
||||||
public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
||||||
|
|
||||||
private final PuzzleTemplateMapper templateMapper;
|
private final PuzzleTemplateMapper templateMapper;
|
||||||
private final PuzzleElementMapper elementMapper;
|
private final PuzzleElementMapper elementMapper;
|
||||||
private final PuzzleGenerationRecordMapper recordMapper;
|
private final PuzzleGenerationRecordMapper recordMapper;
|
||||||
@Lazy
|
|
||||||
private final PuzzleImageRenderer imageRenderer;
|
private final PuzzleImageRenderer imageRenderer;
|
||||||
@Lazy
|
|
||||||
private final PuzzleElementFillEngine fillEngine;
|
private final PuzzleElementFillEngine fillEngine;
|
||||||
@Lazy
|
|
||||||
private final ScenicRepository scenicRepository;
|
private final ScenicRepository scenicRepository;
|
||||||
@Lazy
|
|
||||||
private final PuzzleDuplicationDetector duplicationDetector;
|
private final PuzzleDuplicationDetector duplicationDetector;
|
||||||
@Lazy
|
|
||||||
private final PrinterService printerService;
|
private final PrinterService printerService;
|
||||||
|
private final ThreadPoolExecutor puzzleGenerateExecutor;
|
||||||
|
|
||||||
|
public PuzzleGenerateServiceImpl(
|
||||||
|
PuzzleTemplateMapper templateMapper,
|
||||||
|
PuzzleElementMapper elementMapper,
|
||||||
|
PuzzleGenerationRecordMapper recordMapper,
|
||||||
|
@Lazy PuzzleImageRenderer imageRenderer,
|
||||||
|
@Lazy PuzzleElementFillEngine fillEngine,
|
||||||
|
@Lazy ScenicRepository scenicRepository,
|
||||||
|
@Lazy PuzzleDuplicationDetector duplicationDetector,
|
||||||
|
@Lazy PrinterService printerService) {
|
||||||
|
this.templateMapper = templateMapper;
|
||||||
|
this.elementMapper = elementMapper;
|
||||||
|
this.recordMapper = recordMapper;
|
||||||
|
this.imageRenderer = imageRenderer;
|
||||||
|
this.fillEngine = fillEngine;
|
||||||
|
this.scenicRepository = scenicRepository;
|
||||||
|
this.duplicationDetector = duplicationDetector;
|
||||||
|
this.printerService = printerService;
|
||||||
|
this.puzzleGenerateExecutor = new ThreadPoolExecutor(
|
||||||
|
4,
|
||||||
|
256,
|
||||||
|
30,
|
||||||
|
TimeUnit.SECONDS,
|
||||||
|
new LinkedBlockingQueue<>(256),
|
||||||
|
new ThreadPoolExecutor.CallerRunsPolicy()
|
||||||
|
);;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PuzzleGenerateResponse generate(PuzzleGenerateRequest request) {
|
public PuzzleGenerateResponse generate(PuzzleGenerateRequest request) {
|
||||||
|
// 默认使用同步模式
|
||||||
|
return generateSync(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PuzzleGenerateResponse generateSync(PuzzleGenerateRequest request) {
|
||||||
|
return doGenerate(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long generateAsync(PuzzleGenerateRequest request) {
|
||||||
|
// 1. 参数校验
|
||||||
|
validateRequest(request);
|
||||||
|
|
||||||
|
// 2. 查询模板
|
||||||
|
PuzzleTemplateEntity template = templateMapper.getByCode(request.getTemplateCode());
|
||||||
|
if (template == null) {
|
||||||
|
throw new IllegalArgumentException("模板不存在: " + request.getTemplateCode());
|
||||||
|
}
|
||||||
|
if (template.getStatus() != 1) {
|
||||||
|
throw new IllegalArgumentException("模板已禁用: " + request.getTemplateCode());
|
||||||
|
}
|
||||||
|
Long resolvedScenicId = resolveScenicId(template, request.getScenicId());
|
||||||
|
|
||||||
|
// 3. 创建 PENDING 状态的记录
|
||||||
|
PuzzleGenerationRecordEntity record = createRecord(template, request, resolvedScenicId);
|
||||||
|
record.setStatus(0); // 生成中
|
||||||
|
recordMapper.insert(record);
|
||||||
|
Long recordId = record.getId();
|
||||||
|
|
||||||
|
log.info("异步拼图生成任务已提交: recordId={}, templateCode={}, 当前队列大小={}",
|
||||||
|
recordId, request.getTemplateCode(), puzzleGenerateExecutor.getQueue().size());
|
||||||
|
|
||||||
|
// 4. 提交到线程池异步执行
|
||||||
|
puzzleGenerateExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
doGenerateInternal(request, template, resolvedScenicId, record);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("异步拼图生成失败: recordId={}, templateCode={}",
|
||||||
|
recordId, request.getTemplateCode(), e);
|
||||||
|
recordMapper.updateFail(recordId, e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return recordId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心生成逻辑(同步执行)
|
||||||
|
*/
|
||||||
|
private PuzzleGenerateResponse doGenerate(PuzzleGenerateRequest request) {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
log.info("开始生成拼图: templateCode={}, userId={}, faceId={}",
|
log.info("开始生成拼图: templateCode={}, userId={}, faceId={}",
|
||||||
request.getTemplateCode(), request.getUserId(), request.getFaceId());
|
request.getTemplateCode(), request.getUserId(), request.getFaceId());
|
||||||
|
|
||||||
// 业务层校验:faceId 必填
|
// 参数校验
|
||||||
if (request.getFaceId() == null) {
|
validateRequest(request);
|
||||||
throw new IllegalArgumentException("人脸ID不能为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. 查询模板和元素
|
// 1. 查询模板和元素
|
||||||
PuzzleTemplateEntity template = templateMapper.getByCode(request.getTemplateCode());
|
PuzzleTemplateEntity template = templateMapper.getByCode(request.getTemplateCode());
|
||||||
@@ -135,16 +208,66 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
|||||||
record.setContentHash(contentHash);
|
record.setContentHash(contentHash);
|
||||||
recordMapper.insert(record);
|
recordMapper.insert(record);
|
||||||
|
|
||||||
|
// 9. 执行核心生成逻辑
|
||||||
|
return doGenerateInternal(request, template, resolvedScenicId, record, startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验请求参数
|
||||||
|
*/
|
||||||
|
private void validateRequest(PuzzleGenerateRequest request) {
|
||||||
|
if (request.getFaceId() == null) {
|
||||||
|
throw new IllegalArgumentException("人脸ID不能为空");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心生成逻辑(内部方法,同步/异步共用)
|
||||||
|
* 注意:此方法会在调用线程中执行渲染和上传操作
|
||||||
|
*
|
||||||
|
* @param request 生成请求
|
||||||
|
* @param template 模板
|
||||||
|
* @param resolvedScenicId 景区ID
|
||||||
|
* @param record 生成记录(已插入数据库)
|
||||||
|
* @return 生成结果(异步模式下不关心返回值)
|
||||||
|
*/
|
||||||
|
private PuzzleGenerateResponse doGenerateInternal(PuzzleGenerateRequest request,
|
||||||
|
PuzzleTemplateEntity template,
|
||||||
|
Long resolvedScenicId,
|
||||||
|
PuzzleGenerationRecordEntity record) {
|
||||||
|
return doGenerateInternal(request, template, resolvedScenicId, record, System.currentTimeMillis());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 核心生成逻辑(内部方法,同步/异步共用)
|
||||||
|
*/
|
||||||
|
private PuzzleGenerateResponse doGenerateInternal(PuzzleGenerateRequest request,
|
||||||
|
PuzzleTemplateEntity template,
|
||||||
|
Long resolvedScenicId,
|
||||||
|
PuzzleGenerationRecordEntity record,
|
||||||
|
long startTime) {
|
||||||
|
List<PuzzleElementEntity> elements = elementMapper.getByTemplateId(template.getId());
|
||||||
|
if (elements.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("模板没有配置元素: " + request.getTemplateCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按z-index排序元素
|
||||||
|
elements.sort(Comparator.comparing(PuzzleElementEntity::getZIndex,
|
||||||
|
Comparator.nullsFirst(Comparator.naturalOrder())));
|
||||||
|
|
||||||
|
// 准备dynamicData
|
||||||
|
Map<String, String> finalDynamicData = buildDynamicData(template, request, resolvedScenicId, elements);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 9. 渲染图片
|
// 渲染图片
|
||||||
BufferedImage resultImage = imageRenderer.render(template, elements, finalDynamicData);
|
BufferedImage resultImage = imageRenderer.render(template, elements, finalDynamicData);
|
||||||
|
|
||||||
// 10. 上传原图到OSS(未裁切)
|
// 上传原图到OSS(未裁切)
|
||||||
String originalImageUrl = uploadImage(resultImage, template.getCode(), request.getOutputFormat(), request.getQuality());
|
String originalImageUrl = uploadImage(resultImage, template.getCode(), request.getOutputFormat(), request.getQuality());
|
||||||
log.info("原图上传成功: url={}", originalImageUrl);
|
log.info("原图上传成功: url={}", originalImageUrl);
|
||||||
|
|
||||||
// 11. 处理用户区域裁切
|
// 处理用户区域裁切
|
||||||
String finalImageUrl = originalImageUrl; // 默认使用原图
|
String finalImageUrl = originalImageUrl;
|
||||||
BufferedImage finalImage = resultImage;
|
BufferedImage finalImage = resultImage;
|
||||||
|
|
||||||
if (StrUtil.isNotBlank(template.getUserArea())) {
|
if (StrUtil.isNotBlank(template.getUserArea())) {
|
||||||
@@ -155,12 +278,11 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
|||||||
log.info("裁切后图片上传成功: userArea={}, url={}", template.getUserArea(), finalImageUrl);
|
log.info("裁切后图片上传成功: userArea={}, url={}", template.getUserArea(), finalImageUrl);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("图片裁切失败,使用原图: userArea={}", template.getUserArea(), e);
|
log.error("图片裁切失败,使用原图: userArea={}", template.getUserArea(), e);
|
||||||
// 裁切失败时使用原图
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 12. 更新记录为成功
|
// 更新记录为成功
|
||||||
long duration = (int) (System.currentTimeMillis() - startTime);
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
long fileSize = estimateFileSize(finalImage, request.getOutputFormat());
|
long fileSize = estimateFileSize(finalImage, request.getOutputFormat());
|
||||||
recordMapper.updateSuccess(
|
recordMapper.updateSuccess(
|
||||||
record.getId(),
|
record.getId(),
|
||||||
@@ -172,23 +294,22 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
|||||||
(int) duration
|
(int) duration
|
||||||
);
|
);
|
||||||
|
|
||||||
log.info("拼图生成成功(新生成): recordId={}, originalUrl={}, finalUrl={}, duration={}ms",
|
log.info("拼图生成成功: recordId={}, originalUrl={}, finalUrl={}, duration={}ms",
|
||||||
record.getId(), originalImageUrl, finalImageUrl, duration);
|
record.getId(), originalImageUrl, finalImageUrl, duration);
|
||||||
|
|
||||||
// 13. 检查是否自动添加到打印队列
|
// 检查是否自动添加到打印队列
|
||||||
if (template.getAutoAddPrint() != null && template.getAutoAddPrint() == 1) {
|
if (template.getAutoAddPrint() != null && template.getAutoAddPrint() == 1) {
|
||||||
try {
|
try {
|
||||||
Integer printRecordId = printerService.addUserPhotoFromPuzzle(
|
Integer printRecordId = printerService.addUserPhotoFromPuzzle(
|
||||||
request.getUserId(),
|
request.getUserId(),
|
||||||
resolvedScenicId,
|
resolvedScenicId,
|
||||||
request.getFaceId(),
|
request.getFaceId(),
|
||||||
originalImageUrl, // 使用原图URL添加到打印队列
|
originalImageUrl,
|
||||||
record.getId()
|
record.getId()
|
||||||
);
|
);
|
||||||
log.info("自动添加到打印队列成功: recordId={}, printRecordId={}", record.getId(), printRecordId);
|
log.info("自动添加到打印队列成功: recordId={}, printRecordId={}", record.getId(), printRecordId);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("自动添加到打印队列失败: recordId={}", record.getId(), e);
|
log.error("自动添加到打印队列失败: recordId={}", record.getId(), e);
|
||||||
// 添加失败不影响拼图生成流程
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,13 +320,12 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
|||||||
finalImage.getHeight(),
|
finalImage.getHeight(),
|
||||||
(int) duration,
|
(int) duration,
|
||||||
record.getId(),
|
record.getId(),
|
||||||
false, // isDuplicate=false
|
false,
|
||||||
null // originalRecordId=null
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("拼图生成失败: templateCode={}", request.getTemplateCode(), e);
|
log.error("拼图生成失败: templateCode={}", request.getTemplateCode(), e);
|
||||||
// 更新记录为失败
|
|
||||||
recordMapper.updateFail(record.getId(), e.getMessage());
|
recordMapper.updateFail(record.getId(), e.getMessage());
|
||||||
throw new RuntimeException("图片生成失败: " + e.getMessage(), e);
|
throw new RuntimeException("图片生成失败: " + e.getMessage(), e);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,12 +151,7 @@ public class FaceMatchingOrchestrator {
|
|||||||
processSourceRelations(context, searchResult, faceId, isNew);
|
processSourceRelations(context, searchResult, faceId, isNew);
|
||||||
|
|
||||||
// 步骤7: 异步生成拼图模板
|
// 步骤7: 异步生成拼图模板
|
||||||
Thread thread = asyncGeneratePuzzleTemplate(context.face.getScenicId(), faceId, context.face.getMemberId());
|
asyncGeneratePuzzleTemplate(context.face.getScenicId(), faceId, context.face.getMemberId(), scene);
|
||||||
if (Strings.CI.equals(scene, "printer")) {
|
|
||||||
if (thread != null) {
|
|
||||||
thread.join();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return searchResult;
|
return searchResult;
|
||||||
|
|
||||||
@@ -367,101 +362,74 @@ public class FaceMatchingOrchestrator {
|
|||||||
*
|
*
|
||||||
* @return
|
* @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)) {
|
if (redisTemplate.hasKey("puzzle_generated:face:" + faceId)) {
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
redisTemplate.opsForValue().set(
|
redisTemplate.opsForValue().set(
|
||||||
"puzzle_generated:face:" + faceId,
|
"puzzle_generated:face:" + faceId,
|
||||||
"1",
|
"1",
|
||||||
60 * 10, TimeUnit.SECONDS);
|
60 * 10, TimeUnit.SECONDS);
|
||||||
Thread thread = new Thread(() -> {
|
try {
|
||||||
try {
|
log.info("开始异步生成景区拼图模板: scenicId={}, faceId={}", scenicId, faceId);
|
||||||
log.info("开始异步生成景区拼图模板: scenicId={}, faceId={}", scenicId, faceId);
|
|
||||||
|
|
||||||
// 查询该景区所有启用状态的拼图模板
|
// 查询该景区所有启用状态的拼图模板
|
||||||
List<PuzzleTemplateDTO> templateList = puzzleTemplateService.listTemplates(
|
List<PuzzleTemplateDTO> templateList = puzzleTemplateService.listTemplates(
|
||||||
scenicId, null, 1); // 查询启用状态的模板
|
scenicId, null, 1); // 查询启用状态的模板
|
||||||
|
|
||||||
if (templateList == null || templateList.isEmpty()) {
|
if (templateList == null || templateList.isEmpty()) {
|
||||||
log.info("景区不存在启用的拼图模板,跳过生成: scenicId={}", scenicId);
|
log.info("景区不存在启用的拼图模板,跳过生成: scenicId={}", scenicId);
|
||||||
return;
|
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);
|
|
||||||
}
|
}
|
||||||
}, "PuzzleTemplateGenerator-" + scenicId);
|
|
||||||
thread.start();
|
log.info("景区存在 {} 个启用的拼图模板,开始逐个生成: scenicId={}", templateList.size(), scenicId);
|
||||||
return thread;
|
|
||||||
|
// 获取人脸信息用于动态数据
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user