Skip to content

媒体处理模块 (ruoyi-common-media)

1. 模块概述

ruoyi-common-media 是一个功能强大的媒体处理模块,提供了图片处理、GIF动画生成、二维码生成和海报制作等核心功能。该模块基于 Builder 设计模式,提供流畅的链式调用 API,支持多种输入源和输出格式,适用于各种媒体处理场景。

核心特性:

  • 图片处理 (ImageBuilder) - 支持图片缩放、裁剪、旋转、翻转、水印添加、滤镜效果等丰富的图片处理功能
  • GIF动画生成 (GifBuilder) - 支持从多张图片创建 GIF 动画,可配置帧延迟、循环次数、背景色等参数
  • 二维码生成 (QrCodeBuilder) - 基于 ZXing 库实现二维码生成,支持自定义尺寸、颜色、边距,可嵌入 Logo
  • 海报制作 (PosterBuilder) - 支持动态海报生成,可添加文本、图片、二维码、几何图形等多种元素
  • Builder 模式 - 所有功能都采用 Builder 模式,支持方法链式调用,代码简洁优雅
  • 多种输入源 - 支持 File、URL、InputStream、byte[] 等多种输入源
  • 多种输出格式 - 支持 BufferedImage、byte[]、InputStream、文件保存、HTTP 响应等多种输出方式
  • 高性能处理 - 基于 Thumbnailator 库实现高质量图片处理,性能优异

1.1 技术栈

技术版本说明
ZXing3.5.3Google 开源的二维码生成库
Thumbnailator0.4.20高性能图片处理库
Java AWT-原生图形绘制支持

1.2 Maven 依赖

xml
<dependency>
    <groupId>plus.ruoyi</groupId>
    <artifactId>ruoyi-common-media</artifactId>
</dependency>

1.3 模块结构

ruoyi-common-media/
├── builder/                    # Builder 构建器
│   ├── ImageBuilder.java      # 图片处理构建器
│   ├── GifBuilder.java        # GIF 动画构建器
│   ├── QrCodeBuilder.java     # 二维码构建器
│   └── PosterBuilder.java     # 海报构建器
├── enums/                     # 枚举类型
│   ├── OutputFormat.java      # 输出格式枚举
│   ├── PosterItemType.java    # 海报元素类型
│   └── ResizeMode.java        # 缩放模式
├── model/                     # 数据模型
│   └── PosterItem.java        # 海报元素模型
├── options/                   # 配置选项
│   ├── QrCodeOptions.java     # 二维码配置
│   ├── WatermarkOptions.java  # 水印配置
│   ├── FilterOptions.java     # 滤镜配置
│   └── AnimationOptions.java  # 动画配置
├── utils/                     # 工具类
│   ├── ImageUtils.java        # 图片处理工具
│   ├── GifUtils.java          # GIF 工具
│   ├── QrCodeUtils.java       # 二维码工具
│   ├── PosterUtils.java       # 海报工具
│   ├── ColorUtils.java        # 颜色工具
│   └── FontUtils.java         # 字体工具
└── exception/                 # 异常类
    └── MediaException.java    # 媒体处理异常

2. 图片处理 (ImageBuilder)

ImageBuilder 提供了强大的图片处理能力,支持缩放、裁剪、旋转、水印、滤镜等多种操作。

2.1 初始化方式

ImageBuilder 支持多种初始化方式:

java
import plus.ruoyi.common.media.builder.ImageBuilder;

// 从文件路径初始化
ImageBuilder builder1 = ImageBuilder.of("/path/to/image.jpg");

// 从 URL 初始化
ImageBuilder builder2 = ImageBuilder.of("https://example.com/image.jpg");

// 从字节数组初始化
byte[] imageBytes = ...;
ImageBuilder builder3 = ImageBuilder.of(imageBytes);

// 从输入流初始化
InputStream inputStream = ...;
ImageBuilder builder4 = ImageBuilder.of(inputStream);

// 从 BufferedImage 初始化
BufferedImage bufferedImage = ...;
ImageBuilder builder5 = ImageBuilder.of(bufferedImage);

2.2 图片缩放

支持多种缩放模式:

java
import plus.ruoyi.common.media.enums.ResizeMode;

// 固定尺寸缩放
BufferedImage resized = ImageBuilder.of("/path/to/image.jpg")
    .resize(800, 600, ResizeMode.EXACT)
    .build();

// 保持宽高比缩放(按宽度)
BufferedImage scaled = ImageBuilder.of("/path/to/image.jpg")
    .resize(800, 600, ResizeMode.FIT_TO_WIDTH)
    .build();

// 按比例缩放
BufferedImage percentage = ImageBuilder.of("/path/to/image.jpg")
    .scale(0.5) // 缩放到原尺寸的50%
    .build();

// 宽度固定,高度自适应
BufferedImage autoHeight = ImageBuilder.of("/path/to/image.jpg")
    .resize(800, 0, ResizeMode.AUTO)
    .build();

ResizeMode 说明:

模式说明适用场景
EXACT精确缩放到指定尺寸需要固定尺寸的场景
FIT_TO_WIDTH宽度固定,高度等比缩放列表展示、宽度受限场景
FIT_TO_HEIGHT高度固定,宽度等比缩放高度受限场景
AUTO自动选择最佳缩放方式保持宽高比的通用场景

2.3 图片裁剪

支持按坐标裁剪和居中裁剪:

java
// 按坐标裁剪
BufferedImage cropped = ImageBuilder.of("/path/to/image.jpg")
    .crop(100, 100, 400, 300) // x, y, width, height
    .build();

// 居中裁剪为正方形
BufferedImage square = ImageBuilder.of("/path/to/image.jpg")
    .cropCenter(500, 500)
    .build();

// 先缩放再裁剪(生成缩略图)
BufferedImage thumbnail = ImageBuilder.of("/path/to/image.jpg")
    .resize(800, 600, ResizeMode.AUTO)
    .cropCenter(400, 400)
    .build();

