java生成带水印的pdf文件

目录

一、pom.xml中添加pdfbox依赖

二、新建一个PDFWatermarkService

三、临时写一个controller接口进行测试

四、下载测试,查看水印效果


一、pom.xml中添加pdfbox依赖

复制代码
<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.27</version>
</dependency>

二、新建一个PDFWatermarkService

复制代码
import cn.hutool.core.io.IoUtil;
import com.esop.resurge.func.exception.enums.ResponseEnum;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDType1Font;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.util.Matrix;
import org.springframework.stereotype.Service;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.awt.*;
import java.io.*;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Service
@Slf4j
public class PDFWatermarkService {

    /**
     * 添加水印并输出到响应流
     */
    public void addWatermarkAndPreview(InputStream pdfInputStream,
                                       String username,
                                       HttpServletResponse response) throws IOException {
        PDDocument document = null;
        try {
            // 加载PDF文档
            document = PDDocument.load(pdfInputStream);

            // 添加水印
            addWatermark(document, username);

            // 设置响应头
            response.setContentType("application/pdf");
            response.setHeader("Content-Disposition", "inline; filename=preview.pdf");

            // 输出到浏览器
            document.save(response.getOutputStream());
        } finally {
            if (document != null) {
                document.close();
            }
        }
    }

    /**
     * 在PDF每一页添加水印
     */
    private void addWatermark(PDDocument document, String username) throws IOException {
        // 构造水印文本
        String watermarkText = username + " " +
                LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

        // 遍历每一页
        for (PDPage page : document.getPages()) {
            addWatermarkToPage(page, document, watermarkText);
        }
    }

    /**
     * 在单页添加多个水印
     */
    private void addWatermarkToPage(PDPage page, PDDocument document, String watermarkText) throws IOException {
        // 创建图形状态(设置透明度)
        PDExtendedGraphicsState graphicsState = new PDExtendedGraphicsState();
        graphicsState.setNonStrokingAlphaConstant(0.2f); // 半透明

        try (PDPageContentStream contentStream = new PDPageContentStream(document, page,
                PDPageContentStream.AppendMode.APPEND, true, true)) {

            // 设置图形状态
            contentStream.setGraphicsStateParameters(graphicsState);

            // 设置字体和颜色
            contentStream.setFont(PDType1Font.HELVETICA_BOLD, 20);
            contentStream.setNonStrokingColor(Color.LIGHT_GRAY);

            // 获取页面尺寸
            float pageWidth = page.getMediaBox().getWidth();
            float pageHeight = page.getMediaBox().getHeight();

            // 计算文本尺寸
            float fontSize = 20;
            float textWidth = PDType1Font.HELVETICA_BOLD.getStringWidth(watermarkText) / 1000 * fontSize;
            float textHeight = PDType1Font.HELVETICA_BOLD.getFontDescriptor().getFontBoundingBox().getHeight() / 1000 * fontSize;

            // 定义水印间距
            float horizontalSpacing = textWidth + 100;  // 水平间距
            float verticalSpacing = textHeight + 100;   // 垂直间距

            // 计算需要的水印数量,并确保水印不会超出页面边界
            // 添加边距保护
            float margin = 20f;
            int horizontalCount = (int) ((pageWidth - textWidth - margin * 2) / horizontalSpacing) + 1;
            int verticalCount = (int) ((pageHeight - textHeight - margin * 2) / verticalSpacing) + 1;


            // 在页面上添加多个水印
            for (int row = 0; row < verticalCount; row++) {
                for (int col = 0; col < horizontalCount; col++) {
                    // 计算当前水印位置,确保水印在页面边界内
                    float x = col * horizontalSpacing + margin + (horizontalSpacing / 2);
                    float y = row * verticalSpacing + margin + (verticalSpacing / 2);

                    // 保存图形状态
                    contentStream.saveGraphicsState();

                    // 移动到指定位置并旋转45度
                    contentStream.transform(Matrix.getTranslateInstance(x, y));
                    contentStream.transform(Matrix.getRotateInstance(Math.toRadians(45), 0, 0));

                    // 绘制文本
                    contentStream.beginText();
                    contentStream.newLineAtOffset(0, 0);
                    contentStream.showText(watermarkText);
                    contentStream.endText();

                    // 恢复图形状态
                    contentStream.restoreGraphicsState();
                }
            }
        }
    }




