SpringBoot Thymeleaf iText7 生成 PDF(2023/08/29)

SpringBoot Thymeleaf iText7 生成 PDF(2023/08/29)

文章目录

  • [SpringBoot Thymeleaf iText7 生成 PDF(2023/08/29)](#SpringBoot Thymeleaf iText7 生成 PDF(2023/08/29))
    • [1. 前言](#1. 前言)
    • [2. 技术思路](#2. 技术思路)
    • [3. 实现过程](#3. 实现过程)
    • [4. 测试](#4. 测试)

1. 前言

近期在项目种遇到了实时生成复杂 PDF 的需求,经过一番调研和测试,最终选择了采用 Thymeleaf 和 iText7 来实现需求,本文将详细介绍实现过程。

2. 技术思路

  1. 通过 Thymeleaf 渲染生成需要的页面内容;
  2. 通过 iText7 html2pdf 库将 Thymeleaf 渲染的结果转换成 PDF;
  3. 将 PDF 内容写入到接口输出流中返回给前端浏览器展示;

3. 实现过程

  1. Maven 引入依赖;

    xml 复制代码
    <!-- Thymeleaf -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
    <!-- iText html2pdf -->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>html2pdf</artifactId>
        <version>5.0.0</version>
    </dependency>
    
    <!-- lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>
    
    <!-- 获取资源文件 -->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>5.8.21</version>
    </dependency>
  2. 编写 Thymeleaf 模板 resources/templates/demo.html

    html 复制代码
    <!DOCTYPE html>
    <html lang="zh">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>PDF Demo</title>
    
        <style>
            body {
                padding-top: 50px;
                padding-left: 60px;
                padding-right: 60px;
                font-family: 'KaiTi', serif;
            }
    
            .title {
                color: red;
                text-align: center;
                margin-bottom: 50px;
            }
    
            table {
                width: 100%;
                border: 1px solid black;
                border-spacing: 0;
            }
    
            th {
                border: 1px solid black;
                background-color: rgb(128, 128, 128);
            }
    
            td {
                border: 1px solid black;
            }
        </style>
    </head>
    
    <body>
    <h1 class="title" th:text="${title}"></h1>
    
    <table>
        <thead>
        <tr>
            <th>序号</th>
            <th>姓名</th>
            <th>年龄</th>
            <th>性别</th>
        </tr>
        </thead>
        <tbody th:each="student, studentStat : ${students}">
        <tr>
            <td th:text="${studentStat.count}"></td>
            <td th:text="${student.name}"></td>
            <td th:text="${student.age}"></td>
            <td th:text="${student.sex}"></td>
        </tr>
        </tbody>
    </table>
    
    </body>
    </html>
  3. 添加中文字体资源 resources/fonts/simkai.ttf

  4. 编写 PDF 页码事件处理 handler/PageEventHandler

    java 复制代码
    package com.xiaoqqya.itextpdf.handler;
    
    import com.itextpdf.kernel.events.Event;
    import com.itextpdf.kernel.events.IEventHandler;
    import com.itextpdf.kernel.events.PdfDocumentEvent;
    import com.itextpdf.kernel.geom.Rectangle;
    import com.itextpdf.kernel.pdf.PdfDocument;
    import com.itextpdf.kernel.pdf.PdfPage;
    import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
    import com.itextpdf.layout.Canvas;
    import com.itextpdf.layout.element.Paragraph;
    import com.itextpdf.layout.properties.TextAlignment;
    
    /**
     * 页码事件处理.
     *
     * @author <a href="mailto:xiaoQQya@126.com">xiaoQQya</a>
     * @since 2023/08/29
     */
    public class PageEventHandler implements IEventHandler {
    
        @Override
        public void handleEvent(Event event) {
            PdfDocumentEvent documentEvent = (PdfDocumentEvent) event;
            PdfDocument document = documentEvent.getDocument();
            PdfPage page = documentEvent.getPage();
            Rectangle pageSize = page.getPageSize();
    
            PdfCanvas pdfCanvas = new PdfCanvas(page.getLastContentStream(), page.getResources(), document);
            Canvas canvas = new Canvas(pdfCanvas, pageSize);
            float x = (pageSize.getLeft() + pageSize.getRight()) / 2;
            float y = pageSize.getBottom() + 15;
            Paragraph paragraph = new Paragraph("-- " + document.getPageNumber(page) + " --")
                    .setFontSize(10);
            canvas.showTextAligned(paragraph, x, y, TextAlignment.CENTER);
            canvas.close();
        }
    }
  5. 编写 Student 实体类 model/domain/Student

    java 复制代码
    package com.xiaoqqya.itextpdf.model.domain;
    
    import lombok.AllArgsConstructor;
    import lombok.Builder;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    /**
     * 学生.
     *
     * @author <a href="mailto:xiaoQQya@126.com">xiaoQQya</a>
     * @since 2023/08/29
     */
    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public class Student {
    
        /**
         * 姓名
         */
        private String name;
    
        /**
         * 、
         * 年龄
         */
        private Integer age;
    
        /**
         * 性别
         */
        private String sex;
    }
  6. 编写 Service service/PdfService 生成 PDF;

    java 复制代码
    package com.xiaoqqya.itextpdf.service.impl;
    
    import cn.hutool.core.io.resource.ResourceUtil;
    import com.itextpdf.html2pdf.ConverterProperties;
    import com.itextpdf.html2pdf.HtmlConverter;
    import com.itextpdf.html2pdf.resolver.font.DefaultFontProvider;
    import com.itextpdf.io.font.FontProgramFactory;
    import com.itextpdf.kernel.events.PdfDocumentEvent;
    import com.itextpdf.kernel.geom.PageSize;
    import com.itextpdf.kernel.pdf.PdfDocument;
    import com.itextpdf.kernel.pdf.PdfWriter;
    import com.itextpdf.layout.font.FontProvider;
    import com.xiaoqqya.itextpdf.exception.CustomException;
    import com.xiaoqqya.itextpdf.handler.PageEventHandler;
    import com.xiaoqqya.itextpdf.model.domain.Student;
    import com.xiaoqqya.itextpdf.service.PdfService;
    import org.springframework.stereotype.Service;
    import org.thymeleaf.TemplateEngine;
    import org.thymeleaf.context.Context;
    
    import javax.annotation.Resource;
    import java.io.IOException;
    import java.io.OutputStream;
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * PDF Service.
     *
     * @author <a href="mailto:xiaoQQya@126.com">xiaoQQya</a>
     * @since 2023/08/29
     */
    @Service
    public class PdfServiceImpl implements PdfService {
    
        @Resource
        private TemplateEngine templateEngine;
    
        /**
         * 生成 PDF.
         *
         * @param outputStream 输出流
         */
        @Override
        public void generatePdf(OutputStream outputStream) {
            // 模拟数据
            List<Student> students = new ArrayList<>();
            students.add(Student.builder().name("小红").age(18).sex("女").build());
            students.add(Student.builder().name("小强").age(21).sex("男").build());
            students.add(Student.builder().name("熊大").age(19).sex("男").build());
    
            // 生成 Thymeleaf 上下文
            Context context = new Context();
            context.setVariable("title", "PDF Demo");
            context.setVariable("students", students);
            String demo = templateEngine.process("demo", context);
    
            // 生成 PDF, 并添加页码
            try (PdfWriter pdfWriter = new PdfWriter(outputStream); PdfDocument pdfDocument = new PdfDocument(pdfWriter)) {
                pdfDocument.setDefaultPageSize(PageSize.A4);
                pdfDocument.addEventHandler(PdfDocumentEvent.INSERT_PAGE, new PageEventHandler());
    
                ConverterProperties converterProperties = new ConverterProperties();
                FontProvider fontProvider = new DefaultFontProvider(true, true, false);
                fontProvider.addFont(FontProgramFactory.createFont(ResourceUtil.readBytes("fonts/simkai.ttf")));
                converterProperties.setFontProvider(fontProvider);
                HtmlConverter.convertToPdf(demo, pdfDocument, converterProperties);
            } catch (IOException e) {
                throw new CustomException(e.getMessage());
            }
        }
    }
  7. 编写 Controller controller/PdfController 返回给前端浏览器展示;

    java 复制代码
    package com.xiaoqqya.itextpdf.controller;
    
    import com.xiaoqqya.itextpdf.service.PdfService;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    import javax.annotation.Resource;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    /**
     * PDF Controller.
     *
     * @author <a href="mailto:xiaoQQya@126.com">xiaoQQya</a>
     * @since 2023/08/29
     */
    @RestController
    @RequestMapping(value = "/pdf")
    public class PdfController {
    
        @Resource
        private PdfService pdfService;
    
        /**
         * 生成 PDF.
         */
        @GetMapping
        public void generatePdf(HttpServletResponse response) throws IOException {
            pdfService.generatePdf(response.getOutputStream());
        }
    }

4. 测试

浏览器访问 http://localhost:8080/pdf 查看效果。

参考文章:

相关推荐
m0_571957582 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
魔道不误砍柴功4 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2344 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨4 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
种树人202408194 小时前
如何在 Spring Boot 中启用定时任务
spring boot
Chrikk5 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*5 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue5 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man5 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟5 小时前
使用python向钉钉群聊发送消息
java·python·钉钉