2.4 图片旋转与翻转

java
// 旋转90度(顺时针)
BufferedImage rotated = ImageBuilder.of("/path/to/image.jpg")
    .rotate(90)
    .build();

// 旋转任意角度
BufferedImage rotated45 = ImageBuilder.of("/path/to/image.jpg")
    .rotate(45.5)
    .build();

// 水平翻转(镜像)
BufferedImage flippedH = ImageBuilder.of("/path/to/image.jpg")
    .flipHorizontal()
    .build();

// 垂直翻转
BufferedImage flippedV = ImageBuilder.of("/path/to/image.jpg")
    .flipVertical()
    .build();

// 组合操作
BufferedImage combined = ImageBuilder.of("/path/to/image.jpg")
    .rotate(90)
    .flipHorizontal()
    .build();

2.5 文字水印

java
import java.awt.*;

// 简单文字水印
BufferedImage withWatermark = ImageBuilder.of("/path/to/image.jpg")
    .addTextWatermark("版权所有 © 2024",
        new Font("微软雅黑", Font.BOLD, 36),
        Color.WHITE,
        100, 100, // x, y 坐标
        0.7f) // 透明度 (0.0-1.0)
    .build();

// 右下角水印(负数表示从右下角计算)
BufferedImage bottomRight = ImageBuilder.of("/path/to/image.jpg")
    .addTextWatermark("保密文件",
        new Font("宋体", Font.PLAIN, 24),
        new Color(255, 0, 0, 128), // 半透明红色
        -150, -50,
        1.0f)
    .build();

// 多个水印
BufferedImage multiWatermark = ImageBuilder.of("/path/to/image.jpg")
    .addTextWatermark("顶部水印", new Font("微软雅黑", Font.BOLD, 32),
        Color.WHITE, 100, 50, 0.6f)
    .addTextWatermark("底部水印", new Font("微软雅黑", Font.BOLD, 28),
        Color.LIGHT_GRAY, 100, -50, 0.5f)
    .build();

2.6 图片水印

java
// 简单图片水印
BufferedImage withLogoWatermark = ImageBuilder.of("/path/to/image.jpg")
    .addImageWatermark("/path/to/logo.png",
        50, 50, // x, y 坐标
        150, 150, // width, height
        0.8f) // 透明度
    .build();

// 右下角 Logo 水印
BufferedImage cornerLogo = ImageBuilder.of("/path/to/image.jpg")
    .addImageWatermark("/path/to/logo.png",
        -180, -180, // 右下角偏移
        150, 150,
        0.9f)
    .build();

// 平铺水印
BufferedImage tiled = ImageBuilder.of("/path/to/image.jpg")
    .tileWatermark("/path/to/watermark.png",
        200, 200, // 每个水印间距
        0.3f) // 透明度
    .build();

// 组合水印(文字 + 图片)
BufferedImage combined = ImageBuilder.of("/path/to/image.jpg")
    .addImageWatermark("/path/to/logo.png", 50, 50, 100, 100, 0.8f)
    .addTextWatermark("版权所有", new Font("微软雅黑", Font.BOLD, 24),
        Color.WHITE, 160, 100, 0.8f)
    .build();

2.7 滤镜效果

java
// 灰度滤镜(黑白效果)
BufferedImage grayscale = ImageBuilder.of("/path/to/image.jpg")
    .grayscale()
    .build();

// 调整亮度
BufferedImage brighter = ImageBuilder.of("/path/to/image.jpg")
    .brightness(1.5f) // 增加50%亮度
    .build();

// 调整对比度
BufferedImage highContrast = ImageBuilder.of("/path/to/image.jpg")
    .contrast(1.8f)
    .build();

// 模糊效果
BufferedImage blurred = ImageBuilder.of("/path/to/image.jpg")
    .blur(5) // 模糊半径
    .build();

// 锐化效果
BufferedImage sharpened = ImageBuilder.of("/path/to/image.jpg")
    .sharpen()
    .build();

// 复古效果(组合滤镜)
BufferedImage vintage = ImageBuilder.of("/path/to/image.jpg")
    .grayscale()
    .brightness(0.9f)
    .contrast(1.2f)
    .build();

滤镜参数说明:

  • 亮度: 1.0 为原始亮度,小于 1.0 变暗,大于 1.0 变亮
  • 对比度: 1.0 为原始对比度,小于 1.0 降低,大于 1.0 增强
  • 模糊半径越大,模糊效果越明显

2.8 输出方式

java
import plus.ruoyi.common.media.enums.OutputFormat;

// 输出为 BufferedImage
BufferedImage image = ImageBuilder.of("/path/to/image.jpg")
    .resize(800, 600, ResizeMode.AUTO)
    .build();

// 输出为字节数组
byte[] imageBytes = ImageBuilder.of("/path/to/image.jpg")
    .resize(800, 600, ResizeMode.AUTO)
    .toBytes();

// 输出为 InputStream
InputStream inputStream = ImageBuilder.of("/path/to/image.jpg")
    .resize(800, 600, ResizeMode.AUTO)
    .toInputStream();

// 保存为文件
ImageBuilder.of("/path/to/input.jpg")
    .resize(800, 600, ResizeMode.AUTO)
    .save("/path/to/output.jpg");

// 写入输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ImageBuilder.of("/path/to/image.jpg")
    .resize(800, 600, ResizeMode.AUTO)
    .writeTo(outputStream);

// 直接输出到 HTTP 响应
public void downloadImage(HttpServletResponse response) {
    ImageBuilder.of("/path/to/image.jpg")
        .resize(800, 600, ResizeMode.AUTO)
        .toResponse(response);
}

// 指定输出格式
ImageBuilder.of("/path/to/image.jpg")
    .resize(800, 600, ResizeMode.AUTO)
    .format(OutputFormat.PNG)
    .save("/path/to/output.png");

2.9 完整示例

java
/**
 * 图片处理服务示例
 */
public class ImageProcessingService {

    /**
     * 生成商品缩略图
     */
    public byte[] generateProductThumbnail(String imageUrl) {
        return ImageBuilder.of(imageUrl)
            .resize(400, 400, ResizeMode.EXACT)
            .addTextWatermark("商品图",
                new Font("微软雅黑", Font.BOLD, 20),
                new Color(255, 255, 255, 180),
                -80, -30, 0.8f)
            .format(OutputFormat.JPG)
            .toBytes();
    }

    /**
     * 生成用户头像
     */
    public void generateAvatar(String inputPath, String outputPath) {
        ImageBuilder.of(inputPath)
            .cropCenter(500, 500)
            .resize(200, 200, ResizeMode.EXACT)
            .format(OutputFormat.PNG)
            .save(outputPath);
    }

    /**
     * 批量处理图片
     */
    public void batchProcess(List<String> imageUrls, String outputDir) {
        for (int i = 0; i < imageUrls.size(); i++) {
            ImageBuilder.of(imageUrls.get(i))
                .resize(800, 600, ResizeMode.AUTO)
                .addTextWatermark("© 版权所有",
                    new Font("Arial", Font.BOLD, 24),
                    Color.WHITE, -120, -40, 0.7f)
                .save(outputDir + "/image_" + i + ".jpg");
        }
    }
}

3. GIF 动画生成 (GifBuilder)

GifBuilder 提供了创建 GIF 动画的能力,支持从多张图片生成动画,可配置帧延迟、循环次数等参数。

3.1 基本用法

java
import plus.ruoyi.common.media.builder.GifBuilder;

// 从图片文件创建 GIF
GifBuilder.of(600, 400) // 指定 GIF 尺寸
    .addFrame("/path/to/frame1.jpg")
    .addFrame("/path/to/frame2.jpg")
    .addFrame("/path/to/frame3.jpg")
    .delay(500) // 每帧延迟 500ms
    .loop(0) // 无限循环
    .save("/path/to/output.gif");

// 从 URL 加载图片创建 GIF
GifBuilder.of(800, 600)
    .addFrame("https://example.com/image1.jpg")
    .addFrame("https://example.com/image2.jpg")
    .delay(1000)
    .save("/path/to/output.gif");

// 从逗号分隔的 URL 批量添加帧
String imageUrls = "https://example.com/1.jpg,https://example.com/2.jpg";
GifBuilder.of(400, 300)
    .addFrame(imageUrls) // 自动分割并添加
    .delay(800)
    .save("/path/to/output.gif");

3.2 批量添加帧

java
// 批量添加文件路径
List<String> framePaths = Arrays.asList(
    "/path/to/frame1.jpg",
    "/path/to/frame2.jpg",
    "/path/to/frame3.jpg"
);
GifBuilder.of(600, 400)
    .addFrames(framePaths)
    .delay(500)
    .save("/path/to/output.gif");

// 批量添加 BufferedImage
List<BufferedImage> frames = new ArrayList<>();
frames.add(ImageBuilder.of("/path/to/1.jpg").build());
frames.add(ImageBuilder.of("/path/to/2.jpg").build());
GifBuilder.of(400, 300)
    .addFrames(frames)
    .delay(800)
    .save("/path/to/output.gif");

// 从目录加载所有图片
File directory = new File("/path/to/frames");
File[] imageFiles = directory.listFiles((dir, name) ->
    name.toLowerCase().endsWith(".jpg") || name.toLowerCase().endsWith(".png"));

GifBuilder builder = GifBuilder.of(800, 600);
for (File file : imageFiles) {
    builder.addFrame(file.getAbsolutePath());
}
builder.delay(1000).save("/path/to/output.gif");

3.3 高级配置

java
// 设置背景颜色
GifBuilder.of(600, 400)
    .addFrame("/path/to/frame1.png")
    .addFrame("/path/to/frame2.png")
    .backgroundColor(Color.WHITE) // 透明区域填充白色
    .delay(500)
    .save("/path/to/output.gif");

// 设置图像质量
GifBuilder.of(800, 600)
    .addFrame("/path/to/frame1.jpg")
    .addFrame("/path/to/frame2.jpg")
    .quality(0.9f) // 质量 0.0-1.0
    .delay(1000)
    .save("/path/to/output.gif");

// 设置循环次数
GifBuilder.of(400, 300)
    .addFrame("/path/to/frame1.jpg")
    .addFrame("/path/to/frame2.jpg")
    .delay(800)
    .loop(5) // 循环播放 5 次后停止
    .save("/path/to/output.gif");

配置说明:

配置项说明默认值
width / heightGIF 尺寸(像素)必须指定
delay帧延迟(毫秒)100ms
loop循环次数0(无限循环)
backgroundColor背景颜色透明
quality图像质量(0.0-1.0)0.8

3.4 帧管理与输出

java
// 清除所有帧
GifBuilder builder = GifBuilder.of(600, 400)
    .addFrame("/path/to/frame1.jpg")
    .addFrame("/path/to/frame2.jpg");
builder.clearFrames(); // 清除所有帧

// 重复添加同一帧(创建停顿效果)
GifBuilder.of(600, 400)
    .addFrame("/path/to/frame1.jpg")
    .addFrame("/path/to/frame1.jpg") // 重复添加创建停顿
    .addFrame("/path/to/frame2.jpg")
    .delay(200)
    .save("/path/to/output.gif");

// 输出为字节数组
byte[] gifBytes = GifBuilder.of(600, 400)
    .addFrame("/path/to/frame1.jpg")
    .addFrame("/path/to/frame2.jpg")
    .delay(500)
    .toBytes();

// 输出为 InputStream
InputStream gifStream = GifBuilder.of(600, 400)
    .addFrame("/path/to/frame1.jpg")
    .addFrame("/path/to/frame2.jpg")
    .delay(500)
    .toInputStream();

