Spring Boot | 将 html 页面转化为 pdf 文件

废话不多说,先看效果图。

不知道怎么回事,简书上传图片总是失败。效果图看这里吧: 效果图

本示例重要功能点:

  • 访问 url 直接下载 pdf 文件,前后端分离的项目可能通过这种方式下载 pdf 文件;
  • 将 html 页面转换成 pdf 文件,支持中文、图片
  • 1、创建 Spring Boot 项目

    进入 http://start.spring.io 创建 Spring Boot 项目,Spring Boot 版本为 2.7.0,选择如下依赖:

  • Starter:spring-boot-starter-web
  • spring-boot-starter-thymeleaf
  • lombok
  • 2、修改 pom.xml 文件,添加将 html 页面转换成 pdf 文件需要的依赖:

    <dependency>
        <groupId>org.xhtmlrenderer</groupId>
        <artifactId>flying-saucer-pdf</artifactId>
        <version>9.1.22</version>
    </dependency>
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
    </dependency>
    <dependency>
        <groupId>ognl</groupId>
        <artifactId>ognl</artifactId>
        <version>3.1.29</version>
    </dependency>
    <dependency>
        <groupId>com.github.jtidy</groupId>
        <artifactId>jtidy</artifactId>
        <version>1.0.2</version>
    </dependency>
    

    3、创建演示数据需要的实体类 —— Student.java

    import lombok.Builder;
    import lombok.Data;
    @Data
    @Builder
    public class Student {
        private Integer id;
        private String name;
        private String gender;
        private Integer age;
    

    4、创建具体的业务处理类 —— PdfService.java

    import com.lowagie.text.DocumentException;
    import com.lowagie.text.pdf.BaseFont;
    import com.wangc.downloadpdf.entity.Student;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Service;
    import org.thymeleaf.TemplateEngine;
    import org.thymeleaf.context.Context;
    import org.w3c.tidy.Tidy;
    import org.xhtmlrenderer.pdf.ITextRenderer;
    import javax.annotation.Resource;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.UnsupportedEncodingException;
    import java.nio.charset.StandardCharsets;
    import java.util.List;
    @Slf4j
    @Service
    public class PdfService {
        @Resource
        private TemplateEngine templateEngine;
        public ByteArrayInputStream exportPdf(String template, List<Student> students) throws Exception {
            Context context = new Context();
            context.setVariable("students", students);
            String content = convertToXhtml(templateEngine.process(template, context));
            ByteArrayInputStream byteArrayInputStream = null;
            try {
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                ITextRenderer renderer = new ITextRenderer();
                renderer.getFontResolver().addFont("c:/Windows/Fonts/simsun.ttc", BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
                renderer.setDocumentFromString(content);
                renderer.layout();
                renderer.createPDF(byteArrayOutputStream, false);
                renderer.finishPDF();
                byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
            } catch (DocumentException e) {
                log.error(e.getMessage(), e);
            return byteArrayInputStream;
        private String convertToXhtml(String htmlContent) throws UnsupportedEncodingException {
            Tidy tidy = new Tidy();
            tidy.setInputEncoding("UTF-8");
            tidy.setOutputEncoding("UTF-8");
            tidy.setXHTML(true);
            ByteArrayInputStream inputStream = new ByteArrayInputStream(htmlContent.getBytes(StandardCharsets.UTF_8));
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            tidy.parseDOM(inputStream, outputStream);
            return outputStream.toString("UTF-8");
    

    5、创建控制器 —— PdfController.java

    import com.wangc.downloadpdf.entity.Student;
    import com.wangc.downloadpdf.service.PdfService;
    import org.apache.commons.io.IOUtils;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.servlet.ModelAndView;
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletResponse;
    import java.io.ByteArrayInputStream;
    import java.util.List;
    import java.util.stream.Collectors;
    import java.util.stream.IntStream;
    @Controller
    public class PdfController {
        @Resource
        private PdfService pdfService;
        @GetMapping("/downloadPdf")
        public void downloadPdf(HttpServletResponse response) throws Exception {
            List<Student> students = createTestData();
            ByteArrayInputStream byteArrayInputStream = pdfService.exportPdf("students", students);
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition", "attachment; filename=receipt.pdf");
            IOUtils.copy(byteArrayInputStream, response.getOutputStream());
        // 效果预览
        @GetMapping("/view")
        public ModelAndView view() throws Exception {
            List<Student> students = createTestData();
            ModelAndView mv = new ModelAndView();
            mv.setViewName("students");
            mv.addObject("students", students);
            return mv;
        // 测试数据
        private List<Student> createTestData() {
            final List<Student> students = IntStream.range(1, 10)
                    .mapToObj(v -> Student.builder()
                            .id(v)
                            .name("学生" + v)
                            .age(16)
                            .gender(v % 2 == 0 ? "男" : "女")
                            .build())
                    .collect(Collectors.toList());
            return students;
    

    6、创建 html 模板 —— students.html

    <!DOCTYPE HTML>
    <html xmlns="http://www.w3.org/1999/xhtml"
          xmlns:th="http://www.thymeleaf.org">
        <meta charset="UTF-8">
        <title>Students View</title>
        <style>
            body {
                /* 宋体 */
                font-family:SimSun;
                font-size: 12px;
        </style>
    </head>
    <div class="container">
        <img src="http://127.0.0.1:9090/image-01.png" />
        <h1 th:text="'Students / Count:' + ${#lists.size(students)}"></h1>
        <div class="table-responsive">
            <table class="table">
                    <th>Id</th>
                    <th>姓名</th>
                    <th>性别</th>
                    <th>年龄</th>
                <tr th:each="student : ${students}">
                    <td th:text="${student.id}"></td>
                    <td th:text="${student.name}"></td>
                    <td th:text="${student.gender}"></td>
                    <td th:text="${student.age}"></td>
            </table>