本篇文章将深入探讨如何使用 SpringBoot 3.3 结合 Thymeleaf、JavaScript 实现高效的PDF文档生成。
项目环境准备
1. 开发环境
JDK 17:确保您的项目运行在Java 17环境下。
Maven 3.8+:用于项目构建和依赖管理。
SpringBoot 3.3:最新版本的SpringBoot框架。
Thymeleaf:流行的Java模板引擎。
OpenPDF:开源的PDF生成库。
Bootstrap 5:用于前端页面的布局和样式。
JavaScript:用于增强页面交互性。
2. 项目结构
项目的基本结构如下:
pdf-demo ├── src │ ├── main │ │ ├── java │ │ │ └── com.icoderoad.pdfdemo │ │ │ ├── controller │ │ │ ├── service │ │ │ └── PdfDemoApplication.java │ │ ├── resources │ │ │ ├── templates │ │ │ ├── fonts/SimSun.ttf │ │ │ ├── static │ │ │ └── application.yml ├── pom.xml
项目依赖配置(pom.xml)
首先,我们需要在 pom.xml 中添加必要的依赖。
<?xml versinotallow="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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.icoderoad</groupId> <artifactId>pdfdemo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>pdfdemo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>17</java.version> <openpdf.version>1.3.29</openpdf.version> </properties> <dependencies> <!-- Spring Boot Starter Web --> <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> <!-- OpenPDF 库 --> <dependency> <groupId>com.github.librepdf</groupId> <artifactId>openpdf</artifactId> <version>${openpdf.version}</version> </dependency> <!-- HTML转PDF工具(flying-saucer) --> <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>flying-saucer-core</artifactId> <version>9.1.20</version> </dependency> <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>flying-saucer-pdf-openpdf</artifactId> <version>9.1.20</version> </dependency> <!-- 日志框架 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
后端代码实现
1. 主启动类
创建主启动类 PdfDemoApplication.java。
package com.icoderoad.pdfdemo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class PdfdemoApplication { public static void main(String[] args) { SpringApplication.run(PdfdemoApplication.class, args); } }
2. 控制器层
创建控制器类 PdfController.java,负责处理请求和生成PDF文档。
// PdfController.javapackage com.icoderoad.pdfdemo.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import com.icoderoad.pdfdemo.service.PdfService; import jakarta.servlet.http.HttpServletResponse; @Controller public class PdfController { @Autowired private PdfService pdfService; /** * 显示数据预览页面 * @param model 模型数据 * @return 模板名称 */ @GetMapping("/preview") public String preview(Model model) { model.addAttribute("title", "PDF文档预览"); model.addAttribute("content", "这是一个使用Thymeleaf渲染的HTML内容,将被转换为PDF文档。"); return "preview"; } /** * 生成PDF文档并下载 * @param response HTTP响应 * @throws Exception 异常 */ @GetMapping("/download") public void downloadPdf(HttpServletResponse response) throws Exception { String htmlContent = pdfService.generateHtmlContent(); pdfService.generatePdf(response, htmlContent); } }
3. 服务层
创建服务类 PdfService.java,负责处理HTML内容的生成和PDF文档的生成。
package com.icoderoad.pdfdemo.service; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.io.ClassPathResource; import org.springframework.stereotype.Service; import org.thymeleaf.TemplateEngine; import org.thymeleaf.context.Context; import org.xhtmlrenderer.pdf.ITextFontResolver; import org.xhtmlrenderer.pdf.ITextRenderer; import com.lowagie.text.DocumentException; import jakarta.servlet.http.HttpServletResponse; @Service public class PdfService { @Autowired private TemplateEngine templateEngine; /** * 生成HTML内容 * @return 渲染后的HTML字符串 */ public String generateHtmlContent() { Context context = new Context(); context.setVariable("title", "PDF文档标题"); context.setVariable("content", "这是PDF文档的主要内容:" + "“成功并不是终点,失败也不是终结,最重要的是继续前行的勇气。在人生的旅途中,我们会遇到许多挑战与挫折,这些都是成长的必经之路。每一次跌倒都是一次学习的机会,每一次失败都为成功铺设了基础。只要我们保持信念,不断努力,最终会到达梦想的彼岸。无论前方的路有多么坎坷,只要心怀希望,我们就有无限的可能性去改变自己的命运,实现心中的理想。”" + "由Thymeleaf模板引擎渲染。" + ""); return templateEngine.process("pdf_template", context); } /** * 将HTML内容转换为PDF并写入响应 * @param response HTTP响应 * @param htmlContent HTML内容 * @throws IOException IO异常 * @throws DocumentException 文档异常 */ public void generatePdf(HttpServletResponse response, String htmlContent) throws IOException, DocumentException { // 设置响应类型 response.setContentType("application/pdf"); response.setHeader("Content-Disposition", "attachment; filename=generated.pdf"); // 创建ITextRenderer实例 ITextRenderer renderer = new ITextRenderer(); // 设置字体路径,使用 classpath 加载字体 ITextFontResolver fontResolver = renderer.getFontResolver(); ClassPathResource fontResource = new ClassPathResource("fonts/SimSun.ttf"); System.out.println(fontResource.getFile().getAbsolutePath()); fontResolver.addFont(fontResource.getFile().getAbsolutePath(), "Identity-H", true); renderer.setDocumentFromString(htmlContent); renderer.layout(); // 输出PDF到响应输出流 try (OutputStream outputStream = response.getOutputStream()) { renderer.createPDF(outputStream); outputStream.flush(); } } }
代码说明:
generateHtmlContent():使用Thymeleaf模板引擎渲染HTML内容,填充模板变量。
generatePdf():使用 ITextRenderer 将HTML内容转换为PDF文档,并写入HTTP响应,供用户下载。
注意事项:
模板引擎配置:TemplateEngine 需要在配置类中进行相应设置,这里通过自动装配注入。
字符编码:确保HTML内容的字符编码为UTF-8,避免中文乱码。
前端代码实现
1. Thymeleaf模板
在 src/main/resources/templates 目录下创建 preview.html 和 pdf_template.html 两个模板文件。
1.1 preview.html
用于在浏览器中预览内容。
<!-- preview.html --> <!DOCTYPE html> <html lang="zh-CN" xmlns:th="http://www.thymeleaf.org"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title th:text="${title}">PDF预览</title> <!-- 引入Bootstrap CSS --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"> </head> <body> <div class="container mt-5"> <h1 class="text-center" th:text="${title}">PDF文档预览</h1> <p class="mt-4" th:text="${content}">这里是内容部分。</p> <div class="text-center mt-5"> <a href="/pdf-demo/download" class="btn btn-primary">下载PDF文档</a> </div> </div> <!-- 引入Bootstrap JS --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> </body> </html>
代码说明:
使用 Bootstrap 5 进行简单的页面布局和样式设计。
通过Thymeleaf的 th:text 属性渲染动态内容。
提供一个下载PDF的按钮,链接到 /download`。
1.2 pdf_template.html
用于生成PDF的模板。
<!DOCTYPE html> <html lang="zh-CN" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"/> <title th:text="${title}">PDF文档</title> <style> /* 内联CSS样式,确保在PDF中正确渲染 */ body { font-family: "SimSun", serif; padding: 20px; line-height: 1.6; } h1 { text-align: center; margin-bottom: 40px; color: #333; } p { font-size: 16px; color: #555; } .footer { text-align: center; margin-top: 50px; font-size: 12px; color: #999; } </style> </head> <body> <h1 th:text="${title}">PDF文档标题</h1> <p th:text="${content}">这是PDF文档的内容部分,由Thymeleaf模板引擎渲染。</p> </body> </html>
代码说明:
内联样式:为了确保PDF中样式正确渲染,使用内联CSS样式。
字体设置:指定 font-family 为中文字体(如 "SimSun"),避免中文显示乱码。
内容布局:简单的标题、内容和页脚布局,满足基本的PDF文档需求。