3.5 完整示例

java
/**
 * GIF 动画服务示例
 */
public class GifAnimationService {

    /**
     * 生成产品展示 GIF
     */
    public byte[] generateProductGif(List<String> productImages) {
        return GifBuilder.of(600, 600)
            .addFrames(productImages)
            .delay(1500) // 每帧停留 1.5 秒
            .loop(0)
            .backgroundColor(Color.WHITE)
            .quality(0.9f)
            .toBytes();
    }

    /**
     * 生成加载动画
     */
    public void generateLoadingGif(String outputPath) {
        List<BufferedImage> frames = new ArrayList<>();

        for (int i = 0; i < 8; i++) {
            BufferedImage frame = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = frame.createGraphics();

            // 绘制圆弧动画
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setColor(new Color(66, 133, 244));
            g2d.setStroke(new BasicStroke(6));
            g2d.drawArc(15, 15, 70, 70, i * 45, 90);

            g2d.dispose();
            frames.add(frame);
        }

        GifBuilder.of(100, 100)
            .addFrames(frames)
            .delay(100)
            .loop(0)
            .backgroundColor(Color.WHITE)
            .save(outputPath);
    }
}

4. 二维码生成 (QrCodeBuilder)

QrCodeBuilder 基于 ZXing 库实现二维码生成,支持自定义尺寸、颜色、边距,可嵌入 Logo。

4.1 基本用法

java
import plus.ruoyi.common.media.builder.QrCodeBuilder;

// 基本二维码
BufferedImage qrCode = QrCodeBuilder.of("https://ruoyi.plus")
    .build();

// 指定尺寸
BufferedImage qrCode2 = QrCodeBuilder.of("https://ruoyi.plus")
    .size(300)
    .build();

// 自定义前景色和背景色
BufferedImage qrCode3 = QrCodeBuilder.of("https://ruoyi.plus")
    .size(400)
    .foregroundColor(Color.BLACK)
    .backgroundColor(Color.WHITE)
    .build();

// 自定义颜色(十六进制)
BufferedImage qrCode4 = QrCodeBuilder.of("https://ruoyi.plus")
    .size(350)
    .foregroundColor("#1890FF") // 蓝色前景
    .backgroundColor("#F0F2F5") // 灰色背景
    .build();

4.2 设置边距

java
// 设置边距
BufferedImage qrCode = QrCodeBuilder.of("https://ruoyi.plus")
    .size(300)
    .margin(2) // 边距,默认为1
    .build();

// 无边距
BufferedImage qrCode2 = QrCodeBuilder.of("https://ruoyi.plus")
    .size(300)
    .margin(0)
    .build();
java
// 嵌入本地 Logo
BufferedImage qrWithLogo = QrCodeBuilder.of("https://ruoyi.plus")
    .size(400)
    .logo("/path/to/logo.png")
    .build();

// 嵌入 URL Logo
BufferedImage qrWithRemoteLogo = QrCodeBuilder.of("https://ruoyi.plus")
    .size(400)
    .logo("https://example.com/logo.png")
    .build();

// 指定 Logo 尺寸
BufferedImage qrWithSizedLogo = QrCodeBuilder.of("https://ruoyi.plus")
    .size(500)
    .logo("/path/to/logo.png")
    .logoSize(100) // Logo 宽高为 100px
    .build();

// 设置 Logo 缩放比例
BufferedImage qrWithScaledLogo = QrCodeBuilder.of("https://ruoyi.plus")
    .size(500)
    .logo("/path/to/logo.png")
    .logoScale(0.25f) // Logo 占二维码的 25%
    .build();

// Logo 带边框和圆角
BufferedImage qrWithStyledLogo = QrCodeBuilder.of("https://ruoyi.plus")
    .size(500)
    .logo("/path/to/logo.png")
    .logoSize(80)
    .logoBorderWidth(3) // 边框宽度
    .logoBorderColor(Color.WHITE) // 边框颜色
    .logoBorderRadius(10) // 圆角半径
    .build();

4.4 容错级别

java
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

// 低容错级别(7% 可恢复)
BufferedImage qrL = QrCodeBuilder.of("https://ruoyi.plus")
    .size(300)
    .errorCorrectionLevel(ErrorCorrectionLevel.L)
    .build();

// 中低容错级别(15% 可恢复)
BufferedImage qrM = QrCodeBuilder.of("https://ruoyi.plus")
    .size(300)
    .errorCorrectionLevel(ErrorCorrectionLevel.M)
    .build();

// 中高容错级别(25% 可恢复)
BufferedImage qrQ = QrCodeBuilder.of("https://ruoyi.plus")
    .size(300)
    .errorCorrectionLevel(ErrorCorrectionLevel.Q)
    .build();

// 最高容错级别(30% 可恢复,嵌入 Logo 时推荐)
BufferedImage qrH = QrCodeBuilder.of("https://ruoyi.plus")
    .size(300)
    .errorCorrectionLevel(ErrorCorrectionLevel.H)
    .build();

容错级别说明:

级别可恢复比例适用场景
L7%普通二维码,无 Logo
M15%默认级别,适合大多数场景
Q25%需要一定容错能力
H30%嵌入 Logo 时推荐

4.5 输出方式

java
// 输出为 BufferedImage
BufferedImage qrImage = QrCodeBuilder.of("https://ruoyi.plus")
    .size(300)
    .build();

// 输出为字节数组
byte[] qrBytes = QrCodeBuilder.of("https://ruoyi.plus")
    .size(300)
    .toBytes();

// 输出为 InputStream
InputStream qrStream = QrCodeBuilder.of("https://ruoyi.plus")
    .size(300)
    .toInputStream();

// 保存为文件
QrCodeBuilder.of("https://ruoyi.plus")
    .size(300)
    .save("/path/to/qrcode.png");