    /**
     * 添加水印并输出为下载文件
     */
    public void addWatermarkAndDownload(String documentId,
                                        HttpServletResponse response) throws IOException {
        // 获取当前登录用户
        String username = getCurrentUsername();

        // 获取原始PDF文件流
        InputStream pdfStream = getPDFInputStream(documentId);

        if (pdfStream == null) {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            response.setContentType("text/plain;charset=UTF-8");
            response.getWriter().write("文件未找到");
            return;
        }

        PDDocument document = null;
        ServletOutputStream outputStream = null;
        InputStream processedPdfStream = null;
        try {
            // 加载PDF文档
            document = PDDocument.load(pdfStream);

            // 添加水印
            addWatermark(document, username);

            // 将处理后的文档转换为InputStream
            processedPdfStream = convertPDDocumentToInputStream(document);

            String filename = URLEncoder.encode(documentId + "_" + username + "_" +
                    LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) +
                    ".pdf", "UTF-8");
            response.reset();
            response.setCharacterEncoding("UTF-8");
            // 设置返回内容格式
            response.addHeader("Content-Type","application/octet-stream");
            // 设置下载弹窗的文件名和格式(文件名要包括名字和文件格式)
            response.setHeader("Content-Disposition", "attachment;filename=" + filename+"");
            response.setCharacterEncoding("UTF-8");

            int len = 0;
            byte[] buffer = new byte[1024];
            outputStream = response.getOutputStream();
            while ((len = processedPdfStream.read(buffer)) > 0) {
                outputStream.write(buffer, 0, len);//将缓冲区的数据输出到客户端浏览器
            }
            processedPdfStream.close();
            outputStream.flush();
            outputStream.close();

        } finally {
            if (document != null) {
                document.close();
            }
            if(processedPdfStream!=null){
                processedPdfStream.close();
            }
            // 关闭流对象
            if(pdfStream!=null){
                pdfStream.close();
            }
            IoUtil.close(outputStream);
        }
    }


    /**
     * 将PDDocument转换为InputStream
     */
    public InputStream convertPDDocumentToInputStream(PDDocument document) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        try {
            document.save(out);
            byte[] pdfBytes = out.toByteArray();
            return new ByteArrayInputStream(pdfBytes);
        } finally {
            out.close();
        }
    }

    //获取系统登录用户(按实际自己系统的方式获取登录用户名)
    private String getCurrentUsername() {
        // 根据你的安全框架实现(如Spring Security)
        // return SecurityContextHolder.getContext().getAuthentication().getName();
        return "testUser"; // 示例
    }

    //根据文档ID获取PDF文件输入流
    private InputStream getPDFInputStream(String documentId) throws FileNotFoundException {
        try {
            // 加入从E盘temp目录读取文件(也可以是linux目录,或者通过其它方式获取的文件路径)
            String filePath = "E:\\temp\\" + documentId + ".pdf";
            File file = new File(filePath);

            if (file.exists() && file.isFile()) {
                return new FileInputStream(file);
            } else {
                log.warn("PDF文件未找到: {}", filePath);
                return null;
            }
        } catch (FileNotFoundException e) {
            log.error("读取PDF文件失败: {}", documentId, e);
            return null;
        }
    }

}

三、临时写一个controller接口进行测试

复制代码
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {   

    @Autowired
    private PDFWatermarkService pdfWatermarkService;

    @ApiOperation(value = "下载pdf", notes = "通过Swagger可以直接下载带水印的PDF文件",httpMethod = "GET",produces="application/octet-stream")
    @ApiResponses(value = {
            @ApiResponse(code = 200, message = "成功下载PDF文件"),
            @ApiResponse(code = 404, message = "文件未找到")
    })
    @GetMapping(value="/download/{documentId}",produces = "application/octet-stream")
    public FeedBack<String> downloadPDF(@ApiParam(value = "文档ID", required = true) @PathVariable String documentId,
                            HttpServletRequest request,
                            HttpServletResponse response) throws IOException {
        // 添加水印并输出为下载文件
        pdfWatermarkService.addWatermarkAndDownload(documentId, response);
        return ResponseEnum.getSuccessFeedback().setData("下载成功");
    }


}

四、下载测试,查看水印效果

启动项目,浏览器输入controller地址进行下载,文件水印效果如下:

相关推荐
MC皮蛋侠客3 分钟前
使用Python实现DLT645-2007智能电表协议
python·网络协议·tcp/ip·能源
渣哥12 分钟前
震惊!Java注解背后的实现原理,竟然如此简单又高深!
java
中等生17 分钟前
Python 的循环引入问题
python
hqxstudying18 分钟前
JAVA限流方法
java·开发语言·安全·限流
shylyly_25 分钟前
Linux->多线程2
java·linux·多线程·线程安全·线程同步·线程互斥·可重入
站大爷IP1 小时前
Python字符串全解析:从基础操作到高级技巧
python
小蒜学长1 小时前
基于实例教学的软件工程专业教学系统
java·spring boot·后端·软件工程
中等生1 小时前
FastAPI vs Flask 性能对比:异步的真正优势在哪里?
python
DFT计算杂谈1 小时前
EPWpy教程:一个脚本完成能带、声子、电声耦合、弛豫时间计算
python