文档打印工具类-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);
}
posted @ 2025-12-30 15:23  小城边  阅读(5)  评论(0)    收藏  举报