SpringBoot+Thymeleaf 后端转html,pdf HTML生成PDF SpringBoot生成PDF Java PDF生成

news2024/10/7 2:29:37

SpringBoot 生成PDF

Thymeleaf企业级真实应用:将HTML界面数据转换为PDF输出

参考: https://blog.51cto.com/u_13146445/6190475

https://blog.csdn.net/qq_27242695/article/details/115654447

0. 需求

  • 后端渲染pdf生成 (thymeleaf根据已有的html模板,springboot进行渲染后,生成pdf)
  • pdf中可放置图片(图片非引入文件方式,而是采用Base64方式)
  • 注意:html文件需单独开发,css样式需内嵌,不允许外置css文件
  • 暂时未整理Echarts图表部分,如需可参考顶部url参考部分

1. Thymeleaf说明

# 什么是Thymeleaf
- Thymeleaf是一种现代化的服务器端Java模板引擎,可以用于Web和独立环境中的HTML、XML、JavaScript、CSS和文本。在实际开发中,Thymeleaf可以用于生成动态的HTML页面,支持将数据与模板进行绑定,生成最终的HTML内容。它是一个开源的软件,采用Apache许可证2.0进行发布。

# Thymeleaf具有特点
- 与其他服务器端Java模板引擎相比,Thymeleaf具有以下特点:

- 语法简单易懂,支持自然的HTML标签
- 支持HTML5的规范和特性
- 支持CSS样式的绑定和操作
- 支持表达式语言(Expression Language,简称EL)和Spring表达式语言(Spring Expression Language,简称SpEL)
- 支持标准和Spring MVC的多种模板渲染方式
- 支持多种模板缓存策略
- 支持可扩展的引擎架构

- 在实际开发中,Thymeleaf可以用于生成动态的HTML页面,支持将数据与模板进行绑定,生成最终的HTML内容。它可以作为Web应用程序的模板引擎,也可以作为其他应用程序的模板引擎。由于其简单易用的语法和强大的功能,Thymeleaf已经成为Java领域中最受欢迎的模板引擎之一。

2. 将HTML界面数据转换为PDF输出逻辑说明

# 中心思想
- 使用模板引擎的模板文件和数据模型。模板文件定义了最终输出的PDF页面的结构和样式,而数据模型则提供了模板中要填充的动态数据。

- 具体来说,Thymeleaf使用Java对象作为数据模型,可以通过Spring的控制器将数据注入到数据模型中。然后,Thymeleaf将数据模型与模板文件结合起来,生成HTML内容。最后,使用PDF生成库将HTML内容转换为PDF输出。
# 操作说明
- 在实现PDF输出功能时,可以使用Spring Boot提供的`spring-boot-starter-thymeleaf`依赖,该依赖包含了Thymeleaf、PDF生成库以及其他必需的依赖项。可以在控制器中使用Thymeleaf的`TemplateEngine`对象将数据模型和模板文件合并,生成HTML内容。然后,可以使用PDF生成库将HTML内容转换为PDF格式。

- 需要注意的是,PDF输出可能需要一些特定的CSS样式和HTML标记,以便正确呈现和格式化PDF页面。因此,在生成PDF输出之前,可能需要对模板文件进行调整和优化,以确保输出的PDF页面具有所需的外观和布局。

# 具体步骤
- 定义HTML模板,需要输出的数据以HTML格式创建一个模板,生成.HTML文件
- 引入Thymeleaf中TemplateEngine->生成文本输出的Java模板引擎框架、Context->Web应用程序的上下文对象。生成html 模板渲染工具。处理上边我们定义的模板。得到一个String类的结果
- 读取这个结果byte[],将byte数组 转换为 Base64字符串
- 最后将Base64字符串转换为PDF格式的数据,输出路径

3. 具体实现

3.1. 依赖

<!--thymeleaf-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- html 转 pdf 需要用的jar -->
<dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-pdf</artifactId>
    <version>9.1.6</version>
</dependency>

3.2. 定义好html模板

需要转成thymeleaf格式,如头部标签

@page为pdf的尺寸

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Hello World!</title>
    <style>
        .setAa {
            background-color: red;
        }
        @page{
            size:297mm 210mm;
        }
    </style>
</head>

<body>
<h1 class="setAa" th:text="'Hello, ' + ${name} + '!'"></h1>
<p th:text="'You are ' + ${age} + ' years old.'"></p>
<img alt="" th:src="${imgSrc}"  style="margin:0 auto;"/>
</body>
</html>

