SpringBoot 集成 PDFBox 实现电子签章

Apache PDFBox 是一个开源的 Java 库,用于处理 PDF 文档。它提供了一系列强大的功能,包括创建、渲染、拆分、合并、加密、解密 PDF 文件,以及从 PDF 中提取文本和元数据等。PDFBox 支持 PDF 1.7 标准,并且兼容大多数现代 PDF 格式和特性。

1、使用 Maven 集成 PDFBox

在 pom.xml 文件中引入依赖

<dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>2.0.24</version> <!-- 请检查最新的版本 -->
</dependency>

2、编写工具类

package cn.iocoder.yudao.module.contract.service.content;import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.springframework.http.ResponseEntity;import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;public class PDFBoxUtil {/*** 加载 PDF 文档*/public static PDDocument loadPdf(byte[] input) throws IOException {return PDDocument.load(input);}/*** 添加印章到 PDF 文档中** @param document       PDF 文档对象* @param imageByteArray 印章图像的二进制数据* @param x              横坐标* @param y              纵坐标* @param h              高度* @param pageIdx        页码* @throws IOException 异常*/public static void addStampToPdf(PDDocument document, byte[] imageByteArray, int x, int y, int h, int pageIdx) throws IOException {// 加载签章图像PDImageXObject pdImage = PDImageXObject.createFromByteArray(document, imageByteArray, "签章");// 获取 PDF 文档的第一个页面PDPage page = document.getPage(pageIdx);// 计算签章图像的尺寸float desiredHeight = h; // 目标高度float scale = desiredHeight / pdImage.getHeight();// 创建一个内容流以添加签章try (PDPageContentStream contentStream = new PDPageContentStream(document, page, PDPageContentStream.AppendMode.APPEND, true, true)) {// 在 PDF 页面上绘制签章图像contentStream.drawImage(pdImage, x, y, pdImage.getWidth() * scale, pdImage.getHeight() * scale);}// 可选:也可以向 PDF 添加一个签名字段
//        addSignatureField(document);}/*** 将 BufferedImage 转换为字节数组** @param image 要转换的图像* @return 字节数组*/private static byte[] imageToBytes(BufferedImage image) {try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {ImageIO.write(image, "png", os);return os.toByteArray();} catch (IOException e) {throw new RuntimeException("Failed to convert image to bytes", e);}}/*** 裁剪图像** @param image 要裁剪的图像* @param page PDF 页面* @param x 开始裁剪的横坐标* @param y 开始裁剪的纵坐标* @param w 需要裁剪的宽度* @param h 需要裁剪的高度* @return 裁剪后的图片*/private static BufferedImage cropImage(BufferedImage image, PDPage page, int x, int y, int w, int h) {PDRectangle mediaBox = PDRectangle.A4; // 使用默认的 A4 大小// 将 PDF 单位转换为图像坐标int width = (int) (mediaBox.getWidth() * (image.getWidth() / page.getMediaBox().getWidth()));int height = (int) (mediaBox.getHeight() * (image.getHeight() / page.getMediaBox().getHeight()));// 裁剪图像return image.getSubimage(x, y, width - w, height - h);}/*** 将 PDF 转换为多个图片** @param pdfBytes PDF 二进制数据* @param dpi      DPI 值* @return 裁剪后的图片列表* @throws IOException 异常*/public static List<byte[]> convertPdfToImages(byte[] pdfBytes, int numberOfPages, int dpi, int x, int y, int w, int h) throws IOException {List<byte[]> croppedImages = new ArrayList<>();try (PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes))) {PDFRenderer renderer = new PDFRenderer(document);if (numberOfPages == 0) {numberOfPages = document.getNumberOfPages();}for (int i = 0; i < numberOfPages; i++) {// 渲染页面BufferedImage image = renderer.renderImageWithDPI(i, dpi); // 300 DPI// 裁剪图像BufferedImage croppedImage = cropImage(image, document.getPage(i), x, y, w, h);byte[] croppedImageBytes = imageToBytes(croppedImage);croppedImages.add(croppedImageBytes);}}return croppedImages;}/*** 将 PDF 转换为 Base64 编码的 JSON** @param fileContent PDF 二进制数据* @param x 开始裁剪的横坐标* @param y 开始裁剪的纵坐标* @param w 需要裁剪的宽度* @param h 需要裁剪的高度* @return Base64 编码的 JSON* @throws Exception 异常*/public static ResponseEntity<String> convertPdfToBase64(byte[] fileContent, int x, int y, int w, int h) throws Exception {List<byte[]> imageBytesList = convertPdfToImages(fileContent, 0, 300, x, y, w, h);List<String> base64Images = new ArrayList<>();for (byte[] imageBytes : imageBytesList) {String base64Image = Base64.getEncoder().encodeToString(imageBytes);base64Images.add(base64Image);}ObjectMapper mapper = new ObjectMapper();String jsonResult = mapper.writeValueAsString(base64Images);return ResponseEntity.ok().body(jsonResult);}
}

3、编写控制器用于浏览器直接打开

第五步会编写控制器用于在 VUE 前端预览 PDF 文件

/*** 测试添加数字签名** @param filename 文件名* @param x x坐标* @param y y坐标* @param h 高度* @param i 宽度*/@GetMapping("/stamp/{filename}/p")@Parameter(name = "x", description = "添加签名的 x 坐标", required = true, example = "x")@Parameter(name = "y", description = "添加签名的 y 坐标", required = true, example = "y")@Parameter(name = "h", description = "签名的显示高度", required = true, example = "h")@Parameter(name = "i", description = "签名所在页数下标", required = true, example = "i")public ResponseEntity<ByteArrayResource> stampTest(@PathVariable String filename, @RequestParam("x") Integer x, @RequestParam("y") Integer y,@RequestParam("h") Integer h, @RequestParam("i") Integer i) throws Exception {// 从数据库中获取文件内容,这里需要修改为你们自己的获取方式来获取源 PDF 文件的字节数组byte[] fileContent = fileApi.getFileContent(4L, filename);ByteArrayOutputStream out = new ByteArrayOutputStream();// 添加数字签名try (PDDocument document = PDFBoxUtil.loadPdf(fileContent)) {// 这里需要修改为你们自己的获取方式来获取签名文件的字节数组byte[] imageByteArray = fileApi.getFileContent(4L, "2c095928083c5ee82e6e229089892191d7790a3a42616dfd5a49daae68c27f41.png");PDFBoxUtil.addStampToPdf(document, imageByteArray, x, y, h, i);document.save(out);} catch (IOException e) {e.printStackTrace();}// 创建 ByteArrayResourceByteArrayResource resource = new ByteArrayResource(out.toByteArray());return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + filename + "\"").contentType(MediaType.APPLICATION_PDF).body(resource);}

4、浏览器测试

直接打开连接http://IP:端口/你们自己的控制器前缀/stamp/文件名/p?x=100&y=200&h=80&i=1进行测试

5、编写控制器用于在 VUE 前端预览 PDF 文件

我这边在预览的时候不想保留边距、页眉、页脚的数据,所以有裁剪参数,不需要的话需要自行修改

/*** 根据合约名称获取合约 PDF 文件,并返回图片的 Base64 编码** @param filename合约标识* @return 图片的 Base64 编码*/@GetMapping(value = "/get/{filename}", produces = MediaType.IMAGE_PNG_VALUE)@Parameter(name = "x", description = "每一页开始裁剪的 x 横坐标", required = true, example = "x")@Parameter(name = "y", description = "每一页开始裁剪的 y 纵坐标", required = true, example = "y")@Parameter(name = "h", description = "每一页需要裁剪掉的高度 h", required = true, example = "h")@Parameter(name = "w", description = "每一个需要裁剪掉的宽度 w", required = true, example = "w")public ResponseEntity<String> getPageImage(@PathVariable String filename, @RequestParam("x") int x, @RequestParam("y") int y,@RequestParam("h") int h, @RequestParam("w") int w) {// 从数据库中获取文件内容,这里需要修改为你们自己的获取方式来获取源 PDF 文件的字节数组byte[] fileContent = fileApi.getFileContent(4L, filename);try {return PDFBoxUtil.convertPdfToBase64(fileContent, x, y, w, h);} catch (IOException e) {throw new RuntimeException("获取 PDF 文件截图异常", e);} catch (Exception e) {throw new RuntimeException("读取 PDF 文件异常", e);}}

6、编写 VUE 代码

<template><Dialog :title="dialogTitle" v-model="dialogVisible"><div v-if="formLoading">{{message}}</div><div id="pdf-container"></div></Dialog>
</template>
<script setup lang="ts">defineOptions({ name: 'ContentWXPreview' })const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中
const message = ref('数据正在加载请稍后 ... ...')/** 打开弹窗 */
const open = async (title: string, code: string) => {dialogVisible.value = truedialogTitle.value = title + '_预览'formLoading.value = truetry {fetch('http://IP:端口/你们自己的控制器前缀/stamp/文件名/p?x=250&y=188&w=520&h=385', {method: 'GET',headers: {'Content-Type': 'application/octet-stream'}}).then(response => response.text()).then(base64Images => {const container = document.getElementById('pdf-container')if (container) {container.innerHTML = '' // 清空容器const images = JSON.parse(base64Images)images.forEach(base64Image => {let img = document.createElement('img')img.src = `data:image/png;base64,${base64Image}`container.appendChild(img)})}formLoading.value = false})} finally {formLoading.value = false}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
</script><style lang="scss">
#pdf-container {display: flex;flex-direction: column;align-items: center;
}#pdf-container > img {max-width: 100%; 
}
</style>

7、预览显示

扩展:虽然 PDFBox 很强大,但是在读取文件、文件识别、文字替换等方面使用起来不是特别方便,需要有一定的学习成本。对于我这边偶尔开发 PDF 文档处理半路子来说太难了,所以会在SpringBoot 集成 SpirePDF 实现文本替换-CSDN博客中说明如何使用 Spider.PDF 进行文本替换

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/420533.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CMDB是什么软件?对企业有什么用?

配置管理数据库缩写为CMDB&#xff0c;英文全称为Configuration Management Database&#xff0c;是一种用于记录和管理企业信息技术基础设施&#xff08;IT基础设施&#xff09;和所有相关资源的数据库软件。 CMDB的主要功能是收集、存储和分析所有IT基础设施和服务的信息&am…

【C语言】选择排序及优化、冒泡排序、计数排序的实现

目录 一、选择排序1.1 常规版&#xff08;一次排好一个数&#xff09;1.1.1 基本思想1.1.2 实现思路1.1.3 代码 1.2 优化版&#xff08;一次排好两个数&#xff09;1.2.1 实现思路1.2.2 代码 1.3 时间复杂度 二、冒泡排序2.1 实现思路2.2 代码2.3 时间复杂度 三、计数排序3.1 基…

56页PPT | 大数据决策分析平台怎么建设?经典实践方案推荐

一、现状和目标 企业用户现状&#xff1a;数据分散&#xff0c;利用率低&#xff0c;业务需求变化快但IT响应慢。 问题&#xff1a;数据展示不及时、不准确&#xff0c;缺乏深入分析工具&#xff0c;报表制作效率低下。 目标&#xff1a;建设统一的数据整合平台&#xff0c;…

GS-SLAM论文阅读笔记--MM-Gaussian

介绍 这是一篇多模态的GS-SLAM&#xff0c;也已经被IROS2024录用。由于多传感器融合的GS-SLAM还是比较少的&#xff0c;所以应该仔细阅读一篇。 文章目录 介绍1.背景介绍2.关键内容2.1 跟踪2.2 重定位2.3 建图2.4总体流程 3.文章贡献 1.背景介绍 传统的SLAM方法往往受到地图…

Patch 35586779: WLS PATCH SET UPDATE 10.3.6.0.231017

以上补丁请自行去oracle官网下载&#xff0c;需要技术支持的请联系&#xff1a;https://item.taobao.com/item.htm?spm2013.1.w4023-17257245948.4.19611db9bzrKBx&id608692494369

软件质量保障:故障演练介绍

目录 背景&#xff1a;架构变化带来的问题 什么是故障演练 为什么需要故障演练 故障演练场景有哪些 不同演练类型和目标 如何对工具进行评估 功能评测项 告警评测项 观测指标评测项 总结 背景&#xff1a;架构变化带来的问题 随着架构越来越复杂、应用越来越多样&…

【设计模式】Template Method伪代码

1. 不好的代码 1.1 lib.cpp class Library{ public:void Step1(){//...}void Step3(){//...}void Step5(){//...} };1.2 app.cpp class Application{ public:bool Step2(){//...}void Step4(){//...} };int main() {Library lib();Application app();lib.Step1();if(app.Ste…

Python 从入门到实战12(流程控制-跳出循环语句)

我们的目标是&#xff1a;通过这一套资料学习下来&#xff0c;通过熟练掌握python基础&#xff0c;然后结合经典实例、实践相结合&#xff0c;使我们完全掌握python&#xff0c;并做到独立完成项目开发的能力。 上篇文章我们通过举例学习了流程控制语句中的循环语句。今天继续讨…

低代码开发:业务与技术的完美融合

正文&#xff1a; 随着数字化转型的加速&#xff0c;企业对应用软件的需求日益增长。然而&#xff0c;传统的开发方式往往费时费力&#xff0c;难以满足市场的快速变化。在此背景下&#xff0c;低代码开发平台应运而生&#xff0c;它们正逐步改变我们的工作方式&#xff0c;让…

从头开始学Spring—06初识声明式事务

目录 1.概念 1.1编程式事务 1.2声明式事务 2.JdbcTemplate 2.1准备工作 2.1.1加入依赖 2.1.2创建jdbc.properties 2.1.3配置Spring的配置文件 2.2测试 2.2.1在测试类装配JdbcTemplate 2.2.2测试增删改功能 2.2.3查询一条数据为实体类对象 2.2.4查询多条数据为一个…

LabVIEW灵活集成与调试的方法

在LabVIEW开发中&#xff0c;为了构建一个既便于调试又能灵活集成到主VI中的控制VI&#xff0c;开发者需要采用适当的编程方式和架构。常见的选择包括模块化设计、状态机架构以及事件驱动编程。这些方法有助于简化调试过程、提高系统的稳定性&#xff0c;并确保代码的重用性和可…

博客常见问题

hexo g 生成静态文件 hexo s 本地预览 hexo d 同步上传到git 1、输入hexo d &#xff0c;上传到git时&#xff0c;报错 看了下git的配置&#xff0c;没有问题&#xff0c;单机过去也能直接到我的git上 可能是传不过去&#xff0c;token的问题 最下面开发者设置&#xff0c;找到…

知网合作商AEPH出版,学生/教师均可投稿,优先录用教育社科领域,往期最快2周见刊

AEPH出版社旗下有5本学术期刊&#xff0c;专门出版自然科学、社会科学研究与教育领域论文的高影响力期刊&#xff0c;拥有正规ISSN号&#xff0c;出版类型涉及应用和理论方面的原创和未曾公开发表的研究论文&#xff0c;分配独立DOI号。AEPH作为中国知网&#xff08;CNKI&#…

当你忘记很久前的 DJANGO + UWSGI 项目是怎么启动的

在后端项目代码推到云服务器后&#xff0c;通常需要手动重启相关服务才会更新生效。 本人生产环境中用的是UWSGI服务器&#xff0c;更新步骤如下&#xff1a; 文章目录 UWSGI服务启动方式SYSTEMCTL 命令查看查看当前运行的 UWSGI 进程其他&#xff1a;查看 UWSGI 日志文件 重启…

Codeforces Round 970 (Div. 3)(ABCDEF)

Codeforces Round 970 (Div. 3) A:Sakurakos Exams 签到 题意:给定1,2的数量,判断是否能用加减符号使得这些1,2计算出0 void solve() {cin>>n>>m;if(n%2)cout<<"NO\n";else{if(m%20||n)cout<<"YES\n";else cout<<"…

H5咖啡品牌官网响应式HTML网站模板源码

源码名称&#xff1a;咖啡品牌官网响应式HTML网站模板源码 源码介绍&#xff1a;一款咖啡品牌官网响应式HTML网站模板源码&#xff0c;源码含有11个页面&#xff0c;可用于咖啡品牌官网。 需求环境&#xff1a;H5 下载地址&#xff1a; https://www.51888w.com/307.html

echarts 柱状图数据集结合堆叠图

效果图&#xff1a; 1.使用echarts的数据集&#xff0c;可以动态展示多组数据统计a,b,c,d…&#xff1b; 2.其中每个数据又使用堆叠图展示详细数据&#xff0c;比如a可以分成成功和失败的次数进行堆叠&#xff0c; 3.所有数据使用不同颜色进行区分&#xff0c;而每个数据的失败…

Makefile学习总结

Makefile学习总结 目录 Makefile学习总结1. Makefile介绍2. Makefile规则3. Makefile文件里的赋值方法4. Makefile常用函数4.1 字符串替换和分析函数4.2 文件名函数4.3 其他函数 5. Makefile使用示例6、多级目录通用Makefile Demo6.1 一般通用Makefile的设计思想6.2 Demo分析 参…

可筛选的课程表设计excel表格@在线写作共享表格课程表设计模板参考

文章目录 abstract表格任务1. 时间段与课次安排2. 课程种类多样3. 教师与教室安排4. 课程颜色编码5. 课表标注 参考方案:样式预览全表添加不影响筛选列的跨列显示内容方案1方案2(pass) 针对指定老师筛选并生成课表&#x1f47a;在线表格链接(wps)要点表格说明&#x1f47a;列交…

Pow(x, n)

优质博文&#xff1a;IT-BLOG-CN 题目 实现pow(x, n) &#xff0c;即计算x的整数n次幂函数&#xff08;即&#xff0c;xn &#xff09;。 示例 1&#xff1a; 输入&#xff1a;x 2.00000, n 10 输出&#xff1a;1024.00000 示例 2&#xff1a; 输入&#xff1a;x 2.100…