SpringBoot手把手实现生成PDF功能的示例代码

 更新时间:2025年12月01日 09:25:06   作者:众纳  
这篇文章主要介绍了如何使用Thymeleaf模板和iText库配合Flying-pdf工具,实现在业务系统中生成PDF文件的功能,感兴趣的小伙伴可以了解下

本文介绍了如何使用Thymeleaf模板和iText库配合Flying-pdf工具,实现在业务系统中生成PDF文件的功能,以订单和发货单为例,详细讲解了字体配置、Maven依赖、工具类实现和控制层方法。

需求:

在开发的业务系统中我希望能有一个转出PDF功能。比如:转出订单,转出发货单等

原理:

使用itext 与flying-pdf 对 thymeleaf 模板填充后生成PDF

实现:

1.准备中文字体,用于PDF中关于中文文字的显示。

存放位置

2.Maven 主要依赖

<!--itext 生成PDF-->
<dependency>
    <groupId>com.lowagie</groupId>
    <artifactId>itext</artifactId>
    <version>${itext.version}</version>
</dependency>

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>3.9</version>
</dependency>
<!--xlsx(07)-->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>3.9</version>
</dependency>

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

3.工具类

import com.lowagie.text.pdf.BaseFont;
import com.lowagie.text.xml.xmp.XmpWriter;
import lombok.extern.slf4j.Slf4j;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import org.w3c.dom.Document;
import org.xhtmlrenderer.pdf.ITextFontResolver;
import org.xhtmlrenderer.pdf.ITextRenderer;


import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.*;
import java.util.List;
import java.util.Map;

/**
 * pdf处理工具类
 *
 */
@Slf4j
public class PdfUtil {