3.3. html 模板渲染工具类

import com.lowagie.text.DocumentException;
import com.lowagie.text.pdf.BaseFont;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.annotation.Resource;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/**
 * @Author:wangdi
 * @Date:2023/6/9 14:58
 * @Des: HtmlToPdfUtil 转换pdf工具类
 */
@Component
public class HtmlToPDFUtil {

    @Resource
    private TemplateEngine templateEngine;


    /**
     * 使用 Thymeleaf 渲染 HTML
     *
     * @param template HTML模板路径
     * @param params   渲染的参数
     * @return 返回渲染后的html代码
     * @throws Exception
     */
    public String render(String template, Map<String, Object> params) {
        Context context = new Context();
        if (params.size() > 0) {
            context.setVariables(params);
        }
        //将数据填充到模板里,开始处理模板
        return templateEngine.process(template, context);
    }

    /**
     * 根据html生成pdf的base64格式
     *
     * @param html
     * @return
     */
    public static String getPDFBase64ByHtml(String html) throws DocumentException, IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();//构建字节输出流
        ITextRenderer renderer = new ITextRenderer();
        ITextFontResolver fontResolver = renderer.getFontResolver();
        //指定文件字体添加到PDF库,指定字体不作为内部字体,而是外部字体被加载
        fontResolver.addFont("font/SimSun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        renderer.setDocumentFromString(html);
        renderer.layout();
        renderer.createPDF(baos);
        return new BASE64Encoder().encode(baos.toByteArray());
    }


    /**
     * 根据pdf的base64格式和路径生成pdf文件
     *
     * @param base64 pdf的base64格式
     * @param path   生成pdf的路径
     * @return
     */
    public static String base64ToPDF(String base64, String path) {


        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
        String fileAdd = sdf.format(new Date());
        //先判断文件是否存在
        path = path + "/" + fileAdd;
        String fileName = path + "/" + System.currentTimeMillis() + ".pdf";//新的文件名

        BufferedInputStream bin = null;
        FileOutputStream fout = null;
        BufferedOutputStream bout = null;
        BASE64Decoder decoder = new BASE64Decoder();
        try {
            byte[] bytes = decoder.decodeBuffer(base64);

            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
            // 创建从底层输入流中读取数据的缓冲输入流对象
            bin = new BufferedInputStream(bais);

            //获取文件夹路径
            File file = new File(path);
            //如果文件夹不存在则创建
            if (!file.exists() && !file.isDirectory()) {
                file.mkdirs();
            }
            // 创建到指定文件的输出流
            fout = new FileOutputStream(fileName);
            // 为文件输出流对接缓冲输出流对象
            bout = new BufferedOutputStream(fout);
            byte[] buffers = new byte[1024];
            int len = bin.read(buffers);
            while (len != -1) {
                bout.write(buffers, 0, len);
                len = bin.read(buffers);
            }
            // 刷新此输出流并强制写出所有缓冲的输出字节,必须这行代码,否则有可能有问题
            bout.flush();
            //返回存储的路径
            return fileName;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                bin.close();
                fout.close();
                bout.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return "";
    }


}

字体

SimSun.ttf 自行搜索下载

HTML中的字体,pdf不能识别,需添加字体文件

注意,在添加字体文件时,需要确保字体文件的路径正确,并且字体文件能够被读取到。此外,还需要确保字体文件的格式正确,可以使用BaseFont.IDENTITY_H指定字体编码,使用BaseFont.NOT_EMBEDDED指定字体文件是否嵌入到PDF文件中。

ITextRenderer说明

ITextRenderer是一个基于iText库的Java库,它可以将HTML、XHTML或XML等文档渲染成为PDF、XLS、PNG、JPEG等格式的文件。

ITextRenderer库提供了一个ITextRenderer类,该类提供了丰富的API,用于将HTML、XHTML或XML文档转换成为PDF等格式的文件。该类内部使用了iText库的PDF生成和操作功能,同时也支持使用Flying Saucer库对文档进行渲染和布局。

使用ITextRenderer库进行PDF输出的基本流程如下:

1)创建一个ITextRenderer对象;
2)使用setDocument()方法将要转换的文档设置到渲染器中;
3)使用layout()方法对文档进行排版布局;
4)使用createPDF()方法将文档渲染为PDF,并输出到输出流或文件中。

ITextFontResolver说明

ITextFontResolver是ITextRenderer库中的一个类,它用于管理和解析字体文件,为PDF生成提供字体支持。

