spring上传文件添加水印

发布于:2025-05-07 ⋅ 阅读:(15) ⋅ 点赞:(0)

1、实现  MultipartFile

package com.pojo.common.core.domain;


import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.multipart.MultipartFile;

public class InMultipartFile implements MultipartFile {

    private final String name;

    private String originalFilename;

    @Nullable
    private String contentType;

    private final byte[] content;


    /**
     * Create a new MockMultipartFile with the given content.
     * @param name the name of the file
     * @param content the content of the file
     */
    public InMultipartFile(String name, @Nullable byte[] content) {
        this(name, "", null, content);
    }

    /**
     * Create a new MockMultipartFile with the given content.
     * @param name the name of the file
     * @param contentStream the content of the file as stream
     * @throws IOException if reading from the stream failed
     */
    public InMultipartFile(String name, InputStream contentStream) throws IOException {
        this(name, "", null, FileCopyUtils.copyToByteArray(contentStream));
    }

    /**
     * Create a new MockMultipartFile with the given content.
     * @param name the name of the file
     * @param originalFilename the original filename (as on the client's machine)
     * @param contentType the content type (if known)
     * @param content the content of the file
     */
    public InMultipartFile(
            String name, @Nullable String originalFilename, @Nullable String contentType, @Nullable byte[] content) {

        Assert.hasLength(name, "Name must not be null");
        this.name = name;
        this.originalFilename = (originalFilename != null ? originalFilename : "");
        this.contentType = contentType;
        this.content = (content != null ? content : new byte[0]);
    }

    /**
     * Create a new MockMultipartFile with the given content.
     * @param name the name of the file
     * @param originalFilename the original filename (as on the client's machine)
     * @param contentType the content type (if known)
     * @param contentStream the content of the file as stream
     * @throws IOException if reading from the stream failed
     */
    public InMultipartFile(
            String name, @Nullable String originalFilename, @Nullable String contentType, InputStream contentStream)
            throws IOException {

        this(name, originalFilename, contentType, FileCopyUtils.copyToByteArray(contentStream));
    }


    @Override
    public String getName() {
        return this.name;
    }

    @Override
    public String getOriginalFilename() {
        return this.originalFilename;
    }

    @Override
    @Nullable
    public String getContentType() {
        return this.contentType;
    }

    @Override
    public boolean isEmpty() {
        return (this.content.length == 0);
    }

    @Override
    public long getSize() {
        return this.content.length;
    }

    @Override
    public byte[] getBytes() throws IOException {
        return this.content;
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return new ByteArrayInputStream(this.content);
    }

    @Override
    public void transferTo(File dest) throws IOException, IllegalStateException {
        FileCopyUtils.copy(this.content, dest);
    }

}

2、添加水印工具类

package com.pojo.common.core.utils;

