使用 Spring Boot 集成 Thymeleaf 和 Flying Saucer 实现 PDF 导出

在 Spring Boot 项目中,生成 PDF 报表或发票是常见需求。本文将介绍如何使用 Spring Boot 集成 Thymeleaf 模板引擎和 Flying Saucer 实现 PDF 导出,并提供详细的代码实现和常见问题解决方案。


目录

      • 一、项目依赖
      • [二、创建 Thymeleaf 模板](#二、创建 Thymeleaf 模板)
      • [三、创建 PDF 生成工具类](#三、创建 PDF 生成工具类)
      • [四、在 Spring Boot 中使用 PDF 生成功能](#四、在 Spring Boot 中使用 PDF 生成功能)
      • 五、常见错误及解决方案
        • [1. 中文字符乱码](#1. 中文字符乱码)
        • [2. 图片加载失败](#2. 图片加载失败)
        • [3. 样式不生效](#3. 样式不生效)
        • [4. 模板路径识别错误](#4. 模板路径识别错误)
        • [5. 依赖冲突](#5. 依赖冲突)
        • [6. 内存不足](#6. 内存不足)
      • 总结

一、项目依赖

要实现 PDF 导出功能,首先需要在项目中添加以下依赖:

xml 复制代码
<dependencies>
    <!-- Thymeleaf模板引擎 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>

    <!-- Flying Saucer for PDF generation -->
    <dependency>
        <groupId>org.xhtmlrenderer</groupId>
        <artifactId>flying-saucer-pdf</artifactId>
        <version>9.1.22</version>
    </dependency>

    <!-- iTextPDF (com.lowagie 版本,用于兼容 Flying Saucer) -->
    <dependency>
        <groupId>com.lowagie</groupId>
        <artifactId>itext</artifactId>
        <version>2.1.7</version>
    </dependency>
</dependencies>

注意 :Flying Saucer 使用的是旧版 iText 库 com.lowagie,而不是新版 com.itextpdf,确保使用正确版本以避免依赖冲突。


二、创建 Thymeleaf 模板

新建一个 HTML 模板用于生成发票内容,例如在 resources/templates/pdf/invoice.html 文件中编写如下 HTML:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>发票</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        h1 { text-align: center; }
        table { width: 100%; border-collapse: collapse; margin-top: 20px; }
        th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        th { background-color: #f2f2f2; }
        .total { font-weight: bold; }
        .footer { margin-top: 30px; text-align: center; font-size: 12px; color: #888; }
    </style>
</head>
<body>
<h1>发票</h1>
<p>发票编号:<span th:text="${invoiceNumber}"></span></p>
<p>开具日期:<span th:text="${invoiceDate}"></span></p>
<p>买家姓名:<span th:text="${buyerName}"></span></p>

<h2>商品详情</h2>
<table>
    <thead>
    <tr>
        <th>商品名称</th>
        <th>描述</th>
        <th>数量</th>
        <th>单价</th>
        <th>小计</th>
    </tr>
    </thead>
    <tbody>
    <tr th:each="item : ${goodsItems}">
        <td th:text="${item.goodsName}"></td>
        <td th:text="${item.goodsDesc}"></td>
        <td th:text="${item.quantity}"></td>
        <td th:text="${item.unitPrice}"></td>
        <td th:text="${item.totalPrice}"></td>
    </tr>
    </tbody>
</table>

<p class="total">总金额:<span th:text="${finalAmount}"></span></p>
<div class="footer">感谢您的购买!</div>
</body>
</html>

三、创建 PDF 生成工具类

接下来,创建 PdfUtil 工具类,通过 Thymeleaf 渲染 HTML 并使用 Flying Saucer 将 HTML 转为 PDF:

java 复制代码
package com.xyh.transaction.utils;

import com.xyh.transaction.entity.dto.goods.GoodsInvoice;
import com.xyh.transaction.exception.BusinessException;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.xhtmlrenderer.pdf.ITextRenderer;

import java.io.ByteArrayOutputStream;
import java.util.List;

public class PdfUtil {

    public static byte[] generateInvoicePdf(String buyerName, String invoiceNumber,
                                            List<GoodsInvoice> goodsItems, String finalAmount) {
        TemplateEngine templateEngine = new TemplateEngine();

        // 使用 Thymeleaf 渲染 HTML
        Context context = new Context();
        context.setVariable("buyerName", buyerName);
        context.setVariable("invoiceNumber", invoiceNumber);
        context.setVariable("goodsItems", goodsItems);
        context.setVariable("finalAmount", finalAmount);
        String htmlContent = templateEngine.process("pdf/invoice.html", context);

        // 将 HTML 转换为 PDF
        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            ITextRenderer renderer = new ITextRenderer();
            renderer.getFontResolver().addFont("/path/to/your/font/simhei.ttf", true); // 防止中文乱码
            renderer.setDocumentFromString(htmlContent);
            renderer.layout();
            renderer.createPDF(outputStream);
            return outputStream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException("发票PDF 生成失败");
        }
    }
}

四、在 Spring Boot 中使用 PDF 生成功能

在 Spring Boot 控制器中调用 PdfUtil.generateInvoicePdf,将生成的 PDF 返回给用户或作为附件发送邮件:

java 复制代码
@RestController
@RequestMapping("/invoice")
public class InvoiceController {

    @GetMapping("/generate")
    public ResponseEntity<byte[]> generateInvoice() {
        byte[] pdfBytes = PdfUtil.generateInvoicePdf("张三", "INV-12345",
                goodsItems, "1000.00");
        
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_PDF);
        headers.setContentDispositionFormData("attachment", "invoice.pdf");
        
        return new ResponseEntity<>(pdfBytes, headers, HttpStatus.OK);
    }
}

五、常见错误及解决方案

1. 中文字符乱码
  • 问题:生成的 PDF 中,中文字符可能出现乱码。
  • 解决方案 :确保 addFont 引用支持中文的字体文件(如 simhei.ttf)。
2. 图片加载失败
  • 问题:HTML 中的图片在 PDF 中无法正常显示。

  • 解决方案 :将图片路径设置为绝对路径或通过 setBaseURL 指定资源根路径:

    java 复制代码
    renderer.getSharedContext().setBaseURL("file:/path/to/resources/");
3. 样式不生效
  • 问题:Flying Saucer 不支持高级 CSS。
  • 解决方案 :使用基本的 table 布局和简单 CSS,不依赖 flex、CSS 变量等。
4. 模板路径识别错误
  • 问题:模板引擎找不到指定 HTML 文件。
  • 解决方案 :检查文件路径和模板配置,确保模板放置在 resources/templates/pdf 目录下。
5. 依赖冲突
  • 问题:Flying Saucer 使用旧版 iText,与其他依赖产生冲突。
  • 解决方案 :独立模块管理依赖,使用 maven-shade-plugin 处理冲突类。
6. 内存不足
  • 问题:生成大型 PDF 时出现内存溢出。
  • 解决方案:增加 JVM 内存配置,或简化 HTML 结构降低内存占用。

总结

使用 Spring Boot 集成 Thymeleaf 和 Flying Saucer 实现 PDF 导出是生成发票、报告等文档的高效方式。通过以上实现步骤和常见问题解决方案,希望可以帮助您顺利在项目中集成此功能。

相关推荐
还是鼠鼠31 分钟前
tlias智能学习辅助系统--Maven 高级-私服介绍与资源上传下载
java·spring boot·后端·spring·maven
还是大剑师兰特2 小时前
Spring面试题及详细答案 125道(1-15) -- 核心概念与基础1
spring·大剑师·spring面试题·spring教程
python_1364 小时前
web请求和响应
java·spring·github
舒一笑5 小时前
Started TttttApplication in 0.257 seconds (没有 Web 依赖导致 JVM 正常退出)
jvm·spring boot·后端
ciku6 小时前
Spring AI Starter和文档解读
java·人工智能·spring
javadaydayup6 小时前
Apollo 凭什么能 “干掉” 本地配置?
spring boot·后端·spring
FFF-X7 小时前
Vue3 路由缓存实战:从基础到进阶的完整指南
vue.js·spring boot·缓存
代码AI弗森9 小时前
PDF OCR + 大模型:让文档理解不止停留在识字
pdf·ocr
耳东哇10 小时前
spring ai-openai-vl模型应用qwen-vl\gpt-文字识别-java
java·人工智能·spring
smileNicky20 小时前
SpringBoot系列之从繁琐配置到一键启动之旅
java·spring boot·后端