当前位置: 首页 > news >正文

SpringBoot+Freemark根据html模板动态导出PDF

SpringBoot+Freemark根据html模板导出PDF

  • 1、引入maven
  • 2、两个工具类
    • 2.1 test.html模板
    • 2.2 test.html模板中的Freemark语法
  • 3、controller导出pdf

1、引入maven

导出pdf的一些必要jar包
		<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.13</version></dependency><dependency><groupId>com.itextpdf.tool</groupId><artifactId>xmlworker</artifactId><version>5.5.13</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId><version>2.3.6.RELEASE</version></dependency><dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.28</version></dependency><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.4.3</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-io</artifactId><version>1.3.2</version></dependency>

2、两个工具类

GeneratePDF.java

注意1:工具类中的变量templates 表示在resource建个templates目录, test.html表示生成pdf的模板,water.png表示pdf的水印,这里我没加水印,如果要加水印,只用把下面我注释的三行代码放开就行。
注意2: test.html和water.png都放在templates目录
package cn.yx.zg.utils.pdf;import cn.hutool.core.io.resource.ClassPathResource;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfGState;
import com.itextpdf.text.pdf.PdfPageEvent;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerHelper;import java.io.*;
import java.nio.charset.Charset;public class GeneratePDF {public final static String TEMPLATE_DIRECTORY_INSPECTION = "/templates/";public final static String TEMPLATE_NAME_INSPECTION = "test.html";public final static String TEMPLATE_WATER_INSPECTION = "water.png";/*** HTML 转 PDF** @param content html内容* @param outPath 输出pdf路径* @return 是否创建成功*/public static boolean html2Pdf(String content, String outPath) throws FileNotFoundException {return html2Pdf(content, new FileOutputStream(outPath));}public static boolean html2Pdf(String content, OutputStream out) {try {Document document = new Document(); //创建一个标准的A4纸文档PdfWriter writer = PdfWriter.getInstance(document, out);//书写器与ducument文档关联document.open();//打开文档
//            addWaterMark(writer);writer.setPageEvent(new PdfPageEvent() {@Overridepublic void onOpenDocument(PdfWriter pdfWriter, Document document) {}@Overridepublic void onStartPage(PdfWriter pdfWriter, Document document) {
//                    addWaterMark(pdfWriter);}@Overridepublic void onEndPage(PdfWriter pdfWriter, Document document) {}@Overridepublic void onCloseDocument(PdfWriter pdfWriter, Document document) {}@Overridepublic void onParagraph(PdfWriter pdfWriter, Document document, float v) {}@Overridepublic void onParagraphEnd(PdfWriter pdfWriter, Document document, float v) {}@Overridepublic void onChapter(PdfWriter pdfWriter, Document document, float v, Paragraph paragraph) {}@Overridepublic void onChapterEnd(PdfWriter pdfWriter, Document document, float v) {}@Overridepublic void onSection(PdfWriter pdfWriter, Document document, float v, int i, Paragraph paragraph) {}@Overridepublic void onSectionEnd(PdfWriter pdfWriter, Document document, float v) {}@Overridepublic void onGenericTag(PdfWriter pdfWriter, Document document, Rectangle rectangle, String s) {}});XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(content.getBytes()), null, Charset.forName("UTF-8"));document.close();//关闭文档} catch (Exception e) {System.out.println("生成模板内容失败" + e.fillInStackTrace());return false;}return true;}/*** HTML 转 PDF** @param content html内容* @return PDF字节数组*/public static byte[] html2Pdf(String content) {ByteArrayOutputStream outputStream = null;try {Document document = new Document();outputStream = new ByteArrayOutputStream();PdfWriter writer = PdfWriter.getInstance(document, outputStream);document.open();XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(content.getBytes()), null, Charset.forName("UTF-8"));document.close();} catch (Exception e) {System.out.println("------生成pdf失败-------");}return outputStream.toByteArray();}public static void addWaterMark(PdfWriter writer) {Image image = null;try {ClassPathResource resource = new ClassPathResource(TEMPLATE_DIRECTORY_INSPECTION + TEMPLATE_WATER_INSPECTION);image = Image.getInstance(resource.getUrl());// 加入水印PdfContentByte waterMar = writer.getDirectContentUnder();// 开始设置水印waterMar.beginText();// 设置水印透明度PdfGState gs = new PdfGState();// 设置笔触字体不透明度为0.4fgs.setStrokeOpacity(0.2f);gs.setFillOpacity(0.2f);// 设置等比缩放image.scalePercent(25);// 依照比例缩放// image.scaleAbsolute(200,100);//自定义大小image.setRotation(45f);// 设置透明度waterMar.setGState(gs);// 添加水印图片
//                waterMar.addImage(image);image.setAbsolutePosition(110f, 130f);//结束设置waterMar.endText();waterMar.stroke();try {waterMar.addImage(image);} catch (DocumentException e) {e.printStackTrace();} finally {waterMar = null;gs = null;}} catch (BadElementException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}}

GetHtmlContent.java

package cn.yx.zg.utils.pdf;import freemarker.cache.ClassTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;import java.io.StringWriter;
import java.io.Writer;
import java.util.Map;public class GetHtmlContent {/*** 获取模板内容** @param templateDirectory 模板文件夹* @param templateName      模板文件名* @param paramMap          模板参数* @return* @throws Exception*/public static String getTemplateContent(String templateDirectory, String templateName, Map<String, Object> paramMap) throws Exception {Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);//不兼容配置try {configuration.setTemplateLoader(new ClassTemplateLoader(GetHtmlContent.class, templateDirectory));
//            configuration.setDirectoryForTemplateLoading(new File(templateDirectory));//加载模板} catch (Exception e) {e.printStackTrace();}Writer out = new StringWriter();Template template = configuration.getTemplate(templateName, "UTF-8");//缓存template.process(paramMap, out);out.flush();out.close();return out.toString();}}

2.1 test.html模板

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"/><title>测试表单</title><style>body {font-family: SimSun;}.f-s-1 {font-size: 12px;}.table-main {width: 100%;border-collapse: collapse;border-right: 1px solid rgba(0, 0, 0, 0.7);border-bottom: 1px solid rgba(0, 0, 0, 0.7);margin-bottom: 30px;}.table-main td {border-left: 1px solid rgba(0, 0, 0, 0.7);border-top: 1px solid rgba(0, 0, 0, 0.7);padding: 4px;}.th1 {font-weight: bold;background-color: #d7d7d7;}.th2 {background-color: #f1f1f1;}.text-center{text-align: center;}</style>
</head>
<body><h5 style="margin-top: 30px">学生列表</h5>
<table class="table-main f-s-1"><thead><tr><td class="th1" style="width: 28%">姓名</td><td class="th1 text-center" style="width: 12%">性别</td><td class="th1 text-center" style="width: 12%">年龄</td><td class="th1 text-center" style="width: 12%">地址</td></tr></thead><tbody><#list resultItems as item><tr><td class="text-center">${item.name}</td><td class="text-center">${item.sex}</td><td class="text-center">${item.age}</td><td class="text-center">${item.address}</td></tr></#list></tbody>
</table></body>
</html>

2.2 test.html模板中的Freemark语法

1、 <#list resultItems as item> 表示遍历后台返回的resultItems
2、下面判断age字段是否为空。
<#if age?? >存在<#else >不存在
</#if>
<#--判断数据是否存在,存在则则遍历-->
<#if resultItems??><#list resultItems as item>${item}</#list>
</#if>

3、controller导出pdf

注意: 代码中设置的变量要和html模板中的变量一致。
package cn.yx.zg.controller;import cn.yx.zg.utils.pdf.GeneratePDF;
import cn.yx.zg.utils.pdf.GetHtmlContent;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;@RestController
public class TestExportPdfController {@RequestMapping("/test")public void test(HttpServletResponse response) throws Exception {Map<String, Object> paramMap = new HashMap<>();List<Map<String, Object>> resultItems = new ArrayList<>();for (int i = 0; i < 100; i++) {Map<String, Object> items = new HashMap<>();items.put("name", "张三" + i);items.put("age", "18" + i);items.put("sex", "男" + i);items.put("address", "河南" + i);resultItems.add(items);}paramMap.put("resultItems", resultItems);String destFileName = "测试表单.pdf";destFileName = new String(destFileName.getBytes(), StandardCharsets.ISO_8859_1);OutputStream out;//设置响应response.setHeader("Content-Disposition", "attachment;filename=" + destFileName);response.setContentType("application/pdf");out = response.getOutputStream();String templateContent = GetHtmlContent.getTemplateContent(GeneratePDF.TEMPLATE_DIRECTORY_INSPECTION, GeneratePDF.TEMPLATE_NAME_INSPECTION, paramMap);GeneratePDF.html2Pdf(templateContent, out);out.flush();}
}

相关文章:

SpringBoot+Freemark根据html模板动态导出PDF

SpringBootFreemark根据html模板导出PDF 1、引入maven2、两个工具类2.1 test.html模板2.2 test.html模板中的Freemark语法 3、controller导出pdf 1、引入maven 导出pdf的一些必要jar包 <dependency><groupId>org.projectlombok</groupId><artifactId>…...

XPath数据提取与贴吧爬虫应用示例

XPath数据提取与贴吧爬虫应用示例 XpathXpath概述Xpath Helper插件 XPath语法基本语法查找特定节点选取未知节点选取若干路径 lxml模块使用说明使用示例 百度贴吧爬虫 Xpath Xpath概述 XPath&#xff08;XML Path Language&#xff09;是一种用于在XML文档中定位和选择节点的语…...

字符串匹配-KMP算法

KMP算法&#xff0c;字符串匹配算法&#xff0c;给定一个主串S&#xff0c;和一个字串T,返回字串T与之S匹配的数组下标。 在学KMP算法之前&#xff0c;对于两个字符串&#xff0c;主串S&#xff0c;和字串T&#xff0c;我们根据暴力匹配&#xff0c;定义两个指针&#xff0c;i指…...

Java面向对象之UML类图

UML类图 表示 public 类型&#xff0c; - 表示 private 类型&#xff0c;#表示protected类型方法的写法&#xff1a;方法的类型(、-) 方法名(参数名&#xff1a; 参数类型)&#xff1a;返回值类型...

【机器学习】西瓜书学习心得及课后习题参考答案—第4章决策树

这一章学起来较为简单&#xff0c;也比较好理解。 4.1基本流程——介绍了决策树的一个基本的流程。叶结点对应于决策结果&#xff0c;其他每个结点则对应于一个属性测试&#xff1b;每个结点包含的样本集合根据属性测试的结果被划分到子结点中&#xff1b;根结点包含样本全集&a…...

2023.8.2

2022河南萌新联赛第&#xff08;三&#xff09;场&#xff1a;河南大学\神奇数字.cpp //题意&#xff1a;给定三个正整数a b c,求x满足满足abc同余x的个数。 //这个考虑同余的性质&#xff0c;就是两个数的差去取模为0的数肯定是这两个数的同余数,。因此我们计算三个数两两之…...

windows运行窗口常用快捷键命令

winr打开运行窗口,然后输入快捷命令:&#xff08;当然utools和win11搜索也挺好用的&#xff09; cmd : 命令行窗口&#xff08;命令提示符窗口、cmd窗口&#xff09;regedit : 注册表mspaint : 画图工具services.msc : 本地服务设置(比如查看mysql服务是否启动成功)devmgmt.ms…...

HDFS的QJM方案

Quorum Journal Manager仲裁日志管理器 介绍主备切换&#xff0c;脑裂问题解决---ZKFailoverController&#xff08;zkfc&#xff09;主备切换&#xff0c;脑裂问题解决-- Fencing&#xff08;隔离&#xff09;机制主备数据状态同步问题解决 HA集群搭建集群基础环境准备HA集群规…...

安装win版本的neo4j(2023最新版本)

安装win版本的neo4j 写在最前面安装 win版本的neo4j1. 安装JDK2.下载配置环境变量&#xff08;也可选择直接点击快捷方式&#xff0c;就可以不用配环境了&#xff09;3. 启动neo4j 测试代码遇到的问题及解决&#xff08;每次环境都太离谱了&#xff0c;各种问题&#xff09;连接…...

ChatGPT结合知识图谱构建医疗问答应用 (二) - 构建问答流程

一、ChatGPT结合知识图谱 上篇文章对医疗数据集进行了整理&#xff0c;并写入了知识图谱中&#xff0c;本篇文章将结合 ChatGPT 构建基于知识图谱的问答应用。 下面是上篇文章的地址&#xff1a; ChatGPT结合知识图谱构建医疗问答应用 (一) - 构建知识图谱 这里实现问答的流程…...

聊天系统登录后端实现

定义返回的数据格式 # Restful API from flask import jsonifyclass HttpCode(object):# 响应正常ok 200# 没有登陆错误unloginerror 401# 没有权限错误permissionerror 403# 客户端参数错误paramserror 400# 服务器错误servererror 500def _restful_result(code, messa…...

Ajax笔记_01(知识点、包含代码和详细解析)

Ajax_01笔记 前置知识点 在JavaScript中 问题1&#xff1a;将数组转为字符串&#xff0c;以及字符串转为数组的方式。 问题2、将对象转为字符串&#xff0c;以及字符串转为对象的方法。 方法&#xff1a; 问题1&#xff1a; 将数组转为字符串可以使用 join() 方法。例如&…...

Eureka 学习笔记2:EurekaClient

版本 awsVersion ‘1.11.277’ EurekaClient 接口实现了 LookupService 接口&#xff0c;拥有唯一的实现类 DiscoveryClient 类。 LookupService 接口提供以下功能&#xff1a; 获取注册表根据应用名称获取应用根据实例 id 获取实例信息 public interface LookupService<…...

Spring引入并启用log4j日志框架-----Spring框架

<?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://ma…...

Redis实现延时队列

缓存队列延时向接口报工&#xff0c;并支持多实例部署。 引入依赖 <dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-data</artifactId><version>3.17.4</version> </dependency> 注入RedisClient …...

无限遍历,Python实现在多维嵌套字典、列表、元组的JSON中获取数据

目录 背景 思路 新建两个函数A和B&#xff0c;函数 A处理字典数据&#xff0c;被调用后&#xff0c;判断传递的参数&#xff0c;如果参数为字典&#xff0c;则调用自身&#xff1b; 如果是列表或者元组&#xff0c;则调用列表处理函数B&#xff1b; 函数 B处理列表&#x…...

信息学奥赛一本通——1180:分数线划定

文章目录 题目【题目描述】【输入】【输出】【输入样例】【输出样例】【提示】 AC代码 题目 【题目描述】 世博会志愿者的选拔工作正在 A 市如火如荼的进行。为了选拔最合适的人才&#xff0c;A市对所有报名的选手进行了笔试&#xff0c;笔试分数达到面试分数线的选手方可进入…...

SpringApplication对象的构建及spring.factories的加载时机

构建SpringApplication对象源码: 1、调用启动类的main()方法,该方法中调用SpringApplication的run方法。 SpringBootApplication public class SpringbootdemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootdemoApplication.class, …...

基于传统检测算法hog+svm实现图像多分类

直接上效果图&#xff1a; 代码仓库和视频演示b站视频005期&#xff1a; 到此一游7758258的个人空间-到此一游7758258个人主页-哔哩哔哩视频 代码展示&#xff1a; 数据集在datasets文件夹下 运行01train.py即可训练 训练结束后会保存模型在本地 运行02pyqt.py会有一个可视化…...

slice() 方法,使用 concat() 方法, [...originalArray],find(filter),移出类名 removeAttr()

在JavaScript中&#xff0c;在 JavaScript 中&#xff0c;clone 不是一个原生的数组方法。但是你可以使用其他方法来实现克隆数组的功能。 以下是几种常见的克隆数组的方法&#xff1a; 使用 slice() 方法&#xff1a; const originalArray [1, 2, 3]; const clonedArray …...

【JVM】- 内存结构

引言 JVM&#xff1a;Java Virtual Machine 定义&#xff1a;Java虚拟机&#xff0c;Java二进制字节码的运行环境好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收的功能数组下标越界检查&#xff08;会抛异常&#xff0c;不会覆盖到其他代码…...

【第二十一章 SDIO接口(SDIO)】

第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

如何将联系人从 iPhone 转移到 Android

从 iPhone 换到 Android 手机时&#xff0c;你可能需要保留重要的数据&#xff0c;例如通讯录。好在&#xff0c;将通讯录从 iPhone 转移到 Android 手机非常简单&#xff0c;你可以从本文中学习 6 种可靠的方法&#xff0c;确保随时保持连接&#xff0c;不错过任何信息。 第 1…...

Neo4j 集群管理:原理、技术与最佳实践深度解析

Neo4j 的集群技术是其企业级高可用性、可扩展性和容错能力的核心。通过深入分析官方文档,本文将系统阐述其集群管理的核心原理、关键技术、实用技巧和行业最佳实践。 Neo4j 的 Causal Clustering 架构提供了一个强大而灵活的基石,用于构建高可用、可扩展且一致的图数据库服务…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

微信小程序云开发平台MySQL的连接方式

注&#xff1a;微信小程序云开发平台指的是腾讯云开发 先给结论&#xff1a;微信小程序云开发平台的MySQL&#xff0c;无法通过获取数据库连接信息的方式进行连接&#xff0c;连接只能通过云开发的SDK连接&#xff0c;具体要参考官方文档&#xff1a; 为什么&#xff1f; 因为…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

JS设计模式(4):观察者模式

JS设计模式(4):观察者模式 一、引入 在开发中&#xff0c;我们经常会遇到这样的场景&#xff1a;一个对象的状态变化需要自动通知其他对象&#xff0c;比如&#xff1a; 电商平台中&#xff0c;商品库存变化时需要通知所有订阅该商品的用户&#xff1b;新闻网站中&#xff0…...

【Android】Android 开发 ADB 常用指令

查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案

目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后&#xff0c;迭代器会失效&#xff0c;因为顺序迭代器在内存中是连续存储的&#xff0c;元素删除后&#xff0c;后续元素会前移。 但一些场景中&#xff0c;我们又需要在执行删除操作…...