From 2fb6aa42cf1d3ee09819b81a93e4536dcb4a0788 Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Thu, 15 Jan 2026 13:36:13 +0800 Subject: [PATCH] =?UTF-8?q?feat(image):=20=E6=B7=BB=E5=8A=A0=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E5=8F=A0=E5=8A=A0=E5=8A=9F=E8=83=BD=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 OverlayImageConfig 类用于配置叠加图片参数 - 支持通过 imageKey 从 dynamicData 动态获取图片 URL - 提供默认图片 URL 配置选项 - 支持设置叠加图片宽高比例(0.0-1.0 范围) - 实现图片适配模式配置(CONTAIN、COVER、FILL、SCALE_DOWN) - 添加圆角半径配置,支持自动圆形效果 - 支持水平垂直对齐方式设置(CENTER、LEFT、RIGHT、TOP、BOTTOM) - 提供水平垂直偏移量调节功能 - 更新配置验证逻辑,增加叠加图片配置校验 - 修改图片 URL 校验规则,支持动态数据填充 - 更新 JSON Schema 配置模板,包含叠加图片配置项 --- .../puzzle/element/config/ImageConfig.java | 141 +++++++++++++++++- 1 file changed, 135 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/ycwl/basic/puzzle/element/config/ImageConfig.java b/src/main/java/com/ycwl/basic/puzzle/element/config/ImageConfig.java index 066c80dc..d74a74aa 100644 --- a/src/main/java/com/ycwl/basic/puzzle/element/config/ImageConfig.java +++ b/src/main/java/com/ycwl/basic/puzzle/element/config/ImageConfig.java @@ -29,9 +29,83 @@ public class ImageConfig implements ElementConfig { /** * 圆角半径(像素,0为直角) + * 注意:当 borderRadius >= min(width, height) / 2 时,效果为圆形 */ private Integer borderRadius = 0; + /** + * 叠加图片配置 + * 用于在主图上叠加另一张图片(如二维码中心的头像) + */ + private OverlayImageConfig overlayImage; + + /** + * 叠加图片配置类 + */ + @Data + public static class OverlayImageConfig { + /** + * 叠加图片的数据源 key(从 dynamicData 获取 URL) + * 例如:faceAvatar + */ + private String imageKey; + + /** + * 叠加图片的默认 URL(当 dynamicData 中无对应值时使用) + */ + private String defaultImageUrl; + + /** + * 叠加图片宽度占主图宽度的比例(0.0 - 1.0) + * 默认 0.45(与现有水印实现一致) + */ + private Double widthRatio = 0.45; + + /** + * 叠加图片高度占主图高度的比例(0.0 - 1.0) + * 默认 0.45 + */ + private Double heightRatio = 0.45; + + /** + * 叠加图片的适配模式 + * 默认 COVER(与现有水印实现一致) + */ + private String imageFitMode = "COVER"; + + /** + * 叠加图片的圆角半径 + * 默认 -1 表示自动计算为圆形(min(width, height) / 2) + */ + private Integer borderRadius = -1; + + /** + * 叠加图片的水平对齐方式 + * CENTER - 居中(默认) + * LEFT - 左对齐 + * RIGHT - 右对齐 + */ + private String horizontalAlign = "CENTER"; + + /** + * 叠加图片的垂直对齐方式 + * CENTER - 居中(默认) + * TOP - 顶部对齐 + * BOTTOM - 底部对齐 + */ + private String verticalAlign = "CENTER"; + + /** + * 水平偏移量(像素),正值向右,负值向左 + */ + private Integer offsetX = 0; + + /** + * 垂直偏移量(像素),正值向下,负值向上 + */ + private Integer offsetY = 0; + } + @Override public void validate() { // 校验圆角半径 @@ -50,12 +124,55 @@ public class ImageConfig implements ElementConfig { } } - // 校验图片URL - if (StrUtil.isBlank(defaultImageUrl)) { - throw new IllegalArgumentException("默认图片URL不能为空"); + // 校验图片URL(注意:现在可以通过 dynamicData 动态填充,所以允许为空) + if (StrUtil.isNotBlank(defaultImageUrl)) { + if (!defaultImageUrl.startsWith("http://") && !defaultImageUrl.startsWith("https://")) { + throw new IllegalArgumentException("图片URL必须以http://或https://开头: " + defaultImageUrl); + } } - if (!defaultImageUrl.startsWith("http://") && !defaultImageUrl.startsWith("https://")) { - throw new IllegalArgumentException("图片URL必须以http://或https://开头: " + defaultImageUrl); + + // 校验叠加图片配置 + if (overlayImage != null) { + validateOverlayImage(overlayImage); + } + } + + private void validateOverlayImage(OverlayImageConfig overlay) { + // 校验比例范围 + if (overlay.getWidthRatio() != null && (overlay.getWidthRatio() <= 0 || overlay.getWidthRatio() > 1)) { + throw new IllegalArgumentException("叠加图片宽度比例必须在 0-1 之间: " + overlay.getWidthRatio()); + } + if (overlay.getHeightRatio() != null && (overlay.getHeightRatio() <= 0 || overlay.getHeightRatio() > 1)) { + throw new IllegalArgumentException("叠加图片高度比例必须在 0-1 之间: " + overlay.getHeightRatio()); + } + + // 校验对齐方式 + if (StrUtil.isNotBlank(overlay.getHorizontalAlign())) { + String align = overlay.getHorizontalAlign().toUpperCase(); + if (!"CENTER".equals(align) && !"LEFT".equals(align) && !"RIGHT".equals(align)) { + throw new IllegalArgumentException("水平对齐方式只能是CENTER、LEFT或RIGHT: " + overlay.getHorizontalAlign()); + } + } + if (StrUtil.isNotBlank(overlay.getVerticalAlign())) { + String align = overlay.getVerticalAlign().toUpperCase(); + if (!"CENTER".equals(align) && !"TOP".equals(align) && !"BOTTOM".equals(align)) { + throw new IllegalArgumentException("垂直对齐方式只能是CENTER、TOP或BOTTOM: " + overlay.getVerticalAlign()); + } + } + + // 校验叠加图片URL + if (StrUtil.isNotBlank(overlay.getDefaultImageUrl())) { + if (!overlay.getDefaultImageUrl().startsWith("http://") && !overlay.getDefaultImageUrl().startsWith("https://")) { + throw new IllegalArgumentException("叠加图片URL必须以http://或https://开头: " + overlay.getDefaultImageUrl()); + } + } + + // 校验适配模式 + if (StrUtil.isNotBlank(overlay.getImageFitMode())) { + String mode = overlay.getImageFitMode().toUpperCase(); + if (!"CONTAIN".equals(mode) && !"COVER".equals(mode) && !"FILL".equals(mode) && !"SCALE_DOWN".equals(mode)) { + throw new IllegalArgumentException("叠加图片适配模式只能是CONTAIN、COVER、FILL或SCALE_DOWN: " + overlay.getImageFitMode()); + } } } @@ -64,7 +181,19 @@ public class ImageConfig implements ElementConfig { return "{\n" + " \"defaultImageUrl\": \"https://example.com/image.jpg\",\n" + " \"imageFitMode\": \"CONTAIN|COVER|FILL|SCALE_DOWN\",\n" + - " \"borderRadius\": 0\n" + + " \"borderRadius\": 0,\n" + + " \"overlayImage\": {\n" + + " \"imageKey\": \"faceAvatar\",\n" + + " \"defaultImageUrl\": \"https://example.com/default-avatar.png\",\n" + + " \"widthRatio\": 0.45,\n" + + " \"heightRatio\": 0.45,\n" + + " \"imageFitMode\": \"COVER\",\n" + + " \"borderRadius\": -1,\n" + + " \"horizontalAlign\": \"CENTER\",\n" + + " \"verticalAlign\": \"CENTER\",\n" + + " \"offsetX\": 0,\n" + + " \"offsetY\": 0\n" + + " }\n" + "}"; } }