    /**
     * 以文件流形式下载到浏览器
     *
     * @param templateEngine   配置
     * @param templateName 模板名称
     * @param listVars     模板参数集
     * @param response     HttpServletResponse
     * @param fileName     下载文件名称
     */
    public static void download(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response, String fileName) {
        // 设置编码、文件ContentType类型、文件头、下载文件名
        response.setCharacterEncoding(XmpWriter.UTF8);
        response.setContentType("application/pdf");
        try {
            response.setHeader("Content-Disposition", "attachment;fileName=" +
                    new String(fileName.getBytes("gb2312"), "ISO8859-1"));
        } catch (UnsupportedEncodingException e) {
            log.error(e.getMessage(), e);
        }
        try (ServletOutputStream out = response.getOutputStream()) {
            generateAll(templateEngine, templateName, out, listVars);
            out.flush();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * pdf下载到特定位置
     *
     * @param templateEngine   配置
     * @param templateName 模板名称
     * @param listVars     模板参数集
     * @param filePath     下载文件路径
     */
    public static void save(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, String filePath) {
        try (OutputStream out = new FileOutputStream(filePath);) {
            generateAll(templateEngine, templateName, out, listVars);
            out.flush();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * pdf预览
     *
     * @param templateEngine   配置
     * @param templateName 模板名称
     * @param listVars     模板参数集
     * @param response     HttpServletResponse
     */
    public static void preview(TemplateEngine templateEngine, String templateName, List<Map<String, Object>> listVars, HttpServletResponse response) {
        try (ServletOutputStream out = response.getOutputStream()) {
            generateAll(templateEngine, templateName, out, listVars);
            out.flush();
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * 按模板和参数生成html字符串,再转换为flying-saucer识别的Document
     *
     * @param templateName 模板名称
     * @param variables   模板参数
     * @return Document
     */
    private static Document generateDoc(TemplateEngine templateEngine, String templateName, Map<String, Object> variables)  {
        // 声明一个上下文对象,里面放入要存到模板里面的数据
        final Context context = new Context();
        context.setVariables(variables);
        StringWriter stringWriter = new StringWriter();
        try(BufferedWriter writer = new BufferedWriter(stringWriter)) {
            templateEngine.process(templateName,context, writer);
            writer.flush();
            DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
            return builder.parse(new ByteArrayInputStream(stringWriter.toString().getBytes()));
        }catch (Exception e){
            //ResponseEnum.TEMPLATE_PARSE_ERROR.assertFail(e);
        }
        return null;
    }

    /**
     * 核心: 根据Thymeleaf 模板生成pdf文档
     *
     * @param templateEngine 配置
     * @param templateName 模板名称
     * @param out          输出流
     * @param listVars     模板参数
     * @throws Exception 模板无法找到、模板语法错误、IO异常
     */
    private static void generateAll(TemplateEngine templateEngine, String templateName, OutputStream out, List<Map<String, Object>> listVars) throws Exception {
        // 断言参数不为空
        //ResponseEnum.TEMPLATE_DATA_NULL.assertNotEmpty(listVars);
        ITextRenderer renderer = new ITextRenderer();
        //设置字符集(宋体),此处必须与模板中的<body style="font-family: SimSun">一致,区分大小写,不能写成汉字"宋体"
        ITextFontResolver fontResolver = renderer.getFontResolver();
        //避免中文为空设置系统字体
        fontResolver.addFont("static/fonts/simsun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
        // 如果linux下有问题,可使用以下方式解决。
        // 有一个项目是用docker部署的,一直报错找不到simsun.ttf文件,但需要将simsun.ttf上传到/usr/share/fonts
        //fontResolver.addFont(CommonUtil.isLinux() ? "/usr/share/fonts/simsun.ttf" : "static/fonts/simsun.ttf", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

        //根据参数集个数循环调用模板,追加到同一个pdf文档中
        //(注意:此处从1开始,因为第0是创建pdf,从1往后则向pdf中追加内容)
        for (int i = 0; i < listVars.size(); i++) {
            Document docAppend = generateDoc(templateEngine, templateName, listVars.get(i));
            renderer.setDocument(docAppend, null);
            //展现和输出pdf
            renderer.layout();
            if(i==0){
                renderer.createPDF(out, false);
            }else {
                //写下一个pdf页面
                renderer.writeNextDocument();
            }

        }
        renderer.finishPDF(); //完成pdf写入
    }
}

4.控制层方法 (原理是把对象传过去,由thymeleaf解析)

import com.flo.po.Dto.OrderDetailedDto;
import com.flo.po.Dto.OrderDto;
import com.flo.service.OrderDetailedService;
import com.flo.service.OrderService;
import com.flo.util.PdfUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.thymeleaf.TemplateEngine;

import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 文档预览、下载
 *
 */
@RestController
@RequestMapping(value = "/document")
@Api(tags = "文档预览、下载API")
public class DocumentController {

    @Autowired
    private TemplateEngine templateEngine;


    @Autowired
    private OrderService orderService;//订单表头

    @Autowired
    private OrderDetailedService orderDetailedService;//订单表身

    /**
     * pdf预览
     * @param response HttpServletResponse
     */
    @GetMapping(value = "/pdf/preview")
    @ApiOperation(value="pdf预览")
    public void preview(HttpServletResponse response,Integer id) {

        OrderDto orderDto=orderService.findById(id);
        List<OrderDetailedDto> list=orderDetailedService.findAll(new OrderDetailedDto(){{setOrderId(id);}});

        // 构造freemarker模板引擎参数,listVars.size()个数对应pdf页数
        List<Map<String,Object>> listVars = new ArrayList<>();
        Map<String,Object> variables = new HashMap<>(4);
        variables.put("title","订单明细");
        variables.put("custName",orderDto.getCustomName());
        variables.put("adr",orderDto.getAddress());
        variables.put("tel",orderDto.getTel());
        variables.put("list",list);
        listVars.add(variables);
        PdfUtil.preview(templateEngine,"pdfPage",listVars,response);
    }
}

5.PDF模板(pdfPage.html)

注意事项:

1.尽可能把CSS样式放在这个模板里,外部引入可能会存在一些问题。

2.关于图片,切记一定要给完整路径,而非相对路径。如./uploadFile/logo.png 需要加上http://地址/uploadFile/logo.png

3.图片名称不能包含中文,否则会找不到路径(是由于编码为UTF8的原因)

位置

        

效果:

到此这篇关于SpringBoot手把手实现生成PDF功能的示例代码的文章就介绍到这了,更多相关SpringBoot PDF内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MyBatis-Plus多数据源配置与读写分离全过程

    MyBatis-Plus多数据源配置与读写分离全过程

    文章介绍了如何在SpringBoot中使用MyBatis-Plus实现多数据库操作,包括纯粹多库和读写分离的配置方法,重点演示了多数据源的设置与使用
    2025-09-09
  • Java实现将html字符串插入到PPT幻灯片

    Java实现将html字符串插入到PPT幻灯片

    Java后端代码操作PPT幻灯片时,可直接在幻灯片中绘制形状,并在形状中添加文本字符串内容。本篇文章主要介绍通过java实现将html字符串添加到PPT幻灯片的的方法,可添加文字、图片、视频、音频等。以下是具体方法和步骤。
    2021-11-11
  • idea报错java: 非法字符: ‘\ufeff‘的解决步骤以及说明

    idea报错java: 非法字符: ‘\ufeff‘的解决步骤以及说明

    这篇文章主要介绍了idea报错java:非法字符:\ufeff的解决步骤以及说明,文章详细解释了为什么在Java中会出现\ufeff错误,这是由于BOM字符被认为是非法字符导致的,通过示例代码将解决的过程介绍的非常详细,需要的朋友可以参考下
    2025-05-05
  • java获取文件的inode标识符的方法

    java获取文件的inode标识符的方法

    这篇文章主要介绍了java获取文件的inode标识符,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-04-04
  • java 通过聚合查询实现elasticsearch的group by后的数量

    java 通过聚合查询实现elasticsearch的group by后的数量

    这篇文章主要介绍了java 通过聚合查询实现elasticsearch的group by后的数量,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-12-12
  • java实现拼图小游戏

    java实现拼图小游戏

    这篇文章主要为大家详细介绍了java实现拼图小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-02-02
  • 教你如何用Java替换Word中带有${}的内容

    教你如何用Java替换Word中带有${}的内容

    这篇文章主要介绍了教你如何用Java替换Word中带有${}的内容,文中有非常详细的代码示例,对正在学习java的小伙伴们有很好的帮助,需要的朋友可以参考下
    2021-04-04
  • 详解Java二叉排序树

    详解Java二叉排序树

    这篇文章主要介绍了Java二叉排序树,包括二叉排序树的定义、二叉排序树的性质、二叉排序树的插入和查找等,感兴趣的小伙伴们可以参考一下
    2015-12-12
  • MyBatis-Plus如何解决主键自增问题

    MyBatis-Plus如何解决主键自增问题

    这篇文章主要介绍了MyBatis-Plus如何解决主键自增问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • SpringCloud修改Feign日志记录级别过程浅析

    SpringCloud修改Feign日志记录级别过程浅析

    OpenFeign源于Netflix的Feign,是http通信的客户端。屏蔽了网络通信的细节,直接面向接口的方式开发,让开发者感知不到网络通信细节。所有远程调用,都像调用本地方法一样完成
    2023-02-02

最新评论