文档打印工具类-DocumentPrintUtil
引入包
<!-- Freemarker 模版 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- html转pdf文档 -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>html2pdf</artifactId>
<version>6.3.0</version>
</dependency>
<!-- docx文件处理 -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j</artifactId>
<version>6.1.2</version>
</dependency>
<!-- XHTML 导入支持(docx4j-html) -->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-ImportXHTML</artifactId>
<version>8.3.11</version>
</dependency>
<!--JAXB API-->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<!-- JAXB 运行时实现-->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>
工具类
package com.example.demo.utils;
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.layout.font.FontProvider;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.docx4j.convert.in.xhtml.XHTMLImporterImpl;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import javax.servlet.http.HttpServletResponse;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Map;
/**
* 文档打印工具类
* pdf 文档打印:freemarker(ftl模版填充字段后转为html) + itextpdf(html 转换为pdf文档)
* docx 文档打印:freemarker(ftl模版填充字段后转为html) + docx4j(html 转换为 docx 文档)
*
* 注意点:模版填充数据使用实体类对象时,实体类属性都要是 String 类型,方便填充模版数据
*/
public class DocumentPrintUtil {
/**
* 生成 pdf 文档
*
* @param data 模版填充数据 (Map、Java实体类)
* @param templateName freemarker 模版名称(xxx.ftl)
* @param filename 下载文件名称 (可带后缀)
* @param inline 是否预览
* @param response web servlet
*/
public static void generateWebPdf(Object data, String templateName, String filename, boolean inline, HttpServletResponse response) {
try (OutputStream outputStream = response.getOutputStream()) {
// 设置文件格式
String encodedFilename = URLEncoder.encode(filename + ".pdf", StandardCharsets.UTF_8.toString());
response.setContentType("application/pdf");
String disposition = inline ? "inline" : "attachment";
response.setHeader("Content-Disposition", disposition + "; filename*=UTF-8''" + encodedFilename);
response.setCharacterEncoding("UTF-8");
// 生成pdf文档
DocumentPrintUtil.generatePdf(data, templateName, outputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 生成 pdf 文档
*
* @param data 数据(Map、Java实体类)
* @param templateName freemarker 模版名称(xxx.ftl)
* @param outputStream 返回 pdf 文件流
*/
public static void generatePdf(Object data, String templateName, OutputStream outputStream) {
try {
// 空参数初始化
DocumentPrintUtil.nullParamInit(data);
// 渲染 Freemarker 模板为 HTML
String html = getHtml(data, templateName);
// 转换 html 成 pdf 文档
ConverterProperties properties = new ConverterProperties();
FontProvider fontProvider = new FontProvider();
// fontProvider.addSystemFonts();
fontProvider.addFont("/fonts/SimHei.ttf"); // 手动注册字体
properties.setFontProvider(fontProvider);
properties.setCharset("UTF-8");
HtmlConverter.convertToPdf(html, outputStream, properties);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 生成 DOCX 文档
*
* @param data 模版填充数据(Map、Java实体类)
* @param templateName freemarker 模版名称(xxx.ftl)
* @param filename 下载文件名称 (不带后缀)
* @param response web servlet
*/
public static void generateWebDocx(Object data, String templateName, String filename, HttpServletResponse response) {
try (OutputStream outputStream = response.getOutputStream()) {
// 设置文件格式
String encodedFilename = URLEncoder.encode(filename + ".docx", StandardCharsets.UTF_8.toString());
response.setContentType("application/docx");
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFilename);
response.setCharacterEncoding("UTF-8");
// 生成pdf文档
DocumentPrintUtil.generateDocx(data, templateName, outputStream);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 生成 DOCX 文档
*
* @param data 数据(Map、Java实体类)
* @param templateName freemarker 模板名称(xxx.ftl)
* @param outputStream 返回 docx 文件流
*/
public static void generateDocx(Object data, String templateName, OutputStream outputStream) {
try {
// 空参数初始化
DocumentPrintUtil.nullParamInit(data);
// 1.渲染 Freemarker 模板为 HTML
String html = getHtml(data, templateName);
// 2.创建
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();
// 3.使用 XHTMLImporterImpl 将 HTML 转换为 Word 内容
XHTMLImporterImpl importer = new XHTMLImporterImpl(wordMLPackage);
MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
documentPart.getContent().addAll(importer.convert(html, null));
// 4.输出为 DOCX
wordMLPackage.save(outputStream);
} catch (Exception e) {
throw new RuntimeException("生成 DOCX 失败", e);
}
}
/**
* 获取 html
* @param data 需要填充数据
* @param templateName 模版名称
*/
public static String getHtml(Object data, String templateName) throws IOException, TemplateException {
Configuration configuration = ApplicationContextAwareUtil.getBean(Configuration.class);
Template template = configuration.getTemplate(templateName, "UTF-8");
StringWriter out = new StringWriter();
template.process(data, out);
return out.toString();
}
/**
* 将对象或 Map 中的 null 字段替换为空字符串
*
* @param object 任意对象(JavaBean 或 Map)
*/
@SuppressWarnings("unchecked")
public static void nullParamInit(Object object) throws Exception {
if (object == null) {
return;
}
// 1.如果是 Map 类型
if (object instanceof Map) {
Map<String, Object> map = (Map<String, Object>) object;
for (Map.Entry<String, Object> entry : map.entrySet()) {
Object value = entry.getValue();
if (value == null) {
// 替换 null 为 ""
entry.setValue("");
} else if (isMap(value) || isEntityObject(value) || isCollection(value)) {
// 递归处理嵌套对象或 Map
DocumentPrintUtil.nullParamInit(value);
}
}
return;
}
// 2.处理集合类型(List、Set 等)
if (DocumentPrintUtil.isCollection(object)) {
Collection<?> collection = (Collection<?>) object;
for (Object item : collection) {
if (item != null) {
DocumentPrintUtil.nullParamInit(item);
}
}
return;
}
// 2.如果是普通 Java Bean
BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass());
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for (PropertyDescriptor pd : propertyDescriptors) {
Method readMethod = pd.getReadMethod();
Method writeMethod = pd.getWriteMethod();
if (readMethod == null || writeMethod == null) {
continue;
}
Object value = readMethod.invoke(object);
Class<?> type = pd.getPropertyType();
if (value == null && type == String.class) {
// 将 null 的字符串属性替换为 ""
writeMethod.invoke(object, "");
} else if (isMap(value) || isEntityObject(value)|| isCollection(value)) {
// 递归处理嵌套对象
DocumentPrintUtil.nullParamInit(value);
}
}
}
/**
* 判断是否 Map
*/
public static boolean isMap(Object obj) {
if (obj == null) return false;
Class<?> clazz = obj.getClass();
return Map.class.isAssignableFrom(clazz);
}
/**
* 判断是否为实体类对象
* 判断标准:不是集合、不是基本类型、不是包装类型、不是 String、不是枚举、不是数组。
*/
public static boolean isEntityObject(Object obj) {
if (obj == null) return false;
Class<?> clazz = obj.getClass();
// 1. 排除基本类型和包装类
if (clazz.isPrimitive()) return false;
// 2. 排除常见的简单类型
if (clazz == String.class ||
Number.class.isAssignableFrom(clazz) ||
clazz == Boolean.class ||
clazz.isEnum() ||
clazz.isArray() ||
Collection.class.isAssignableFrom(clazz) ||
Map.class.isAssignableFrom(clazz)) {
return false;
}
// 3. 其他情况,认为是“实体类”
return true;
}
/**
* 判断是否为集合类型
*/
public static boolean isCollection(Object obj) {
if (obj == null) return false;
return obj instanceof Collection<?> || obj.getClass().isArray();
}
}
项目结构

-
SimHei.ttf
下载链接:[https://www.123865.com/s/5yoovd-yVlKd](https://www.123865.com/s/5yoovd-yVlKd) -
report.ftl
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<style type="text/css">
body { font-family: SimHei; font-size: 12px; }
table { width: 100%; border-collapse: collapse; }
th, td { border: 1px solid #333; padding: 6px; text-align: center; }
</style>
</head>
<body>
<h2>${title}</h2>
<p>生成日期:${date}</p>
<p>负责人:${author}</p>
<table>
<thead>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>邮箱</th>
</tr>
</thead>
<tbody>
<#list users as u>
<tr>
<td>${u.name}</td>
<td>${u.age}</td>
<td>${u.email}</td>
</tr>
</#list>
</tbody>
</table>
</body>
</html>
测试
/**
* 下载 PDF 文件
*/
@GetMapping("/export1")
public void downloadPdf(HttpServletResponse response) {
// 1. 准备数据
Map<String, Object> data = new HashMap<>();
data.put("title", "用户信息报告");
data.put("author", "管理员");
data.put("date", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
List<Map<String, Object>> users = new ArrayList<>();
users.add(new HashMap<String, Object>() {{
put("name", "张三");
put("age", 28);
put("email", "zhangsan@example.com");
}});
users.add(new HashMap<String, Object>() {{
put("name", "李四");
put("age", 32);
put("email", "lisi@example.com");
}});
data.put("users", users);
DocumentPrintUtil.generateWebPdf(data, "report.ftl", "测试", true , response);
}
/**
* 浏览器内预览 PDF
*/
@GetMapping("/export2")
public void previewPdf(HttpServletResponse response) {
// 1. 准备数据
Map<String, Object> data = new HashMap<>();
data.put("title", "用户信息报告");
data.put("author", "管理员");
data.put("date", new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
List<Map<String, Object>> users = new ArrayList<>();
users.add(new HashMap<String, Object>() {{
put("name", "张三");
put("age", 28);
put("email", "zhangsan@example.com");
}});
users.add(new HashMap<String, Object>() {{
put("name", "李四");
put("age", 32);
put("email", "lisi@example.com");
}});
data.put("users", users);
DocumentPrintUtil.generateWebDocx(data, "report.ftl", "测试" , response);
}
浙公网安备 33010602011771号