java根据模板动态生成pdf文档 (springboot+itext )
1.需求说明
根据业务需要,需要在服务器端生成可动态配置的PDF文档,方便数据可视化查看。
2.前期环境准备
1.Adobe Acrobat DC
链接:https://pan.baidu.com/s/1dYBZb8b75D3BbKDN-lJFPA
提取码:yhju
2.pom文件依赖
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.9</version>
<scope>compile</scope>
</dependency>
3.PDFUtils工具类
package com.shine.common.utils;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Map;
/**
* @author
* Date on 2020/8/10 8:49
*/
public class PDFUtils {
private static final Logger log = LoggerFactory.getLogger(PDFUtils.class);
/**
* 根据pdf模板输出流
* @param templateFilePath 模板目录路径
* @param templateFileName 模板文件名
* @param resultMap 包含文件字段名和值的map
* @return 生成的文件字节流
*/
public static ByteArrayOutputStream createPdfStream(String templateFilePath, String templateFileName,
Map<String, String> resultMap){
ByteArrayOutputStream ba = new ByteArrayOutputStream();
PdfStamper stamp =null;
PdfReader reader = null;
try {
log.debug("file:"+templateFilePath + "/" + templateFileName);
reader = new PdfReader(templateFilePath + "/" + templateFileName);
stamp = new PdfStamper(reader, ba);
//使用字体
BaseFont bf = BaseFont.createFont("STSongStd-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
/* 获取模版中的字段 */
AcroFields form = stamp.getAcroFields();
//填充表单
if (resultMap != null) {
for (Map.Entry<String, String> entry : resultMap.entrySet()) {
form.setFieldProperty(entry.getKey(), "textfont", bf, null);
form.setField(entry.getKey(), entry.getValue()!=null?entry.getValue():"");
}
}
//不能编辑
stamp.setFormFlattening(true);
} catch (IOException e) {
log.error("文档构建I/O异常",e);
} catch (DocumentException e) {
log.error("文档构建异常",e);
}
finally {
if(stamp!=null){
try {
stamp.close();
} catch (DocumentException e) {
log.error("流关闭错误",e);
} catch (IOException e) {
log.error("流关闭错误",e);
}
}
if(reader!=null){
reader.close();
}
}
return ba;
}
}
3.上代码
1.准备模板
使用上面提到的工具将需要生成的word、或者excel生成pdf模板
具体操作:1.双击打开Adobe Acrobat DC工具
2.点击“”工具“”,点击右侧“”准备表单“”选项,然后选择一个文件打开pdf文档
3.修改里面的页面上的文本属性(具体如下图片),鼠标点击右键可修改属性
注:整个文档的定义的文本属性不可重复(后面填充数据需要用到),不然数据会被后面的覆盖
记得把生成的模板放入项目中 ,项目能找到的路径中(相对路径或绝对路径都可以)
2.获取数据并填充模板
controller类
package com.shine.web.controller.subsystem;
import com.spire.pdf.PdfDocument;
import com.shine.common.utils.DateUtils;
import com.shine.common.utils.PDFUtils;
import com.shine.framework.bean.ResponseResult;
import com.shine.subsystem.domain.*;
import com.shine.subsystem.service.*;
import com.shine.system.service.ISysUserService;
import com.spire.pdf.PdfPageBase;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import java.awt.geom.Rectangle2D;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.util.*;
import java.util.List;
@GetMapping("/pdf")
@ResponseBody
public ResponseResult CreatePdf(String userIdFk) throws Exception {
Map<String, String> map = new HashMap(130);
ByteArrayOutputStream pdf = null;
//获取后台数据库数据放入map中(key,value形式)key是模板中命名的文本属性
fileMap(map, userIdFk);
FileOutputStream out = null;
String newPath=null;
try {
//重命名pdf
newPath = userIdFk + ".reviewForm." + UUID.randomUUID().toString() + ".pdf";
//新的文件路径 uploadPath是项目的配置文件命名路径 可以自定义 也可生成到本地磁盘中(最后生成的pdf文档路径)
String newFilePath = uploadPath + "/" + "appraisePath" + "/" + newPath;
//填充模板 这里是pdf模板的位置
pdf = PDFUtils.createPdfStream("/upload/templates", "reviewForm_water.pdf", map);
out = new FileOutputStream(newFilePath);
out.write(pdf.toByteArray());
// pteDeclare.setPdf1("pdf"+ File.separator + newPath);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (pdf != null) {
try {
pdf.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (out != null) {
try {
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
//返回前台路径
return ResponseResult.ok().render((Object) newPath);
}
private Map<String, String> fileMap(Map<String, String> map, String userIdFk) throws Exception {
List<PteInformation> pteInformations = pteInformationService.selectAll(userIdFk);
List<PteQual> pteQuals = pteQualService.selectAll(userIdFk);
List<PteTest> pteTests = pteTestService.selectAll(userIdFk);
if (pteInformations.size() > 0 && pteInformations != null) {
PteInformation pteInformation = pteInformations.get(0);
//姓名
map.put("infos_name", pteInformation.getName());
//工作单位
map.put("infos_work", pteInformation.getWork());
//现任专业技术职务
map.put("infos_postTitle", pteQuals.get(0).getPostTitle());
//评审任职资格
map.put("infos_qualifications", "哈哈哈");
//填表时间
map.put("infos_createTime", DateUtils.dateTime(DateUtils.getNowDate()));
/**
* 基本情况
*/
//现名
map.put("info_name", pteInformation.getName());
//性别
map.put("info_sex", checkSex(pteInformation.getSex()));
//民族
map.put("info_national", pteInformation.getNational());
//曾用名
map.put("info_cname", pteInformation.getCname());
//出生年月
map.put("info_birthday", DateUtils.dateTime(pteInformation.getBirthday()));
//政治面貌
map.put("info_landscape", checkLandscape(pteInformation.getLandscape()));
//户籍所在地
map.put("info_residence", pteInformation.getResidence());
}
return map;
}
3.生成结果
也可通过前台预览 根据接口返回的路径,具体就不详细说啦
谢谢观看!!