(代码可能无法简单复用,但可以给你提供前后端实现的思路)
客户提出需求,上传pdf到系统,支持下载和预览,并且预览和下载时给pdf
打上当前登录用户名称水印
主要技术
后端:springboot
前端:vue2.0+elementUI
项目以若依RouYi前后端分离版为基础进行开发
第一步,后端引入pdfbox依赖
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.17</version>
</dependency>
第二步,前端新增相关组件
<el-table-column label="操作" prop="serviceSpecifyName" align="center" show-overflow-tooltip >
<template slot-scope="scope">
<el-button
size="mini"
type="text"
icon="el-icon-edit"
v-hasPermi="['publicPriceInfo:publicPriceInfo:view']"
@click="preview(scope.row)"
>预览</el-button>
<el-button size="mini" type="text" icon="el-icon-edit"
v-hasPermi="['publicPriceInfo:publicPriceInfo:downLoad']"
@click="importTemplate(scope.row)">下载附件
</el-button>
</template>
</el-table-column>
<el-dialog title="PDF 预览" :visible.sync="dialogVisible" @close="releaseBlobUrl" >
<iframe ref="pdfIframe" :src="pdfBlobUrl" width="100%" height="700px"></iframe>
</el-dialog>
js部分
data:{
dialogVisible:false,
pdfBlobUrl: null,
},
methods: {
preview(row){
this.fetchPdfBlob(row.attachmentId).then(() => {
this.dialogVisible = true;
});
},
async fetchPdfBlob(attachmentId) {
try {
const response = await fetchPdfFile(attachmentId);
//const blob = new Blob([response.data], { type: 'application/pdf' });
this.pdfBlobUrl = window.URL.createObjectURL(response);
} catch (error) {
console.error('Error fetching PDF:', error);
}
},
releaseBlobUrl() {
if (this.pdfBlobUrl) {
URL.revokeObjectURL(this.pdfBlobUrl);
this.pdfBlobUrl = null;
}
},
importTemplate(row) {
this.download(
`/costPublicPriceInfo/downloadTemplate/${row.attachmentId}`,
{},
`${row.attachmentName}_${new Date().getTime()}.pdf`,
{ type: "get" }
);
},
}
// 预览pdf
export function fetchPdfFile(attachmentId) {
return request({
url: '/costPublicPriceInfo/preView/'+attachmentId,
method: 'get',
responseType: 'blob' // 指定响应类型为 Blob
});
}
第三步,后端处理
需要注意的是,所获取的文件以你的实际路径为主,例如我的文件都是存在本机上,profile是在配置文件中配好的,将“/profile”替换掉就得到了我实际需要的路径,另外值得注意的是如水印中包含中文,需要自己提供字体,否则会报错,从网上下载即可
@GetMapping(value = "/preView/{attachmentId}",produces = MediaType.APPLICATION_PDF_VALUE)
public void servePdf(@PathVariable("attachmentId") Long attachmentId, HttpServletResponse response) throws Exception {
MainAttachment mainAttachment = mainAttachmentMapper.selectMainAttachmentByAttachmentId(attachmentId);
SysDept sysDept = sysDeptMapper.selectDeptById(getDeptId());
if (mainAttachment==null){
throw new ServiceException("查找附件失败,请联系管理员处理!");
}
String path=mainAttachment.getAttachmentPath().replace("/profile", profile);
try (InputStream inputStream = Files.newInputStream(Paths.get(path))) {
InputStream targetStream = pdfUtils(inputStream,sysDept.getDeptName());
// 设置 Content-Type
response.setContentType(MediaType.APPLICATION_PDF_VALUE);
// 设置 Content-Disposition
// 使用 inline 表示在浏览器中预览,使用 attachment 表示下载
String fileName = "example.pdf"; // 或者使用 mainAttachment.getFileName()
String dispositionValue = "inline; filename*=UTF-8''" + URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
response.setHeader("Content-Disposition", dispositionValue);
try (OutputStream out = response.getOutputStream()) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = targetStream.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
out.flush();
}
} catch (IOException e) {
throw new ServiceException("PDF 文件读取或处理失败: " + e.getMessage());
}
}
@GetMapping("/downloadTemplate/{attachmentId}")
public void downloadPreTemplate(HttpServletRequest request, HttpServletResponse response,@PathVariable("attachmentId") Long attachmentId) throws Exception {
MainAttachment mainAttachment = mainAttachmentMapper.selectMainAttachmentByAttachmentId(attachmentId);
SysDept sysDept = sysDeptMapper.selectDeptById(getDeptId());
if (mainAttachment==null){
throw new ServiceException("附件下载失败,请联系管理员处理!");
}
String fileName = mainAttachment.getAttachmentName();
String path=mainAttachment.getAttachmentPath().replace("/profile", profile);
try {
InputStream inputStream = Files.newInputStream(Paths.get(path));
InputStream targetStream=pdfUtils(inputStream,sysDept.getDeptName());
ExcelUtils.downFileByInputStream(request, response, targetStream, fileName);
} catch (Exception e) {
throw new ServiceException("下载附件失败,请联系管理员!");
}
}
//工具类方法
public static void downFileByInputStream(HttpServletRequest request, HttpServletResponse response, InputStream inputStream, String fileName) throws Exception {
byte[] buffer = new byte[1024];
BufferedInputStream bis = null;
OutputStream os = null;
String finalFileName;
try {
final String userAgent = request.getHeader("USER-AGENT");
//IE浏览器
if (StringUtils.contains(userAgent, "MSIE") || StringUtils.contains(userAgent, "Trident")) {
finalFileName = URLEncoder.encode(fileName, "UTF8");
}
//google,火狐浏览器
else if (StringUtils.contains(userAgent, "Mozilla")) {
finalFileName = new String(fileName.getBytes(), "ISO8859-1");
}
//其他浏览器
else {
finalFileName = URLEncoder.encode(fileName, "UTF8");
}
response.setCharacterEncoding("UTF-8");
// 设置强制下载不打开
response.setContentType("application/force-download");
// 设置文件名
response.addHeader("Content-Disposition", "attachment;fileName=" + finalFileName);
bis = new BufferedInputStream(inputStream);
os = response.getOutputStream();
int i = bis.read(buffer);
while (i != -1) {
os.write(buffer, 0, i);
i = bis.read(buffer);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (bis != null) {
try {
bis.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (os != null) {
try {
os.flush();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//pdf处理工具类
public InputStream pdfUtils(InputStream inputStream,String waterRemark) throws IOException {
// 从提供的 InputStream 加载 PDF 文档
PDDocument document = PDDocument.load(inputStream);
// 加载支持中文的字体
PDType0Font chineseFont;
// 使用 ClassPathResource 获取字体文件的 InputStream
ClassPathResource fontResource = new ClassPathResource("font/simsun.ttf");
InputStream fontInputStream = fontResource.getInputStream();
chineseFont = PDType0Font.load(document, fontInputStream);
// 遍历 PDF 的每一页
for (int i = 0; i < document.getNumberOfPages(); i++) {
PDPage page = document.getPage(i);
PDPageContentStream contentStream = new PDPageContentStream(document, page,
PDPageContentStream.AppendMode.APPEND, true, true);
// 设置字体和字号
contentStream.setFont(chineseFont, 20);
// 创建一个 Extended Graphics State 对象并设置透明度
PDExtendedGraphicsState gs = new PDExtendedGraphicsState();
gs.setNonStrokingAlphaConstant(0.2f);
gs.setStrokingAlphaConstant(0.2f);
// 开启图形状态
contentStream.setGraphicsStateParameters(gs);
// 设置非描边颜色(用于设置颜色)
contentStream.setNonStrokingColor(new PDColor(new float[]{0.5f, 0.5f, 0.5f}, PDDeviceRGB.INSTANCE));
// 设置描边颜色(用于设置颜色)
contentStream.setStrokingColor(new PDColor(new float[]{0.5f, 0.5f, 0.5f}, PDDeviceRGB.INSTANCE));
// 计算页面的尺寸
float pageWidth = page.getMediaBox().getWidth();
float pageHeight = page.getMediaBox().getHeight();
// 计算文本水印的宽度和高度
float textWidth = chineseFont.getStringWidth(waterRemark) / 2500 * 50;
float textHeight = 50; // 字体大小即为文本高度
// 定义额外的上下间距
float extraSpacing = 50; // 可以根据需要调整这个值
// 计算每行的起始位置
float yPosition = pageHeight; // 从顶部开始
while (yPosition > -100) {
// 计算每行水印的起始位置
float xPosition = -100; // 从左侧开始
while (xPosition + textWidth <= pageWidth+500) {
// 设置文本水印的起始位置
contentStream.beginText();
contentStream.setTextMatrix(1, 0, 0, 1, xPosition, yPosition);
// 旋转45度
contentStream.setTextRotation(Math.toRadians(45), xPosition + textWidth / 2, yPosition + textHeight / 2);
// 添加文本水印
contentStream.showText(waterRemark);
contentStream.endText();
// 移动到下一行的起始位置
xPosition += textWidth+5;
}
// 移动到下一行,增加额外的间距
yPosition -= (textHeight + extraSpacing);
}
contentStream.close();
}
// 将修改后的 PDF 保存到 ByteArrayOutputStream 中
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
document.save(outputStream);
document.close();
// 将 ByteArrayOutputStream 转换为 ByteArrayInputStream
InputStream resultInputStream = new ByteArrayInputStream(outputStream.toByteArray());
return resultInputStream;
}
最终效果
预览
下载后打开