当前位置: 首页 > news >正文

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);// 将处理后的文档转换为InputStreamprocessedPdfStream = 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 {   @Autowiredprivate 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地址进行下载,文件水印效果如下:

http://www.dtcms.com/a/342412.html

相关文章:

  • 【从零构建企业级线程池管理系统:Python并发编程实战指南】
  • 医疗智能体高质量问诊路径开发:基于数智立体化三维评估框架(go语言)
  • [新启航]长轴深孔检测 - 激光频率梳 3D 轮廓检测
  • Go语言中的迭代器模式与安全访问实践
  • Linux应用层开发--线程池介绍
  • 【网络运维】Shell:变量数值计算
  • redis-缓存-双写一致性
  • 【Django:基础知识】
  • 掌控不平等的力量:深入解析帕雷托分布与二八法则的数学内核
  • python测试开发django-1.开始hello world!
  • 《零基础入门AI:深度学习之NLP基础学习》
  • 在Python中, list相减 要从一个列表(valid_points)中排除另一个列表(yuanjian_jiaodian)的所有元素
  • Linux CentOS 安装 .net core 3.1
  • 银河麒麟V10系统离线安装zabbix-agent教程
  • 18维度解密·架构魔方:一览无遗的平衡艺术
  • nginx-重定向-正则表达式-路由匹配优先级
  • Qt截图工具项目开发教程 - 从零开始构建系统截图工具
  • 【ARM】Keil MDK如何指定单文件的优化等级
  • 牛津大学xDeepMind 自然语言处理(5)
  • 基于 Kubernetes 的 WordPress 网站部署(使用 ConfigMap)
  • Spring两个核心IoCDI(一)
  • javaweb开发笔记—— 前端工程化
  • 当安全遇上资源瓶颈:轻量级加密为何成为 IoT 时代的刚需?
  • 基于 FPGA 的电磁超声脉冲压缩检测系统
  • 家里Windows,公司Linux?通过cpolar,WSL开发环境无缝切换
  • Python数据可视化利器:Matplotlib从入门到实战全解析
  • 今天我们继续学习计算机网络技术,Cisco软件,三层交换机以及RIP动态协议
  • 从零开始:JDK 在 Windows、macOS 和 Linux 上的下载、安装与环境变量配置
  • DeepSeek R2难产:近期 DeepSeek-V3.1 发布,迈向 Agent 时代的第一步
  • 《杠杆》电视剧分析学习