// 直接输出到 HTTP 响应
public void generateQrCode(String content, HttpServletResponse response) {
    QrCodeBuilder.of(content)
        .size(300)
        .toResponse(response);
}

// 指定输出格式
QrCodeBuilder.of("https://ruoyi.plus")
    .size(300)
    .format(OutputFormat.JPG)
    .save("/path/to/qrcode.jpg");

4.6 完整示例

java
/**
 * 二维码服务示例
 */
public class QrCodeService {

    /**
     * 生成带 Logo 的品牌二维码
     */
    public byte[] generateBrandQrCode(String url, String logoUrl) {
        return QrCodeBuilder.of(url)
            .size(500)
            .foregroundColor("#1890FF")
            .backgroundColor(Color.WHITE)
            .errorCorrectionLevel(ErrorCorrectionLevel.H)
            .logo(logoUrl)
            .logoSize(80)
            .logoBorderWidth(3)
            .logoBorderColor(Color.WHITE)
            .logoBorderRadius(8)
            .format(OutputFormat.PNG)
            .toBytes();
    }

    /**
     * 生成支付二维码
     */
    public byte[] generatePaymentQrCode(String paymentUrl) {
        return QrCodeBuilder.of(paymentUrl)
            .size(400)
            .foregroundColor(Color.BLACK)
            .backgroundColor(Color.WHITE)
            .errorCorrectionLevel(ErrorCorrectionLevel.H)
            .margin(2)
            .toBytes();
    }

    /**
     * 生成活动二维码(带 Logo)
     */
    public void generateEventQrCode(String eventUrl, String logoPath, String outputPath) {
        QrCodeBuilder.of(eventUrl)
            .size(600)
            .foregroundColor("#FF5722")
            .backgroundColor(Color.WHITE)
            .errorCorrectionLevel(ErrorCorrectionLevel.H)
            .logo(logoPath)
            .logoScale(0.2f)
            .logoBorderWidth(4)
            .logoBorderColor(Color.WHITE)
            .logoBorderRadius(12)
            .save(outputPath);
    }
}

5. 海报生成 (PosterBuilder)

PosterBuilder 支持动态海报生成,可添加文本、图片、二维码、几何图形等多种元素。

5.1 基本用法

java
import plus.ruoyi.common.media.builder.PosterBuilder;

// 纯色背景海报
BufferedImage poster = PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .build();

// 图片背景海报(本地文件)
BufferedImage poster2 = PosterBuilder.of("/path/to/bg.jpg", 800, 1200)
    .build();

// 图片背景海报(URL)
BufferedImage poster3 = PosterBuilder.of("https://example.com/bg.jpg", 800, 1200)
    .build();

// 渐变背景海报
BufferedImage poster4 = PosterBuilder.of(800, 1200)
    .gradientBackground(
        new Color(102, 126, 234), // 起始颜色
        new Color(118, 75, 162),  // 结束颜色
        GradientDirection.TOP_TO_BOTTOM) // 渐变方向
    .build();

5.2 添加文本

java
// 基本文本
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addText("Hello World", 48, "#333333", 100, 200)
    .build();

// 带字体样式的文本
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addText("标题文字",
        new Font("微软雅黑", Font.BOLD, 64),
        Color.BLACK,
        100, 200)
    .build();

// 居中文本
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addCenterText("居中显示的标题", 64, "#000000", 300)
    .build();

// 多行文本(自动换行)
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addMultiLineText(
        "这是一段很长的文本内容,系统会自动根据指定的最大宽度进行换行处理。",
        32, "#333333",
        100, 400, // 起始坐标
        600, // 最大宽度
        50) // 行高
    .build();

// 多种文本组合
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addText("主标题", 72, "#000000", 100, 150)
    .addText("副标题", 36, "#666666", 100, 250)
    .addCenterText("居中内容", 48, "#1890FF", 500)
    .addMultiLineText("详细描述文本...", 24, "#999999", 100, 600, 600, 40)
    .build();

5.3 添加图片

java
// 添加本地图片
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addImage("/path/to/product.jpg", 100, 300, 300, 300)
    .build();

// 添加网络图片
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addImage("https://example.com/product.jpg", 100, 300, 300, 300)
    .build();

// 居中添加图片
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addCenterImage("/path/to/logo.png", 200, 100, 100)
    .build();

// 添加圆形图片(头像)
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addCircleImage("/path/to/avatar.jpg", 400, 300, 80)
    .build();

// 添加圆角图片
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addRoundImage("/path/to/product.jpg", 100, 300, 300, 300, 20)
    .build();

// 添加带边框的圆形图片
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addCircleImage("/path/to/avatar.jpg", 400, 300, 80, 4, Color.WHITE)
    .build();

5.4 添加二维码

java
// 基本二维码
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addQrCode("https://ruoyi.plus", 300, 900, 200)
    .build();

// 居中二维码
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addCenterQrCode("https://ruoyi.plus", 1000, 200)
    .build();

// 带 Logo 的二维码
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addQrCode("https://ruoyi.plus", 300, 900, 200, "/path/to/logo.png")
    .build();

5.5 添加几何图形

java
// 添加矩形
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addRectangle(50, 50, 700, 200, "#1890FF")
    .build();

// 添加圆角矩形
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addRoundRectangle(50, 50, 700, 200, 20, "#1890FF")
    .build();

// 添加圆形
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addCircle(400, 600, 100, "#FF5722")
    .build();

// 添加线条
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addLine(100, 500, 700, 500, 2, "#CCCCCC")
    .build();

// 添加半透明遮罩层
PosterBuilder.of("https://example.com/bg.jpg", 800, 1200)
    .addRectangle(0, 0, 800, 1200, "rgba(0,0,0,0.5)")
    .addText("标题", 64, "#FFFFFF", 100, 600)
    .build();

5.6 输出方式

java
// 输出为 BufferedImage
BufferedImage poster = PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addCenterText("海报标题", 64, "#000000", 200)
    .build();

