1. 动态生成HTML并转为PDF
在Java中,利用Thymeleaf模板引擎生成动态HTML内容后,可以通过Flying Saucer库将其转换为PDF文件。这个过程将动态数据渲染到预定义模板中,适用于生成格式化良好的报告、发票等PDF文件。
实现思路:生成HTML内容 ----> 处理成String ----> 使用Flying Saucer库生成PDF。
1.1 依赖
<!-- Thymeleaf模板引擎依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- Flying Saucer依赖(html -> pdf) -->
<dependency>
<groupId>org.eclipse.birt.runtime.3_7_1</groupId>
<artifactId>com.lowagie.text</artifactId>
<version>2.1.7</version>
</dependency>
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.1.18</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.4.2</version>
</dependency>
1.2 实现步骤
1.2.1 实现参数接收类
首先,我们定义一个ParamEntity
类,用来接收模板需要的数据。这个类包含了多种数据类型,并提供了格式化方法,如对金额的格式化显示。
package camellia.utilities.thymeleaf.model;
import lombok.Data;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
* 参数实体类,包含多种数据类型。
*/
@Data
public class ParamEntity implements Serializable {
private static final long serialVersionUID = 2971651722456178553L;
// 基本类型
private String name;
private String value;
// 数字类型
private Integer intValue;
private Long longValue;
private BigDecimal decimalValue;
// 布尔类型
private Boolean isActive;
// 日期时间类型
private LocalDate dateValue;
private LocalDateTime dateTimeValue;
// 列表和数组类型
private String[] tags;
private List<String> interests;
// 嵌套对象类型
private ParamEntity relatedEntity;
// 自定义格式化的值,例如金额格式
public String getFormattedDecimalValue() {
if (decimalValue != null) {
return String.format("$%,.2f", decimalValue);
}
return null;
}
}
1.2.2 设计HTML模版
然后,我们创建一个HTML模板来展示这些动态数据。利用Thymeleaf的表达式语言,可以直接在模板中插入对象的属性,生成一个格式化的HTML页面。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Thymeleaf 渲染测试</title>
<style>
body {
font-family: 'SimSun', '宋体', sans-serif;
}
</style>
</head>
<body>
<h1>Thymeleaf 渲染测试示例</h1>
<h2>基本信息</h2>
<p><strong>名称:</strong> <span th:text="${paramEntity.name}">默认名称</span></p>
<p><strong>值:</strong> <span th:text="${paramEntity.value}">默认值</span></p>
<h2>数值类型</h2>
<p><strong>整数值:</strong> <span th:text="${paramEntity.intValue}">0</span></p>
<p><strong>长整型值:</strong> <span th:text="${paramEntity.longValue}">0</span></p>
<p><strong>小数值:</strong> <span th:text="${paramEntity.formattedDecimalValue}">0.00</span></p>
<h2>布尔值</h2>
<p><strong>是否启用:</strong> <span th:text="${paramEntity.isActive ? '是' : '否'}">否</span></p>
<h2>日期与时间</h2>
<p><strong>日期:</strong> <span th:text="${paramEntity.dateValue}">2024-01-01</span></p>
<p><strong>日期时间:</strong> <span th:text="${paramEntity.dateTimeValue}">2024-01-01 12:00:00</span></p>
<h2>标签与兴趣</h2>
<p><strong>标签:</strong>
<ul>
<li th:each="tag : ${paramEntity.tags}" th:text="${tag}">标签1</li>
</ul>
</p>
<p><strong>兴趣:</strong>
<ul>
<li th:each="interest : ${paramEntity.interests}" th:text="${interest}">兴趣1</li>
</ul>
</p>
<h2>嵌套实体</h2>
<p><strong>相关实体名称:</strong> <span th:text="${paramEntity.relatedEntity.name}">默认名称</span></p>
<p><strong>相关实体值:</strong> <span th:text="${paramEntity.relatedEntity.value}">默认值</span></p>
<p><strong>相关实体是否启用:</strong> <span th:text="${paramEntity.relatedEntity.isActive ? '是' : '否'}">否</span></p>
</body>
</html>
注意: 在HTML模板中,
font-family: 'SimSun', '宋体', sans-serif;
用来指定字体格式。如果不明确指定字体,后续生成的PDF可能会导致中文字符无法正常渲染,显示为空。因此,确保选择一个支持中文的字体,并在HTML中正确指定它。
1.2.3 数据传递到Thymeleaf模板引擎
通过model.addAttribute("paramEntity", entity)
将ParamEntity
对象传递给模板,然后通过Context
对象传递给Thymeleaf引擎进行渲染。
model.addAttribute("paramEntity", entity);
Context context = new Context();
context.setVariables(model.asMap());
注意: 手动控制模板渲染时,灵活性更高,不必依赖Spring自动处理视图。
1.2.4 通过模板引擎渲染HTML
模板渲染后,得到的HTML是一个String
类型,稍后可以转换成PDF。
String html = templateEngine.process("render", context);
1.2.5 生成PDF
我们利用Flying Saucer和iTextRenderer来将HTML转为PDF。需要注意的是,如果涉及中文字体,必须提供字体文件路径。
public static ByteArrayOutputStream htmlConvertPdf(String html, String fontPath) {
if (fontPath == null || fontPath.isEmpty()) {
throw new IllegalArgumentException("字体路径不能为空。");
}
ByteArrayOutputStream pdfStream = new ByteArrayOutputStream();
ITextRenderer iTextRenderer = new ITextRenderer();
try {
iTextRenderer.setDocumentFromString(html);
iTextRenderer.getFontResolver().addFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
iTextRenderer.layout();
iTextRenderer.createPDF(pdfStream);
} catch (DocumentException | IOException e) {
throw new RuntimeException("PDF转换过程中发生错误: " + e.getMessage(), e);
}
return pdfStream;
}
1.3 使用案例及效果
最后,我们编写控制器方法来接收数据并返回PDF文件。通过ResponseEntity
包装PDF字节流,提供下载。
@PostMapping("/convert")
public ResponseEntity<byte[]> convert(@RequestBody ParamEntity entity, Model model) {
model.addAttribute("paramEntity", entity);
Context context = new Context();
context.setVariables(model.asMap());
String html = templateEngine.process("render", context);
ByteArrayOutputStream pdfStream = HtmlConvertPdf.htmlConvertPdf(html , "font/simsun.ttc");
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=rendered.pdf")
.contentType(MediaType.APPLICATION_PDF)
.body(pdfStream.toByteArray());
}
- 生成效果:
完整代码:
代码可能有些许调整,simsun.ttc字体包自行下载。