diff --git a/src/main/java/com/ycwl/basic/image/watermark/edge/PuzzleDefaultWatermarkTemplateBuilder.java b/src/main/java/com/ycwl/basic/image/watermark/edge/PuzzleDefaultWatermarkTemplateBuilder.java new file mode 100644 index 00000000..bb92db50 --- /dev/null +++ b/src/main/java/com/ycwl/basic/image/watermark/edge/PuzzleDefaultWatermarkTemplateBuilder.java @@ -0,0 +1,141 @@ +package com.ycwl.basic.image.watermark.edge; + +import com.ycwl.basic.puzzle.entity.PuzzleElementEntity; +import com.ycwl.basic.puzzle.entity.PuzzleTemplateEntity; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +/** + * 拼图默认水印模板构建器 + * + * 布局说明: + * - 白色背景 + * - 顶部90%为原图区域(COVER模式) + * - 底部10%为信息区域: + * - 左侧(距左5%):二维码(宽高为图片的8%)+ 头像(可选) + * - 右侧(距右5%):景区名 + 日期时间(右对齐) + */ +@Component +public class PuzzleDefaultWatermarkTemplateBuilder extends AbstractWatermarkTemplateBuilder { + + public static final String STYLE = "puzzle_default"; + + // 布局比例配置 + private static final double IMAGE_HEIGHT_RATIO = 0.90; // 原图占90%高度 + private static final double MARGIN_X_RATIO = 0.05; // 左右边距为宽度的5% + private static final double QRCODE_SIZE_RATIO = 0.08; // 二维码为图片的8% + + // 文字配置 + private static final int SCENIC_FONT_SIZE = 52; + private static final int DATETIME_FONT_SIZE = 42; + private static final String SCENIC_COLOR = "#333333"; + private static final String DATETIME_COLOR = "#999999"; + + @Override + public String getStyle() { + return STYLE; + } + + @Override + public WatermarkTemplateResult build(WatermarkRequest request) { + int imageWidth = request.getImageWidth(); + int imageHeight = request.getImageHeight(); + + // 画布尺寸 = 原图尺寸 + int canvasWidth = imageWidth; + int canvasHeight = imageHeight; + + // 原图区域占90%高度,底部信息区占10%高度 + int originalImageHeight = (int) (imageHeight * IMAGE_HEIGHT_RATIO); + int bottomAreaHeight = imageHeight - originalImageHeight; + + // 创建模板(白色背景) + PuzzleTemplateEntity template = createTemplateWithColor( + "watermark_puzzle_default_" + System.currentTimeMillis(), + canvasWidth, + canvasHeight, + "#FFFFFF" + ); + + List elements = newElementList(); + Map dynamicData = newDynamicData(); + + // 1. 原图元素(顶部90%区域,COVER模式) + PuzzleElementEntity originalImageElement = createImageElement( + "originalImage", "原图", + 0, 0, + canvasWidth, originalImageHeight, 1, + FIT_MODE_COVER, null, null + ); + elements.add(originalImageElement); + dynamicData.put("originalImage", request.getOriginalImageUrl()); + + // 2. 计算底部区域元素位置 + int marginX = (int) (canvasWidth * MARGIN_X_RATIO); + int qrcodeSize = (int) (canvasHeight * QRCODE_SIZE_RATIO); // 二维码为高度的8% + + // 二维码垂直居中于底部区域 + int qrcodeX = marginX; + int qrcodeY = originalImageHeight + (bottomAreaHeight - qrcodeSize) / 2; + + // 3. 二维码元素 + PuzzleElementEntity qrcodeElement = createImageElement( + "qrcode", "二维码", + qrcodeX, qrcodeY, + qrcodeSize, qrcodeSize, 10, + FIT_MODE_CONTAIN, null, null + ); + elements.add(qrcodeElement); + dynamicData.put("qrcode", request.getQrcodeUrl()); + + // 4. 头像元素(二维码中央,可选) + if (request.getFaceUrl() != null && !request.getFaceUrl().isEmpty()) { + int avatarDiameter = (int) (qrcodeSize * 0.45); + int avatarX = qrcodeX + (qrcodeSize - avatarDiameter) / 2; + int avatarY = qrcodeY + (qrcodeSize - avatarDiameter) / 2; + + PuzzleElementEntity faceElement = createCircleImageElement( + "face", "头像", + avatarX, avatarY, + avatarDiameter, 20 + ); + elements.add(faceElement); + dynamicData.put("face", request.getFaceUrl()); + } + + // 5. 计算右侧文字区域 + int textRightX = canvasWidth - marginX; + int textWidth = textRightX - qrcodeX - qrcodeSize - marginX; + + // 文字与二维码垂直居中 + int totalTextHeight = SCENIC_FONT_SIZE + DATETIME_FONT_SIZE + 5; + int textY = originalImageHeight + (bottomAreaHeight - totalTextHeight) / 2; + + // 6. 景区名文字(右对齐) + PuzzleElementEntity scenicTextElement = createTextElement( + "scenicLine", "景区名", + qrcodeX + qrcodeSize + marginX, textY, + textWidth, SCENIC_FONT_SIZE + 10, 30, + "PingFang SC", SCENIC_FONT_SIZE, SCENIC_COLOR, + "NORMAL", TEXT_ALIGN_RIGHT + ); + elements.add(scenicTextElement); + dynamicData.put("scenicLine", request.getScenicLine() != null ? request.getScenicLine() : ""); + + // 7. 日期时间文字(右对齐) + int datetimeY = textY + SCENIC_FONT_SIZE + 5; + PuzzleElementEntity datetimeTextElement = createTextElement( + "datetimeLine", "日期时间", + qrcodeX + qrcodeSize + marginX, datetimeY, + textWidth, DATETIME_FONT_SIZE + 10, 30, + "PingFang SC", DATETIME_FONT_SIZE, DATETIME_COLOR, + "NORMAL", TEXT_ALIGN_RIGHT + ); + elements.add(datetimeTextElement); + dynamicData.put("datetimeLine", request.getDatetimeLine() != null ? request.getDatetimeLine() : ""); + + return createResult(template, elements, dynamicData); + } +} diff --git a/src/main/java/com/ycwl/basic/image/watermark/edge/PuzzlePrintWatermarkTemplateBuilder.java b/src/main/java/com/ycwl/basic/image/watermark/edge/PuzzlePrintWatermarkTemplateBuilder.java new file mode 100644 index 00000000..ca7c06c8 --- /dev/null +++ b/src/main/java/com/ycwl/basic/image/watermark/edge/PuzzlePrintWatermarkTemplateBuilder.java @@ -0,0 +1,155 @@ +package com.ycwl.basic.image.watermark.edge; + +import com.ycwl.basic.puzzle.entity.PuzzleElementEntity; +import com.ycwl.basic.puzzle.entity.PuzzleTemplateEntity; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.Map; + +/** + * 拼图打印水印模板构建器 + * + * 布局说明: + * - 白色背景 + * - 四周留1%白边 + * - 内部区域:顶部90%为原图区域(COVER模式) + * - 底部10%为信息区域: + * - 左侧(距左5%):二维码(宽高为图片的8%)+ 头像(可选) + * - 右侧(距右5%):景区名 + 日期时间(右对齐) + */ +@Component +public class PuzzlePrintWatermarkTemplateBuilder extends AbstractWatermarkTemplateBuilder { + + public static final String STYLE = "puzzle_print"; + + // 布局比例配置 + private static final double BORDER_RATIO = 0.01; // 四周白边为1% + private static final double IMAGE_HEIGHT_RATIO = 0.90; // 原图占内容区90%高度 + private static final double MARGIN_X_RATIO = 0.05; // 左右边距为宽度的5% + private static final double QRCODE_SIZE_RATIO = 0.08; // 二维码为图片的8% + + // 文字配置 + private static final int SCENIC_FONT_SIZE = 52; + private static final int DATETIME_FONT_SIZE = 42; + private static final String SCENIC_COLOR = "#333333"; + private static final String DATETIME_COLOR = "#999999"; + + @Override + public String getStyle() { + return STYLE; + } + + @Override + public WatermarkTemplateResult build(WatermarkRequest request) { + int imageWidth = request.getImageWidth(); + int imageHeight = request.getImageHeight(); + + // 计算白边尺寸(基于原图尺寸的1%) + int borderX = (int) (imageWidth * BORDER_RATIO); + int borderY = (int) (imageHeight * BORDER_RATIO); + + // 画布尺寸 = 原图尺寸 + 四周白边 + int canvasWidth = imageWidth + borderX * 2; + int canvasHeight = imageHeight + borderY * 2; + + // 内容区起始位置(白边内) + int contentStartX = borderX; + int contentStartY = borderY; + + // 内容区尺寸 = 原图尺寸 + int contentWidth = imageWidth; + int contentHeight = imageHeight; + + // 原图区域占90%高度,底部信息区占10%高度 + int originalImageHeight = (int) (contentHeight * IMAGE_HEIGHT_RATIO); + int bottomAreaHeight = contentHeight - originalImageHeight; + + // 创建模板(白色背景) + PuzzleTemplateEntity template = createTemplateWithColor( + "watermark_puzzle_print_" + System.currentTimeMillis(), + canvasWidth, + canvasHeight, + "#FFFFFF" + ); + + List elements = newElementList(); + Map dynamicData = newDynamicData(); + + // 1. 原图元素(内容区顶部90%,COVER模式) + PuzzleElementEntity originalImageElement = createImageElement( + "originalImage", "原图", + contentStartX, contentStartY, + contentWidth, originalImageHeight, 1, + FIT_MODE_COVER, null, null + ); + elements.add(originalImageElement); + dynamicData.put("originalImage", request.getOriginalImageUrl()); + + // 2. 计算底部区域元素位置(相对于内容区) + int marginX = (int) (contentWidth * MARGIN_X_RATIO); + int qrcodeSize = (int) (contentHeight * QRCODE_SIZE_RATIO); // 二维码为高度的8% + + // 二维码垂直居中于底部区域 + int qrcodeX = contentStartX + marginX; + int qrcodeY = contentStartY + originalImageHeight + (bottomAreaHeight - qrcodeSize) / 2; + + // 3. 二维码元素 + PuzzleElementEntity qrcodeElement = createImageElement( + "qrcode", "二维码", + qrcodeX, qrcodeY, + qrcodeSize, qrcodeSize, 10, + FIT_MODE_CONTAIN, null, null + ); + elements.add(qrcodeElement); + dynamicData.put("qrcode", request.getQrcodeUrl()); + + // 4. 头像元素(二维码中央,可选) + if (request.getFaceUrl() != null && !request.getFaceUrl().isEmpty()) { + int avatarDiameter = (int) (qrcodeSize * 0.45); + int avatarX = qrcodeX + (qrcodeSize - avatarDiameter) / 2; + int avatarY = qrcodeY + (qrcodeSize - avatarDiameter) / 2; + + PuzzleElementEntity faceElement = createCircleImageElement( + "face", "头像", + avatarX, avatarY, + avatarDiameter, 20 + ); + elements.add(faceElement); + dynamicData.put("face", request.getFaceUrl()); + } + + // 5. 计算右侧文字区域 + int textRightX = contentStartX + contentWidth - marginX; + int textWidth = textRightX - qrcodeX - qrcodeSize - marginX; + + // 文字与二维码垂直居中 + int totalTextHeight = SCENIC_FONT_SIZE + DATETIME_FONT_SIZE + 5; + int textY = contentStartY + originalImageHeight + (bottomAreaHeight - totalTextHeight) / 2; + + // 6. 景区名文字(右对齐) + PuzzleElementEntity scenicTextElement = createTextElement( + "scenicLine", "景区名", + qrcodeX + qrcodeSize + marginX, textY, + textWidth, SCENIC_FONT_SIZE + 10, 30, + "PingFang SC", SCENIC_FONT_SIZE, SCENIC_COLOR, + "NORMAL", TEXT_ALIGN_RIGHT + ); + elements.add(scenicTextElement); + dynamicData.put("scenicLine", request.getScenicLine() != null ? request.getScenicLine() : ""); + + // 7. 日期时间文字(右对齐) + int datetimeY = textY + SCENIC_FONT_SIZE + 5; + PuzzleElementEntity datetimeTextElement = createTextElement( + "datetimeLine", "日期时间", + qrcodeX + qrcodeSize + marginX, datetimeY, + textWidth, DATETIME_FONT_SIZE + 10, 30, + "PingFang SC", DATETIME_FONT_SIZE, DATETIME_COLOR, + "NORMAL", TEXT_ALIGN_RIGHT + ); + elements.add(datetimeTextElement); + dynamicData.put("datetimeLine", request.getDatetimeLine() != null ? request.getDatetimeLine() : ""); + + return createResult(template, elements, dynamicData); + } +} diff --git a/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java b/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java index 903e3c06..03e1478f 100644 --- a/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/pc/impl/FaceServiceImpl.java @@ -727,7 +727,7 @@ public class FaceServiceImpl implements FaceService { String dt = DateUtil.format(face.getCreateAt(), "yyyyMMdd"); String path = "pages/videoSynthesis/bind_face"; - String filePath = "wxa_face/"+dt+"/f" + faceId + ".jpg"; + String filePath = "f" + faceId + ".jpg"; IStorageAdapter adapter = StorageFactory.use(); if (adapter.isExists(filePath)) { String url = adapter.getUrl(filePath); @@ -743,13 +743,13 @@ public class FaceServiceImpl implements FaceService { try { File file = new File(filePath); WxMpUtil.generateUnlimitedWXAQRCode(appId, appSecret, path, faceId.toString(), file); - String url = adapter.uploadFile(null, file, filePath); + String url = adapter.uploadFile(null, file, "wxa_face", dt, filePath); file.delete(); adapter.setAcl(StorageAcl.PUBLIC_READ, filePath); url = url.replace("-internal.aliyuncs.com", ".aliyuncs.com"); return url; } catch (Exception e) { - throw new BaseException("生成二维码失败"); + throw new BaseException("生成二维码失败:"+e.getMessage()); } }