文章目录
- 前言
- 一、准备工作
- 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", "女");// 在项目中生成pdfFillFormFieldsToPdfTemplateUtil 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效果如下: