一、准备工作
1. POM依赖
Spring Boot这里是用的2.3.12这个版本,比较老,但是别的版本应该也差不多
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.12.RELEASE</spring-boot.version>
</properties>
紧接着放入IText依赖,我是随便放了一个稳定版本,别的版本应该也可以,这个真没有过多的研究,感觉应该差不多,需要使用中文字体的话,导入itext-asian,一般应该是都需要的。
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
2. 准备字体
字体的话是比较方便的,如果是windows电脑的话,自己电脑里面就有,直接去C:\Windows\Fonts
里面就有自己电脑上的字体,一般采用的宋体,这个就看自己的需要了,不过操作的时候可要小心了,别直接裁剪出去了
找到自己需要使用的字体,随后复制到自己项目的resource
目录下,我这里用的是仿宋,不过这个字体有一个坑,下面和大家说
3. 绘制模版
绘制采用Adobe全家桶中的Adobe Acrobat DC,这个工具的主要的功能就是处理PDF,下面给大家提供个老版本的工具,因为只是简单的绘制模版,其次电脑配置不高,就整个简单的画下算了,百度网盘链接:
【Win DC】Acrobat DC CC2019(右键管理员打开setup.exe安装前退出杀毒).rar
链接: https://pan.baidu.com/s/1a_B-E_dhgdFDaJzioEakUw 提取码: vbe3
打开自己word绘制一个表格模版;
这里就简单画画算了
然后直接保存为PDF格式
随后打开PDF处理工具 Adobe Acrobat DC,点击文件,点击创建,创建一个表单
创建表单之后选择文档
选择PDF之后开始绘制模版
双击表单域,之后设置名字,设置字体、
设置好之后,点击保存,保存PDF模版
准备工作结束,接下来进入正题!!!
二、单个模版导出PDF
上代码:首先是到处单个模版的,这个比较简单,直接渲染就可以
测试类中的代码,如果要是导出放在某个位置,可以直接借鉴这个代码
/**
* 导出PDF单个
*/
@Test
void exportPdfByTemplate() {
// 模板文件路径
String inputFileName = "D:\\Code\\pdf-handle\\src\\main\\resources\\pdf\\template.pdf";
// 生成的文件路径
String outputFileName = "D:\\Code\\pdf-handle\\src\\main\\resources\\pdf\\result.pdf";
OutputStream os = null;
PdfStamper ps = null;
PdfReader reader = null;
PdfStamper stamper = null;
try {
os = Files.newOutputStream(new File(outputFileName).toPath());
// 1. 读入PDF表单
reader = new PdfReader(inputFileName);
// 2. 根据表单生成一个新的PDF
ps = new PdfStamper(reader, os);
// 3. 获取PDF表单
AcroFields form = ps.getAcroFields();
// 4. 给表单添加中文字体
BaseFont bf = BaseFont.createFont("font/simsun.ttc,0", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
form.addSubstitutionFont(bf);
// 5. 查询数据
Map<String, Object> data = new HashMap<String, Object>();
data.put("content", "内容");
data.put("name", "姓名");
data.put("leader", "王则");
data.put("sportleader", "王策");
data.put("phone", "188888888");
// 6. 遍历data 给PDF表单表格赋值
for (String key : data.keySet()) {
form.setField(key, data.get(key).toString());
}
ps.setFormFlattening(true);
System.out.println("===============PDF导出成功=============");
} catch (Exception e) {
System.out.println("===============PDF导出失败=============");
e.printStackTrace();
} finally {
try {
ps.close();
reader.close();
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
这里大家注意,如果你也使用的仿宋,那么记得选择,你是使用的仿宋中的那种字体,如果不写索引的话会报错,我当初因为这个问题,研究了好久。
下面是controller接口的实现方法:
/**
* 下载pdf(单个)
*/
@RequestMapping("/exportPdfByTemplate")
public void exportPdfByTemplate(HttpServletResponse response) throws FileNotFoundException {
// 获取模版
String template = "/pdf/template.pdf";
InputStream inputStream = this.getClass().getResourceAsStream(template);
if (inputStream == null) {
throw new FileNotFoundException(template);
}
PdfStamper ps = null;
PdfReader reader = null;
try {
// 1. 读入PDF表单
reader = new PdfReader(inputStream);
// 2. 根据表单生成一个新的PDF
ps = new PdfStamper(reader, response.getOutputStream());
// 3. 获取PDF表单
AcroFields form = ps.getAcroFields();
// 4. 给表单添加中文字体
BaseFont bf = BaseFont.createFont("font/simsun.ttc,0", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
form.addSubstitutionFont(bf);
// 5. 查询数据
Map<String, Object> data = new HashMap<String, Object>();
data.put("content", "内容");
data.put("name", "名称");
data.put("leader", "代表人");
data.put("sportleader", "校长");
data.put("phone", "11635788709");
// 6. 遍历data 给PDF表单表格赋值
for (String key : data.keySet()) {
form.setField(key, data.get(key).toString());
}
ps.setFormFlattening(true);
System.out.println("===============PDF导出成功=============");
} catch (Exception e) {
System.out.println("===============PDF导出失败=============");
e.printStackTrace();
} finally {
try {
if (ps != null) {
ps.close();
}
if (reader != null) {
reader.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
三、遍历模版导出PDF
这种肯定是比较简单,但是很多时候会有一些复杂的情况出现,比如我们pdf很复杂,并且需要生成多个,并且每个表格一样但是里面的数据不一样,我在网上找了很多方案,最后想到一种方法,直接遍历模版,填充数据,最后将模版拼装起来,这样就实现了我们想要的效果
类似于这种:
话不多说,我就直接上代码了,核心的原理就是,先拿到数据,然后遍历数组,获取模版,填充数据,之后合并生成的PDF,最后导出。
首先是测试代码:
/**
* 导出PDF遍历
*/
@Test
void exportPdfByForTemplate() {
try {
// 模板文件路径
String inputFileName = "D:\\Code\\pdf-handle\\src\\main\\resources\\pdf\\forTemplate.pdf";
// 生成的文件路径
String outputFileName = "D:\\Code\\pdf-handle\\src\\main\\resources\\pdf\\forResult.pdf";
List<Map<String, Object>> data = initData();
// 创建输出文件流
OutputStream outputStream = Files.newOutputStream(Paths.get(outputFileName));
Document document = new Document();
PdfCopy copy = new PdfCopy(document, outputStream);
document.open();
for (Map<String, Object> datum : data) {
ByteArrayOutputStream tempOutputStream = new ByteArrayOutputStream();
PdfStamper ps = null;
PdfReader reader = null;
try {
// 1. 读入PDF表单
reader = new PdfReader(inputFileName);
// 2. 根据表单生成一个新的PDF
ps = new PdfStamper(reader, tempOutputStream);
// 3. 获取PDF表单
AcroFields form = ps.getAcroFields();
// 4. 给表单添加中文字体
BaseFont bf = BaseFont.createFont("font/simsun.ttc,0", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
form.addSubstitutionFont(bf);
// 5. 遍历datum 给pdf表单表格赋值
for (String key : datum.keySet()) {
form.setField(key, datum.get(key).toString());
}
ps.setFormFlattening(true);
ps.close();
// 读取临时生成的PDF内容
ByteArrayInputStream tempInputStream = new ByteArrayInputStream(tempOutputStream.toByteArray());
PdfReader tempReader = new PdfReader(tempInputStream);
// 合并PDF
int numberOfPages = tempReader.getNumberOfPages();
for (int i = 1; i <= numberOfPages; ) {
copy.addPage(copy.getImportedPage(tempReader, i++));
}
tempReader.close();
System.out.println("===============PDF部分导出成功=============");
} catch (Exception e) {
System.out.println("===============PDF部分导出失败=============");
e.printStackTrace();
} finally {
try {
if (ps != null) {
ps.close();
}
if (reader != null) {
reader.close();
}
tempOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
document.close();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 初始化数据
public List<Map<String, Object>> initData() {
List<Map<String, Object>> res = new ArrayList<>();
for (int i = 0; i < 3; i++) {
Map<String, Object> item = new HashMap<>();
item.put("name", "条目名称_" + i);
item.put("selfScore", "10_" + i);
item.put("againScore", "11_" + i);
item.put("finallyScore", "12_" + i);
item.put("content", "检查过程、资料来源及计算的文字说明_" + i);
item.put("Instructions", "简要说明_" + i);
item.put("leader", "王晨林_" + i);
item.put("leads", "王策_" + i);
item.put("time", "2024年12月0" + (i + 1) + "日");
res.add(item);
}
return res;
}
下面是controller接口的实现方法:
/**
* 下载pdf(多个)
*/
@RequestMapping("/exportPdfByForTemplate")
public void exportPdfByForTemplate(HttpServletResponse response) throws IOException {
try {
// 模版url
String template = "/pdf/forTemplate.pdf";
// 总PDF
ServletOutputStream outputStream = response.getOutputStream();
Document document = new Document();
PdfCopy copy = new PdfCopy(document, outputStream);
document.open();
// 获取数据
List<Map<String, Object>> data = initData();
for (Map<String, Object> datum : data) {
try {
// 获取拆分的需要遍历模版
InputStream inputStream = this.getClass().getResourceAsStream(template);
if (inputStream == null) {
throw new FileNotFoundException(template);
}
ByteArrayOutputStream tempOutputStream = new ByteArrayOutputStream();
PdfStamper ps = null;
PdfReader reader = null;
// 读入pdf表单
reader = new PdfReader(inputStream);
// 根据表单生成一个新的pdf
ps = new PdfStamper(reader, tempOutputStream);
// 获取pdf表单
AcroFields form = ps.getAcroFields();
// 给表单添加中文字体
BaseFont bf = BaseFont.createFont("font/simsun.ttc,0", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
form.addSubstitutionFont(bf);
// 查询数据
// 遍历data 给pdf表单表格赋值
for (String key : datum.keySet()) {
form.setField(key, datum.get(key).toString());
}
ps.setFormFlattening(true);
ps.close();
reader.close();
// 读取临时生成的PDF内容
ByteArrayInputStream tempInputStream = new ByteArrayInputStream(tempOutputStream.toByteArray());
PdfReader tempReader = new PdfReader(tempInputStream);
// 合并PDF
int numberOfPages = tempReader.getNumberOfPages();
for (int i = 1; i <= numberOfPages; ) {
copy.addPage(copy.getImportedPage(tempReader, i++));
}
tempReader.close();
System.out.println("===============PDF部分导出成功=============");
} catch (Exception e) {
System.out.println("===============PDF部分导出失败=============");
e.printStackTrace();
}
}
document.close();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
// 初始化数据
public List<Map<String, Object>> initData() {
List<Map<String, Object>> res = new ArrayList<>();
for (int i = 0; i < 3; i++) {
Map<String, Object> item = new HashMap<>();
item.put("name", "条目名称_" + i);
item.put("selfScore", "10_" + i);
item.put("againScore", "11_" + i);
item.put("finallyScore", "12_" + i);
item.put("content", "检查过程、资料来源及计算的文字说明_" + i);
item.put("Instructions", "简要说明_" + i);
item.put("leader", "王晨林_" + i);
item.put("leads", "王策_" + i);
item.put("time", "2024年12月0" + (i + 1) + "日");
res.add(item);
}
return res;
}
到此我的业务需求就实现了,如果有业务更复杂的小伙伴,可以自己再研究研究!!
下面是测试项目的地址,其实内容在上面已经贴出来了,供大家参考:
https://gitee.com/wwttz/pdf-handle
如果有什么问题,可以给我留言,看到必答。