目录
一、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地址进行下载,文件水印效果如下: