现在的业务越来越复杂了,有些业务场景已经不能满足与EXCEL导出和WORD导出了,例如准考证打印,电子证书等等,这些都是动态数据导出的PDF。接下来我们就看一下怎么实现PDF的动态导出吧。
第一步,我们需要制作一个PDF模板,可以先使用WORD去制作,制作完成以后再转为PDF。
当转为PDF以后,我们就需要去给PDF设置表单域了,表单域的名称和你要填充的数据名称需要一一对应。
这里推荐几个可以编辑表单域的软件:Adobe Acrobat 、 万兴PDF、PDFill、Nitro
我这里懒省事用的万兴PDF(免费版有水印),具体哪个更好用一点请大家自行判断。
接下来第二步则是在项目中集成itextpdf,项目中使用的是SpringBoot 2.7 , 同时还集成了lombok.
<dependency>
<groupId>com.itextpdfgroupId>
<artifactId>itextpdfartifactId>
<version>5.5.13version>
dependency>
编写导出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();
}
}
在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
**/
@Override
public 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可以替换为 attachment
response.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();
// 根据模板生成新的PDF
pdfStamper = 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);
}
}
}
}
编写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: 业务接口
**/
@Resource
private 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