往期文章

  • springcloud整合knike4j聚合微服务接口文档
  • spring源码 - 条件注解@ConditionnalOnClass的原理分析
  • 用最简单的话讲最明白的红黑树

文章目录

  • 往期文章
  • 一、介绍
  • 二、使用html模版生成html页面文本
    • 1. 使用jsoup工具生成html页面文本
    • 2. 使用模版引擎生成html页面文本
  • 三、将html页面文本转成pdf文件

一、介绍

在我们日常开发中,经常会遇到导出pdf这种需求,比如导出合同、导出业务报告等。这中导出功能都有一个特点,导出的pdf中有大量相同的文本布局以及样式,只有涉及到用户本人的信息时出现不同的内容。我们把这些相同的部分称作模版,在模版中放置一些变量来代表用户信息,比如用户姓名、年龄等。这样我们在导出pdf的时候,在数据库中把用户信息查出来,对模版中对应的变量进行替换,再把替换的结果转成pdf文件就可以了。

模版的类型有很多种:html模版、doc模版、excel模版、pdf模版等等。项目中使用哪一种要具体情况具体考虑。

将变量替换后的模版转成pdf文件的工具也有很多,最主流最方面的当然要数itextpdf了。它可以将常见的任何形式的模版转成pdf文件。

前几天俺就遇到一个导出pdf的需求,而且该pdf有点花里胡哨,明显存在大量css样式,所以我们就采用html作为模版,通过itextpdf将html转成pdf。主要步骤如下:

  1. 将html模版生成html页面文本,对模版进行变量替换
  2. 将html页面文本转成pdf文件

二、使用html模版生成html页面文本

如何对html模版进行变量替换,生成html页面文本,这里向大家提供两个方案,这两个方案各有优缺点,可依个人情况选择。

  • 使用jsoup工具

    该工具处理html文本十分友好。你可以直接根据id、class等属性来获取对应的html元素(如:getElementsByAttributeValue("id", "value")),然后对获取的元素通过text()方法设置文本内容。这有点类似python的爬虫工具beautifulSoup

    • 优点:只要知道html模版的结构就行。
    • 缺点:处理复杂的结构如表格时,可能会对模版的html标签结构进行修改,因此处理逻辑较为复杂
  • 使用模版引擎,以thymeleaf为例

    类似于jsp,thymeleaf支持HTML5作为模版文件,其提供的模版引擎十分强大,而且在spring官方文档中首推的模版引擎就是thymleaf,spring也默认集成了thymleaf,足以可见他的强大。

    • 优点:利用thymleaf模版引擎,只需三四行代码就可以完成整个html模版的变量替换。
    • 缺点:需要对thymleaf的使用有基本的了解。

