feat(puzzle): 实现免费拼图下载时自动添加水印功能

- 在AppPuzzleController中新增水印相关依赖注入
- 添加免费拼图判断逻辑,区分付费与免费拼图下载流程
- 实现addWatermarkForFreePuzzle方法处理水印添加
- 集成景区信息、人脸信息、二维码等水印模板数据
- 添加水印任务创建与等待机制,支持30秒超时处理
- 增加水印操作的日志记录与异常处理机制
- 优化免费拼图下载的安全性保护措施
This commit is contained in:
2026-01-19 18:58:08 +08:00
parent e647ad75c6
commit 63180159d2

View File

@@ -1,20 +1,32 @@
package com.ycwl.basic.controller.mobile; package com.ycwl.basic.controller.mobile;
import cn.hutool.core.date.DateUtil;
import com.ycwl.basic.biz.OrderBiz; 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.order.IsBuyRespVO;
import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO; import com.ycwl.basic.model.mobile.scenic.content.ContentPageVO;
import com.ycwl.basic.model.pc.face.entity.FaceEntity; 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.PriceCalculationRequest;
import com.ycwl.basic.pricing.dto.PriceCalculationResult; import com.ycwl.basic.pricing.dto.PriceCalculationResult;
import com.ycwl.basic.pricing.dto.ProductItem; import com.ycwl.basic.pricing.dto.ProductItem;
import com.ycwl.basic.pricing.enums.ProductType; import com.ycwl.basic.pricing.enums.ProductType;
import com.ycwl.basic.pricing.service.IPriceCalculationService; 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.entity.PuzzleGenerationRecordEntity;
import com.ycwl.basic.puzzle.mapper.MemberPuzzleMapper;
import com.ycwl.basic.puzzle.repository.PuzzleRepository; import com.ycwl.basic.puzzle.repository.PuzzleRepository;
import com.ycwl.basic.repository.FaceRepository; 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.service.printer.PrinterService;
import com.ycwl.basic.utils.ApiResponse; import com.ycwl.basic.utils.ApiResponse;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -26,6 +38,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j
@RestController @RestController
@RequestMapping("/api/mobile/puzzle/v1") @RequestMapping("/api/mobile/puzzle/v1")
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -36,6 +49,10 @@ public class AppPuzzleController {
private final IPriceCalculationService iPriceCalculationService; private final IPriceCalculationService iPriceCalculationService;
private final PrinterService printerService; private final PrinterService printerService;
private final OrderBiz orderBiz; private final OrderBiz orderBiz;
private final MemberPuzzleMapper memberPuzzleMapper;
private final WatermarkEdgeTaskCreator watermarkEdgeTaskCreator;
private final FaceService faceService;
private final ScenicRepository scenicRepository;
/** /**
* 根据faceId查询三拼图数量 * 根据faceId查询三拼图数量
@@ -82,6 +99,7 @@ public class AppPuzzleController {
/** /**
* 根据recordId下载拼图资源 * 根据recordId下载拼图资源
* 如果是免费赠送的拼图,会添加水印后返回
*/ */
@GetMapping("/download/{recordId}") @GetMapping("/download/{recordId}")
public ApiResponse<List<String>> download(@PathVariable("recordId") Long recordId) { public ApiResponse<List<String>> download(@PathVariable("recordId") Long recordId) {
@@ -96,9 +114,88 @@ public class AppPuzzleController {
if (resultImageUrl == null || resultImageUrl.isEmpty()) { if (resultImageUrl == null || resultImageUrl.isEmpty()) {
return ApiResponse.fail("该拼图记录没有可用的图片URL"); 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)); 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查询拼图价格 * 根据recordId查询拼图价格
*/ */