You've already forked FrameTour-BE
feat(puzzle): 添加水印拼图功能支持
- 在 PuzzleEdgeRenderTaskEntity 中新增 taskType 和 watermarkType 字段 - 添加 TASK_TYPE_PUZZLE 和 TASK_TYPE_WATERMARK 常量定义 - 新增 PuzzleWatermarkMapper 依赖注入 - 实现 handleWatermarkTaskSuccess 方法处理水印拼图任务成功逻辑 - 修改 taskSuccess 方法根据任务类型分别处理原始拼图和水印拼图 - 新增 createWatermarkRenderTask 方法创建水印拼图边缘渲染任务 - 为水印拼图任务添加独立的存储目录和文件命名规则 - 实现水印拼图结果写入 puzzle_watermark 表的功能
This commit is contained in:
@@ -34,6 +34,18 @@ public class PuzzleEdgeRenderTaskEntity {
|
||||
@TableField("face_id")
|
||||
private Long faceId;
|
||||
|
||||
/**
|
||||
* 任务类型:PUZZLE-原始拼图 WATERMARK-水印拼图
|
||||
*/
|
||||
@TableField("task_type")
|
||||
private String taskType;
|
||||
|
||||
/**
|
||||
* 水印类型(仅task_type=WATERMARK时有效)
|
||||
*/
|
||||
@TableField("watermark_type")
|
||||
private String watermarkType;
|
||||
|
||||
@TableField("content_hash")
|
||||
private String contentHash;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import cn.hutool.core.util.StrUtil;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.ycwl.basic.model.pc.puzzle.entity.PuzzleWatermarkEntity;
|
||||
import com.ycwl.basic.model.task.req.ClientStatusReqVo;
|
||||
import com.ycwl.basic.puzzle.edge.dto.PuzzleEdgeRenderTaskDTO;
|
||||
import com.ycwl.basic.puzzle.edge.dto.PuzzleEdgeTaskFailRequest;
|
||||
@@ -16,6 +17,7 @@ import com.ycwl.basic.puzzle.entity.PuzzleElementEntity;
|
||||
import com.ycwl.basic.puzzle.entity.PuzzleGenerationRecordEntity;
|
||||
import com.ycwl.basic.puzzle.entity.PuzzleTemplateEntity;
|
||||
import com.ycwl.basic.puzzle.mapper.PuzzleGenerationRecordMapper;
|
||||
import com.ycwl.basic.puzzle.mapper.PuzzleWatermarkMapper;
|
||||
import com.ycwl.basic.puzzle.repository.PuzzleRepository;
|
||||
import com.ycwl.basic.service.pc.processor.PuzzleRelationProcessor;
|
||||
import com.ycwl.basic.service.printer.PrinterService;
|
||||
@@ -55,6 +57,15 @@ public class PuzzleEdgeRenderTaskService {
|
||||
private static final int STATUS_SUCCESS = 2;
|
||||
private static final int STATUS_FAIL = 3;
|
||||
|
||||
/**
|
||||
* 任务类型:原始拼图(成功后更新 puzzle_generation_record)
|
||||
*/
|
||||
public static final String TASK_TYPE_PUZZLE = "PUZZLE";
|
||||
/**
|
||||
* 任务类型:水印拼图(成功后写入 puzzle_watermark)
|
||||
*/
|
||||
public static final String TASK_TYPE_WATERMARK = "WATERMARK";
|
||||
|
||||
private static final int MAX_SYNC_TASKS = 5;
|
||||
private static final long LEASE_MILLIS = TimeUnit.SECONDS.toMillis(20);
|
||||
private static final long UPLOAD_URL_EXPIRE_MILLIS = TimeUnit.HOURS.toMillis(1);
|
||||
@@ -133,6 +144,7 @@ public class PuzzleEdgeRenderTaskService {
|
||||
private final ConcurrentHashMap<Long, WaitFutureEntry> waitFutures = new ConcurrentHashMap<>();
|
||||
|
||||
private final PuzzleGenerationRecordMapper recordMapper;
|
||||
private final PuzzleWatermarkMapper puzzleWatermarkMapper;
|
||||
private final PuzzleRepository puzzleRepository;
|
||||
private final PrinterService printerService;
|
||||
private final PuzzleRelationProcessor puzzleRelationProcessor;
|
||||
@@ -200,15 +212,35 @@ public class PuzzleEdgeRenderTaskService {
|
||||
throw new IllegalStateException("任务状态更新失败");
|
||||
}
|
||||
|
||||
PuzzleGenerationRecordEntity record = recordMapper.getById(task.getRecordId());
|
||||
if (record == null) {
|
||||
log.warn("边缘渲染任务回报成功,但生成记录不存在: taskId={}, recordId={}", taskId, task.getRecordId());
|
||||
return;
|
||||
}
|
||||
|
||||
IStorageAdapter storage = StorageFactory.use();
|
||||
String resultImageUrl = storage.getUrl(task.getOriginalObjectKey());
|
||||
|
||||
// 根据任务类型决定写入哪个表
|
||||
String taskType = task.getTaskType();
|
||||
if (TASK_TYPE_WATERMARK.equals(taskType)) {
|
||||
// 水印拼图任务:写入 puzzle_watermark 表
|
||||
handleWatermarkTaskSuccess(task, resultImageUrl);
|
||||
} else {
|
||||
// 原始拼图任务(默认):更新 puzzle_generation_record 表
|
||||
handlePuzzleTaskSuccess(task, req, resultImageUrl);
|
||||
}
|
||||
|
||||
// 通知等待方任务完成
|
||||
completeWaitFuture(taskId, TaskWaitResult.success(resultImageUrl));
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理原始拼图任务成功
|
||||
*/
|
||||
private void handlePuzzleTaskSuccess(PuzzleEdgeRenderTaskEntity task,
|
||||
PuzzleEdgeTaskSuccessRequest req,
|
||||
String resultImageUrl) {
|
||||
PuzzleGenerationRecordEntity record = recordMapper.getById(task.getRecordId());
|
||||
if (record == null) {
|
||||
log.warn("边缘渲染任务回报成功,但生成记录不存在: taskId={}, recordId={}", task.getId(), task.getRecordId());
|
||||
return;
|
||||
}
|
||||
|
||||
Long resultFileSize = req != null ? req.getResultFileSize() : null;
|
||||
Integer resultWidth = req != null ? req.getResultWidth() : null;
|
||||
Integer resultHeight = req != null ? req.getResultHeight() : null;
|
||||
@@ -234,9 +266,6 @@ public class PuzzleEdgeRenderTaskService {
|
||||
record.getId()
|
||||
);
|
||||
|
||||
// 通知等待方任务完成
|
||||
completeWaitFuture(taskId, TaskWaitResult.success(resultImageUrl));
|
||||
|
||||
PuzzleTemplateEntity template = puzzleRepository.getTemplateById(task.getTemplateId());
|
||||
if (template != null && template.getAutoAddPrint() != null && template.getAutoAddPrint() == 1) {
|
||||
try {
|
||||
@@ -254,6 +283,26 @@ public class PuzzleEdgeRenderTaskService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理水印拼图任务成功
|
||||
*/
|
||||
private void handleWatermarkTaskSuccess(PuzzleEdgeRenderTaskEntity task, String resultImageUrl) {
|
||||
PuzzleWatermarkEntity watermark = new PuzzleWatermarkEntity();
|
||||
watermark.setRecordId(task.getRecordId());
|
||||
watermark.setFaceId(task.getFaceId());
|
||||
watermark.setWatermarkType(task.getWatermarkType());
|
||||
watermark.setWatermarkUrl(resultImageUrl);
|
||||
|
||||
try {
|
||||
puzzleWatermarkMapper.insert(watermark);
|
||||
log.info("水印拼图任务成功,已写入puzzle_watermark: taskId={}, recordId={}, watermarkType={}",
|
||||
task.getId(), task.getRecordId(), task.getWatermarkType());
|
||||
} catch (Exception e) {
|
||||
log.error("水印拼图任务成功,但写入puzzle_watermark失败: taskId={}, recordId={}",
|
||||
task.getId(), task.getRecordId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public void taskFail(Long taskId, PuzzleEdgeTaskFailRequest req) {
|
||||
// IP 验证已在拦截器层完成,此处无需验证 accessKey
|
||||
Long workerId = DEFAULT_WORKER_ID;
|
||||
@@ -472,6 +521,8 @@ public class PuzzleEdgeRenderTaskService {
|
||||
task.setTemplateCode(template.getCode());
|
||||
task.setScenicId(record.getScenicId());
|
||||
task.setFaceId(record.getFaceId());
|
||||
task.setTaskType(TASK_TYPE_PUZZLE);
|
||||
task.setWatermarkType(null);
|
||||
task.setContentHash(record.getContentHash());
|
||||
task.setStatus(STATUS_PENDING);
|
||||
task.setAttemptCount(0);
|
||||
@@ -491,6 +542,118 @@ public class PuzzleEdgeRenderTaskService {
|
||||
return taskId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建水印拼图边缘渲染任务(供中心业务侧调用)
|
||||
* 成功后将结果写入 puzzle_watermark 表
|
||||
*
|
||||
* @param recordId 原始拼图生成记录ID
|
||||
* @param faceId 人脸ID(可选)
|
||||
* @param watermarkType 水印类型(如 print、free_download)
|
||||
* @param template 模板配置
|
||||
* @param sortedElements 元素列表(按z-index排序)
|
||||
* @param finalDynamicData 动态数据
|
||||
* @param outputFormat 输出格式
|
||||
* @param quality 输出质量
|
||||
* @return 任务ID
|
||||
*/
|
||||
public Long createWatermarkRenderTask(Long recordId,
|
||||
Long faceId,
|
||||
String watermarkType,
|
||||
PuzzleTemplateEntity template,
|
||||
List<PuzzleElementEntity> sortedElements,
|
||||
Map<String, String> finalDynamicData,
|
||||
String outputFormat,
|
||||
Integer quality) {
|
||||
if (recordId == null) {
|
||||
throw new IllegalArgumentException("recordId不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(watermarkType)) {
|
||||
throw new IllegalArgumentException("watermarkType不能为空");
|
||||
}
|
||||
if (template == null || template.getId() == null) {
|
||||
throw new IllegalArgumentException("template不能为空");
|
||||
}
|
||||
if (sortedElements == null) {
|
||||
sortedElements = List.of();
|
||||
}
|
||||
if (finalDynamicData == null) {
|
||||
finalDynamicData = Map.of();
|
||||
}
|
||||
|
||||
String normalizedFormat = normalizeOutputFormat(outputFormat);
|
||||
Integer outputQuality = quality != null ? quality : 90;
|
||||
String ext = "PNG".equals(normalizedFormat) ? "png" : "jpeg";
|
||||
String fileName = UUID.randomUUID().toString().replace("-", "") + "." + ext;
|
||||
|
||||
// 水印拼图使用单独的目录
|
||||
String originalObjectKey = String.format("puzzle_watermark/%s/%s/%s", template.getCode(), watermarkType, fileName);
|
||||
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put("recordId", recordId);
|
||||
payload.put("watermarkType", watermarkType);
|
||||
|
||||
Map<String, Object> templatePayload = new HashMap<>();
|
||||
templatePayload.put("id", template.getId());
|
||||
templatePayload.put("code", template.getCode());
|
||||
templatePayload.put("canvasWidth", template.getCanvasWidth());
|
||||
templatePayload.put("canvasHeight", template.getCanvasHeight());
|
||||
templatePayload.put("backgroundType", template.getBackgroundType());
|
||||
templatePayload.put("backgroundColor", template.getBackgroundColor());
|
||||
templatePayload.put("backgroundImage", template.getBackgroundImage());
|
||||
payload.put("template", templatePayload);
|
||||
|
||||
List<Map<String, Object>> elementPayloadList = new ArrayList<>();
|
||||
for (PuzzleElementEntity e : sortedElements) {
|
||||
Map<String, Object> elementPayload = new HashMap<>();
|
||||
elementPayload.put("id", e.getId());
|
||||
elementPayload.put("type", e.getElementType());
|
||||
elementPayload.put("key", e.getElementKey());
|
||||
elementPayload.put("name", e.getElementName());
|
||||
elementPayload.put("x", e.getXPosition());
|
||||
elementPayload.put("y", e.getYPosition());
|
||||
elementPayload.put("width", e.getWidth());
|
||||
elementPayload.put("height", e.getHeight());
|
||||
elementPayload.put("zIndex", e.getZIndex());
|
||||
elementPayload.put("rotation", e.getRotation());
|
||||
elementPayload.put("opacity", e.getOpacity());
|
||||
elementPayload.put("config", e.getConfig());
|
||||
elementPayloadList.add(elementPayload);
|
||||
}
|
||||
payload.put("elements", elementPayloadList);
|
||||
payload.put("dynamicData", finalDynamicData);
|
||||
|
||||
Map<String, Object> outputPayload = new HashMap<>();
|
||||
outputPayload.put("format", normalizedFormat);
|
||||
outputPayload.put("quality", outputQuality);
|
||||
payload.put("output", outputPayload);
|
||||
|
||||
PuzzleEdgeRenderTaskEntity task = new PuzzleEdgeRenderTaskEntity();
|
||||
task.setRecordId(recordId);
|
||||
task.setTemplateId(template.getId());
|
||||
task.setTemplateCode(template.getCode());
|
||||
task.setScenicId(template.getScenicId());
|
||||
task.setFaceId(faceId);
|
||||
task.setTaskType(TASK_TYPE_WATERMARK);
|
||||
task.setWatermarkType(watermarkType);
|
||||
task.setContentHash(null); // 水印任务不需要内容哈希去重
|
||||
task.setStatus(STATUS_PENDING);
|
||||
task.setAttemptCount(0);
|
||||
task.setOutputFormat(normalizedFormat);
|
||||
task.setOutputQuality(outputQuality);
|
||||
task.setOriginalObjectKey(originalObjectKey);
|
||||
task.setCroppedObjectKey(null);
|
||||
task.setPayloadJson(JacksonUtil.toJson(payload));
|
||||
|
||||
Long taskId = taskIdSequence.incrementAndGet();
|
||||
Date now = new Date();
|
||||
task.setId(taskId);
|
||||
task.setCreateTime(now);
|
||||
task.setUpdateTime(now);
|
||||
|
||||
taskCache.put(taskId, task);
|
||||
return taskId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册任务等待,返回用于等待的 CompletableFuture
|
||||
* 调用方应在 createRenderTask 之后立即调用此方法
|
||||
|
||||
Reference in New Issue
Block a user