// 输出为字节数组
byte[] posterBytes = PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addCenterText("海报标题", 64, "#000000", 200)
    .toBytes();

// 输出为 InputStream
InputStream posterStream = PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addCenterText("海报标题", 64, "#000000", 200)
    .toInputStream();

// 保存为文件
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addCenterText("海报标题", 64, "#000000", 200)
    .save("/path/to/poster.jpg");

// 直接输出到 HTTP 响应
public void generatePoster(HttpServletResponse response) {
    PosterBuilder.of(800, 1200)
        .background(Color.WHITE)
        .addCenterText("海报标题", 64, "#000000", 200)
        .toResponse(response);
}

5.7 完整示例

java
/**
 * 海报生成服务示例
 */
public class PosterService {

    /**
     * 生成商品分享海报
     */
    public byte[] generateProductSharePoster(Product product, User user) {
        return PosterBuilder.of(750, 1334) // 手机屏幕比例
            .background(Color.WHITE)
            // 商品图片
            .addRoundImage(product.getImageUrl(), 50, 50, 650, 650, 12)
            // 商品名称
            .addMultiLineText(product.getName(), 36, "#333333", 50, 740, 650, 50)
            // 价格
            .addText("¥" + product.getPrice(), 48, "#FF5722", 50, 870)
            // 分隔线
            .addLine(50, 950, 700, 950, 1, "#EEEEEE")
            // 用户信息
            .addCircleImage(user.getAvatarUrl(), 100, 1020, 40, 2, Color.WHITE)
            .addText(user.getNickname(), 28, "#333333", 160, 1035)
            .addText("推荐好物给你", 24, "#999999", 160, 1075)
            // 二维码
            .addQrCode(product.getShareUrl(), 530, 980, 160)
            .addText("扫码查看", 20, "#999999", 570, 1160)
            .toBytes();
    }

    /**
     * 生成邀请海报
     */
    public byte[] generateInvitePoster(User inviter, String inviteCode) {
        String qrContent = "https://ruoyi.plus/invite?code=" + inviteCode;

        return PosterBuilder.of("https://example.com/invite-bg.jpg", 750, 1334)
            // 半透明遮罩
            .addRectangle(0, 600, 750, 734, "rgba(255,255,255,0.95)")
            // 邀请人信息
            .addCircleImage(inviter.getAvatarUrl(), 375, 700, 60, 3, Color.WHITE)
            .addCenterText(inviter.getNickname(), 32, "#333333", 800)
            .addCenterText("邀请你加入", 28, "#666666", 850)
            // 二维码
            .addCenterQrCode(qrContent, 1000, 200)
            // 提示文字
            .addCenterText("长按识别二维码", 24, "#999999", 1230)
            .toBytes();
    }

    /**
     * 生成活动海报
     */
    public void generateEventPoster(Event event, String outputPath) {
        PosterBuilder.of(event.getBgImageUrl(), 1200, 630) // 社交分享尺寸
            // 半透明遮罩
            .addRectangle(0, 0, 1200, 630, "rgba(0,0,0,0.5)")
            // 活动标题
            .addMultiLineText(event.getTitle(), 64, "#FFFFFF", 100, 250, 1000, 80)
            // 分类标签
            .addText(event.getCategory(), 32, "#FFD700", 100, 550)
            // 作者头像
            .addCircleImage(event.getAuthor().getAvatarUrl(), 1100, 550, 60)
            .save(outputPath);
    }
}

6. API 参考

6.1 ImageBuilder API

方法参数返回类型说明
of(String)文件路径或 URLImageBuilder静态工厂方法,创建实例
of(byte[])字节数组ImageBuilder从字节数组创建
of(InputStream)输入流ImageBuilder从输入流创建
of(BufferedImage)图片对象ImageBuilder从 BufferedImage 创建
resize(int, int, ResizeMode)宽度, 高度, 模式ImageBuilder缩放图片
scale(double)缩放比例ImageBuilder按比例缩放
crop(int, int, int, int)x, y, 宽度, 高度ImageBuilder按坐标裁剪
cropCenter(int, int)宽度, 高度ImageBuilder居中裁剪
rotate(double)角度ImageBuilder旋转图片
flipHorizontal()-ImageBuilder水平翻转
flipVertical()-ImageBuilder垂直翻转
addTextWatermark(...)文字, 字体, 颜色, 坐标, 透明度ImageBuilder添加文字水印
addImageWatermark(...)图片, 坐标, 尺寸, 透明度ImageBuilder添加图片水印
tileWatermark(...)图片, 间距, 透明度ImageBuilder平铺水印
grayscale()-ImageBuilder灰度滤镜
brightness(float)亮度值ImageBuilder调整亮度
contrast(float)对比度值ImageBuilder调整对比度
blur(int)半径ImageBuilder模糊效果
sharpen()-ImageBuilder锐化效果
format(OutputFormat)输出格式ImageBuilder设置输出格式
build()-BufferedImage构建并返回图片
toBytes()-byte[]输出为字节数组
toInputStream()-InputStream输出为输入流
save(String)文件路径void保存到文件
writeTo(OutputStream)输出流void写入输出流
toResponse(HttpServletResponse)HTTP 响应void输出到响应

6.2 GifBuilder API

方法参数返回类型说明
of(int, int)宽度, 高度GifBuilder静态工厂方法
addFrame(String)文件路径/URLGifBuilder添加单帧
addFrame(BufferedImage)图片对象GifBuilder添加单帧
addFrames(List)路径列表/图片列表GifBuilder批量添加帧
delay(int)毫秒GifBuilder设置帧延迟
loop(int)循环次数GifBuilder设置循环次数
backgroundColor(Color)颜色GifBuilder设置背景色
quality(float)0.0-1.0GifBuilder设置质量
clearFrames()-GifBuilder清除所有帧
toBytes()-byte[]输出为字节数组
toInputStream()-InputStream输出为输入流
save(String)文件路径void保存到文件

