java文件上传时给pdf、word、excel、ppt、图片添加水印

前言

在开发的过程中,因为文件的特殊性,需要给pdf、word、excel、ppt、图片添加水印。添加水印可以在文件上传时添加,也可以在文件下载时添加。因为业务的某些原因,文件需要在浏览器预览,如果用户将文件另存为则无法添加水印,所以此文章主要介绍文件上传时添加水印。至于文件下载时添加水印功能也很简单,稍微修改即可。

一:文件上传

1.1 controller控制层

java 复制代码
@ApiOperation("上传文件")
    @PostMapping("/file/upload")
    public String uploadFile(@RequestParam(value = "file") MultipartFile file) {
        if (file == null || file.isEmpty()) {
            throw new BusinessException("上传文件为空");
        }
        String originalFilename = file.getOriginalFilename();
        if (StringUtils.isBlank(originalFilename)) {
            throw new BusinessException("上传文件名为空");
        }
        String filePath = obsClientHelper.upload(file, file.getOriginalFilename());
        return filePath;
    }

1.2 文件上传到服务器

java 复制代码
    /**
     * 上传文件到服务器
     *
     * @param uploadFile 文件
     * @param fileName   文件名称
     * @return 文件路径
     */
    @SneakyThrows
    public String upload(MultipartFile uploadFile, String fileName) {
        String objectKey = directory + "/" + DATE_TIME_FORMATTER.format(LocalDate.now()) + "/" + UUID.randomUUID() + "/" + fileName;
        InputStream inputStream = null;
        //上传文件添加水印
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String watermark = UserContext.currentUser().getUserName() + "(" + UserContext.currentUser().getUserId() + ")" + "\n" + simpleDateFormat.format(new Date());
        if (fileName.endsWith("pdf")) {
            inputStream = PdfWatermarkUtils.addWatermarkInputStream(uploadFile.getInputStream(), new PdfWatermarkPageEventHelper(watermark));
        } else if (fileName.endsWith(ImageConstants.PICTURE_JPG) || fileName.endsWith(ImageConstants.PICTURE_PNG)) {
            inputStream = ImageWatermarkUtil.imgWatermarkInputStream(watermark, uploadFile.getInputStream(), -40);
        } else if (fileName.endsWith("docx") || fileName.endsWith("xlsx") || fileName.endsWith("pptx")) {
            TextWaterMarkDTO waterMarkDTO = new TextWaterMarkDTO(fileName, watermark);
            inputStream = OfficeWatermarkUtil.doMarkInputStream(uploadFile.getInputStream(), waterMarkDTO);
        } else {
            inputStream = uploadFile.getInputStream();
        }
        if (inputStream == null) {
            throw new RuntimeException("上传文件时,文件添加水印失败");
        }
        try (ObsClient obsClient = new ObsClient(ak, sk, endPoint)) {
            PutObjectResult putObjectResult = obsClient.putObject(bucketName, objectKey, inputStream);
            if (putObjectResult.getStatusCode() != HttpServletResponse.SC_OK) {
                throw new RuntimeException("文件上传失败,OBS响应码:" + putObjectResult.getStatusCode());
            }
            return putObjectResult.getObjectKey();
        } catch (IOException e) {
            throw new RuntimeException("文件上传失败", e);
        }
    }

二:PDF添加水印

2.1 引入依赖

xml 复制代码
<dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>kernel</artifactId>
            <version>7.1.11</version>
        </dependency>

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>layout</artifactId>
            <version>7.1.11</version>
        </dependency>

        <!--没有该包的话,会有中文显示问题-->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.4.3</version>
        </dependency>

        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>

        <dependency>
            <groupId>com.itextpdf.tool</groupId>
            <artifactId>xmlworker</artifactId>
            <version>5.5.11</version>
        </dependency>

2.2 PdfWatermarkUtils

