You've already forked FrameTour-BE
test(puzzle
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
package com.ycwl.basic.puzzle.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.ycwl.basic.biz.FaceStatusManager;
|
||||
import com.ycwl.basic.model.pc.mp.MpConfigEntity;
|
||||
@@ -16,23 +15,16 @@ import com.ycwl.basic.puzzle.mapper.PuzzleGenerationRecordMapper;
|
||||
import com.ycwl.basic.puzzle.repository.PuzzleRepository;
|
||||
import com.ycwl.basic.puzzle.service.IPuzzleGenerateService;
|
||||
import com.ycwl.basic.puzzle.util.PuzzleDuplicationDetector;
|
||||
import com.ycwl.basic.puzzle.util.PuzzleImageRenderer;
|
||||
import com.ycwl.basic.repository.ScenicRepository;
|
||||
import com.ycwl.basic.service.pc.processor.PuzzleRelationProcessor;
|
||||
import com.ycwl.basic.service.printer.PrinterService;
|
||||
import com.ycwl.basic.storage.StorageFactory;
|
||||
import com.ycwl.basic.utils.WxMpUtil;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
@@ -52,11 +44,9 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
||||
|
||||
private final PuzzleRepository puzzleRepository;
|
||||
private final PuzzleGenerationRecordMapper recordMapper;
|
||||
private final PuzzleImageRenderer imageRenderer;
|
||||
private final PuzzleElementFillEngine fillEngine;
|
||||
private final ScenicRepository scenicRepository;
|
||||
private final PuzzleDuplicationDetector duplicationDetector;
|
||||
private final PrinterService printerService;
|
||||
private final PuzzleEdgeRenderTaskService puzzleEdgeRenderTaskService;
|
||||
private final FaceStatusManager faceStatusManager;
|
||||
private final PuzzleRelationProcessor puzzleRelationProcessor;
|
||||
@@ -64,21 +54,17 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
||||
public PuzzleGenerateServiceImpl(
|
||||
PuzzleRepository puzzleRepository,
|
||||
PuzzleGenerationRecordMapper recordMapper,
|
||||
@Lazy PuzzleImageRenderer imageRenderer,
|
||||
@Lazy PuzzleElementFillEngine fillEngine,
|
||||
@Lazy ScenicRepository scenicRepository,
|
||||
@Lazy PuzzleDuplicationDetector duplicationDetector,
|
||||
@Lazy PrinterService printerService,
|
||||
PuzzleEdgeRenderTaskService puzzleEdgeRenderTaskService,
|
||||
@Lazy FaceStatusManager faceStatusManager,
|
||||
@Lazy PuzzleRelationProcessor puzzleRelationProcessor) {
|
||||
this.puzzleRepository = puzzleRepository;
|
||||
this.recordMapper = recordMapper;
|
||||
this.imageRenderer = imageRenderer;
|
||||
this.fillEngine = fillEngine;
|
||||
this.scenicRepository = scenicRepository;
|
||||
this.duplicationDetector = duplicationDetector;
|
||||
this.printerService = printerService;
|
||||
this.puzzleEdgeRenderTaskService = puzzleEdgeRenderTaskService;
|
||||
this.faceStatusManager = faceStatusManager;
|
||||
this.puzzleRelationProcessor = puzzleRelationProcessor;
|
||||
@@ -340,84 +326,6 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
||||
return record.getId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心生成逻辑(同步执行)
|
||||
*/
|
||||
private PuzzleGenerateResponse doGenerate(PuzzleGenerateRequest request) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
log.info("开始生成拼图: templateCode={}, userId={}, faceId={}",
|
||||
request.getTemplateCode(), request.getUserId(), request.getFaceId());
|
||||
|
||||
// 参数校验
|
||||
validateRequest(request);
|
||||
|
||||
// 1. 查询模板和元素(使用缓存)
|
||||
PuzzleTemplateEntity template = puzzleRepository.getTemplateByCode(request.getTemplateCode());
|
||||
if (template == null) {
|
||||
throw new IllegalArgumentException("模板不存在: " + request.getTemplateCode());
|
||||
}
|
||||
|
||||
if (template.getStatus() != 1) {
|
||||
throw new IllegalArgumentException("模板已禁用: " + request.getTemplateCode());
|
||||
}
|
||||
|
||||
// 2. 校验景区隔离
|
||||
Long resolvedScenicId = resolveScenicId(template, request.getScenicId());
|
||||
|
||||
List<PuzzleElementEntity> elements = puzzleRepository.getElementsByTemplateId(template.getId());
|
||||
if (elements.isEmpty()) {
|
||||
throw new IllegalArgumentException("模板没有配置元素: " + request.getTemplateCode());
|
||||
}
|
||||
|
||||
// 3. 按z-index排序元素
|
||||
elements.sort(Comparator.comparing(PuzzleElementEntity::getZIndex,
|
||||
Comparator.nullsFirst(Comparator.naturalOrder())));
|
||||
|
||||
// 4. 准备dynamicData(合并自动填充和手动数据)
|
||||
Map<String, String> finalDynamicData = buildDynamicData(template, request, resolvedScenicId, elements);
|
||||
|
||||
// 5. 执行重复图片检测
|
||||
// 如果所有IMAGE元素使用相同URL,抛出DuplicateImageException
|
||||
duplicationDetector.detectDuplicateImages(finalDynamicData, elements);
|
||||
|
||||
// 6. 计算内容哈希
|
||||
String contentHash = duplicationDetector.calculateContentHash(finalDynamicData);
|
||||
|
||||
// 7. 查询历史记录(去重核心逻辑)
|
||||
PuzzleGenerationRecordEntity duplicateRecord = duplicationDetector.findDuplicateRecord(
|
||||
template.getId(), contentHash, resolvedScenicId);
|
||||
|
||||
if (duplicateRecord != null) {
|
||||
// 发现重复内容,直接返回历史记录
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
log.info("检测到重复内容,复用历史记录: recordId={}, imageUrl={}, duration={}ms",
|
||||
duplicateRecord.getId(), duplicateRecord.getResultImageUrl(), duration);
|
||||
|
||||
// 直接返回历史图片URL(语义化生成成功)
|
||||
return PuzzleGenerateResponse.success(
|
||||
duplicateRecord.getResultImageUrl(),
|
||||
duplicateRecord.getResultFileSize(),
|
||||
duplicateRecord.getResultWidth(),
|
||||
duplicateRecord.getResultHeight(),
|
||||
(int) duration,
|
||||
duplicateRecord.getId(),
|
||||
true, // isDuplicate=true
|
||||
duplicateRecord.getId() // originalRecordId(复用时指向自己)
|
||||
);
|
||||
}
|
||||
|
||||
// 8. 没有历史记录,创建新的生成记录
|
||||
PuzzleGenerationRecordEntity record = createRecord(template, request, resolvedScenicId);
|
||||
record.setContentHash(contentHash);
|
||||
recordMapper.insert(record);
|
||||
|
||||
// 清除生成记录缓存(新记录插入后列表和数量都会变化)
|
||||
puzzleRepository.clearRecordCacheByFace(request.getFaceId());
|
||||
|
||||
// 9. 执行核心生成逻辑
|
||||
return doGenerateInternal(request, template, resolvedScenicId, record, startTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验请求参数
|
||||
*/
|
||||
@@ -427,105 +335,6 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 核心生成逻辑(内部方法,同步/异步共用)
|
||||
* 注意:此方法会在调用线程中执行渲染和上传操作
|
||||
*
|
||||
* @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 = puzzleRepository.getElementsByTemplateId(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 {
|
||||
// 渲染图片
|
||||
BufferedImage resultImage = imageRenderer.render(template, elements, finalDynamicData);
|
||||
|
||||
// 上传图片到OSS
|
||||
String imageUrl = uploadImage(resultImage, template.getCode(), request.getOutputFormat(), request.getQuality());
|
||||
log.info("图片上传成功: url={}", imageUrl);
|
||||
|
||||
// 更新记录为成功
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
long fileSize = estimateFileSize(resultImage, request.getOutputFormat());
|
||||
recordMapper.updateSuccess(
|
||||
record.getId(),
|
||||
imageUrl,
|
||||
fileSize,
|
||||
resultImage.getWidth(),
|
||||
resultImage.getHeight(),
|
||||
(int) duration
|
||||
);
|
||||
|
||||
// 清除生成记录缓存(状态已更新)
|
||||
puzzleRepository.clearRecordCache(record.getId(), request.getFaceId());
|
||||
|
||||
log.info("拼图生成成功: recordId={}, imageUrl={}, duration={}ms",
|
||||
record.getId(), imageUrl, duration);
|
||||
|
||||
// 检查是否自动添加到打印队列
|
||||
if (template.getAutoAddPrint() != null && template.getAutoAddPrint() == 1) {
|
||||
try {
|
||||
Integer printRecordId = printerService.addUserPhotoFromPuzzle(
|
||||
request.getUserId(),
|
||||
resolvedScenicId,
|
||||
request.getFaceId(),
|
||||
imageUrl,
|
||||
record.getId() // 拼图记录ID,用于关联 puzzle_generation_record 表
|
||||
);
|
||||
log.info("自动添加到打印队列成功: recordId={}, printRecordId={}", record.getId(), printRecordId);
|
||||
} catch (Exception e) {
|
||||
log.error("自动添加到打印队列失败: recordId={}", record.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
return PuzzleGenerateResponse.success(
|
||||
imageUrl,
|
||||
fileSize,
|
||||
resultImage.getWidth(),
|
||||
resultImage.getHeight(),
|
||||
(int) duration,
|
||||
record.getId(),
|
||||
false,
|
||||
null
|
||||
);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("拼图生成失败: templateCode={}", request.getTemplateCode(), e);
|
||||
recordMapper.updateFail(record.getId(), e.getMessage());
|
||||
// 清除生成记录缓存(状态已更新)
|
||||
puzzleRepository.clearRecordCache(record.getId(), request.getFaceId());
|
||||
throw new RuntimeException("图片生成失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建生成记录
|
||||
*/
|
||||
@@ -550,53 +359,6 @@ public class PuzzleGenerateServiceImpl implements IPuzzleGenerateService {
|
||||
return record;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传图片到OSS
|
||||
*/
|
||||
private String uploadImage(BufferedImage image, String templateCode, String format, Integer quality) throws IOException {
|
||||
// 确定格式
|
||||
String outputFormat = StrUtil.isNotBlank(format) ? format.toUpperCase() : "PNG";
|
||||
if (!"PNG".equals(outputFormat) && !"JPEG".equals(outputFormat) && !"JPG".equals(outputFormat)) {
|
||||
outputFormat = "PNG";
|
||||
}
|
||||
|
||||
// 转换为字节数组
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ImageIO.write(image, outputFormat, baos);
|
||||
byte[] imageBytes = baos.toByteArray();
|
||||
|
||||
// 生成文件名
|
||||
String fileName = String.format("%s.%s",
|
||||
UUID.randomUUID().toString().replace("-", ""),
|
||||
outputFormat.toLowerCase()
|
||||
);
|
||||
|
||||
// 使用项目现有的存储工厂上传(转换为InputStream)
|
||||
try {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes);
|
||||
String contentType = "PNG".equals(outputFormat) ? "image/png" : "image/jpeg";
|
||||
return StorageFactory.use().uploadFile(contentType, inputStream, "puzzle", templateCode, fileName);
|
||||
} catch (Exception e) {
|
||||
log.error("上传图片失败: fileName={}", fileName, e);
|
||||
throw new IOException("图片上传失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 估算文件大小(字节)
|
||||
*/
|
||||
private long estimateFileSize(BufferedImage image, String format) {
|
||||
try {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
String outputFormat = StrUtil.isNotBlank(format) ? format.toUpperCase() : "PNG";
|
||||
ImageIO.write(image, outputFormat, baos);
|
||||
return baos.size();
|
||||
} catch (IOException e) {
|
||||
log.warn("估算文件大小失败", e);
|
||||
return 0L;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建dynamicData(合并自动填充和手动数据)
|
||||
* 优先级: 手动传入的数据 > 自动填充的数据
|
||||
|
||||
Reference in New Issue
Block a user