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

1,369 阅读14分钟

​ 本文源自Recently祝祝,创自Recently祝祝。转载请标注出处。

 

目录

1.Thymeleaf说明

1.1什么是Thymeleaf

1.2Thymeleaf具有特点

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

2.1中心思想

2.2操作说明 

2.3具体步骤

3.具体实现 

 3.1添加依赖

 3.2定义HTML模板

 3.3html 模板渲染工具

HtmlTemplate

 3.4 读取html 模板渲染结果并且转换为Base64字符串

字体路径 

 PdfUtils 

 Base64Utils

 3.4.1 库:ITextRenderer说明

 3.4.2 类:ITextFontResolver说明

3.4.3转换为Base64总结说明

 3.5 base64转成PDF后返回当前pdf的路径,本地下载

3.5.1 ByteArrayOutputStream说明

 3.5.2 方法整合使用

3.6 base64转成PDF后,调用接口,触发浏览器下载

 3.6.1 方法整合使用

4.几个html报告模板

Template1: 

Template2: 


 Thymeleaf模板引擎使用,Java又一神器

1.Thymeleaf说明

1.1什么是Thymeleaf

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

1.2Thymeleaf具有特点

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

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

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

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

2.1中心思想

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

具体来说,Thymeleaf使用Java对象作为数据模型,可以通过Spring的控制器将数据注入到数据模型中。然后,Thymeleaf将数据模型与模板文件结合起来,生成HTML内容。最后,使用PDF生成库将HTML内容转换为PDF输出。

2.2操作说明 

在实现PDF输出功能时,可以使用Spring Boot提供的spring-boot-starter-thymeleaf依赖,该依赖包含了Thymeleaf、PDF生成库以及其他必需的依赖项。可以在控制器中使用Thymeleaf的TemplateEngine对象将数据模型和模板文件合并,生成HTML内容。然后,可以使用PDF生成库将HTML内容转换为PDF格式。

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

2.3具体步骤

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

3.具体实现 

 3.1添加依赖

 <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.1.8.RELEASE</version>
  </parent>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

 3.2定义HTML模板

​编辑

<html>
    <head>
        <title>Hello World!</title>
    </head>
    <body>
        <h1>Hello, ${name}!</h1>
        <p>You are ${age} years old.</p>
    </body>
</html>

 3.3html 模板渲染工具

HtmlTemplate

import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;

import javax.annotation.Resource;
import java.util.Map;


/**
 * html 模板渲染工具
 */
@Component
public class HtmlTemplate {
    @Resource
    private TemplateEngine templateEngine;

    /**
     * 使用 Thymeleaf 渲染 HTML
     * @param template HTML模板
     * @param params 参数
     * @return
     * @throws Exception
     */
    public String render(String template, Map<String,Object> params) throws Exception {
        // 创建模板上下文
        Context context = new Context();
        // 设置变量
        context.setVariables(params);
        //将数据填充到模板里,开始处理模板
        return templateEngine.process(template, context);
    }

}

 3.4 读取html 模板渲染结果并且转换为Base64字符串

字体路径

​编辑

PdfUtils

import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.ByteArrayOutputStream;

public class PdfUtils {
   
    public static String getPdfBase64ByHtml(String html) throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();//构建字节输出流
        ITextRenderer renderer = new ITextRenderer();
        ITextFontResolver fontResolver = renderer.getFontResolver();
        //指定文件字体添加到PDF库,指定字体不作为内部字体,而是外部字体被加载
        fontResolver.addFont("pdf/font/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        renderer.setDocumentFromString(html);
        renderer.layout();
        renderer.createPDF(baos);
        return Base64Utils.encode(baos.toByteArray());
    }
}

在添加了字体文件后,可以使用setDocumentFromString()方法将HTML文档设置到渲染器中,并使用layout()方法对文档进行排版布局。接下来,使用createPDF()方法将文档渲染为PDF,并输出到输出流中。

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

Base64Utils


import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import java.io.*;

/**
 * Base64 转换工具
 */
public class Base64Utils {

    /**
     * byte数组 转换为 Base64字符串
     */
    public static String encode(byte[] data) {
        return new BASE64Encoder().encode(data);
    }

    /**
     * Base64字符串 转换为 byte数组
     */
    public static byte[] decode(String base64) {
        try {
            return new BASE64Decoder().decodeBuffer(base64);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new byte[0];
    }

    /**
     * 把文件内容编码为 Base64字符串, 只能编码小文件(例如文本、图片等)
     */
    public static String encodeFile(File file) throws Exception {
        InputStream in = null;
        ByteArrayOutputStream bytesOut = null;

        try {
            in = new FileInputStream(file);
            bytesOut = new ByteArrayOutputStream((int) file.length());

            byte[] buf = new byte[1024];
            int len = -1;

            while ((len = in.read(buf)) != -1) {
                bytesOut.write(buf, 0, len);
            }
            bytesOut.flush();

            return encode(bytesOut.toByteArray());

        } finally {
            close(in);
            close(bytesOut);
        }
    }

    /**
     * 把 Base64字符串 转换为 byte数组, 保存到指定文件
     */
    public static void decodeFile(String base64, File file) throws Exception {
        OutputStream fileOut = null;
        try {
            fileOut = new FileOutputStream(file);
            fileOut.write(decode(base64));
            fileOut.flush();
        } finally {
            close(fileOut);
        }
    }

    private static void close(Closeable c) {
        if (c != null) {
            try {
                c.close();
            } catch (IOException e) {
                // nothing
            }
        }
    }

}

 3.4.1 库: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,并输出到输出流或文件中。

引入

 <dependency>
    <groupId>org.xhtmlrenderer</groupId>
    <artifactId>flying-saucer-pdf</artifactId>
    <version>9.1.18</version>
 </dependency>

 3.4.2 类:ITextFontResolver说明

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

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

DEMO说明:

// 创建一个ITextRenderer对象
ITextRenderer renderer = new ITextRenderer();

// 创建一个ITextFontResolver对象
ITextFontResolver fontResolver = renderer.getFontResolver();

// 添加字体文件
fontResolver.addFont("pdf/font/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

首先创建了一个ITextRenderer对象,然后通过getFontResolver()方法获取了ITextFontResolver对象,并将要使用的字体文件添加到了ITextFontResolver对象中。

3.4.3转换为Base64总结说明

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

 3.5 base64转成PDF后返回当前pdf的路径,本地下载

public class Base64Util {

    public static String base64StringToPDF(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 "";
    }
}

3.5.1 ByteArrayOutputStream说明

ByteArrayOutputStream是Java IO库中的一个类,它提供了一个缓存区,可以将数据写入到内存中的字节数组中。 当数据写入缓存区时,如果缓存区的大小不足,ByteArrayOutputStream会自动扩展缓存区的大小,以容纳更多的数据。

ByteArrayOutputStream的主要作用是在内存中创建一个可变长度的字节数组,将数据写入到字节数组中,然后通过调用toByteArray()方法获取完整的字节数组。通常情况下,ByteArrayOutputStream用于缓存中间结果,以便在后续的操作中使用。

 3.5.2 方法整合使用

@Autowired
private HtmlTemplate htmlTemplate;


@Override
public String changeTaskReport() throws Exception {
        Map<String, Object> map = new HashMap();
        StringBuffer sb = new StringBuffer();

        data.put("name", "Alice");
        data.put("age", 20);

        String html = htmlTemplate.render("Template.html", map);
        String base64 = PdfUtils.getPdfBase64ByHtml(html);
        String pdfAdd = Base64Util.base64StringToPDF(base64, "D:\uploadFiles\PDF");
        return pdfAdd;
}

3.6 base64转成PDF后,调用接口,触发浏览器下载

@Slf4j
@Component
public class PdfUtil {
    public static void base64StringToPDF(HttpServletResponse response, String base64, String newName) {
        BASE64Decoder decoder = new BASE64Decoder();
        BufferedInputStream bin = null;
        try {
            byte[] bytes = decoder.decodeBuffer(base64);
            response.reset();
            response.setContentType("application/pdf");
            response.setCharacterEncoding("utf-8");
            response.setHeader("Content-Disposition", "attachment; filename="" + URLEncoder.encode(newName, "UTF-8") + ".pdf");

            ServletOutputStream outputStream = response.getOutputStream();
            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
            bin = new BufferedInputStream(bais);
            byte[] buffers = new byte[1024];
            int len = bin.read(buffers);
            while (len != -1) {
                outputStream.write(buffers, 0, len);
                len = bin.read(buffers);
            }
            outputStream.flush();
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bin != null) {
                    bin.close();
                }

            } catch (IOException e) {
                log.error(e.getMessage());
            }
        }
    }

}

  1. byte[] bytes = Base64.getDecoder().decode(base64);:使用Base64类的getDecoder()方法获取Base64解码器,并使用解码器对base64字符串进行解码,得到原始的PDF文件字节数组。

  2. try块中进行以下操作:

    • response.reset();:重置响应对象,以清除任何之前的设置。
    • response.setContentType("application/pdf");:设置响应的内容类型为PDF文件。
    • response.setCharacterEncoding("utf-8");:设置响应的字符编码为UTF-8。
    • String encodedName = URLEncoder.encode(newName, "UTF-8");:对newName进行URL编码,以确保中文文件名的正确性。
    • response.setHeader("Content-Disposition", "attachment; filename="" + encodedName + ".pdf"");:设置响应头,指定文件名,使用encodedName作为文件名,并添加.pdf扩展名。
    • ServletOutputStream outputStream = response.getOutputStream();:获取响应的输出流,用于将文件内容写入响应。
    • ByteArrayInputStream bais = new ByteArrayInputStream(bytes);:创建一个ByteArrayInputStream对象,将PDF文件字节数组作为数据源。
    • bin = new BufferedInputStream(bais);:创建一个BufferedInputStream对象,用于读取字节流。
    • byte[] buffers = new byte[1024];:创建一个缓冲区,用于读取和写入字节。
    • int len = bin.read(buffers);:从输入流中读取字节到缓冲区,并获取读取的字节数。

 3.6.1 方法整合使用

无返回值 

@Autowired
private HtmlTemplate htmlTemplate;


@Override
public void downloadPdf(HttpServletResponse response,String newName) throws Exception {
   Map<String, Object> map = new HashMap();
        StringBuffer sb = new StringBuffer();

        data.put("name", "Alice");
        data.put("age", 20);

        String html = htmlTemplate.render("Template.html", map);
        String base64 = PdfUtils.getPdfBase64ByHtml(html);
        PdfUtil.base64StringToPDF( response,base64, newName);

}

@RestController
@RequestMapping("/pdf")
public class PdfController {
//http://127.0.0.1:18088/pdf/download?id=1&newName=报告
    @Autowired
    private FileService fileService;

    @GetMapping("/download")
    public void getpdfDownload(HttpServletResponse response, @RequestParam("id") String  id, @RequestParam("newName") String newName) throws Exception {
        String id1=id;
        String name1=newName;
        fileService.downloadPdf(response,id,newName);
    }
}

4.几个html报告模板

Template1: 

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <title>报告模板1</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></meta>
    <style type="text/css">
        p {
            text-indent: 2em;
        }

        body {
            font-family: SimSun;
        }

    </style>
</head>
<body>


<div style="width: 100%;text-align: center;">
    <h2 style=""><span th:text="${recentlyName}"></span></h2>
    <table style="table-layout:fixed;margin: auto" border="1" cellspacing="0" cellpadding="0">
        <tbody>
        <tr style="height: 35px; text-align: center">
            <td style="width: 16%">
                编写人员
            </td>
            <td style="width: 26%" colspan="2">
                <span th:text="${recentlyAuthor}"></span>
            </td>
            <td style="width: 24%" colspan="3">
                时间
            </td>
            <td style="width: 36%;" colspan="2">
                <span th:text="${recentlyTime}"></span>
            </td>
        </tr>

        <tr style="height: 100px;">
            <td>
                摘要
            </td>
            <td colspan="7" valign="top">
                <p style="text-align: left;">
                    <span>
                        <span th:text="${reportSummary}"></span>
                    </span>
                </p>
            </td>
        </tr>
        <tr style="height: 100px;">
            <td>
                完成情况
            </td>
            <td colspan="7" valign="top">
                <p style="text-align: left;">
                    <span>
                        <span th:text="${recentlyStatus}"></span>
                    </span>
                </p>
            </td>
        </tr>
        <tr style="height: 100px;">
            <td>
                情况
            </td>
            <td colspan="7" valign="top">
                <p style="text-align: left;">
                    <span>
                        <span th:text="${recentlyExecutionStatus}"></span>
                    </span>
                </p>
            </td>
        </tr>
        <tr style="height: 100px;">
            <td>
                具体内容
            </td>
            <td colspan="7" valign="top">
                <p style="text-align: left;">
                    <span>
                        <span th:text="${recentlyMeasures}"></span>
                    </span>
                </p>
            </td>
        </tr>
        <tr style="height: 100px;">
            <td>
                问题
            </td>
            <td colspan="7" valign="top">
                <p style="text-align: left;">
                    <span>
                        <span th:text="${recentlyProblem}"></span>
                    </span>
                </p>
            </td>
        </tr>
        <tr style="height: 100px;">
            <td>
               建议
            </td>
            <td colspan="7" valign="top">
                <p style="text-align: left;">
                    <span>
                        <span th:text="${suggestions}"></span>
                    </span>
                </p>
            </td>
        </tr>
        <tr style="height: 100px;">
            <td>
                报告结论
            </td>
            <td colspan="7" valign="top">
                    <p style="text-align: left;">
                        <span th:if="${recentlyType} eq 2">
                            <strong>通过</strong>
                        </span>
                        <span th:if="${recentlyType} eq 3">
                            <strong>驳回</strong>
                            <span style="display: block;">原因:<span th:text="${rejectReason}"/></span>
                        </span>
                    </p>
                <p style="padding-left: 300px"><strong>机构(章)</strong></p>
            </td>
        </tr>
        </tbody>
    </table>
    <p><strong>备注:本表一式两份,科(办)和机构各执一份。</strong></p>
</div>
</body>
</html>

 ​编辑

Template2: 

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <title>Template2</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <style type="text/css">
        p {
            text-indent: 2em;
        }

        body {
            font-family: SimSun;
        }

    </style>
</head>
<body>


<div style="width: 100%;text-align: center;">
    <h2 style="">核查报告单</h2>
    <p style="padding-left: 30%;"><strong>编码: <span th:text="${code}"/></strong></p>
    <table style="table-layout:fixed;margin: auto" border="1" cellspacing="0" cellpadding="0">
        <tbody>
        <tr style=";height: 35px;text-align: center">
            <td style="width: 16%">
                姓名
            </td>
            <td style="width: 26%" colspan="2">
                <span th:text="${name}"/>
            </td>
            <td style="width: 24%" colspan="3">
                身份证号码
            </td>
            <td style="width: 36%;" colspan="2">
                <span th:text="${idcard}"/>
            </td>
        </tr>
        <tr style=";height: 35px;text-align: center">
            <td>
               年龄
            </td>
            <td colspan="2">
                <span th:text="${age}"/>
            </td>
            <td colspan="3">
                住址
            </td>
            <td colspan="2">
                <span th:text="${addr}"/>
            </td>
        </tr>
        <tr style="height: 480px;text-align: center">
            <td rowspan="2">
                报告(可附页)
            </td>
            <td colspan="7" valign="top">
                <p style="text-align: left;">
                    <span>
                        <strong>方式:</strong>
                        <span th:text="${methodName}"/>
                    </span>
                </p>
                <p style="text-align: left;">
                    <span>
                        <strong>经过:</strong>
                        <span th:text="${after}"/>
                    </span>
                </p>
                <p style="text-align: left;">
                    <span>
                        <strong>结论:</strong>
                        <span th:text="${conclusion}"/>
                    </span>
                </p>
            </td>
        </tr>
        <tr style="height: 45px;">
            <td width="15%">
                检查人
            </td>
            <td colspan="3" width="120px">
                <span th:text="${investigator}"/>
            </td>
            <td colspan="2" width="18%">
                经过人
            </td>
            <td>
                <span th:text="${reviewer}"/>
            </td>

        </tr>
        <tr style="height: 100px;">
            <td>
                结论
            </td>
            <td colspan="7" valign="top">
                <strong>
                    <p style="text-align: left;">
                        <span th:if="${checkResult} eq 1">
                           通过
                        </span>
                        <span th:if="${checkResult} eq 2">
                           不通过
                        </span>
                        <span th:if="${checkResult} eq 3">
                            驳回
                        </span>
                    </p>
                </strong>

                <p style="text-align: left">
                    <strong><span th:text="${checkResult} == '1'?'':${resultReason}"/></strong>
                </p>
                <p style="padding-left: 300px"><strong>机构(章)</strong></p>
                <p style="padding-left: 300px"><strong> <span th:text="${date}"/></strong></p>
            </td>
        </tr>
        </tbody>
    </table>
    <p><strong>备注:本表一式两份,科(办)和机构各执一份。</strong></p>
</div>
</body>
</html>

 ​编辑

5.防御性代码保护

HtmlTemplate

@Component
public class HtmlTemplate {
    @Resource
    private TemplateEngine templateEngine;

    public String render(String template, Map<String, Object> params) throws Exception {
        try {
            validateTemplate(template);
            validateParams(params);

            Context context = createContext(params);
            String result = processTemplate(template, context);
            return result;
        } catch (Exception e) {
            handleException(e);
            throw e;
        }
    }