6.3 QrCodeBuilder API

方法参数返回类型说明
of(String)二维码内容QrCodeBuilder静态工厂方法
size(int)像素QrCodeBuilder设置尺寸
foregroundColor(Color/String)颜色QrCodeBuilder设置前景色
backgroundColor(Color/String)颜色QrCodeBuilder设置背景色
margin(int)边距QrCodeBuilder设置边距
errorCorrectionLevel(ErrorCorrectionLevel)容错级别QrCodeBuilder设置容错级别
logo(String)Logo 路径/URLQrCodeBuilder设置 Logo
logoSize(int)像素QrCodeBuilder设置 Logo 尺寸
logoScale(float)缩放比例QrCodeBuilder设置 Logo 缩放
logoBorderWidth(int)像素QrCodeBuilderLogo 边框宽度
logoBorderColor(Color)颜色QrCodeBuilderLogo 边框颜色
logoBorderRadius(int)像素QrCodeBuilderLogo 圆角半径
format(OutputFormat)输出格式QrCodeBuilder设置输出格式
build()-BufferedImage构建并返回图片
toBytes()-byte[]输出为字节数组
toInputStream()-InputStream输出为输入流
save(String)文件路径void保存到文件
toResponse(HttpServletResponse)HTTP 响应void输出到响应

6.4 PosterBuilder API

方法参数返回类型说明
of(int, int)宽度, 高度PosterBuilder纯色背景创建
of(String, int, int)背景图, 宽度, 高度PosterBuilder图片背景创建
background(Color)颜色PosterBuilder设置背景色
gradientBackground(...)起始色, 结束色, 方向PosterBuilder渐变背景
addText(...)文字, 字号/字体, 颜色, 坐标PosterBuilder添加文本
addCenterText(...)文字, 字号, 颜色, Y坐标PosterBuilder居中文本
addMultiLineText(...)文字, 字号, 颜色, 坐标, 最大宽度, 行高PosterBuilder多行文本
addImage(...)图片, X, Y, 宽, 高PosterBuilder添加图片
addCenterImage(...)图片, Y, 宽, 高PosterBuilder居中图片
addCircleImage(...)图片, X, Y, 半径, [边框宽度, 边框色]PosterBuilder圆形图片
addRoundImage(...)图片, X, Y, 宽, 高, 圆角PosterBuilder圆角图片
addQrCode(...)内容, X, Y, 尺寸, [Logo]PosterBuilder添加二维码
addCenterQrCode(...)内容, Y, 尺寸PosterBuilder居中二维码
addRectangle(...)X, Y, 宽, 高, 颜色PosterBuilder添加矩形
addRoundRectangle(...)X, Y, 宽, 高, 圆角, 颜色PosterBuilder圆角矩形
addCircle(...)X, Y, 半径, 颜色PosterBuilder添加圆形
addLine(...)X1, Y1, X2, Y2, 宽度, 颜色PosterBuilder添加线条
build()-BufferedImage构建并返回图片
toBytes()-byte[]输出为字节数组
toInputStream()-InputStream输出为输入流
save(String)文件路径void保存到文件
toResponse(HttpServletResponse)HTTP 响应void输出到响应

6.5 枚举类型

ResizeMode (缩放模式):

说明
EXACT精确缩放到指定尺寸
FIT_TO_WIDTH宽度固定,高度等比缩放
FIT_TO_HEIGHT高度固定,宽度等比缩放
AUTO自动选择最佳缩放方式

OutputFormat (输出格式):

说明
PNGPNG 格式,支持透明
JPGJPG 格式,压缩率高
GIFGIF 格式,支持动画
BMPBMP 格式,无压缩
WEBPWebP 格式,高压缩率

GradientDirection (渐变方向):

说明
TOP_TO_BOTTOM从上到下
LEFT_TO_RIGHT从左到右
DIAGONAL对角线

7. 最佳实践

7.1 性能优化

java
// 1. 批量处理时分批执行,避免内存溢出
public void processManyImages(List<String> imageUrls) {
    int batchSize = 10;
    for (int i = 0; i < imageUrls.size(); i += batchSize) {
        int end = Math.min(i + batchSize, imageUrls.size());
        List<String> batch = imageUrls.subList(i, end);

        batch.forEach(url -> {
            ImageBuilder.of(url)
                .resize(800, 600, ResizeMode.AUTO)
                .save("/output/" + UUID.randomUUID() + ".jpg");
        });

        System.gc(); // 批次间触发 GC
    }
}

// 2. 限制并行处理数量
public List<byte[]> parallelProcess(List<String> urls) {
    ForkJoinPool customPool = new ForkJoinPool(4);
    try {
        return customPool.submit(() ->
            urls.parallelStream()
                .map(url -> ImageBuilder.of(url)
                    .resize(800, 600, ResizeMode.AUTO)
                    .toBytes())
                .collect(Collectors.toList())
        ).get();
    } catch (Exception e) {
        throw new MediaException("批量处理失败", e);
    } finally {
        customPool.shutdown();
    }
}

// 3. 缓存远程图片
@Cacheable(value = "remote-images", key = "#url")
public BufferedImage loadAndCacheImage(String url) {
    return ImageBuilder.of(url).build();
}

7.2 异常处理

java
// 1. 使用备用图片
public BufferedImage loadImageSafe(String url, String fallbackPath) {
    try {
        return ImageBuilder.of(url).build();
    } catch (Exception e) {
        log.warn("加载图片失败: {}, 使用备用图片", url);
        return ImageBuilder.of(fallbackPath).build();
    }
}

// 2. 重试机制
public BufferedImage loadImageWithRetry(String url, int maxRetries) {
    int retries = 0;
    Exception lastException = null;

    while (retries < maxRetries) {
        try {
            return ImageBuilder.of(url).build();
        } catch (Exception e) {
            lastException = e;
            retries++;
            try {
                Thread.sleep(1000 * retries);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
            }
        }
    }

    throw new MediaException("加载图片失败,已重试" + maxRetries + "次", lastException);
}

