SpringBoot Thymeleaf iText7 生成 PDF(2023/08/29)
文章目录
- SpringBoot Thymeleaf iText7 生成 PDF(2023/08/29)
- 1. 前言
- 2. 技术思路
- 3. 实现过程
- 4. 测试
1. 前言
近期在项目种遇到了实时生成复杂 PDF 的需求,经过一番调研和测试,最终选择了采用 Thymeleaf 和 iText7 来实现需求,本文将详细介绍实现过程。
2. 技术思路
- 通过 Thymeleaf 渲染生成需要的页面内容;
- 通过 iText7 html2pdf 库将 Thymeleaf 渲染的结果转换成 PDF;
- 将 PDF 内容写入到接口输出流中返回给前端浏览器展示;
3. 实现过程
-
Maven 引入依赖;
<!-- 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>
-
编写 Thymeleaf 模板
resources/templates/demo.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>
-
添加中文字体资源
resources/fonts/simkai.ttf
; -
编写 PDF 页码事件处理
handler/PageEventHandler
;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 {@Overridepublic 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();} }
-
编写 Student 实体类
model/domain/Student
;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; }
-
编写 Service
service/PdfService
生成 PDF;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 {@Resourceprivate TemplateEngine templateEngine;/*** 生成 PDF.** @param outputStream 输出流*/@Overridepublic 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());}} }
-
编写 Controller
controller/PdfController
返回给前端浏览器展示;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 {@Resourceprivate PdfService pdfService;/*** 生成 PDF.*/@GetMappingpublic void generatePdf(HttpServletResponse response) throws IOException {pdfService.generatePdf(response.getOutputStream());} }
4. 测试
浏览器访问 http://localhost:8080/pdf
查看效果。
参考文章:
- 使用itext7将HTML转为pdf · Issue #12 · ydq/blog (github.com);