1. 使用jsoup工具生成html页面文本

  • 引入依赖

    我们引入spring-boot-starter-webjsoup的依赖

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- 处理xml风格的工具包,对html有效,包含Jsoup -->
    <dependency><groupId>com.itextpdf</groupId><artifactId>styled-xml-parser</artifactId><version>7.2.3</version>
    </dependency>
    
  • 创建模版

    在resources下新建目录templates,并创建一个html模版文件:StudentReport.html

    <!DOCTYPE html>
    <html><head><meta charset="utf-8"><title></title></head><body><h1 style="text-align: center;">学生报告</h1><!-- 班级信息 --><table border="1" style="text-align: center"><tr><td>学校</td><td>年级</td><td>班级</td><td>学生人数</td></tr><tr><!-- 变量:学校 --><td id="school"></td><!-- 变量:年级 --><td id="grade"></td><!-- 变量:班级 --><td id="class"></td><!-- 变量:学生人数 --><td id="studentNum"></td></tr></table><h3>班级概况:</h3><!-- 变量:班级概况 --><p id="situation" style="text-indent: 2em;"></p><h3>学生列表:</h3><!-- 学生列表 --><table border="1"><tbody><!-- 表头 --><tr><th>姓名</th><th>性别</th><th>年龄</th><th>父亲</th><th>母亲</th></tr><!-- 学生数据 --><tr id="studentList"><!-- 变量:姓名 --><td id="name"></td><!-- 变量:性别 --><td id="sex"></td><!-- 变量:年龄 --><td id="age"></td><!-- 变量:父亲 --><td id="father"></td><!-- 变量:母亲 --><td id="mother"></td></tr></tbody></table></body>
    </html>
    

    在浏览器里打开该html模版如下图所示

  • 变量替换的逻辑

    如果html模版的结构相对来讲比较简单的话,变量替换的逻辑便不难理解。但若遇到复杂的结构,该逻辑便有点力不从心了,因为它具有一定的局限性,而且针对复杂的结构,变量替换的逻辑相对也会更加复杂。

    // 变量替换,src-html模版位置,params-进行变量替换的真实数据,key与html模版中标签的id属性一致,value为真实数据
    public static String placeholder(String src, Map<String, Object> params) throws IOException {File file = new File(src);// 通过Jsoup创建Document对象,Document就可以表示整个html文本了。Document document = Jsoup.parse(file, "utf-8");// 设置内容文本,真正进行变量替换的方法setText(document, params);// 将变量替换好以后,输出html文本String outerHtml = document.outerHtml();System.out.println(outerHtml);return outerHtml;
    }// 给html模版设置文本数据,document-html模版,params-进行变量替换的真实数据
    private static void setText(Document document, Map<String, Object> params) {Set<Map.Entry<String, Object>> entrySet = params.entrySet();for (Map.Entry<String, Object> entry : entrySet) {// 获取最后一个对应的elementElement element = document.getElementsByAttributeValue("id", entry.getKey()).last();if ("tr".equals(element.tagName())) {List<Map<String, Object>> counselList = (List<Map<String, Object>>)entry.getValue();// 设置行,就是把列表数据设置到html的表格行中setRowsText(document, element, counselList);} else {// 对html元素设置文本element.text(entry.getValue().toString());}}
    }// 把列表数据设置到html的表格行中,document-html模版,element-表示一行的元素,即tr标签。list-真实列表数据
    private static void setRowsText(Document document, Element element, List<Map<String, Object>> list) {if (list.isEmpty()) {return;}Iterator<Map<String, Object>> iterator = list.iterator();do {Map<String, Object> counsel = iterator.next();// 设置文本数据setText(document, counsel);if (iterator.hasNext()) {// 追加一行appendTableRow(element);}} while (iterator.hasNext());// 如果list集合中还有元素,则复制当前element追加到当前element后面,并循环到前面一步,// 如果list集合中没有元素了,则说明内容已经写完了,返回即可
    }// 扩展一行
    private static void appendTableRow(Element element) {Node parent = element.parent();Element tbody = (Element) parent;tbody.appendChild((Node) element.clone());
    }
    
  • 测试

    我们写一个Controller,通过接口来测试上面的方法

    @RestController
    @RequestMapping("/student")
    public class StudentController {@GetMapping("/placehold/jsoup")public String jsoup() throws IOException {// 获取html模版文件File tmpl = new ClassPathResource("templates/StudentReport.html").getFile();// 模拟数据库中查询的数据Map<String, Object> params = new HashMap<>();params.put("school", "家里蹲大学");params.put("grade", "八年级");params.put("class", "三班");params.put("studentNum", 999);params.put("situation", "这个班的学生相当吊炸天,勿以善小而不为,勿以恶小而为之,关关雎鸠,在水之洲。窈窕淑女,君子好逑。");List<Map<String, Object>> counselList = new ArrayList<>();counselList.add(getCounsel("周一", "男", 32, "周一他爸", "周一他妈"));counselList.add(getCounsel("周二", "女", 42, "周二他爸", "周二他妈"));counselList.add(getCounsel("周三", "男", 54, "周三他爸", "周三他妈"));counselList.add(getCounsel("周四", "男", 13, "周四他爸", "周四他妈"));counselList.add(getCounsel("周五", "女", 43, "周五他爸", "周五他妈"));counselList.add(getCounsel("周六", "女", 74, "周六他爸", "周六他妈"));counselList.add(getCounsel("周日", "男", 22, "周日他爸", "周日他妈"));params.put("studentList", counselList);String html = JsoupPlaceholdUtil.placeholder(tmpl, params);return html;}private Map<String, Object> getCounsel(String name, String sex, Integer age, String father, String mother) {Map<String, Object> params = new HashMap<>();params.put("name", name);params.put("sex", sex);params.put("age", age);params.put("father", father);params.put("mother", mother);return params;}
    }
    
  • 在浏览器中访问该接口,localhost:port/student/placehold/jsoup

    从浏览器中我们可以看到,真实数据已经完美地放在html文本中了

2. 使用模版引擎生成html页面文本

模版引擎我们选择thymleaf的原因是spring天然支持,无需对其集成进行多余的配置,只需要引入依赖就可以使用了。

  • 引入依赖

    我们引入spring-boot-starter-webthymeleaf的依赖

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- thymeleaf模版引擎 -->
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
  • 创建模版

    使用thymleaf模版引擎,就需要按照它的要求通过给html标签添加各种th:属性来写html模版。在resources下新建目录templates,并创建一个html模版文件:StudentReportTH.html

    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org" lang="en">
    <head><meta charset="utf-8"><title></title>
    </head>
    <body>
    <h1 style="text-align: center;">学生报告</h1>
    <!-- 班级信息 -->
    <table border="1" style="text-align: center"><tr><td>学校</td><td>年级</td><td>班级</td><td>学生人数</td></tr><tr><td th:text="${school}">XX学校</td><td th:text="${grade}">XX年级</td><td th:text="${class}">XX班级</td><td th:text="${studentNum}">0</td></tr>
    </table>
    <h3>班级概况:
    </h3>
    <p id="situation" style="text-indent: 2em;"></p>
    <h3>学生列表:
    </h3>
    <!-- 学生列表 -->
    <table border="1"><tbody><!-- 表头 --><tr><th>姓名</th><th>性别</th><th>年龄</th><th>父亲</th><th>母亲</th></tr><!-- 学生数据 --><tr th:each="student in ${studentList}" th:if="${studentList}"><td th:text="${student.name}">XXX</td><td th:text="${student.sex}">XX</td><td th:text="${student.age}">XXX</td><td th:text="${student.father}">XXX</td><td th:text="${student.mother}">XXX</td></tr></tbody>
    </table>
    </body>
    </html>
    

    在浏览器里打开该html模版如下图所示

  • 变量替换

    有了thymeleaf,变量替换的任何细节我们都不用关心,只需要把模版和数据交给它就可以了。只需要仅仅4行代码

    另外在springboot中已经自动将thymleaf添加到IOC容器中了,我们只需要依赖注入就可以了。

    @Autowired
    private WebApplicationContext applicationContext;
    @Autowired
    private LocaleResolver localeResolver;
    @Autowired
    private SpringTemplateEngine springTemplateEngine;public String thymeleaf(HttpServletRequest request, HttpServletResponse response) {// 实际数据Map<String, Object> params = new HashMap<>();// 变量替换Writer writer = new FastStringWriter();WebExpressionContext context = new WebExpressionContext(springTemplateEngine.getConfiguration(),request,response,applicationContext.getServletContext(),localeResolver.resolveLocale(request),params);// springboot对thymeleaf的默认配置为 prefix="classpath:templates", suffix=".html"springTemplateEngine.process("StudentReportTH", context,writer);return s = writer.toString();
    }
    
  • 测试

    我们写一个Controller,通过接口来测试上面的方法

    @RestController
    @RequestMapping("/student")
    public class StudentController {@Autowiredprivate WebApplicationContext applicationContext;@Autowiredprivate LocaleResolver localeResolver;@Autowiredprivate SpringTemplateEngine springTemplateEngine;private Map<String, Object> getCounsel(String name, String sex, Integer age, String father, String mother) {Map<String, Object> params = new HashMap<>();params.put("name", name);params.put("sex", sex);params.put("age", age);params.put("father", father);params.put("mother", mother);return params;}@GetMapping("/placehold/thymeleaf")public String thymeleaf(HttpServletRequest request, HttpServletResponse response) {Map<String, Object> params = new HashMap<>();params.put("school", "家里蹲大学");params.put("grade", "八年级");params.put("class", "三班");params.put("studentNum", 999);params.put("situation", "这个班的学生相当吊炸天,勿以善小而不为,勿以恶小而为之,关关雎鸠,在水之洲。窈窕淑女,君子好逑。");List<Map<String, Object>> counselList = new ArrayList<>();counselList.add(getCounsel("周一", "男", 32, "周一他爸", "周一他妈"));counselList.add(getCounsel("周二", "女", 42, "周二他爸", "周二他妈"));counselList.add(getCounsel("周三", "男", 54, "周三他爸", "周三他妈"));counselList.add(getCounsel("周四", "男", 13, "周四他爸", "周四他妈"));counselList.add(getCounsel("周五", "女", 43, "周五他爸", "周五他妈"));counselList.add(getCounsel("周六", "女", 74, "周六他爸", "周六他妈"));counselList.add(getCounsel("周日", "男", 22, "周日他爸", "周日他妈"));params.put("studentList", counselList);Writer writer = new FastStringWriter();WebExpressionContext context = new WebExpressionContext(springTemplateEngine.getConfiguration(),request,response,applicationContext.getServletContext(),localeResolver.resolveLocale(request),params);// springboot对thymeleaf的默认配置为 prefix="classpath:templates", suffix=".html"springTemplateEngine.process("StudentReportTH", context,writer);return s = writer.toString();}
    }
    
  • 在浏览器中访问该接口,localhost:port/student/placehold/thymeleaf

    从浏览器中我们可以看到,真实数据已经完美地放在html文本中了,处理变量替换的逻辑也就四行。

三、将html页面文本转成pdf文件

上面我们通过两种方式对html模版进行变量替换并得到html文本内容了。接下来要做的就是把html文本内容转成pdf。

在上面pom.xml的基础上中引入依赖

<dependencies><!-- itext核心包 --><dependency><groupId>com.itextpdf</groupId><artifactId>itext7-core</artifactId><version>7.2.3</version></dependency><!-- html转pdf,包含类似于jsoup依赖的操作html文档的依赖 --><dependency><groupId>com.itextpdf</groupId><artifactId>html2pdf</artifactId><version>4.0.3</version></dependency></dependencies>

使用itextpdf将html文件转成pdf的过程也是相当简单。

// 设置字体
ConverterProperties converterProperties = new ConverterProperties();
FontSet fontSet = new FontSet();
if (!fontSet.addFont("C:\\Windows\\Fonts\\simhei.ttf")) {throw new RuntimeException("获取字体失败");
}
converterProperties.setFontProvider(new FontProvider(fontSet));
// html转pdf, 并将pdf作为字节数组保存在bos中
ByteArrayOutputStream bos = new ByteArrayOutputStream();
HtmlConverter.convertToPdf(jsoupHtml, bos, converterProperties);

然后我们对上面两种方式生成的html文本内容进行转换。

  • 对jsoup生成的html文本内容进行转换并测试

    @GetMapping("/export/jsoup")
    public void exportJsoup(HttpServletResponse response) throws IOException {String jsoupHtml = jsoup();// 设置字体ConverterProperties converterProperties = new ConverterProperties();FontSet fontSet = new FontSet();if (!fontSet.addFont("C:\\Windows\\Fonts\\simhei.ttf")) {throw new RuntimeException("获取字体失败");}converterProperties.setFontProvider(new FontProvider(fontSet));ByteArrayOutputStream bos = new ByteArrayOutputStream();HtmlConverter.convertToPdf(jsoupHtml, bos, converterProperties);String fileName = "将jsoup生成的html转换成pdf文件";// 设置中文文件名fileName = new String(fileName.getBytes("utf-8"),"iso8859-1");String encode = URLEncoder.encode(fileName, "iso8859-1");ServletOutputStream outputStream = response.getOutputStream();response.setContentType("application/x-download");response.addHeader("Content-Disposition", "attachment; filename=" + encode + ".pdf");response.setCharacterEncoding("UTF-8");outputStream.write(bos.toByteArray());
    }
    

    调用接口下载pdf文件

    然后打开下载的pdf文件

  • 对thymeleaf生成的html文本内容进行转换并测试

    与上面的步骤相同,接口如下

    @GetMapping("/export/thymeleaf")
    public void exportThymeleaf(HttpServletRequest request, HttpServletResponse response) throws IOException {String jsoupHtml = thymeleaf(request, response);// 设置字体ConverterProperties converterProperties = new ConverterProperties();FontSet fontSet = new FontSet();if (!fontSet.addFont("C:\\Windows\\Fonts\\simhei.ttf")) {throw new RuntimeException("获取字体失败");}converterProperties.setFontProvider(new FontProvider(fontSet));ByteArrayOutputStream bos = new ByteArrayOutputStream();HtmlConverter.convertToPdf(jsoupHtml, bos, converterProperties);String fileName = "将thymeleaf生成的html转换成pdf文件";// 设置中文文件名fileName = new String(fileName.getBytes("utf-8"),"iso8859-1");String encode = URLEncoder.encode(fileName, "iso8859-1");ServletOutputStream outputStream = response.getOutputStream();response.setContentType("application/x-download");response.addHeader("Content-Disposition", "attachment; filename=" + encode + ".pdf");response.setCharacterEncoding("UTF-8");outputStream.write(bos.toByteArray());
    }
    

    同样地通过接口将pdf下载到本机,查看pdf

纸上得来终觉浅,绝知此事要躬行。

————我是万万岁,我们下期再见————

springboot项目实现导出pdf功能,这也太简单了吧相关推荐

  1. springboot整合itextpdf导出pdf

    springboot整合itextpdf导出pdf 1.pom依赖 如果使用别的版本有可能会报错,建议使用这2个版本 <dependency><groupId>com.itex ...

  2. cad导出pdf_如何使用浩辰CAD看图王软件来快速导出PDF功能?

    浩辰CAD旗下产品都是具有导出PDF功能的,如果是浩辰CAD平台软件或者专业软件选择 dwg to pdf 打印机,就可以输出pdf文件,如果是浩辰CAD看图王则直接就有导出PDF的功能. 浩辰CAD ...

  3. idea软件 springboot项目启动报错:命令行太长解决

    idea软件 springboot项目启动报错:命令行太长解决 1.找到项目下得.idea文件夹,打开文件workspace.xml 2.搜索标签 <component name="P ...

  4. Java快速实现导出PDF功能

    Word转PDF并导出–Windows环境实现 一.制作word模板,这里需要文件后缀默认是.docx,${xxxx}是需要替换的内容 二.添加poi所需要的jar包文件,这里使用maven进行jar ...

  5. java导出pdf功能记录

    这几天已在做处理导出pdf文件的功能,摸索了几天总算可以了.记录下这几天遇到的问题. 1.网上基本都是基于Itext5和Itext7来处理的.我最终是在Itext5上成功了,itext7应该是模板出问 ...

  6. 前端导出pdf功能(超详细)

    实现思路:将html页面生成pdf文件,需首先将页面转换为图片,然后再输出成pdf. 一.先引入html2canvas npm install html2canvas 二.项目内引入 import h ...

  7. 一步步教你如何在SpringBoot项目中引入支付功能

    听说微信搜索<Java鱼仔>会变更强哦! 本文收录于JavaStarter ,里面有我完整的Java系列文章,学习或面试都可以看看哦 (一)引言 支付功能如今已经成为一个需要盈利的网站的基 ...

  8. react 实现导出PDF功能

    1.首先需要两个库 import jsPDF from 'jspdf'; import html2canvas from 'html2canvas' 2.封装 /* PDF导出封装 title:文档下 ...

  9. java生成pdf电子书_java itext导出PDF功能实现

    java使用itext导出PDF文本绝对定位(实现方法) jar:itext-4.2.1.jar 在很多公文的落款处都需要绝对定位,所以记录此代码如下: PdfWriter writer = PdfW ...

最新文章

  1. VB.NET文件ZIP压缩
  2. pyzbar Unable to find zbar shared library
  3. swoole 异步redis
  4. windows的常用快捷键(实用篇)
  5. 已知函数func的C语言代码框架,第三章习题-ddg..doc
  6. vue打包放到Java项目里_如何把vuejs打包出来的文件整合到springboot里
  7. html选择按钮select,Html选择使用select来改变一个按钮的链接使用Javascript
  8. 三场直播丨达梦DM8数据库体系结构、从零快速搭建一整套监控体系、Oracle Database Server经典体系结构...
  9. 二叉树的深度和广度优先搜索算法
  10. 何小鹏“维权”事后谈造车:心很累 曾购上千瓶白酒缓解压力
  11. 安装包及教程:仿真工具Multisim12.0 简体中文汉化版 安装包及详细安装教程(含安装包和汉化包百度云盘链接)
  12. BUUCTF:[WUSTCTF2020]girlfriend
  13. python量化交易入门教程_搞金融的同学三小时快速入门python从零入门量化交易系列...
  14. 什么是URL?URL是什么意思?
  15. 从零开始学习UCOSII操作系统13--系统移植理论篇
  16. 考研经验贴(南京航空航天大学,电子信息专硕)
  17. tar命令 – 打包和备份的归档工具
  18. Bootstrap下拉菜单(Dropdown)插件实现隐藏操作按钮的简单实现
  19. 上海亚商投顾:沪指缩量反弹 新能源汽车产业链走强
  20. 职业规划师:如何给自己挑选一个好老板

热门文章

  1. uglifyjs 压缩_使用UglifyJS更好地压缩
  2. 电子制造企业如何做好生产管理?从这4个方面入手就够了
  3. 如何在网上获得顾客忠诚
  4. 【洛谷OJ C++】洛谷题单101 入门2分支结构 题解及学习笔记
  5. 用 OpenCV 绘制 OpenCV 的 logo 图标
  6. 《16.shell原理和问答机制引入》
  7. 某音短视频评论采集翻页(移动安全篇)
  8. hppt服务器状态响应码
  9. 机器学习-Whitening(白化)
  10. 使用eclipse创建的第一个jsp项目