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


1万+

被折叠的 条评论
为什么被折叠?



