前言

最近,接到一个模板打印pdf的任务,后来网上找了很多案例,本文做下记录。

如何开始

添加依赖包

<!-- thymeleaf -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!--FlyingSaucer-->
<dependency><groupId>org.xhtmlrenderer</groupId><artifactId>flying-saucer-pdf</artifactId><version>9.1.9</version>
</dependency>

yml配置(可选,不配置默认也可)

spring:# thymeleafthymeleaf:prefix: classpath:/templates/check-template-location: truesuffix: .htmlencoding: UTF-8mode: HTMLcache: falseservlet:content-type: text/html

模板文件

如下文件放置位置,文件都在我的开源项目中,文末有地址。

pdfPage模板文件:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.w3.org/1999/xhtml" layout:decorator="layout">
<head lang="en"><title>Spring Boot Demo - PDF</title><style>@page {size: 210mm 297mm; /*设置纸张大小:A4(210mm 297mm)、A3(297mm 420mm) 横向则反过来*/margin: 0.25in;padding: 1em;@bottom-center{content:"葫芦科技 © 版权所有";font-family: SimSun;font-size: 12px;color:red;};@top-center { content: element(header) };@bottom-right{content:"第" counter(page) "页  共 " counter(pages) "页";font-family: SimSun;font-size: 12px;color:#000;};}body{font-family: 'SimSun'}h2{color: crimson}#myheader{width: 500px;height: 22px;border: 1px solid #000000;}table, th , td  {border: 1px solid grey;border-collapse: collapse;padding: 5px;}table tr:nth-child(odd) {background-color: #f1f1f1;}table tr:nth-child(even) {background-color: #ffffff;}#input1{border-bottom: 1px solid #000000;}</style>
</head>
<!--这样配置不中文不会显示-->
<!--<body style="font-family: 宋体">-->
<body style="font-family: 'SimSun'">
<div>1.标题-中文</div>
<h2 th:text="${title}"></h2><div>2.按钮:按钮的边框需要写css渲染</div>
<button class="a" style="border: 1px solid #000000"> click me t-p</button>
<div id="divsub"></div><div>3.普通div</div>
<div id="myheader">Alice's Adventures in Wonderland</div><div>4.图片 绝对定位到左上角(注意:图片必须用全路径或者http://开头的路径,否则无法显示)</div>
<img th:src="${imageUrl}"/><div>5.普通table表格</div>
<div><table style="width: 700px"><tr><th>姓名</th><th>昵称</th><th>年龄</th></tr><tr th:each="info : ${demoList}"><td th:text="${info.name}"></td><td th:text="${info.nick}"></td><td th:text="${info.age}"></td></tr></table></div><div>6.input控件,边框需要写css渲染 (在模板中一般不用input,因为不存在输入操作)</div>
<div><label>姓名:</label><input id="input1" aria-label="葫芦胡" type="text" value="葫芦胡"/>
</div>
</body>
</html>

避坑:

测试的时候,模板文件中一定至少有一个:th:text="${xxx}",否则会报模板不存在

图片路径需绝对路径,如:http://localhost:8088/hu/imgs/sg.jpg

核心处理类

工具类

/*** pdf处理工具类** @author gourd.hu* @version 1.0*/
@Slf4j
public class PdfUtil {/*** 按模板和参数生成html字符串,再转换为flying-saucer识别的Document** @param templateName 模板名称* @param variables   模板参数* @return Document*/private static Document generateDoc(TemplateEngine templateEngine, String templateName, Map<String, Object> variables)  {// 声明一个上下文对象,里面放入要存到模板里面的数据final Context context = new Context();context.setVariables(variables);StringWriter stringWriter = new StringWriter();try(BufferedWriter writer = new BufferedWriter(stringWriter)) {templateEngine.process(templateName,context, writer);writer.flush();DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();return builder.parse(new ByteArrayInputStream(stringWriter.toString().getBytes()));}catch (Exception e){ResponseEnum.TEMPLATE_PARSE_ERROR.assertFail(e);}return null;}/*** 核心: 根据freemarker模板生成pdf文档** @param templateEngine 配置* @param templateName 模板名称* @param out          输出流* @param listVars     模板参数* @throws Exception 模板无法找到、模板语法错误、IO异常*/private static void generateAll(TemplateEngine templateEngine, String templateName, OutputStream out, List<Map<String, Object>> listVars) throws Exception {// 断言参数不为空ResponseEnum.TEMPLATE_DATA_NULL.assertNotEmpty(listVars);ITextRenderer renderer = new ITextRenderer();//设置字符集(宋体),此处必须与模板中的<body style="font-family: SimSun">一致,区分大小写,不能写成汉字"宋体"ITextFontResolver fontResolver = renderer.getFontResolver();//避免中文为空设置系统字体fontResolver.addFont("static/fonts/simsun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);//根据参数集个数循环调用模板,追加到同一个pdf文档中//(注意:此处从1开始,因为第0是创建pdf,从1往后则向pdf中追加内容)for (int i = 0; i < listVars.size(); i++) {Document docAppend = generateDoc(templateEngine, templateName, listVars.get(i));renderer.setDocument(docAppend, null);//展现和输出pdfrenderer.layout();if(i==0){renderer.createPDF(out, false);}else {//写下一个pdf页面renderer.writeNextDocument();}}renderer.finishPDF(); //完成pdf写入}/*** pdf下载** @param templateEngine   配置* @param templateName 模板名称* @param listVars     模板参数集* @param response     HttpServletResponse* @param fileName     下载文件名称*/public static void download(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response, String fileName) {// 设置编码、文件ContentType类型、文件头、下载文件名response.setCharacterEncoding("utf-8");response.setContentType("multipart/form-data");try {response.setHeader("Content-Disposition", "attachment;fileName=" +new String(fileName.getBytes("gb2312"), "ISO8859-1"));} catch (UnsupportedEncodingException e) {log.error(e.getMessage(), e);}try (ServletOutputStream out = response.getOutputStream()) {generateAll(templateEngine, templateName, out, listVars);out.flush();} catch (Exception e) {log.error(e.getMessage(), e);}}/*** pdf下载到特定位置** @param templateEngine   配置* @param templateName 模板名称* @param listVars     模板参数集* @param filePath     下载文件路径*/public static void save(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, String filePath) {try (OutputStream out = new FileOutputStream(filePath);) {generateAll(templateEngine, templateName, out, listVars);out.flush();} catch (Exception e) {log.error(e.getMessage(), e);}}/*** pdf预览** @param templateEngine   配置* @param templateName 模板名称* @param listVars     模板参数集* @param response     HttpServletResponse*/public static void preview(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response) {try (ServletOutputStream out = response.getOutputStream()) {generateAll(templateEngine, templateName, out, listVars);out.flush();} catch (Exception e) {log.error(e.getMessage(), e);}}
}

Controller

/*** pdf预览、下载** @author gourd.hu* @version 1.0*/
@RestController
@RequestMapping(value = "/pdf")
@Api(tags = "pdf测试API", description = "pdf功能")
public class PdfController {@Autowiredprivate TemplateEngine templateEngine;@Value("${pdf.windowsFileTempLoc}")private String pdfWindowsPath;@Value("${pdf.linuxFileTempLoc}")private String pdfLinuxPath;/*** 文档预览、下载** @author gourd.hu* @version 1.0*/
@RestController
@RequestMapping(value = "/document")
@Api(tags = "文档预览、下载API")
public class DocumentController {@Autowiredprivate TemplateEngine templateEngine;@Value("${pdf.windowsFileTempLoc}")private String pdfWindowsPath;@Value("${pdf.linuxFileTempLoc}")private String pdfLinuxPath;/*** pdf预览** @param response HttpServletResponse*/@GetMapping(value = "/pdf/preview")@ApiOperation(value="pdf预览")public void preview(HttpServletResponse response) {// 构造freemarker模板引擎参数,listVars.size()个数对应pdf页数List<Map<String,Object>> listVars = new ArrayList<>();Map<String,Object> variables = new HashMap<>(4);variables.put("title","测试预览PDF!");variables.put("imageUrl",sslEnabled?"https://localhost:10001/imgs/sg.jpg":"http://localhost:10001/imgs/sg.jpg");List<Map<String,String>> demoList = new ArrayList<>();Map<String,String> demoMap = new HashMap<>(8);demoMap.put("name","哈哈");demoMap.put("nick","娃娃");demoMap.put("age","19");Map<String,String> demoMap2 = new HashMap<>(8);demoMap2.put("name","天天");demoMap2.put("nick","饭饭");demoMap2.put("age","14");demoList.add(demoMap);demoList.add(demoMap2);variables.put("demoList",demoList);Map<String,Object> variables2 = new HashMap<>(4);variables2.put("title","测试预览PDF2!");listVars.add(variables);listVars.add(variables2);PdfUtil.preview(templateEngine,"pdfPage",listVars,response);}/*** pdf下载** @param response HttpServletResponse*/@GetMapping(value = "/pdf/download")@ApiOperation(value="pdf下载")public void download(HttpServletResponse response) {List<Map<String,Object>> listVars = new ArrayList<>(1);Map<String,Object> variables = new HashMap<>(4);variables.put("title","测试下载PDF!");variables.put("imageUrl",sslEnabled?"https://localhost:10001/imgs/sg.jpg":"http://localhost:10001/imgs/sg.jpg");List<Map<String,String>> demoList = new ArrayList<>();Map<String,String> demoMap = new HashMap<>(8);demoMap.put("name","哈哈");demoMap.put("nick","娃娃");demoMap.put("age","19");Map<String,String> demoMap2 = new HashMap<>(8);demoMap2.put("name","天天");demoMap2.put("nick","饭饭");demoMap2.put("age","14");demoList.add(demoMap);demoList.add(demoMap2);variables.put("demoList",demoList);listVars.add(variables);PdfUtil.download(templateEngine,"pdfPage",listVars,response,"测试打印.pdf");}/*** pdf下载到特定位置**/@GetMapping(value = "/pdf/save")@ApiOperation(value="pdf下载到特定位置")public void save() {List<Map<String,Object>> listVars = new ArrayList<>(1);Map<String,Object> variables = new HashMap<>(4);variables.put("title","测试下载PDF!");variables.put("imageUrl",sslEnabled?"https://localhost:10001/imgs/sg.jpg":"http://localhost:10001/imgs/sg.jpg");List<Map<String,String>> demoList = new ArrayList<>();Map<String,String> demoMap = new HashMap<>(8);demoMap.put("name","哈哈");demoMap.put("nick","娃娃");demoMap.put("age","19");Map<String,String> demoMap2 = new HashMap<>(8);demoMap2.put("name","天天");demoMap2.put("nick","饭饭");demoMap2.put("age","14");demoList.add(demoMap);demoList.add(demoMap2);variables.put("demoList",demoList);listVars.add(variables);// pdf文件下载位置String pdfPath = CommonUtil.isLinux() ? pdfLinuxPath : pdfWindowsPath;PdfUtil.save(templateEngine,"pdfPage",listVars,pdfPath);}
}
}

演示效果

结语

至此,pdf打印功能就完成了,如果本文有错误的地方,欢迎评论指正。

===============================================

代码均已上传至本人的开源项目

cloud-plus:https://blog.csdn.net/HXNLYW/article/details/104635673

《SpringBoot2.0 实战》系列-整合FlyingSaucer + thymeleaf 实现模板文件转pdf打印相关推荐

  1. 《SpringBoot2.0 实战》系列-整合thymeleaf 实现模板文件转word打印

    前言 最近,有小伙伴看了我的<模板文件转pdf打印>文章后,私信问我,有没有转word的demo.当时只能遗憾的说没有.所以就有了这篇文章. 如何开始 thymeleaf 依赖包 < ...

  2. 《SpringBoot2.0 实战》系列-整合thymeleaf 实现模板文件转图片

    前言 之前写了两篇关于动态模板转pdf和word的文章:<模板文件转pdf打印>.<模板文件转word打印>,最近又接到一个需求需要转图片,所以本文做下记录. 如何开始 thy ...

  3. WF4.0实战系列索引

    从WF4.0 betal1出来的时候就开始使用WF4.0,由于资料不多,学习过程也非常艰苦.今年四月份的时候打算写WF4.0实战系列,由于今年是本命年故坚持写了24篇文章.这个系列的文章都有一个特点, ...

  4. SpringBoot2.0之四 简单整合MyBatis

    从最开始的SSH(Struts+Spring+Hibernate),到后来的SMM(SpringMVC+Spring+MyBatis),到目前的S(SpringBoot),随着框架的不断更新换代,也为 ...

  5. springboot2.0 多数据源整合问题 At least one JPA metamodel must be present!   at

    2019独角兽企业重金招聘Python工程师标准>>> 数据源代码: 第一个读取配置文件代码: package com.datasource;import org.apache.ib ...

  6. 把html转化为thymeleaf,Springboot和Thymeleaf HTML模板:转换为PDF后,汉字消失

    我们从html模板中生成pdf发票. 但是当html文本为中文时,结果会有问题. 尽管使用UTF-8进行了设置,但特殊字符不会显示. 这是html模板: 结果如下: 我们在模板解析器和html模板标头 ...

  7. 《SpringBoot2.0 实战》系列-整合kafka实现消息发送、消费

    之前写过一篇关于ActiveMq的博文,有兴趣的小伙伴可以点击查看.但是ActiveMq总体性能表现一般,如果对消息队列性能要求较高,业务量较大,那我们需要重新考量.本文就介绍一款性能高的消息队列- ...

  8. elasticsearch 分页_[Springboot实战系列]整合ElasticSearch实现数据模糊搜索

    前言 本文介绍了如何整合搜索引擎elasticsearch与springboot,对外提供数据查询接口. 业务介绍 我的个人网站需要对mysql数据库内存储的京东商品进行模糊查询(模仿淘宝商品搜索), ...

  9. SparkSQL(Spark-1.4.0)实战系列(一)——DataFrames基础

    主要内容 本教程中所有例子跑在Spark-1.4.0集群上 DataFrames简介 DataFrame基本操作实战 DataFrames简介 本文部分内容译自https://databricks.c ...

最新文章

  1. 树莓派4安装Android 并 root (LineageOS 17.1)
  2. 无法挂载 NTFS格式的分区:mount: unknown filesystem type ‘ntfs’
  3. linux内存管理基本概念
  4. centos yum安装时出现 cannot find a valid baseurl for repo: addons
  5. c++中的引用和指针
  6. x11转发:通过ssh远程使用GUI程序
  7. oracle能闪回多久,oracle 闪回基于时间的恢复
  8. mx250是什么_来看看联想小新Pro13 2020款和2019款哪个好?区别是什么?
  9. kitti百度网盘分享 kitti百度云盘,全套kitti分享 自动驾驶
  10. Linux内存分配器SLOB,深入理解Linux内核之SLOB分配器
  11. matlab数学建模程序代码大全,matlab程序代码
  12. 周鸿祎——互联网业界的“搅局者”
  13. 虚幻引擎UE4源码编译安装(x86,arm64平台)
  14. GET请求参数中文乱码的解决办法
  15. 并查集+最小生成树(Kruskal)+最短路(Floyd、Dijkstra)
  16. 代理服务器(Proxy)原理
  17. OpenSSH之Windows账户访问操作
  18. vim下区块的复制与黏贴
  19. python网易云_python下载网易云音乐
  20. python 笛卡尔积 两个表_多个集合计算笛卡尔积-Python

热门文章

  1. 斑马条码打印机常见故障大盘点
  2. 一个屌丝程序猿的人生(八十八)
  3. 自动化测试平台及可视化界面
  4. c语言 统计已初始化的二维数组a[3][4]中非零元素的个数(用指针实现)
  5. 微信小程序云开发体会——总结软件工程导论大作业
  6. 解决thinkpad或者其他笔记本电脑无线网络不可用问题
  7. 在企业访客管理中引入人脸识别系统有哪些应用?
  8. mysql小计_Mysql必读用SQL实现统计报表中的小计与合计的方法详解
  9. Oracle 实现小计、合计
  10. 魅蓝3卡插上显示无服务器,给魅蓝3插卡的方法步骤 _ 路由器设置|192.168.1.1|无线路由器设置|192.168.0.1 - 路饭网...