文章目录
- 前言
- 一、准备工作
- 1.1 安装软件
- 1.2 准备pdf
- 1.3 设置表单域
- 二、创建项目
- 三、编写代码
- 3.1 编写工具类
- 3.2 测试
- 四、测试结果
前言
最近手上有个任务,就是需要做一个pdf导出的功能。
可选择的技术点比较多,我这边综合考虑之后,使用的是 itext。
大致有两种实现思路:
1️⃣:使用软件【Adobe Acrobat DC】去做一个pdf模版,将表单域指定好,随后使用代码去填充参数,最终得到一个pdf或字节数组。
2️⃣:使用【Freemarker】渲染html页面,最终使用代码将该页面转换为pdf。
我这边当前的需求比较适合第一种方式。
一、准备工作
1.1 安装软件
首先是安装软件(失效的话麻烦评论区留言)
链接:https://pan.baidu.com/s/1O8JtVuK87VYbzx0DGQyJ1g
提取码:a0hy
1.2 准备pdf
新建一个word文档,插入一个表格,效果如下:
然后将word导出为pdf文件。
再使用我们刚刚安装好的软件打开。
1.3 设置表单域
在工具栏中找到“准备表单”功能,点击打开。
然后修改自己想要的文本域字段名、字体大小等配置:
也可以右键新增文本域(我这里的title就是新增的):
随后我们保存pdf即可。最终我们会得到这样一个pdf:
二、创建项目
创建一个普通的 springboot项目,引入依赖如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext7-core</artifactId>
<version>7.2.5</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
在 resources
目录中创建 templates
文件夹,并将我们前边准备好的pdf放进去。效果如下:
我这里准备的文件是 personal_info.pdf
。
三、编写代码
3.1 编写工具类
package org.feng.pdf;
import com.itextpdf.forms.PdfAcroForm;
import com.itextpdf.forms.fields.PdfFormField;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.ResourceUtils;
import java.io.*;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
* 填充pdf表单域
*
* @author fengjinsong
* @date 2023-06-28 16:55:38
* @version: 1.0
*/
@Slf4j
@Data
public class FillFormFieldsToPdfTemplateUtil {
/**
* 默认模板路径
*/
private static final String DEFAULT_TEMPLATE_DIRECTORY;
/**
* 默认字体(pdf显示中文)
*/
private static final PdfFont DEFAULT_FONT;
/**
* 模版路径
*/
private String templateDirectory;
/**
* 字体
*/
private PdfFont pdfFont;
public FillFormFieldsToPdfTemplateUtil() {
}
static {
try {
DEFAULT_TEMPLATE_DIRECTORY = ResourceUtils.getURL("classpath:").getPath() + "/templates/";
DEFAULT_FONT = PdfFontFactory.createFont("STSong-Light", "UniGB-UCS2-H", PdfFontFactory.EmbeddingStrategy.PREFER_NOT_EMBEDDED);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 依据pdf模板,填充表单域,生成pdf文件
*
* @param templateFileName 模版文件名,不能为空
* @param formFieldsParams 表单域需要的参数,不能为空
* @param destPdfPath 目标位置
* @throws IOException 涉及到文件操作
*/
public void generatePdfFile(String templateFileName, String destPdfPath, Map<String, String> formFieldsParams) throws IOException {
// 构造pdf阅读器,指定输入流
PdfReader pdfReader = new PdfReader(new FileInputStream(getTemplatePath(templateFileName)));
// 构造pdf写出器,指定输出流
OutputStream fos = new FileOutputStream(destPdfPath);
// 构造pdfDocument实例
PdfDocument pdf = new PdfDocument(pdfReader, new PdfWriter(fos));
// 设置为a4纸张大小
pdf.setDefaultPageSize(PageSize.A4);
// 替换参数(如果有参数的话)
PdfAcroForm form = PdfAcroForm.getAcroForm(pdf, true);
fillFormFields(form, formFieldsParams);
// 锁定表单,不让修改
form.flattenFields();
pdf.close();
}
/**
* 根据pdf模版和表单域参数获取pdf对应的字节数组
*
* @param templateFileName 模版文件名,不能为空
* @param formFieldsParams 表单域需要的参数,不能为空
* @return 填充了表单域参数之后的pdf字节数组
* @throws IOException 涉及到文件操作
*/
public byte[] generatePdfByteArray(String templateFileName, Map<String, String> formFieldsParams) throws IOException, RuntimeException {
// 构造pdf阅读器,指定输入流
PdfReader pdfReader = new PdfReader(new FileInputStream(getTemplatePath(templateFileName)));
// 构造pdf写出器,指定输出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
PdfWriter pdfWriter = new PdfWriter(outputStream);
// 构造pdfDocument实例
PdfDocument pdf = new PdfDocument(pdfReader, pdfWriter);
// 设置为a4纸张大小
pdf.setDefaultPageSize(PageSize.A4);
// 替换参数(如果有参数的话)
PdfAcroForm form = PdfAcroForm.getAcroForm(pdf, true);
fillFormFields(form, formFieldsParams);
// 锁定表单,不让修改
form.flattenFields();
// 关闭资源
pdf.close();
// 返回最终结果
return outputStream.toByteArray();
}
/**
* 获取模版文件路径
*
* @param templateFileName 模版文件名,在指定的模版目录下
* @return 模版文件路径
*/
private String getTemplatePath(String templateFileName) {
// 拼接完整模版路径
return Objects.nonNull(templateDirectory) ?
templateDirectory + templateFileName : DEFAULT_TEMPLATE_DIRECTORY + templateFileName;
}
/**
* 填充表单域
*
* @param form 表单
* @param formFieldsParams 表单需要的动态参数
*/
private void fillFormFields(PdfAcroForm form, Map<String, String> formFieldsParams) {
// 获取所有的表单域
Map<String, PdfFormField> fields = form.getFormFields();
// 获取当前字体
PdfFont currentFont = pdfFont == null ? DEFAULT_FONT : pdfFont;
formFieldsParams.forEach((key, value) -> {
// 获取某个表单域
Optional<PdfFormField> formFieldOptional = Optional.ofNullable(fields.get(key));
formFieldOptional.ifPresent(formField -> {
// 设置字体、替换值
formField.setFont(currentFont).setValue(value);
});
});
}
}
3.2 测试
准备一个 Controller 类,如下:
package org.feng.controller;
import lombok.extern.slf4j.Slf4j;
import org.feng.pdf.FillFormFieldsToPdfTemplateUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* itextpdf7控制器
*
* @author fengjinsong
* @date 2023-06-29 09:00:04
* @version: 1.0
*/
@Controller
@Slf4j
public class ItextPdf7Controller {
/**
* 下载文件
*
* @param response 响应对象;用于设置响应参数
*/
@GetMapping("/download")
public ResponseEntity<byte[]> download(HttpServletResponse response) throws IOException {
HttpHeaders headers = new HttpHeaders();
// application/octet-stream二进制流数据(文本下载)。
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
// 下载显示的文件名,解决中文名称乱码问题
String downloadFileName = System.currentTimeMillis() + ".pdf";
// 弹出保存框即资源选择器
response.setHeader("Content-Disposition", "attachment;filename=" + downloadFileName);
// 组装参数
Map<String, String> data = new HashMap<>();
data.put("title", "个人简介");
data.put("name", "我的某个朋友");
data.put("age", "30");
data.put("likes", "女");
data.put("birthday", "2022-12-22");
// 使用数据填充pdf模版并转换为字节数组
FillFormFieldsToPdfTemplateUtil pdfTemplateUtil = new FillFormFieldsToPdfTemplateUtil();
byte[] bytes = pdfTemplateUtil.generatePdfByteArray("personal_info.pdf", data);
// 返回结果
return new ResponseEntity<>(bytes, headers, HttpStatus.CREATED);
}
@ResponseBody
@GetMapping("/generate")
public String generatePdfFile() throws IOException {
// 指定输出的目录
String path = ResourceUtils.getURL("classpath:").getPath() + "/templates/";
// 组装参数
Map<String, String> data = new HashMap<>();
data.put("title", "个人简介");
data.put("name", "我的某个朋友");
data.put("age", "30");
data.put("birthday", "2022-12-22");
data.put("likes", "女");
// 在项目中生成pdf
FillFormFieldsToPdfTemplateUtil pdfTemplateUtil = new FillFormFieldsToPdfTemplateUtil();
pdfTemplateUtil.generatePdfFile("personal_info.pdf", path + "dsadsad.pdf", data);
// 返回结果
return "success";
}
}
四、测试结果
在项目中生成pdf文件,发送请求 GET http://localhost:8080/generate
下载生成的pdf文件,发送请求 GET http://localhost:8080/download
生成的pdf效果如下: