Java如何实现PDF预览和添加水印

(代码可能无法简单复用,但可以给你提供前后端实现的思路)

客户提出需求,上传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;
    }

最终效果

 预览

下载后打开

  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值