在ITextRenderer库中,当使用HTML文档生成PDF时,由于PDF不支持HTML中使用的所有字体,因此需要在生成PDF之前将HTML中的字体替换为PDF支持的字体。ITextFontResolver提供了一个addFont()方法,该方法用于将字体文件添加到ITextFontResolver中进行管理,以便在PDF生成时使用。

转换为Base64说明

Base64是一种用于将二进制数据转换成文本数据的编码方式,通过Base64编码可以将图片、音频、视频等二进制数据转换成文本数据,从而方便在网络上传输。

3.4. 图片转Base64 工具类

import org.apache.commons.lang.StringUtils;

import javax.xml.bind.DatatypeConverter;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * @Author:wangdi
 * @Date:2023/6/9 15:34
 * @Des: ImgBase64Util 图片转Base64 工具类
 */
public class ImgBase64Util {

    public final static String IMG_PRE = "data:image/png;base64,";

    public static void main(String[] args) throws Exception {
        //本地图片地址
        String url = "D:/Soft/IDEA/IDEA_PLUGINS/Img/727eee40a3202c6799fffe55c9d6a026.jpg";
        //在线图片地址
        String string = "https://devpress.csdnimg.cn/489fad64a62648818eaaebc28e5c8659.jpg";
        String str = ImageToBase64ByLocal(url);
        System.out.println(str);
        String ste = ImageToBase64ByOnline(string);
//        Base64Utils.Base64ToImage(str,"C:/Users/Administrator/Desktop/test1.jpg");
//        Base64Utils.Base64ToImage(ste, "C:/Users/Administrator/Desktop/test2.jpg");
    }