java 复制代码
    /**
     * pdf加水印
     * @param inputStream 输入流
     * @param pdfPageEventHelper 水印
     * @return InputStream
     * @throws IOException
     */
    public static InputStream addWatermarkInputStream(InputStream inputStream, PdfPageEventHelper pdfPageEventHelper) throws IOException {
        String watermarkText = ((PdfWatermarkPageEventHelper) pdfPageEventHelper).getWatermarkText();
        float fontSize = 13;
        int rowSpace = 150;
        int colSpace = 150;
        boolean linux = SystemUtil.getOsInfo().isLinux();
        String chineseFontPath = null;
        if (linux) {
            chineseFontPath = "/usr/share/fonts/STSONG.TTF";
        } else {
            chineseFontPath = SystemUtil.getUserInfo().getCurrentDir() + "\\fonts\\STSONG.TTF";
        }
        watermarkText = watermarkText.replace("\n", "  ");
        // 加载PDF文件
        PDDocument document = PDDocument.load(inputStream);
        document.setAllSecurityToBeRemoved(true);
        // 加载水印字体
        PDFont font = PDType0Font.load(document, new FileInputStream(chineseFontPath), true);
        // 遍历PDF文件,在每一页加上水印
        for (PDPage page : document.getPages()) {
            PDPageContentStream stream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true);
            PDExtendedGraphicsState r = new PDExtendedGraphicsState();
            // 设置透明度
            r.setNonStrokingAlphaConstant(0.2f);
            r.setAlphaSourceFlag(true);
            stream.setGraphicsStateParameters(r);
            // 设置水印字体颜色
            stream.setStrokingColor(Color.GRAY);
            stream.beginText();
            stream.setFont(font, fontSize);
            stream.newLineAtOffset(0, -15);
            // 获取PDF页面大小
            float pageHeight = page.getMediaBox().getHeight();
            float pageWidth = page.getMediaBox().getWidth();
            // 根据纸张大小添加水印,30度倾斜
            for (int h = 10; h < pageHeight; h = h + rowSpace) {
                for (int w = -10; w < pageWidth; w = w + colSpace) {
                    stream.setTextMatrix(Matrix.getRotateInstance(0.3, w, h));
                    stream.showText(watermarkText);
                }
            }
            // 结束渲染,关闭流
            stream.endText();
            stream.restoreGraphicsState();
            stream.close();
        }
        return pdDocumentConvertorStream(document);
    }