    private void validateTemplate(String template) throws Exception {
        // 检查模板是否为null或空字符串
        if (template == null || template.isEmpty()) {
            throw new IllegalArgumentException("Template is required.");
        }

        // 检查模板路径的合法性,防止恶意操作或非法访问
        if (!isValidTemplatePath(template)) {
            throw new IllegalArgumentException("Invalid template path.");
        }
    }

    private boolean isValidTemplatePath(String template) {
        // 执行额外的安全检查,例如验证模板路径的合法性
        // 返回true表示模板路径合法,返回false表示模板路径非法
        // 可以根据具体需求实现此方法
        return true;
    }

    private void validateParams(Map<String, Object> params) throws Exception {
        // 检查参数是否为null或空
        if (params == null || params.isEmpty()) {
            throw new IllegalArgumentException("Parameters are required.");
        }

        // 检查参数的合法性,例如类型、长度、内容等
        for (Map.Entry<String, Object> entry : params.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();

            // 检查参数key是否为null或空字符串
            if (key == null || key.isEmpty()) {
                throw new IllegalArgumentException("Invalid parameter key.");
            }

            // 检查参数value是否为null
            if (value == null) {
                throw new IllegalArgumentException("Invalid parameter value for key: " + key);
            }

            // 执行额外的安全检查,例如验证参数的类型、长度、内容等
            if (!isValidParameter(value)) {
                throw new IllegalArgumentException("Invalid parameter value for key: " + key);
            }
        }
    }

    private boolean isValidParameter(Object value) {
        // 执行额外的安全检查,例如验证参数的类型、长度、内容等
        // 返回true表示参数合法,返回false表示参数非法
        // 可以根据具体需求实现此方法
        return true;
    }

    private Context createContext(Map<String, Object> params) {
        Context context = new Context();

        // 执行额外的安全检查,例如限制上下文变量的访问权限,防止信息泄露

        context.setVariables(params);
        return context;
    }

    private String processTemplate(String template, Context context) throws Exception {
        // 执行额外的安全检查,例如限制模板的访问路径、资源的访问权限,防止恶意操作或非法访问

        String result = templateEngine.process(template, context);

        // 执行额外的安全检查,例如验证结果的长度、内容等,防止恶意操作或非法输出

        return result;
    }

    private void handleException(Exception e) {
        // 处理异常,例如进行适当的日志记录、错误提示等操作
    }
}

 PdfUtils

@Slf4j
@Component
public class PdfUtils {

    public static String encode(byte[] data) {
        return new BASE64Encoder().encode(data);
    }

    public static String getPdfBase64ByHtml(String html) throws Exception {
        ByteArrayOutputStream baos = null;
        ITextRenderer renderer = null;
        try {
            baos = new ByteArrayOutputStream();

            // 创建渲染器并设置字体解析器
            renderer = createRenderer();
            ITextFontResolver fontResolver = renderer.getFontResolver();
            fontResolver.addFont(getFontFilePath(), BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

            // 设置文档内容并生成PDF
            setDocumentContent(renderer, html);
            renderer.layout();
            renderer.createPDF(baos);

            return Base64Util.encode(baos.toByteArray());
        } finally {
            // 关闭资源
            closeOutputStream(baos);
            disposeRenderer(renderer);
        }
    }

    private static ITextRenderer createRenderer() {
        ITextRenderer renderer = new ITextRenderer();

        // 设置渲染器的相关配置,如分辨率、缩放等

        return renderer;
    }

    private static String getFontFilePath() throws Exception {
        String fontFilePath = "font/simsun.ttc";

        // 执行额外的安全检查,确保字体文件路径合法性

        return fontFilePath;
    }

    private static void setDocumentContent(ITextRenderer renderer, String html) {
        // 执行额外的安全检查,确保HTML内容合法性

        renderer.setDocumentFromString(html);
    }

    private static void closeOutputStream(ByteArrayOutputStream baos) {
        if (baos != null) {
            try {
                baos.close();
            } catch (IOException e) {
                // 处理关闭字节输出流时的异常
                e.printStackTrace();
            }
        }
    }

    private static void disposeRenderer(ITextRenderer renderer) {
        if (renderer != null) {
            try {
                renderer.finishPDF();
            } catch (Exception e) {
                // 处理渲染器完成PDF时的异常
                e.printStackTrace();
            }
            renderer = null;
        }
    }

}

慢就是快,时间对于开发来说不是数据,是哲学