    /**
     * 本地图片转换成base64字符串
     *
     * @param imgFile 图片本地路径
     * @return
     */
    public static String ImageToBase64ByLocal(String imgFile) {// 将图片文件转化为字节数组字符串,并对其进行Base64编码处理


        InputStream in = null;
        byte[] data = null;

        // 读取图片字节数组
        try {
            in = new FileInputStream(imgFile);

            data = new byte[in.available()];
            in.read(data);

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return DatatypeConverter.printBase64Binary(data);
    }


    /**
     * 在线图片转换成base64字符串
     *
     * @param imgURL 图片线上路径
     * @return
     */
    public static String ImageToBase64ByOnline(String imgURL) {
        ByteArrayOutputStream data = new ByteArrayOutputStream();
        InputStream is = null;
        try {
            // 创建URL
            URL url = new URL(imgURL);
            byte[] by = new byte[1024];
            // 创建链接
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(5000);
            is = conn.getInputStream();
            // 将内容读取内存中
            int len = -1;
            while ((len = is.read(by)) != -1) {
                data.write(by, 0, len);
            }
            // 关闭流

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return DatatypeConverter.printBase64Binary(data.toByteArray());
//        // 对字节数组Base64编码
//        BASE64Encoder encoder = new BASE64Encoder();
//        return encoder.encode(data.toByteArray());
    }


    /**
     * base64字符串转换成图片
     *
     * @param imgStr      base64字符串
     * @param imgFilePath 图片存放路径
     * @return
     */
    public static boolean Base64ToImage(String imgStr, String imgFilePath) { // 对字节数组字符串进行Base64解码并生成图片

        if (StringUtils.isEmpty(imgStr)) // 图像数据为空
            return false;

        OutputStream out = null;
        try {

            byte[] b = DatatypeConverter.parseBase64Binary(imgStr);
            for (int i = 0; i < b.length; ++i) {
                if (b[i] < 0) {// 调整异常数据
                    b[i] += 256;
                }
            }

            out = new FileOutputStream(imgFilePath);
            out.write(b);
            out.flush();
            return true;
        } catch (Exception e) {
            return false;
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

}

3.5. 测试使用

在这里插入图片描述


@Autowired
private HtmlToPDFUtil htmlToPDFUtil;


/*
    * 渲染pdf注意:
    * 要生成一个独立的html文件
    * 其中css样式必须内嵌,不可以单独是css文件,否则会渲染不成功
    * 图片的处理采用base64方式进行渲染,可以将图片全部保存到项目里面,采用代码转换base64进行塞参数渲染,或者直接在html中图片就使用base64格式
    * */

@Test
public void changeTaskReport() throws Exception {
    Map<String, Object> data = new HashMap();
    data.put("name", "Alice");
    data.put("age", 20);
    // 此处的图片可以用相对路径,配合Thread.currentThread().getContextClassLoader().getResource("").getPath(); 获取路径使用,注意测试类启动的和SpringBoot启动的路径地址不一致
    data.put("imgSrc", ImgBase64Util.IMG_PRE + ImgBase64Util.ImageToBase64ByLocal("C:\\Users\\wangdi13\\Desktop\\Snipaste_2023-06-09_15-43-25.png"));

    

    String html = htmlToPDFUtil.render("test.html", data);
    System.out.println(html);
    String base64 = HtmlToPDFUtil.getPDFBase64ByHtml(html);
    String pdfAdd = HtmlToPDFUtil.base64ToPDF(base64, "D:\\______________________________________WorkSpace\\demo-wy-test\\src\\main\\resources\\");
    System.out.println(pdfAdd);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/642116.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android开发之数据传递的桥梁——Bundle

解释 在安卓sdk源码中&#xff0c;Bundle类的说明是这样的 A mapping from String keys to various Parcelable values. See Also: PersistableBundle public final class Bundle extends BaseBundle implements Cloneable, Parcelable 字符串的键到持久化值的映射。 作用 …

只用2个小时,我把公司的进销存流程全部搬到了线上!

目录 一、前言 二、线下流程的弊端 三、仅用2个小时&#xff0c;如何将流程搬到线上&#xff1f; &#xff08;1&#xff09;基础资料模块 &#xff08;2&#xff09;采购管理模块 &#xff08;3&#xff09;销售管理模块 &#xff08;4&#xff09;库存管理模块 &…

MySQL之视图,触发器与存储过程

一、视图 视图是一个虚拟表&#xff08;非真实存在&#xff09;&#xff0c;其本质是【根据SQL语句获取动态的数据集&#xff0c;并为其命名】&#xff0c;用户使用时只需使用【名称】即可获取结果集&#xff0c;可以将该结果集当做表来使用。 使用视图我们可以把查询过程中的…

基于虚拟化的物联网沙盒操作系统

了解她的技术 先谈谈虚拟化吧&#xff01; 为什么要有虚拟化&#xff1f;物理CPU&#xff0c;物理内存和存储&#xff0c;物理网络的硬件能力越来越丰富的情况下&#xff0c;为了高效、灵活的使用资源&#xff0c;以及在使用时的资源隔离&#xff0c;把硬件资源抽象成软件资源…

机器学习第一课

实现流程&#xff1a; 数据输入->数据基本处理->特征工程->训练->模型评估->新数据输入->预测结果 数据类型&#xff1a; 类型一&#xff1a;特征值目标值 类型二&#xff1a;只有特征值 一、数据基本处理 达到的标准 二、特征工程 三、机器学习&#…

Java+Swing+mysql图书管理系统

JavaSwingmysql图书管理系统 一、系统介绍二、功能展示1.管理员登陆2.图书查询3.图书入库4.借书5.还书6.图书证管理 三、系统实现1.BookManageMainFrame.java 四、其它1.其他系统实现2.获取源码 一、系统介绍 该系统实现了 用户: 书籍查询&#xff0c;借书&#xff0c;还书功能…

本地serve跑vue或者react打包后的项目

本地跑vue或者react打包后的项目 不需要本地服务器跑打包后的build文件夹&#xff08;也可能是 dist文件夹&#xff09;项目。 一般方案&#xff1a; 方案一&#xff1a;本地电脑运行serve服务&#xff08;本文~~&#xff09;方案二&#xff1a;vscode编辑器安装拓展【live …

KEGG注释:KEGG富集可视化柱状图

很久很久以前&#xff0c;看到过文章中的KEGG富集可视化结果图。是对KEGG通路进行注释的。后来在一些测序公司的宣传页上也见到过类似的图&#xff1a; image.png image.png 其实这个图就是多了一个KEGG通路注释&#xff0c;近期也有小伙伴寻求怎么做。网上很多在线工具可以完…

第二节 柱状图

文章目录 1.数据分析流程图2. 列表与数组2.1 二者区别2.2 shape() 函数2.3 生成柱状图比较2.3.1 列表生成柱状图2.3.2 数组生成柱状图2.3.3 扩展案例练习 3. 坐标值3.1 添加横纵坐标值 4. 其他优化细节4.1 屏幕属性4.2 标题属性4.3 练习案例 5. 知识点总结 1.数据分析流程图 数…

功率放大器的工作原理及特点是什么

功率放大器是一种电子设备&#xff0c;用于将输入信号放大到更大的电压、电流或功率级别&#xff0c;以便驱动更大的负载或产生更大的输出功率。这种类型的放大器常用于音频、无线电通信、雷达、声呐、医疗设备和其他应用中。下面安泰电子将为大家介绍功率放大器的工作原理及特…

糖基化修饰:130548-92-4,Fmoc-L-Thr(Ac4-D-Glcβ)-OH,糖肽按照氨基酸和糖的连接方式分为多种

Fmoc-L-Thr(Ac4-D-Glcβ)-OH&#xff0c;糖基化修饰&#xff0c;即糖肽可以按照氨基酸和糖的连接方式分为四类&#xff1a;O 位糖基化、N 位糖基化&#xff0c;糖肽的基本结构多肽链与糖链通过共价键相连蛋白链上连接糖链的位点称为糖基化位点。由于糖肽糖链的生物合成没有模板…

重磅再推 | 基于OpenSearch向量检索版+大模型,搭建对话式搜索

面向企业开发者的PaaS方案 一周前&#xff0c;阿里云OpenSearch发布的LLM智能问答版&#xff0c;面向行业搜索场景&#xff0c;提供企业专属问答搜索服务。作为一站式免运维的SaaS服务&#xff0c;智能问答版基于内置的LLM大模型提供问答能力&#xff0c;为企业快速搭建问答搜…

Linux系统安装mysql8完整无脑步骤

1&#xff1a;卸载原有REPO源 查询已经存在的mysql及相关依赖包 rpm -qa | grep mysql此时你会看到一堆列表&#xff0c;使用 yum 命令依次卸载 yum remove mysql-xxx-xxx-xxx接下来删除 mysql 的残留目录及文件&#xff0c;先用下面的命令找出所有mysql相关的文件夹 find …

报表工具Stimulsoft Reports用户手册:如何激活控件

Stimulsoft Reports.Net是一个基于.NET框架的报表生成器&#xff0c;能够帮助你创建结构、功能丰富的报表。StimulReport.Net 的报表设计器不仅界面友好&#xff0c;而且使用便捷&#xff0c;能够让你轻松创建所有报表&#xff1b;该报表设计器在报表设计过程中以及报表运行的过…

WhoNet报不是有效dbf解决

由于现在Web已经部署到Linux上了&#xff0c;以前在Windows上导出dbf通过oledb执行sql生成dbf的路径已经不可用了&#xff0c;加上需要安装dataaccess驱动也麻烦&#xff0c;为此换了fastdbf生成dbf文件。 首先还算顺利&#xff0c;开始就碰到中文乱码问题&#xff0c;下载源码…

Qt学习之旅 - QTableWidget控件其他控件设置图标

文章目录 设置列数设置水平表头设置行数设置正文设置QTableWidget表格不可被选中、不可编辑设置表格中某个单元格不可被选中 防止越界int转QString其他控件Scroll Area按键 添加UI设置图标主窗口图标状态栏上显示图标使用QtCreator //QTableWidget控件 //设置列数 ui->table…

人工智能和传统行业的思考

人工智能和传统行业的思考 转载&#xff1a;原文链接 — https://xiaowenz.com/blog/2023/04/decouple-your-time/ 传统之传统 传统产业之所以被称为传统&#xff0c;除了大部分并非依靠计算机技术驱动之外&#xff0c;同时也因为这些产业往往更贴近社会的基础架构或生产要素…

常见中间件

中间件简介 中间件运行在系统软件和应用软件之间&#xff0c;以便于各部件之间的沟通。他充当的功能是&#xff1a;将应用程序运行环境与操作系统隔离&#xff0c;从而让开发者不必为更多系统问题忧虑&#xff0c;直接关注该应用程序在解决问题上的能力。 提供解析网站代码的一…

STM32ARM体系结构(嵌入式学习)

STM32&ARM体系结构 1. STM321.1 简介1.2 STM32的优势1.3 命名规范 2. ARM体系结构2.1 ARM体系结构面试题&#xff1a;谈谈你对ARM的认识&#xff1f;1.ARM公司2.ARM处理器3.ARM技术 目前主流处理器架构&#xff1f;精简指令集RISC和复杂指令集CISC的区别&#xff1f;精简指…

第二类曲线积分

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 第二类曲线积分一、向量场是什么&#xff1f;二、向量场可视化三、计算1. 计算方式一2. 计算方式二 第二类曲线积分 因为之前学习第二类曲线的时候&#xff0c;不是…