From 63180159d20cebe4e6f7f011e9e062b6b8af9108 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Mon, 19 Jan 2026 18:58:08 +0800 Subject: [PATCH] =?UTF-8?q?feat(puzzle):=20=E5=AE=9E=E7=8E=B0=E5=85=8D?= =?UTF-8?q?=E8=B4=B9=E6=8B=BC=E5=9B=BE=E4=B8=8B=E8=BD=BD=E6=97=B6=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E6=B7=BB=E5=8A=A0=E6=B0=B4=E5=8D=B0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在AppPuzzleController中新增水印相关依赖注入 - 添加免费拼图判断逻辑,区分付费与免费拼图下载流程 - 实现addWatermarkForFreePuzzle方法处理水印添加 - 集成景区信息、人脸信息、二维码等水印模板数据 - 添加水印任务创建与等待机制,支持30秒超时处理 - 增加水印操作的日志记录与异常处理机制 - 优化免费拼图下载的安全性保护措施 --- .../mobile/AppPuzzleController.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppPuzzleController.java b/src/main/java/com/ycwl/basic/controller/mobile/AppPuzzleController.java index bd52f819..b83480a0 100644 --- a/src/main/java/com/ycwl/basic/controller/mobile/AppPuzzleController.java +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppPuzzleController.java @@ -1,20 +1,32 @@ package com.ycwl.basic.controller.mobile; +import cn.hutool.core.date.DateUtil; import com.ycwl.basic.biz.OrderBiz; +import com.ycwl.basic.constant.FreeStatus; +import com.ycwl.basic.image.watermark.edge.PuzzleDefaultWatermarkTemplateBuilder; +import com.ycwl.basic.image.watermark.edge.WatermarkEdgeTaskCreator; +import com.ycwl.basic.image.watermark.edge.WatermarkRequest; import com.ycwl.basic.model.mobile.order.IsBuyRespVO; import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO; import com.ycwl.basic.model.pc.face.entity.FaceEntity; +import com.ycwl.basic.model.pc.puzzle.entity.MemberPuzzleEntity; +import com.ycwl.basic.model.pc.scenic.entity.ScenicEntity; import com.ycwl.basic.pricing.dto.PriceCalculationRequest; import com.ycwl.basic.pricing.dto.PriceCalculationResult; import com.ycwl.basic.pricing.dto.ProductItem; import com.ycwl.basic.pricing.enums.ProductType; import com.ycwl.basic.pricing.service.IPriceCalculationService; +import com.ycwl.basic.puzzle.edge.task.PuzzleEdgeRenderTaskService; import com.ycwl.basic.puzzle.entity.PuzzleGenerationRecordEntity; +import com.ycwl.basic.puzzle.mapper.MemberPuzzleMapper; import com.ycwl.basic.puzzle.repository.PuzzleRepository; import com.ycwl.basic.repository.FaceRepository; +import com.ycwl.basic.repository.ScenicRepository; +import com.ycwl.basic.service.pc.FaceService; import com.ycwl.basic.service.printer.PrinterService; import com.ycwl.basic.utils.ApiResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -26,6 +38,7 @@ import java.util.Collections; import java.util.List; import java.util.stream.Collectors; +@Slf4j @RestController @RequestMapping("/api/mobile/puzzle/v1") @RequiredArgsConstructor @@ -36,6 +49,10 @@ public class AppPuzzleController { private final IPriceCalculationService iPriceCalculationService; private final PrinterService printerService; private final OrderBiz orderBiz; + private final MemberPuzzleMapper memberPuzzleMapper; + private final WatermarkEdgeTaskCreator watermarkEdgeTaskCreator; + private final FaceService faceService; + private final ScenicRepository scenicRepository; /** * 根据faceId查询三拼图数量 @@ -82,6 +99,7 @@ public class AppPuzzleController { /** * 根据recordId下载拼图资源 + * 如果是免费赠送的拼图,会添加水印后返回 */ @GetMapping("/download/{recordId}") public ApiResponse> download(@PathVariable("recordId") Long recordId) { @@ -96,9 +114,88 @@ public class AppPuzzleController { if (resultImageUrl == null || resultImageUrl.isEmpty()) { return ApiResponse.fail("该拼图记录没有可用的图片URL"); } + + // 查询该拼图的关联记录,判断是否为免费赠送 + Long faceId = record.getFaceId(); + if (faceId != null) { + MemberPuzzleEntity memberPuzzle = memberPuzzleMapper.getByFaceAndRecord(faceId, recordId); + if (memberPuzzle != null && FreeStatus.isFree(memberPuzzle.getIsFree())) { + // 免费赠送的拼图,需要添加水印 + String watermarkedUrl = addWatermarkForFreePuzzle(record); + if (watermarkedUrl != null) { + return ApiResponse.success(Collections.singletonList(watermarkedUrl)); + } + // 如果水印添加失败,记录日志并返回原图 + log.warn("免费拼图水印添加失败,返回原图: recordId={}", recordId); + } + } + return ApiResponse.success(Collections.singletonList(resultImageUrl)); } + /** + * 为免费赠送的拼图添加水印 + * + * @param record 拼图记录 + * @return 带水印的图片URL,失败返回null + */ + private String addWatermarkForFreePuzzle(PuzzleGenerationRecordEntity record) { + try { + Long faceId = record.getFaceId(); + FaceEntity face = faceRepository.getFace(faceId); + if (face == null) { + log.warn("添加水印失败:未找到人脸信息, faceId={}", faceId); + return null; + } + + // 获取景区信息 + ScenicEntity scenic = scenicRepository.getScenic(face.getScenicId()); + String scenicLine = scenic != null ? scenic.getName() : ""; + + // 获取二维码URL + String qrcodeUrl = faceService.bindWxaCode(faceId); + + // 格式化日期时间 + String datetimeLine = record.getCreateTime() != null + ? DateUtil.format(record.getCreateTime(), "yyyy-MM-dd HH:mm") + : ""; + + // 构建水印请求 + WatermarkRequest request = WatermarkRequest.builder() + .originalImageUrl(record.getResultImageUrl()) + .imageWidth(record.getResultWidth() != null ? record.getResultWidth() : 0) + .imageHeight(record.getResultHeight() != null ? record.getResultHeight() : 0) + .qrcodeUrl(qrcodeUrl) + .faceUrl(face.getFaceUrl()) + .scenicLine(scenicLine) + .datetimeLine(datetimeLine) + .outputFormat("JPEG") + .outputQuality(90) + .build(); + + // 创建水印任务并等待结果 + PuzzleEdgeRenderTaskService.TaskWaitResult result = watermarkEdgeTaskCreator.createAndWait( + PuzzleDefaultWatermarkTemplateBuilder.STYLE, + request, + record.getId(), + faceId, + "free_puzzle_download", + 30_000L // 30秒超时 + ); + + if (result.isSuccess()) { + log.info("免费拼图水印添加成功: recordId={}, url={}", record.getId(), result.getImageUrl()); + return result.getImageUrl(); + } else { + log.error("免费拼图水印添加失败: recordId={}, error={}", record.getId(), result.getErrorMessage()); + return null; + } + } catch (Exception e) { + log.error("免费拼图水印添加异常: recordId={}", record.getId(), e); + return null; + } + } + /** * 根据recordId查询拼图价格 */