7.3 资源管理

java
// 1. 使用 OSS 存储生成的图片
@Service
public class MediaStorageService {

    @Resource
    private OssService ossService;

    public String processAndUpload(String imageUrl, String fileName) {
        byte[] processed = ImageBuilder.of(imageUrl)
            .resize(800, 600, ResizeMode.AUTO)
            .addTextWatermark("© 版权所有",
                new Font("Arial", Font.BOLD, 24),
                Color.WHITE, -120, -40, 0.7f)
            .toBytes();

        return ossService.upload(processed, "images/" + fileName);
    }
}

// 2. 定期清理临时文件
@Scheduled(cron = "0 0 3 * * ?")
public void cleanTempFiles() {
    File tempDir = new File("/tmp/media");
    if (tempDir.exists()) {
        long threshold = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1);
        File[] files = tempDir.listFiles(f -> f.lastModified() < threshold);
        if (files != null) {
            for (File file : files) {
                file.delete();
            }
        }
    }
}

8. 常见问题

8.1 图片处理相关

问题: 图片处理后模糊或失真

原因: 缩放比例过大、输出格式不合适

java
// 解决方案: 选择合适的输出格式和避免放大
BufferedImage highQuality = ImageBuilder.of("/path/to/image.jpg")
    .resize(800, 600, ResizeMode.AUTO)
    .format(OutputFormat.PNG) // PNG 保持质量
    .build();

问题: 内存溢出 (OutOfMemoryError)

原因: 图片尺寸过大、批量处理未控制并发

java
// 解决方案: 分批处理,限制并发
public void processManyImages(List<String> urls) {
    int batchSize = 10;
    for (int i = 0; i < urls.size(); i += batchSize) {
        // 分批处理...
        System.gc();
    }
}

问题: 水印位置不准确

原因: 没有考虑图片尺寸差异

java
// 解决方案: 使用相对位置计算
BufferedImage original = ImageBuilder.of(imageUrl).build();
int x = (int)(original.getWidth() * 0.9) - 100;
int y = (int)(original.getHeight() * 0.95) - 20;

return ImageBuilder.of(original)
    .addTextWatermark(text, font, Color.WHITE, x, y, 0.7f)
    .build();

8.2 GIF 相关

问题: GIF 文件过大

java
// 解决方案: 控制尺寸和帧数
byte[] optimizedGif = GifBuilder.of(400, 300) // 降低尺寸
    .addFrame(frame1)
    .addFrame(frame3) // 跳过部分帧
    .delay(1000) // 增加延迟
    .quality(0.7f) // 降低质量
    .toBytes();

问题: GIF 播放速度不正确

java
// 标准帧率参考:
// 60 FPS → 16ms 延迟
// 30 FPS → 33ms 延迟
// 10 FPS → 100ms 延迟
GifBuilder.of(400, 300)
    .addFrames(frames)
    .delay(100) // 10 FPS
    .toBytes();

8.3 二维码相关

问题: 二维码扫描失败

java
// 解决方案: 确保对比度、尺寸和容错级别
BufferedImage qr = QrCodeBuilder.of("https://ruoyi.plus")
    .size(300) // 最小 200x200
    .foregroundColor(Color.BLACK)
    .backgroundColor(Color.WHITE)
    .errorCorrectionLevel(ErrorCorrectionLevel.H) // 高容错
    .margin(2) // 增加边距
    .build();

8.4 海报相关

问题: 文字超出边界或换行不正确

java
// 解决方案: 使用多行文本自动换行
PosterBuilder.of(800, 1200)
    .background(Color.WHITE)
    .addMultiLineText(
        "这是一段很长的文本...",
        32, "#333333",
        100, 300,
        600, // 最大宽度
        50)  // 行高
    .build();

问题: 图片加载失败

java
// 解决方案: 使用备用图片
public BufferedImage generatePosterSafe(String bgUrl, String fallbackBg) {
    try {
        return PosterBuilder.of(bgUrl, 800, 1200)
            .addCenterText("Hello", 64, "#000000", 600)
            .build();
    } catch (Exception e) {
        return PosterBuilder.of(fallbackBg, 800, 1200)
            .addCenterText("Hello", 64, "#000000", 600)
            .build();
    }
}

问题: 海报生成慢

java
// 解决方案: 缓存远程图片,使用本地资源
@Cacheable(value = "remote-images", key = "#url")
public BufferedImage loadAndCacheImage(String url) {
    return ImageBuilder.of(url).build();
}

// 使用本地图片代替远程图片
PosterBuilder.of("/local/bg.jpg", 800, 1200)
    .addCenterImage("/local/logo.png", 100, 200, 100)
    .build();

9. 总结

ruoyi-common-media 模块提供了完整的媒体处理解决方案:

功能Builder类主要用途
图片处理ImageBuilder缩放、裁剪、旋转、水印、滤镜
GIF动画GifBuilder多帧动画生成、参数配置
二维码QrCodeBuilder二维码生成、Logo嵌入
海报制作PosterBuilder动态海报、多元素组合

适用场景:

  • 图片处理: 商品图片批量处理、用户头像裁剪、图片水印添加
  • GIF动画: 产品展示动画、教程步骤演示、加载动画制作
  • 二维码: 网址短链接、活动报名、支付收款、名片联系方式
  • 海报生成: 商品分享海报、活动宣传海报、用户邀请卡片

技术优势:

  • Builder模式 - 流畅的链式调用API
  • 高性能 - 基于Thumbnailator和ZXing优秀库
  • 多输入源 - 支持URL、文件、流、字节数组
  • 多输出格式 - 支持PNG、JPG、GIF、WebP、BMP
  • 功能丰富 - 覆盖常见媒体处理需求
  • 易于使用 - 简洁的API设计,快速上手