Java基于itextPDF实现pdf动态导出
Java基于itextPDF实现pdf动态导出
- 1、制作PDF导出模板
- 2 、集成itextpdf
- 3 、编写实体
- 4 、编写主要代码
- 5、编写controller并测试
- 补充:踩坑记录
现在的业务越来越复杂了,有些业务场景已经不能满足与EXCEL导出和WORD导出了,例如准考证打印,电子证书等等,这些都是动态数据导出的PDF。接下来我们就看一下怎么实现PDF的动态导出吧。
1、制作PDF导出模板
第一步,我们需要制作一个PDF模板,可以先使用WORD去制作,制作完成以后再转为PDF。


当转为PDF以后,我们就需要去给PDF设置表单域了,表单域的名称和你要填充的数据名称需要一一对应。

这里推荐几个可以编辑表单域的软件:Adobe Acrobat 、 万兴PDF、PDFill、Nitro
我这里懒省事用的万兴PDF(免费版有水印),具体哪个更好用一点请大家自行判断。
2 、集成itextpdf
接下来第二步则是在项目中集成itextpdf,项目中使用的是SpringBoot 2.7 , 同时还集成了lombok.
<dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.13</version>
</dependency>
3 、编写实体
编写导出PDF需要用到的实体,这里注意,实体中的属性名需要和表单域名一一对应。
同时为了方便测试,在无参构造中初始化了一些默认数据。
package com.vinci.pdf.entity;import lombok.Data;/*** @package: com.vinci.pdf.entity* @className: Person* @author: Vinci* @description: 测试用实体* @date: 2023/11/13 9:56*/
@Data
public class Person {/*** @description: 姓名**/private String name;/*** @description: 国籍**/private String nationality;/*** @description: 居住地**/private String address;/*** @description: 民族**/private String nation;/*** @description: 户籍地**/private String registeredResidence;/*** @description: 身高 / 体重**/private String heightAndWeight;/*** @description: 婚姻状况**/private String maritalStatus;/*** @description: 年龄**/private Integer age;/*** @description: 照片**/private String largeHeadPhoto;/*** @description: 这里为了方便测试,在无参构造直接初始化数据来模拟持久化数据。**/public Person() {this.name = "vinci";this.nationality = "中国";this.address = "江苏南京";this.nation = "汉族";this.registeredResidence = "河南漯河";this.heightAndWeight = "178cm / 65Kg";this.maritalStatus = "未婚";this.age = 24;this.largeHeadPhoto = Thread.currentThread().getContextClassLoader().getResource("static/header1.jpg").getFile();}}
4 、编写主要代码
在Service实现类中编写主要功能,将数据填充到PDF中并实现导出。
package com.vinci.pdf.service.impl;import com.itextpdf.text.Document;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.vinci.pdf.entity.Person;
import com.vinci.pdf.service.api.PdfGenerateTestService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Objects;/*** @package: com.vinci.pdf.service.impl* @className: PdfGenerateTestServiceImpl* @author: Vinci* @description: pdf生成测试接口实现* @date: 2023/11/13 10:15*/
@Service
public class PdfGenerateTestServiceImpl implements PdfGenerateTestService {/*** @description: 日志服务**/private static final Logger log = LoggerFactory.getLogger(PdfGenerateTestServiceImpl.class);/*** @description: pdf生成* @author: Vinci* @date: 2023/11/13 10:25**/@Overridepublic void pdfGenerate(HttpServletResponse response) throws UnsupportedEncodingException {// 模板地址URL resource = Thread.currentThread().getContextClassLoader().getResource("templates/aipuu-y1mhx.pdf");if(resource == null){throw new RuntimeException("没有找到模板");}String path = resource.getPath();// PDF的文件名称 及响应头String fileName = "test.pdf";fileName = URLEncoder.encode(fileName, "UTF-8");response.setContentType("application/force-download");//如果想要下载文件的话,这里的inline可以替换为 attachmentresponse.setHeader("Content-Disposition","fileName=" + fileName);OutputStream ops = null;ByteArrayOutputStream bos = null;PdfStamper pdfStamper = null;PdfReader pdfReader = null;try {ops = response.getOutputStream();pdfReader = new PdfReader(path);bos = new ByteArrayOutputStream();// 根据模板生成新的PDFpdfStamper = new PdfStamper(pdfReader, bos);AcroFields form = pdfStamper.getAcroFields();// 设置字体BaseFont font = BaseFont.createFont("C:/WINDOWS/Fonts/SIMSUN.TTC,1",BaseFont.IDENTITY_H,BaseFont.EMBEDDED);form.addSubstitutionFont(font);// 获取数据(这里在无参构造中生成了一些数据,实际开发中可用持久化数据来代替)Person person = new Person();// 通过反射遍历来给PDF中的表单生成数据Field[] fields = person.getClass().getDeclaredFields();for (Field field : fields) {field.setAccessible(true);String key = field.getName();Object value = field.get(person);if(!Objects.equals(key,"largeHeadPhoto")){//处理文本数据form.setField(key, value.toString());}else{// 通过表单域名获取所在页和坐标,左下角为起点int pageNo = form.getFieldPositions(key).get(0).page;Rectangle signRect = form.getFieldPositions(key).get(0).position;float x = signRect.getLeft();float y = signRect.getBottom();// 读图片Image image = Image.getInstance(value.toString());// 获取操作的页面PdfContentByte under = pdfStamper.getOverContent(pageNo);// 根据域的大小缩放图片image.scaleToFit(signRect.getWidth(), signRect.getHeight());// 添加图片image.setAbsolutePosition(x, y);under.addImage(image);}}// 设置PDF为只读pdfStamper.setFormFlattening(true);// 关闭资源pdfStamper.close();Document doc = new Document();PdfCopy copy = new PdfCopy(doc, ops);doc.open();PdfImportedPage importPage = copy.getImportedPage(new PdfReader(bos.toByteArray()), 1);copy.addPage(importPage);doc.close();}catch (Exception e){log.error("发现异常",e);}finally {try {if (ops != null) {ops.flush();ops.close();}if (pdfReader != null) {pdfReader.close();}}catch (Exception e){log.error("发现异常",e);}}}}
5、编写controller并测试
编写Controller来方便我们通过浏览器的请求的方式去测试
package com.vinci.pdf.controller;import com.vinci.pdf.service.api.PdfGenerateTestService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;/*** @package: com.vinci.pdf.controller* @className: PdfGenerateTestController* @author: Vinci* @description:pdf生成测试controller* @date: 2023/11/13 10:16*/
@RestController
@RequestMapping("/pdf")
public class PdfGenerateTestController {/*** @description: 日志打印**/private static final Logger log = LoggerFactory.getLogger(PdfGenerateTestController.class);/*** @description: 业务接口**/@Resourceprivate PdfGenerateTestService pdfGenerateTestService;/*** @description: 测试pdf生成* @author: Vinci* @date: 2023/11/13 10:17**/@GetMapping(value = "/generate")public void pdfGenerate(HttpServletResponse response){try{pdfGenerateTestService.pdfGenerate(response);}catch (Exception e){log.error("发现异常",e);}}
}
这里我们打开浏览器访问 http://localhost:8080/pdf/generate 发现PDF已经在下载了

下载成功后我们打开,发现里面已经有数据了。

补充:踩坑记录
使用万兴PDF编辑图片类型的表单域时一定要注意,去掉背景色,否则导出后你会看不到图片

本文代码下载地址:https://gitee.com/vinci99/springboot-pdf-generate.git
相关文章:
Java基于itextPDF实现pdf动态导出
Java基于itextPDF实现pdf动态导出 1、制作PDF导出模板2 、集成itextpdf3 、编写实体4 、编写主要代码5、编写controller并测试补充:踩坑记录 现在的业务越来越复杂了,有些业务场景已经不能满足与EXCEL导出和WORD导出了,例如准考证打印&#x…...
【Liunx】配置IP地址与MAC地址绑定
配置IP地址与MAC地址绑定 A.查询MAC地址B.绑定前的准备1.资源:(1) 服务器Server1:192.168.122.1(2) 服务器Server1:192.168.122.2 2. Server1按照dhcp服务 C.开始绑定操作1.修改dhcp配置文件2.生效 A.查询MAC地址 点击这里查看【如何查询服务器IP与MAC地址】 B.绑定…...
Mybatis-Plus最新教程
目录 原理:MybatisPlus通过扫描实体类,并基于反射获取实体类信息作为数据库信息。 编辑1.添加依赖 2.常用注解 3.常见配置: 4.条件构造器 5.QueryWrapper 6.UpdateWrapper 7.LambdaQueryWrapper:避免硬编码 8.自定义SQL 9.Iservic…...
【Shell脚本11】Shell 函数
Shell 函数 linux shell 可以用户定义函数,然后在shell脚本中可以随便调用。 shell中函数的定义格式如下: [ function ] funname [()]{action;[return int;]}说明: 1、可以带function fun() 定义,也可以直接fun() 定义,不带任何…...
STM32中独立看门狗和窗口看门狗的使用方法
独立看门狗(Independent Watchdog,IWDG)和窗口看门狗(Window Watchdog,WWDG)是STM32微控制器中提供的两种看门狗定时器。看门狗定时器是一种硬件计时器,用于监视系统的运行状态,并在…...
刷题笔记(第七天)
1.找出对象 obj 不在原型链上的属性(注意这题测试例子的冒号后面也有一个空格~) 返回数组,格式为 key: value结果数组不要求顺序 输入: var C function() {this.foo ‘bar’; this.baz ‘bim’;}; C.prototype.bop ‘bip’; iterate(new C()); 输出…...
python3.7 + pygame1.9.3实现小游戏《外星人入侵》(五):计分
本小节首先在游戏画面中添加一个Play按钮,用于根据需要启动游戏,为此在game_stats.py中输入以下代码: class GameStats():def __init__(self,ai_settings):# 初始化统计信息self.ai_settings ai_settingsself.reset_stats()#让游戏一开始处…...
[量化投资-学习笔记014]Python+TDengine从零开始搭建量化分析平台-Python知识点汇总
以下内容总结了之前章节涉及到的 Python 知识点,看过之前的章节同学,就不用打开了。 1. Restful 访问 TDengine 数据库 知识点: 发送给 TDengine 的 HTTP Body 里面是 SQL 明文,请求方式为 POST。TDenging 返回的结果是 JSON 格…...
[论文分享] Never Mind the Malware, Here’s the Stegomalware
Never Mind the Malware, Here’s the Stegomalware [IEEE Security & Privacy 2022] Luca Caviglione | National Research Council of Italy Wojciech Mazurczyk | Warsaw University of Technology and FernUniversitt in Hagen 近年来,隐写技术已逐渐被观…...
代号:408 —— 1000道精心打磨的计算机考研题
文章目录 📋前言🎯计算机科学与技术专业介绍(14年发布)🧩培养目标🧩毕业生应具备的知识和能力🧩主要课程 🎯代号:408🔥文末送书🧩有什么优势&…...
《QT从基础到进阶·十六》QT实现客户端和服务端的简单交互
QT版本:5.15.2 VS版本:2019 客户端程序主要包含三块:连接服务器,发送消息,关闭客户端 服务端程序主要包含三块:打开消息监听,接收消息并反馈,关闭服务端 1、先打开服务端监听功能 …...
行业追踪,2023-11-13
自动复盘 2023-11-13 凡所有相,皆是虚妄。若见诸相非相,即见如来。 k 线图是最好的老师,每天持续发布板块的rps排名,追踪板块,板块来开仓,板块去清仓,丢弃自以为是的想法,板块去留让…...
开放领域对话系统架构
开放领域对话系统是指针对非特定领域或行业的对话系统,它可以与用户进行自由的对话,不受特定领域或行业的知识和规则的限制。开放领域对话系统需要具备更广泛的语言理解和生成能力,以便与用户进行自然、流畅的对话。 与垂直领域对话系统相比…...
终端神器:tmux
安装tmux简单使用自己的理解(小白专属) 使用的初衷: 在Linux终端下,由于session(会话)和windows(窗口)是绑定一起的,你打开一个终端的黑窗口就是打开一个会话,…...
Elasticsearch学习(一)
ElasticSearch学习(一) 1 什么是Elasticsearch 1.什么是搜索? 百度:我们比如说想找寻任何信息时候就会上百度上搜索一下 比如说:电影、图片、小说等等…(提到搜索的第一印象) 百度 &#x…...
CSS3的常见边框汇总
<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>CSS3 边框</title><style>body, ul, li, dl, dt, dd, h1, h2, h3, h4, h5 {margin: 0;padding: 0;}body {background-color: #F7F7F7;}.wr…...
酷柚易汛ERP-购货订单操作指南
1、应用场景 先下购货订单,收货入库后生成购货单。 2、主要操作 2.1 新增购货订单 打开【购货】-【购货订单】新增购货订单。(*为必填项,其他为选填) ① 录入供应商:点击供应商字段框的 ,在弹框中选择供…...
【数据仓库】数仓分层方法详解与层次调用规范
文章目录 一. 数仓分层的意义1. 清晰数据结构。2. 减少重复开发3. 方便数据血缘追踪4. 把复杂问题简单化5. 屏蔽原始数据的异常6. 数据仓库的可维护性 二. 如何进行数仓分层?1. ODS层2. DW层2.1. DW层分类2.2. DWD层2.3. DWS 3. ADS层 4、层次调用规范 一. 数仓分层…...
记一次线上问题引发的对 Mysql 锁机制分析
背景 最近双十一开门红期间组内出现了一次因 Mysql 死锁导致的线上问题,当时从监控可以看到数据库活跃连接数飙升,导致应用层数据库连接池被打满,后续所有请求都因获取不到连接而失败 整体业务代码精简逻辑如下: Transaction p…...
Android 工厂模式距离传感器逻辑优化
Android 工厂模式距离传感器逻辑优化 接到客户反馈提到距离传感器校准完毕之后,每次测试完成界面都会弹出“请点击校准按钮进行校准!”Toast弹窗,需要对弹窗的显示逻辑进行优化,即只让其在首次进入距离传感器测试界面时弹出&#…...
1.3 VSCode安装与环境配置
进入网址Visual Studio Code - Code Editing. Redefined下载.deb文件,然后打开终端,进入下载文件夹,键入命令 sudo dpkg -i code_1.100.3-1748872405_amd64.deb 在终端键入命令code即启动vscode 需要安装插件列表 1.Chinese简化 2.ros …...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...
算法:模拟
1.替换所有的问号 1576. 替换所有的问号 - 力扣(LeetCode) 遍历字符串:通过外层循环逐一检查每个字符。遇到 ? 时处理: 内层循环遍历小写字母(a 到 z)。对每个字母检查是否满足: 与…...
Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
HybridVLA——让单一LLM同时具备扩散和自回归动作预测能力:训练时既扩散也回归,但推理时则扩散
前言 如上一篇文章《dexcap升级版之DexWild》中的前言部分所说,在叠衣服的过程中,我会带着团队对比各种模型、方法、策略,毕竟针对各个场景始终寻找更优的解决方案,是我个人和我司「七月在线」的职责之一 且个人认为,…...
rknn toolkit2搭建和推理
安装Miniconda Miniconda - Anaconda Miniconda 选择一个 新的 版本 ,不用和RKNN的python版本保持一致 使用 ./xxx.sh进行安装 下面配置一下载源 # 清华大学源(最常用) conda config --add channels https://mirrors.tuna.tsinghua.edu.cn…...
第八部分:阶段项目 6:构建 React 前端应用
现在,是时候将你学到的 React 基础知识付诸实践,构建一个简单的前端应用来模拟与后端 API 的交互了。在这个阶段,你可以先使用模拟数据,或者如果你的后端 API(阶段项目 5)已经搭建好,可以直接连…...
Vue3中的computer和watch
computed的写法 在页面中 <div>{{ calcNumber }}</div>script中 写法1 常用 import { computed, ref } from vue; let price ref(100);const priceAdd () > { //函数方法 price 1price.value ; }//计算属性 let calcNumber computed(() > {return ${p…...