import com.pojo.common.core.domain.InMultipartFile;
import org.springframework.web.multipart.MultipartFile;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class WatermarkUtil {

    /**
     * 添加多行文字水印
     *
     * @param file        原始文件
     * @param lines       水印文本行列表
     * @param font        字体对象
     * @param color       颜色(支持透明度)
     * @param startXRatio 起始X坐标比例(0.0~1.0)
     * @param startYRatio 起始Y坐标比例(0.0~1.0)
     * @param lineSpacing 行间距倍数
     * @return 带水印的MultipartFile
     */
    public static MultipartFile addTextWatermark(
            MultipartFile file,
            List<String> lines,
            Font font,
            Color color,
            float startXRatio,
            float startYRatio,
            float lineSpacing) throws IOException {

        // 读取原始图片(保留透明度通道)
        BufferedImage sourceImage = ImageIO.read(file.getInputStream());
        BufferedImage watermarkedImage = new BufferedImage(
                sourceImage.getWidth(),
                sourceImage.getHeight(),
                BufferedImage.TYPE_INT_ARGB
        );

        // 创建图形上下文
        Graphics2D g2d = watermarkedImage.createGraphics();
        configureGraphicsQuality(g2d);
        g2d.drawImage(sourceImage, 0, 0, null);

        // 设置水印样式
        g2d.setFont(font);
        g2d.setColor(color);
        g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, color.getAlpha() / 255f));

        // 计算实际绘制位置
        int baseX = (int) (sourceImage.getWidth() * startXRatio);
        int baseY = (int) (sourceImage.getHeight() * startYRatio);

        // 绘制多行文本
        drawWrappedText(g2d, lines, baseX, baseY, lineSpacing, sourceImage.getWidth());

        g2d.dispose();

        // 转换回MultipartFile
        return createOutputFile(watermarkedImage, file);
    }

    /**
     * 配置图形渲染质量
     */
    private static void configureGraphicsQuality(Graphics2D g2d) {
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
    }

    /**
     * 智能换行绘制
     */
    private static void drawWrappedText(Graphics2D g2d, List<String> lines,
                                        int startX, int startY, float lineSpacing,
                                        int imageWidth) {
        FontMetrics metrics = g2d.getFontMetrics();
        int lineHeight = metrics.getHeight();
        int currentY = startY + metrics.getAscent();

        for (String line : lines) {
            List<String> wrappedLines = wrapChineseText(line, metrics, imageWidth - startX);
            for (String wrappedLine : wrappedLines) {
                int textWidth = metrics.stringWidth(wrappedLine);
                int x = calculateHorizontalPosition(startX, textWidth, imageWidth);
                g2d.drawString(wrappedLine, x, currentY);
                currentY += lineHeight * lineSpacing;
            }
        }
    }

    /**
     * 中文自动换行算法
     */
    private static List<String> wrapChineseText(String text, FontMetrics metrics, int maxWidth) {
        List<String> result = new ArrayList<>();
        StringBuilder currentLine = new StringBuilder();
        int currentWidth = 0;

        for (int i = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            int charWidth = metrics.charWidth(c);

            if (currentWidth + charWidth > maxWidth) {
                result.add(currentLine.toString());
                currentLine = new StringBuilder();
                currentWidth = 0;
            }

            currentLine.append(c);
            currentWidth += charWidth;
        }

        if (currentLine.length() > 0) {
            result.add(currentLine.toString());
        }

        return result;
    }

    /**
     * 计算水平位置(支持左对齐/居中/右对齐)
     */
    private static int calculateHorizontalPosition(int startX, int textWidth, int imageWidth) {
        // 此处实现居中逻辑,可根据需要扩展
        return startX;
    }

    /**
     * 创建输出文件
     */
    private static MultipartFile createOutputFile(BufferedImage image, MultipartFile originalFile)
            throws IOException {

        String formatName = getImageFormat(originalFile.getContentType());
        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        if (!ImageIO.write(image, "png", baos)) {
            throw new IOException("不支持的图片格式: " + formatName);
        }

        return new InMultipartFile(
                "watermarked." + formatName,
                originalFile.getOriginalFilename(),
                originalFile.getContentType(),
                baos.toByteArray()
        );
    }

    /**
     * 从ContentType提取图片格式
     */
    private static String getImageFormat(String contentType) {
        return contentType.substring("image/".length()).split(";")[0];
    }

}

3、使用

   // 准备水印参数
        List<String> watermarkLines = new ArrayList<>();
        watermarkLines.add("机密文件 严禁外传");
        watermarkLines.add("编号:2023-0012");
        watermarkLines.add("有效期至:2025-12-31");
        // 创建字体(建议使用物理字体文件更可靠)
        Font font = new Font("微软雅黑", Font.BOLD, 16);
        Color color = new Color(255, 0, 0, 180); // 半透明白色
        MultipartFile result = null;
        // 添加水印
        try {
            result = WatermarkUtil.addTextWatermark(
                    file,
                    watermarkLines,
                    font,
                    color,
                    0.05f,   // 左侧5%位置
                    0.7f,    // 顶部70%位置(靠近底部)
                    1.0f     // 1.0倍行间距
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

4、测试效果


网站公告

今日签到

点亮在社区的每一天
去签到