java 复制代码
    /**
     * 将PDDocument转化为InputStream
     *
     * @param document document
     * @return InputStream
     */
    public static InputStream pdDocumentConvertorStream(PDDocument document) {
        try {
            //临时缓冲区
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            document.save(out);
            document.close();
            return new ByteArrayInputStream(out.toByteArray());
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

三:图片添加水印

3.1 ImageWatermarkUtil

java 复制代码
import com.itextpdf.text.Element;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.ColumnText;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.Objects;

@Component
@Slf4j
public class ImageWatermarkUtil {
    /**
     * 水印透明度
     */
    private static final float ALPHA = 0.5f;
    /**
     * 水印文字大小
     */
    public static final int FONT_SIZE = 23;
    /**
     * 水印文字字体
     */
    private static Font FONT;
    /**
     * 水印文字颜色
     */
    private static final Color COLOR = Color.white;
    /**
     * 水印之间的间隔
     */
    private static final int X_MOVE = 60;
    /**
     * 水印之间的间隔
     */
    private static final int Y_MOVE = 60;

    /**
     * 获取文本长度。汉字为1:1,英文和数字为2:1
     */
    private static int getTextLength(String text) {
        int length = text.length();
        for (int i = 0; i < text.length(); i++) {
            String s = String.valueOf(text.charAt(i));
            if (s.getBytes().length > 1) {
                length++;
            }
        }
        length = length % 2 == 0 ? length / 2 : length / 2 + 1;
        return length;
    }

    /**
     * 图片添加水印
     *
     * @param logoText         水印内容
     * @param sourceFileStream 流文件
     * @param degree           倾斜角度
     * @return InputStream
     */
    public static InputStream imgWatermarkInputStream(String logoText, InputStream sourceFileStream, Integer degree) {
        try {
            BufferedImage bufferedImage = addWatermark(logoText, sourceFileStream, degree);
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            ImageIO.write(bufferedImage, "jpg", os);
            return new ByteArrayInputStream(os.toByteArray());
        } catch (IOException e) {
            return null;
        }
    }


    /**
     * 图片添加水印
     *
     * @param logoText         水印内容
     * @param sourceFileStream 流文件
     * @param degree           倾斜角度
     * @return BufferedImage
     */
    public static BufferedImage addWatermark(String logoText, InputStream sourceFileStream, Integer degree) throws IOException {
        //源图片
        Image srcImg = ImageIO.read(sourceFileStream);
        //原图宽度
        int width = srcImg.getWidth(null);
        //原图高度
        int height = srcImg.getHeight(null);
        BufferedImage buffImg = new BufferedImage(srcImg.getWidth(null), srcImg.getHeight(null),
                BufferedImage.TYPE_INT_RGB);
        // 得到画笔对象
        Graphics2D g = buffImg.createGraphics();
        // 设置对线段的锯齿状边缘处理
        g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.drawImage(srcImg.getScaledInstance(srcImg.getWidth(null), srcImg.getHeight(null), Image.SCALE_SMOOTH),
                0, 0, null);
        // 设置水印旋转
        if (null != degree) {
            g.rotate(Math.toRadians(degree), (double) buffImg.getWidth() / 2, (double) buffImg.getHeight() / 2);
        }
        // 设置水印文字颜色
        g.setColor(COLOR);
        // 设置水印文字Font
        g.setFont(FONT);
        // 设置水印文字透明度
        g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, ALPHA));

        int x = -width / 2;
        int y = -height / 2;
        // 字体长度
        int markWidth = FONT_SIZE * getTextLength(logoText);
        // 字体高度

        // 循环添加水印
        while (x < width * 1.5) {
            y = -height;
            while (y < height * 1.5) {
                String[] lines = logoText.split("\n");
                for (String line : lines) {
                    g.drawString(line, x, y);
                    y += FONT_SIZE + Y_MOVE;
                }
            }
            x += markWidth + X_MOVE;
        }
        // 释放资源
        g.dispose();
        return buffImg;
    }


    /**
     * 给图片添加水印文字、可设置水印文字的旋转角度
     *
     * @param logoText         水印内容
     * @param sourceFileStream 输入流文件
     * @param targetFileStream 输出流文件
     * @param degree           倾斜角度
     */
    public static void imageByText(String logoText, InputStream sourceFileStream, OutputStream targetFileStream, Integer degree) {
        if (Objects.isNull(FONT)) {
            FONT = new Font(FontUtil.firstZhSupportedFontName(), Font.BOLD, FONT_SIZE);
        }
        try {
            // 生成图片
            ImageIO.write(addWatermark(logoText, sourceFileStream, degree), "JPG", targetFileStream);
            log.info("图片-添加水印文字成功!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (null != targetFileStream) {
                    targetFileStream.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

3.2 FontUtil

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.awt.*;
import java.util.Arrays;

@Slf4j
public class FontUtil {

    private static String ZH_SUP;

    static {
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        char randomZh = '唐';
        Font[] allFonts = ge.getAllFonts();
        for (Font font : allFonts) {
            if(font.canDisplay(randomZh)){
                ZH_SUP = font.getFontName();
                break;
            }
        }
        if(StringUtils.isEmpty(ZH_SUP)){
            log.error("Zh supported font not found");
            ZH_SUP = allFonts[0].getFontName();
        }
    }

    public static String firstZhSupportedFontName(){
        return ZH_SUP;
    }

    public static java.util.List<String> getAllFontNames(){
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        String[] availableFontFamilyNames = ge.getAvailableFontFamilyNames();
        return Arrays.asList(availableFontFamilyNames);
    }
}

四:docx、xlsx、pptx添加水印

注:ppt、xls、doc由于版本新旧问题,此处先不介绍添加水印方法

4.1 TextWaterMarkDTO

java 复制代码
import com.FontUtil;
import lombok.Data;
import lombok.experimental.Accessors;

@Data
@Accessors(chain = true)
public class TextWaterMarkDTO{

    public TextWaterMarkDTO() {
        this.fontName = FontUtil.firstZhSupportedFontName();
    }
    public TextWaterMarkDTO(String fileName,String text) {
        this.fontName = FontUtil.firstZhSupportedFontName();
        this.fileSuffix = loadFileSuffix(fileName);
        this.text = text;
    }
    String fileSuffix;
    private String text;
    private String fontName;
    private String fontColor;
    private Integer fontSize = 20;
    private Integer rotation = -20;
    Integer intervalHorizontal;
    Integer intervalVertical;
    Integer picWidth;
    Integer picHeight;
    Float alpha;
    boolean enable;

    private String loadFileSuffix(String fileName){
        String realName = fileName.trim();
        int index = realName.lastIndexOf(".");
        if( index < 0){
            return null;
        }
        return fileName.substring(index + 1);
    }

}

4.2 doMarkInputStream

java 复制代码
    /**
     * 添加文件水印,返回InputStream
     *
     * @param input         输入流
     * @param waterMarkInfo waterMarkInfo
     * @return InputStream
     */
    public static InputStream doMarkInputStream(InputStream input, TextWaterMarkDTO waterMarkInfo) {
        InputStream inputStream = null;
        try {
            String fileSuffix = waterMarkInfo.getFileSuffix().toLowerCase();
            switch (fileSuffix) {
                case "docx":
                    inputStream = docxWaterMarkInputStream(input, waterMarkInfo);
                    break;
                case "xlsx":
                    log.info("xlsx");
                    inputStream = xlsxWaterMarkInputStream(input, waterMarkInfo.setPicWidth(1000).setPicHeight(2000));
                    break;
                case "pptx":
                    log.info("pptx");
                    inputStream = pptxAddWatermarkInputStream(input, waterMarkInfo);
                    break;
                default:
                    log.info("in default. fileSuffix:{}", fileSuffix);
                    inputStream = input;
            }
            return inputStream == null ? input : inputStream;
        } catch (Exception e) {
            log.error(ExceptionUtil.stacktraceToString(e));
            return null;
        } finally {
            close(input);
        }


    }

4.3 docx

java 复制代码
@SneakyThrows
    private static InputStream docxWaterMarkInputStream(InputStream input, TextWaterMarkDTO waterMarkInfo) {
        XWPFDocument docx = new XWPFDocument(input);
        //设置默认值
        String watermark = StringUtils.isBlank(waterMarkInfo.getText()) ? DEFAULT_WATERMARK : waterMarkInfo.getText();
        String color = StringUtils.isBlank(waterMarkInfo.getFontColor()) ? DEFAULT_FONT_COLOR : "#" + waterMarkInfo.getFontColor();
        String fontSize = (null == waterMarkInfo.getFontSize()) ? FONT_SIZE : waterMarkInfo.getFontSize() + "pt";
        String rotation = (null == waterMarkInfo.getRotation()) ? STYLE_ROTATION : String.valueOf(waterMarkInfo.getRotation());
        DocxUtil.makeFullWaterMarkByWordArt(docx, watermark, color, fontSize, rotation);
        //将XWPFDocument转化为InputStream
        return convertToInputStream(docx);
    }
java 复制代码
    /**
     * 将XWPFDocument转化为InputStream
     *
     * @param doc XWPFDocument
     * @return InputStream
     */
    public static InputStream convertToInputStream(XWPFDocument doc) {
        try {
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            doc.write(outputStream);
            return new ByteArrayInputStream(outputStream.toByteArray());
        } catch (IOException e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

4.4 xlsx

java 复制代码
@SneakyThrows
    private static InputStream xlsxWaterMarkInputStream(InputStream input, TextWaterMarkDTO waterMark) {
        XSSFWorkbook workbook = new XSSFWorkbook(input);
        Iterator<Sheet> iterator = workbook.sheetIterator();
        BufferedImage image = FontImage.createWatermarkImageFillWithText(waterMark);
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ImageIO.write(image, "png", os);
        int pictureIdx = workbook.addPicture(os.toByteArray(), Workbook.PICTURE_TYPE_PNG);
        while (iterator.hasNext()) {
            XSSFSheet sheet = (XSSFSheet) iterator.next();
            String rID = sheet.addRelation(null, XSSFRelation.IMAGES, workbook.getAllPictures().get(pictureIdx)).getRelationship().getId();
            //set background picture to sheet
            sheet.getCTWorksheet().addNewPicture().setId(rID);
        }
        //将XSSFWorkbook转化为InputStream
        return workbookConvertorStream(workbook);
    }
java 复制代码
    /**
     * 将XSSFWorkbook转化为InputStream
     *
     * @param workbook XSSFWorkbook
     * @return InputStream
     */
    public static InputStream workbookConvertorStream(XSSFWorkbook workbook) {
        try {
            //临时缓冲区
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            //创建临时文件
            workbook.write(out);
            byte[] bookByteAry = out.toByteArray();
            return new ByteArrayInputStream(bookByteAry);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }

4.5 pptx

java 复制代码
@SneakyThrows
    private static InputStream pptxAddWatermarkInputStream(InputStream input, TextWaterMarkDTO waterMarkDto) {
        XMLSlideShow slideShow = new XMLSlideShow(input);
        waterMarkDto.setPicWidth(Double.valueOf(slideShow.getPageSize().width).intValue());
        waterMarkDto.setPicHeight(Double.valueOf(slideShow.getPageSize().height).intValue());
        BufferedImage image = FontImage.createWatermarkImageFillWithText(waterMarkDto);
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ImageIO.write(image, "png", os);
        PictureData pictureData1 = slideShow.addPicture(os.toByteArray(), PictureData.PictureType.PNG);
        for (XSLFSlide slide : slideShow.getSlides()) {
            XSLFPictureShape pictureShape = slide.createPicture(pictureData1);
            pictureShape.setAnchor(new Rectangle(0, 0, slideShow.getPageSize().width, slideShow.getPageSize().height));
        }
        return xmlSlideShowConvertorStream(slideShow);
    }
java 复制代码
    /**
     * 将XMLSlideShow转化为InputStream
     *
     * @param slideShow slideShow
     * @return InputStream
     */
    public static InputStream xmlSlideShowConvertorStream(XMLSlideShow slideShow) {
        try {
            //临时缓冲区
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            //创建临时文件
            slideShow.write(out);
            byte[] bookByteAry = out.toByteArray();
            return new ByteArrayInputStream(bookByteAry);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
            return null;
        }
    }
相关推荐
救救孩子把13 分钟前
深入理解 Java 对象的内存布局
java
落落落sss15 分钟前
MybatisPlus
android·java·开发语言·spring·tomcat·rabbitmq·mybatis
万物皆字节21 分钟前
maven指定模块快速打包idea插件Quick Maven Package
java
夜雨翦春韭28 分钟前
【代码随想录Day30】贪心算法Part04
java·数据结构·算法·leetcode·贪心算法
我行我素,向往自由34 分钟前
速成java记录(上)
java·速成
一直学习永不止步40 分钟前
LeetCode题练习与总结:H 指数--274
java·数据结构·算法·leetcode·数组·排序·计数排序
邵泽明41 分钟前
面试知识储备-多线程
java·面试·职场和发展
程序员是干活的1 小时前
私家车开车回家过节会发生什么事情
java·开发语言·软件构建·1024程序员节
煸橙干儿~~1 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
2401_854391081 小时前
Spring Boot大学生就业招聘系统的开发与部署
java·spring boot·后端