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