Quantcast
Channel: 小蓝博客
Viewing all articles
Browse latest Browse all 3145

Java 使用 Freemarker 生成 Word 和 PDF 文件

$
0
0

Java 使用 Freemarker 生成 Word 和 PDF 文件详解 📄📑

在现代软件开发中,文档生成是一个常见且重要的需求。无论是生成报告、合同,还是自动化文档,能够高效、灵活地生成各种格式的文件对于提升工作效率至关重要。Freemarker 作为一款功能强大的模板引擎,结合 Java 的强大生态系统,能够轻松实现 Word 和 PDF 文件的自动化生成。本文将详细介绍如何使用 Java 与 Freemarker 生成 Word 和 PDF 文件,涵盖环境配置、模板设计、代码实现及优化技巧,并辅以示意图和代码示例,帮助您全面掌握这一技术。

一、Freemarker 概述

Freemarker 是一个基于模板的通用工具,用于生成文本输出(如 HTML、XML、Java 源代码等)。它通过定义模板文件和数据模型,将数据动态填充到模板中,生成最终的文档。Freemarker 简洁灵活,易于集成,是 Java 应用中常用的模板引擎之一。

Freemarker 的主要特点

  • 灵活性高:支持复杂的模板逻辑,满足多样化的文档生成需求。
  • 易于学习:模板语法简单,容易上手。
  • 扩展性强:可与多种 Java 库结合使用,实现多种输出格式。

二、项目环境配置

在开始之前,需要配置好 Java 开发环境,并添加所需的依赖库。

1. 环境准备

  • Java Development Kit (JDK):建议使用 JDK 8 及以上版本。
  • 构建工具:本文以 Maven 为例,管理项目依赖。

2. 添加 Maven 依赖

在项目的 pom.xml 文件中添加以下依赖:

<dependencies>
    <!-- Freemarker 模板引擎 -->
    <dependency>
        <groupId>org.freemarker</groupId>
        <artifactId>freemarker</artifactId>
        <version>2.3.31</version>
    </dependency>
  
    <!-- Apache POI,用于操作 Word 文件 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.3</version>
    </dependency>
  
    <!-- iText,用于生成 PDF 文件 -->
    <dependency>
        <groupId>com.itextpdf</groupId>
        <artifactId>itextpdf</artifactId>
        <version>5.5.13.3</version>
    </dependency>
  
    <!-- Optional: slf4j,用于日志记录 -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.36</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-simple</artifactId>
        <version>1.7.36</version>
    </dependency>
</dependencies>

解释

  • Freemarker:核心模板引擎,用于生成文本内容。
  • Apache POI:用于操作 Word (.docx) 文件。
  • iText:用于生成 PDF 文件。
  • slf4j:日志记录框架,便于调试和监控。

3. 项目结构

推荐的项目结构如下:

project-root/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com/example/
│   │   │       └── DocumentGenerator.java
│   │   └── resources/
│   │       ├── templates/
│   │       │   ├── document.ftl
│   │       │   └── document.html.ftl
├── pom.xml

解释

  • DocumentGenerator.java:主类,负责文档生成逻辑。
  • templates/:存放 Freemarker 模板文件。

    • document.ftl:用于生成 Word 文件的模板。
    • document.html.ftl:用于生成 PDF 文件的 HTML 模板。

三、设计模板文件

模板文件定义了文档的结构和样式,通过占位符填充动态数据。

1. Word 模板 (document.ftl)

<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
    <w:body>
        <w:p>
            <w:r>
                <w:t>姓名:${name}</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:t>年龄:${age}</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:t>职位:${position}</w:t>
            </w:r>
        </w:p>
    </w:body>
</w:document>

解释

  • 使用 Freemarker 语法 ${variable} 作为占位符,动态填充数据。
  • XML 结构符合 Word 的 .docx 格式要求。

2. PDF 模板 (document.html.ftl)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>文档</title>
    <style>
        body { font-family: Arial, sans-serif; }
        .title { font-size: 24px; font-weight: bold; }
        .content { font-size: 16px; }
    </style>
</head>
<body>
    <div class="title">用户信息</div>
    <div class="content">
        <p>姓名:${name}</p>
        <p>年龄:${age}</p>
        <p>职位:${position}</p>
    </div>
</body>
</html>

解释

  • 使用 HTML 结构和 CSS 样式,定义 PDF 文档的外观。
  • 同样使用 ${variable} 占位符填充动态数据。

四、实现文档生成逻辑

DocumentGenerator.java 中实现生成 Word 和 PDF 文件的逻辑。

1. 导入必要的类

package com.example;

import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import com.itextpdf.text.Document;
import com.itextpdf.text.pdf.PdfWriter;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

解释

  • Freemarker:配置和加载模板。
  • Apache POI:操作 Word 文档。
  • iText:生成 PDF 文档。
  • Java IO:文件读写操作。
  • Map:存储动态数据模型。

2. 定义数据模型

private Map<String, Object> getDataModel() {
    Map<String, Object> data = new HashMap<>();
    data.put("name", "张三");
    data.put("age", 30);
    data.put("position", "软件工程师");
    return data;
}

解释

  • 创建一个 Map,存储要填充到模板中的数据。

3. 配置 Freemarker

private Configuration getFreemarkerConfiguration() throws IOException {
    Configuration cfg = new Configuration(Configuration.VERSION_2_3_31);
    cfg.setClassForTemplateLoading(this.getClass(), "/templates");
    cfg.setDefaultEncoding("UTF-8");
    return cfg;
}

解释

  • Template Loading:设置模板文件的加载路径。
  • Encoding:设置模板的默认编码为 UTF-8,确保中文字符正常显示。

4. 生成 Word 文件

public void generateWord() {
    try {
        Configuration cfg = getFreemarkerConfiguration();
        Template template = cfg.getTemplate("document.ftl");
        Map<String, Object> data = getDataModel();

        // 渲染模板到输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Writer out = new OutputStreamWriter(baos);
        template.process(data, out);
        out.flush();

        // 将渲染后的内容写入 Word 文档
        InputStream is = new ByteArrayInputStream(baos.toByteArray());
        XWPFDocument document = new XWPFDocument(is);
        FileOutputStream fos = new FileOutputStream("output.docx");
        document.write(fos);
        fos.close();
        document.close();

        System.out.println("Word 文件生成成功!");
    } catch (IOException | TemplateException e) {
        e.printStackTrace();
    }
}

解释

  1. 加载模板:通过 Freemarker 配置加载 document.ftl 模板。
  2. 渲染模板:将数据模型渲染到模板,生成字节数组。
  3. 创建 Word 文档:使用 Apache POI 将渲染后的内容写入 .docx 文件。
  4. 输出文件:将生成的 Word 文件保存为 output.docx

5. 生成 PDF 文件

public void generatePDF() {
    try {
        Configuration cfg = getFreemarkerConfiguration();
        Template template = cfg.getTemplate("document.html.ftl");
        Map<String, Object> data = getDataModel();

        // 渲染模板到字符串
        StringWriter stringWriter = new StringWriter();
        template.process(data, stringWriter);
        String htmlContent = stringWriter.toString();

        // 使用 iText 将 HTML 转换为 PDF
        Document document = new Document();
        PdfWriter.getInstance(document, new FileOutputStream("output.pdf"));
        document.open();
        // 简单地将 HTML 作为段落添加,复杂的 HTML 需要使用 XMLWorker
        document.add(new com.itextpdf.text.Paragraph(htmlContent));
        document.close();

        System.out.println("PDF 文件生成成功!");
    } catch (IOException | TemplateException | com.itextpdf.text.DocumentException e) {
        e.printStackTrace();
    }
}

解释

  1. 加载模板:通过 Freemarker 配置加载 document.html.ftl 模板。
  2. 渲染模板:将数据模型渲染到模板,生成 HTML 字符串。
  3. 生成 PDF:使用 iText 将 HTML 内容写入 PDF 文件。

    • 注意:iText 直接将 HTML 作为段落添加较为简单,复杂的 HTML 需要使用 iText 的 XMLWorker 或其他扩展库进行解析。
  4. 输出文件:将生成的 PDF 文件保存为 output.pdf

6. 主类实现

public class DocumentGenerator {
    public static void main(String[] args) {
        DocumentGenerator generator = new DocumentGenerator();
        generator.generateWord();
        generator.generatePDF();
    }

    // 以上所有方法...
}

解释

  • 主方法调用 generateWordgeneratePDF 方法,生成 Word 和 PDF 文件。

五、示例运行结果

执行 DocumentGenerator 主类后,将在项目根目录下生成 output.docxoutput.pdf 两个文件,内容如下:

1. Word 文件 (output.docx)

姓名:张三
年龄:30
职位:软件工程师

2. PDF 文件 (output.pdf)

PDF 示例

解释

  • Word 文件内容直接由模板填充。
  • PDF 文件内容通过渲染后的 HTML 生成,外观可根据模板样式调整。

六、优化与高级用法 🌟

1. 使用 XMLWorker 解析复杂 HTML

iText 的 XMLWorker 允许解析复杂的 HTML 内容,保持模板的样式和结构。

添加 XMLWorker 依赖

pom.xml 中添加:

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>xmlworker</artifactId>
    <version>5.5.13.3</version>
</dependency>

修改 PDF 生成方法

import com.itextpdf.text.Document;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerHelper;

public void generatePDF() {
    try {
        Configuration cfg = getFreemarkerConfiguration();
        Template template = cfg.getTemplate("document.html.ftl");
        Map<String, Object> data = getDataModel();

        // 渲染模板到字符串
        StringWriter stringWriter = new StringWriter();
        template.process(data, stringWriter);
        String htmlContent = stringWriter.toString();

        // 使用 iText 和 XMLWorker 将 HTML 转换为 PDF
        Document document = new Document();
        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("output.pdf"));
        document.open();
        XMLWorkerHelper.getInstance().parseXHtml(writer, document, new StringReader(htmlContent));
        document.close();

        System.out.println("PDF 文件生成成功!");
    } catch (IOException | TemplateException | com.itextpdf.text.DocumentException e) {
        e.printStackTrace();
    }
}

解释

  • XMLWorkerHelper:解析 HTML 内容,并保持样式,将其转换为 PDF 格式。
  • 改进效果:PDF 文件将更好地反映模板中的样式和结构。

2. 动态模板路径

根据不同的业务需求,可以动态选择模板文件,实现多样化的文档生成。

public void generateWord(String templateName, String outputPath, Map<String, Object> data) {
    try {
        Configuration cfg = getFreemarkerConfiguration();
        Template template = cfg.getTemplate(templateName);

        // 渲染模板到输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Writer out = new OutputStreamWriter(baos);
        template.process(data, out);
        out.flush();

        // 将渲染后的内容写入 Word 文档
        InputStream is = new ByteArrayInputStream(baos.toByteArray());
        XWPFDocument document = new XWPFDocument(is);
        FileOutputStream fos = new FileOutputStream(outputPath);
        document.write(fos);
        fos.close();
        document.close();

        System.out.println("Word 文件生成成功!");
    } catch (IOException | TemplateException e) {
        e.printStackTrace();
    }
}

解释

  • 模板选择:通过参数传递模板名称,实现动态选择模板。
  • 输出路径:通过参数指定生成文件的路径,增强方法的灵活性。

3. 增强数据模型

数据模型不仅限于简单的键值对,还可以包含列表、嵌套对象等,满足复杂文档需求。

示例:包含列表的模板

Word 模板 (document.ftl)

<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
    <w:body>
        <w:p>
            <w:r>
                <w:t>姓名:${name}</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:t>项目列表:</w:t>
            </w:r>
        </w:p>
        <#list projects as project>
        <w:p>
            <w:r>
                <w:t>- ${project}</w:t>
            </w:r>
        </w:p>
        </#list>
    </w:body>
</w:document>

数据模型

private Map<String, Object> getDataModel() {
    Map<String, Object> data = new HashMap<>();
    data.put("name", "张三");
    data.put("age", 30);
    data.put("position", "软件工程师");
    data.put("projects", Arrays.asList("项目A", "项目B", "项目C"));
    return data;
}

解释

  • 列表渲染:使用 <#list> 指令遍历 projects 列表,生成多个项目条目。
  • 动态内容:根据数据模型中的列表内容,自动生成文档内容。

4. 模板复用与继承

Freemarker 支持模板继承,便于复用公共部分,提升模板管理效率。

示例:基模板与子模板

基模板 (base.ftl)

<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
    <w:body>
        <w:p>
            <w:r>
                <w:t>${title}</w:t>
            </w:r>
        </w:p>
        <#block content>
        <!-- 子模板内容 -->
        </#block>
    </w:body>
</w:document>

子模板 (document.ftl)

<#extends "base.ftl">

<#block content>
    <w:p>
        <w:r>
            <w:t>姓名:${name}</w:t>
        </w:r>
    </w:p>
    <w:p>
        <w:r>
            <w:t>年龄:${age}</w:t>
        </w:r>
    </w:p>
    <w:p>
        <w:r>
            <w:t>职位:${position}</w:t>
        </w:r>
    </w:p>
</#block>

解释

  • 模板继承:子模板通过 <#extends> 指令继承基模板,实现公共部分的复用。
  • 块定义:基模板定义 <#block content>,子模板在此块中填充具体内容。

七、图表与流程图辅助理解

1. 文档生成流程图 📈

flowchart TD;
    A[开始] --> B[准备数据模型]
    B --> C[加载 Freemarker 模板]
    C --> D{生成 Word 或 PDF}
    D -->|Word| E[使用 Apache POI 写入 .docx]
    D -->|PDF| F[使用 iText 生成 .pdf]
    E --> G[保存 Word 文件]
    F --> G[保存 PDF 文件]
    G --> H[结束]

解释

  • 开始:初始化文档生成流程。
  • 准备数据模型:定义需要填充到模板中的数据。
  • 加载 Freemarker 模板:根据需求加载相应的模板文件。
  • 生成 Word 或 PDF:选择生成 Word 或 PDF 文件。
  • 使用 Apache POI 或 iText:分别使用不同的库生成对应格式的文件。
  • 保存文件:将生成的文件保存到指定路径。
  • 结束:完成文档生成流程。

解释

  • 数据模型:包含文档生成所需的所有数据。
  • Freemarker 模板:定义文档的结构和样式。
  • 渲染后的内容:将数据模型填充到模板中,生成最终内容。
  • 写入 Word 文件:使用 Apache POI 将内容写入 .docx 文件。
  • 转换为 PDF 文件:使用 iText 将内容转换为 .pdf 文件。

3. 依赖关系表 📊

组件功能依赖
Freemarker模板渲染,生成文档内容
Apache POI操作 Word 文件,生成 .docxFreemarker, Java IO
iText生成 PDF 文件Freemarker, Java IO
Java IO文件读写操作Java
数据模型提供动态填充的数据Java 集合框架
模板文件定义文档的结构和样式Freemarker

解释

  • Freemarker:核心模板引擎,无需其他依赖即可运行。
  • Apache POI:依赖 Freemarker 渲染后的内容,进行 Word 文件的操作。
  • iText:依赖 Freemarker 渲染后的内容,进行 PDF 文件的生成。
  • Java IO:基础文件操作工具,支撑所有文件读写任务。
  • 数据模型:通过 Java 集合框架构建,提供模板渲染所需的数据。

八、常见问题与解决方案 🛠️

1. 模板渲染错误

问题描述:在渲染模板时,出现 TemplateException,提示无法找到变量或语法错误。

解决方案

  • 检查变量名:确保数据模型中的变量名与模板中的占位符一致。
  • 验证模板语法:检查模板文件的 Freemarker 语法是否正确。
  • 调试数据模型:输出数据模型内容,确认数据是否正确传递。

示例:调试数据模型

private Map<String, Object> getDataModel() {
    Map<String, Object> data = new HashMap<>();
    data.put("name", "张三");
    data.put("age", 30);
    data.put("position", "软件工程师");
    data.put("projects", Arrays.asList("项目A", "项目B", "项目C"));
  
    // 输出数据模型,便于调试
    data.forEach((k, v) -> System.out.println(k + ": " + v));
    return data;
}

解释

  • 输出数据模型:在控制台打印所有变量,确认变量值是否正确。

2. Word 文件样式混乱

问题描述:生成的 Word 文件样式与模板设计不符,布局错乱。

解决方案

  • 验证模板结构:确保模板文件的 XML 结构符合 Word 的格式要求。
  • 使用专业工具编辑模板:建议使用 Microsoft Word 等工具设计并保存模板,避免手动编写错误。
  • 检查数据填充:确认填充的数据不会破坏模板的布局,例如避免过长的文本导致行溢出。

示例:使用模板编辑器

  • 使用 Microsoft Word 创建模板文件,并保存为 .docx 格式。
  • 将模板文件转换为 Freemarker 模板,保留占位符。

3. PDF 文件生成不完整

问题描述:生成的 PDF 文件内容不完整,样式缺失。

解决方案

  • 使用 XMLWorker:iText 直接添加 HTML 内容可能无法正确解析复杂的样式,建议使用 XMLWorker 解析 HTML。
  • 检查 HTML 模板:确保 HTML 模板的结构和样式正确,避免语法错误。
  • 调试 PDF 生成过程:输出渲染后的 HTML 内容,确认内容是否完整。

示例:使用 XMLWorker 解析 HTML

public void generatePDF() {
    try {
        Configuration cfg = getFreemarkerConfiguration();
        Template template = cfg.getTemplate("document.html.ftl");
        Map<String, Object> data = getDataModel();

        // 渲染模板到字符串
        StringWriter stringWriter = new StringWriter();
        template.process(data, stringWriter);
        String htmlContent = stringWriter.toString();

        // 输出渲染后的 HTML,用于调试
        System.out.println("渲染后的 HTML 内容:\n" + htmlContent);

        // 使用 iText 和 XMLWorker 将 HTML 转换为 PDF
        Document document = new Document();
        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("output.pdf"));
        document.open();
        XMLWorkerHelper.getInstance().parseXHtml(writer, document, new StringReader(htmlContent));
        document.close();

        System.out.println("PDF 文件生成成功!");
    } catch (IOException | TemplateException | com.itextpdf.text.DocumentException e) {
        e.printStackTrace();
    }
}

解释

  • 输出 HTML 内容:在控制台打印渲染后的 HTML,确认内容是否完整。
  • 使用 XMLWorker:确保 HTML 内容被正确解析并转换为 PDF。

4. 字体显示不正确

问题描述:生成的 Word 或 PDF 文件中,中文字体显示不正确或乱码。

解决方案

  • 设置正确的编码:确保模板文件和 Java 代码使用 UTF-8 编码。
  • 嵌入字体:在生成 PDF 时,嵌入支持中文的字体,避免字体缺失导致乱码。
  • 检查操作系统字体:确保运行环境中已安装所需的中文字体。

示例:嵌入字体到 PDF

import com.itextpdf.text.Font;
import com.itextpdf.text.pdf.BaseFont;

public void generatePDF() {
    try {
        Configuration cfg = getFreemarkerConfiguration();
        Template template = cfg.getTemplate("document.html.ftl");
        Map<String, Object> data = getDataModel();

        // 渲染模板到字符串
        StringWriter stringWriter = new StringWriter();
        template.process(data, stringWriter);
        String htmlContent = stringWriter.toString();

        // 使用 iText 和 XMLWorker 将 HTML 转换为 PDF
        Document document = new Document();
        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("output.pdf"));
        document.open();

        // 设置支持中文的字体
        BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
        Font font = new Font(bf, 12, Font.NORMAL);
        com.itextpdf.text.Paragraph paragraph = new com.itextpdf.text.Paragraph(htmlContent, font);
        document.add(paragraph);

        document.close();

        System.out.println("PDF 文件生成成功!");
    } catch (IOException | TemplateException | com.itextpdf.text.DocumentException e) {
        e.printStackTrace();
    }
}

解释

  • 嵌入中文字体:使用 iText 提供的 BaseFont 类加载支持中文的字体文件,确保 PDF 中中文显示正常。
  • 应用字体:将加载的字体应用到 PDF 内容中。

九、扩展功能与高级应用

1. 表格生成

Freemarker 可以动态生成包含表格的文档,满足复杂数据展示需求。

示例:生成包含表格的 Word 文档

Word 模板 (document.ftl)

<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
    <w:body>
        <w:p>
            <w:r>
                <w:t>用户列表</w:t>
            </w:r>
        </w:p>
        <w:tbl>
            <w:tr>
                <w:tc>
                    <w:p><w:r><w:t>姓名</w:t></w:r></w:p>
                </w:tc>
                <w:tc>
                    <w:p><w:r><w:t>年龄</w:t></w:r></w:p>
                </w:tc>
                <w:tc>
                    <w:p><w:r><w:t>职位</w:t></w:r></w:p>
                </w:tc>
            </w:tr>
            <#list users as user>
            <w:tr>
                <w:tc>
                    <w:p><w:r><w:t>${user.name}</w:t></w:r></w:p>
                </w:tc>
                <w:tc>
                    <w:p><w:r><w:t>${user.age}</w:t></w:r></w:p>
                </w:tc>
                <w:tc>
                    <w:p><w:r><w:t>${user.position}</w:t></w:r></w:p>
                </w:tc>
            </w:tr>
            </#list>
        </w:tbl>
    </w:body>
</w:document>

数据模型

private Map<String, Object> getDataModel() {
    Map<String, Object> data = new HashMap<>();
    data.put("users", Arrays.asList(
        createUser("张三", 30, "软件工程师"),
        createUser("李四", 28, "产品经理"),
        createUser("王五", 35, "测试工程师")
    ));
    return data;
}

private Map<String, Object> createUser(String name, int age, String position) {
    Map<String, Object> user = new HashMap<>();
    user.put("name", name);
    user.put("age", age);
    user.put("position", position);
    return user;
}

解释

  • 列表渲染:通过 <#list> 指令遍历 users 列表,动态生成表格行。
  • 数据模型:定义包含多个用户信息的列表,满足表格生成需求。

2. 条件渲染

根据数据模型中的条件,动态控制文档内容的显示与隐藏。

示例:根据用户角色显示不同内容

Word 模板 (document.ftl)

<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
    <w:body>
        <w:p>
            <w:r>
                <w:t>用户信息</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:t>姓名:${name}</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:t>年龄:${age}</w:t>
            </w:r>
        </w:p>
        <#if role == "admin">
        <w:p>
            <w:r>
                <w:t>角色:管理员</w:t>
            </w:r>
        </w:p>
        </#if>
        <#if role == "user">
        <w:p>
            <w:r>
                <w:t>角色:普通用户</w:t>
            </w:r>
        </w:p>
        </#if>
    </w:body>
</w:document>

数据模型

private Map<String, Object> getDataModel() {
    Map<String, Object> data = new HashMap<>();
    data.put("name", "张三");
    data.put("age", 30);
    data.put("role", "admin"); // 可变更为 "user"
    return data;
}

解释

  • 条件判断:使用 <#if> 指令,根据 role 变量的值,动态显示不同的内容。

3. 复杂样式与布局

结合 Freemarker 和模板引擎,生成具有复杂样式和布局的文档。

示例:多级标题与段落

Word 模板 (document.ftl)

<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
    <w:body>
        <w:p>
            <w:r>
                <w:t>${title}</w:t>
            </w:r>
        </w:p>
        <#list sections as section>
        <w:p>
            <w:r>
                <w:t>${section.header}</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:t>${section.content}</w:t>
            </w:r>
        </w:p>
        </#list>
    </w:body>
</w:document>

数据模型

private Map<String, Object> getDataModel() {
    Map<String, Object> data = new HashMap<>();
    data.put("title", "项目报告");
    data.put("sections", Arrays.asList(
        createSection("项目概述", "这是项目的概述内容。"),
        createSection("技术栈", "项目使用了 Java, Freemarker, Apache POI 等技术。"),
        createSection("成果展示", "项目取得了显著的成果,具体如下。")
    ));
    return data;
}

private Map<String, Object> createSection(String header, String content) {
    Map<String, Object> section = new HashMap<>();
    section.put("header", header);
    section.put("content", content);
    return section;
}

解释

  • 多级内容:通过列表渲染多个章节,每个章节包含标题和内容。
  • 数据模型:定义多个章节的标题和内容,满足复杂文档结构需求。

十、最佳实践与优化建议 🔧

1. 模板设计规范

  • 保持模板简洁:避免在模板中嵌入过多逻辑,保持模板的可读性和可维护性。
  • 使用模板继承:利用 Freemarker 的模板继承功能,复用公共部分,减少冗余。
  • 分离样式与内容:尽量将样式与内容分离,通过 CSS 或 Word 样式定义文档外观。

2. 数据模型设计

  • 结构化数据:使用嵌套的 Map 和 List,确保数据模型与模板结构对应。
  • 数据验证:在渲染前验证数据模型,确保所有必需的数据都已提供。
  • 动态数据加载:根据需要,动态加载数据模型中的部分内容,提升灵活性。

3. 性能优化

  • 缓存模板:Freemarker 支持模板缓存,避免重复加载,提高渲染效率。

    cfg.setCacheStorage(new freemarker.cache.MruCacheStorage(20, 100));
  • 优化数据处理:在渲染大数据模型时,优化数据处理逻辑,避免内存溢出。
  • 异步处理:对于耗时的文档生成任务,考虑使用异步处理机制,提升应用响应速度。

4. 错误处理

  • 捕获异常:在文档生成过程中,捕获并处理所有可能的异常,确保应用稳定性。

    try {
        // 文档生成逻辑
    } catch (IOException | TemplateException | DocumentException e) {
        // 记录错误日志
        e.printStackTrace();
        // 提示用户或采取其他措施
    }
  • 日志记录:使用日志框架记录关键操作和错误信息,便于后续调试和维护。

5. 安全性考虑

  • 防止模板注入:确保模板文件的来源可信,避免恶意模板注入攻击。
  • 敏感数据保护:在数据模型中处理敏感数据时,确保数据的加密和安全传输。

十一、实用示例与代码解析

示例 1:批量生成用户报告

假设需要为多个用户生成个人报告,可以通过循环调用实现。

数据模型

private Map<String, Object> getDataModel() {
    Map<String, Object> data = new HashMap<>();
    List<Map<String, Object>> users = Arrays.asList(
        createUser("张三", 30, "软件工程师"),
        createUser("李四", 28, "产品经理"),
        createUser("王五", 35, "测试工程师")
    );
    data.put("users", users);
    return data;
}

Word 模板 (document.ftl)

<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
    <w:body>
        <#list users as user>
        <w:p>
            <w:r>
                <w:t>姓名:${user.name}</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:t>年龄:${user.age}</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:t>职位:${user.position}</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:t>------------------------</w:t>
            </w:r>
        </w:p>
        </#list>
    </w:body>
</w:document>

代码实现

public void generateBatchWord() {
    try {
        Configuration cfg = getFreemarkerConfiguration();
        Template template = cfg.getTemplate("document.ftl");
        Map<String, Object> data = getDataModel();

        // 渲染模板到输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Writer out = new OutputStreamWriter(baos);
        template.process(data, out);
        out.flush();

        // 将渲染后的内容写入 Word 文档
        InputStream is = new ByteArrayInputStream(baos.toByteArray());
        XWPFDocument document = new XWPFDocument(is);
        FileOutputStream fos = new FileOutputStream("batch_output.docx");
        document.write(fos);
        fos.close();
        document.close();

        System.out.println("批量 Word 文件生成成功!");
    } catch (IOException | TemplateException e) {
        e.printStackTrace();
    }
}

解释

  • 循环渲染:通过 <#list> 指令遍历 users 列表,为每个用户生成一段内容。
  • 批量输出:将所有用户的内容写入同一个 Word 文档,使用分隔符区分不同用户。

示例 2:生成带有图表的 PDF 报告

在 PDF 报告中添加图表,可以通过生成图像并嵌入到 HTML 模板中实现。

数据模型

private Map<String, Object> getDataModelWithChart() {
    Map<String, Object> data = new HashMap<>();
    data.put("title", "销售报告");
    data.put("summary", "本季度销售情况如下:");
    data.put("salesData", Arrays.asList(
        createSales("一月", 15000),
        createSales("二月", 20000),
        createSales("三月", 18000)
    ));
    return data;
}

private Map<String, Object> createSales(String month, int amount) {
    Map<String, Object> sale = new HashMap<>();
    sale.put("month", month);
    sale.put("amount", amount);
    return sale;
}

HTML 模板 (document.html.ftl)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>${title}</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
    <h1>${title}</h1>
    <p>${summary}</p>
    <canvas id="salesChart" width="400" height="200"></canvas>
    <script>
        var ctx = document.getElementById('salesChart').getContext('2d');
        var salesChart = new Chart(ctx, {
            type: 'bar',
            data: {
                labels: [
                    <#list salesData as sale>
                        "${sale.month}"<#if sale_has_next>,</#if>
                    </#list>
                ],
                datasets: [{
                    label: '销售额',
                    data: [
                        <#list salesData as sale>
                            ${sale.amount}<#if sale_has_next>,</#if>
                        </#list>
                    ],
                    backgroundColor: 'rgba(54, 162, 235, 0.6)'
                }]
            },
            options: {
                scales: {
                    y: {
                        beginAtZero: true
                    }
                }
            }
        });
    </script>
</body>
</html>

解释

  • Chart.js:使用 Chart.js 库生成图表。
  • 数据填充:通过 <#list> 指令动态填充图表的标签和数据。
  • 图表展示:在 PDF 中展示生成的图表。

代码实现

import com.itextpdf.text.Image;
import com.itextpdf.tool.xml.pipeline.html.HtmlPipelineContext;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;

public void generatePDFWithChart() {
    try {
        Configuration cfg = getFreemarkerConfiguration();
        Template template = cfg.getTemplate("document.html.ftl");
        Map<String, Object> data = getDataModelWithChart();

        // 渲染模板到字符串
        StringWriter stringWriter = new StringWriter();
        template.process(data, stringWriter);
        String htmlContent = stringWriter.toString();

        // 使用 iText 和 XMLWorker 将 HTML 转换为 PDF
        Document document = new Document();
        PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream("sales_report.pdf"));
        document.open();

        // 配置字体,支持中文
        XMLWorkerFontProvider fontProvider = new XMLWorkerFontProvider(XMLWorkerFontProvider.DONTLOOKFORFONTS);
        fontProvider.register("STSong-Light");
        HtmlPipelineContext htmlContext = new HtmlPipelineContext(null);
        htmlContext.setFontProvider(fontProvider);
    
        // 解析 HTML 内容
        XMLWorkerHelper.getInstance().parseXHtml(writer, document, new StringReader(htmlContent));

        document.close();

        System.out.println("带图表的 PDF 文件生成成功!");
    } catch (IOException | TemplateException | com.itextpdf.text.DocumentException e) {
        e.printStackTrace();
    }
}

解释

  • 渲染 HTML:将数据模型填充到 HTML 模板中,生成包含图表的 HTML 内容。
  • 解析 HTML:使用 XMLWorker 解析 HTML,并将其转换为 PDF 格式。
  • 嵌入图表:通过 Chart.js 生成的图表作为图像嵌入 PDF 文件中。

示例 3:动态选择模板生成不同类型文档

根据不同的业务需求,动态选择模板文件,生成不同类型的文档。

数据模型

private Map<String, Object> getDataModelForDynamicTemplate(String docType) {
    Map<String, Object> data = new HashMap<>();
    if ("report".equals(docType)) {
        data.put("title", "项目报告");
        data.put("content", "这是项目报告的详细内容。");
    } else if ("invoice".equals(docType)) {
        data.put("invoiceNumber", "INV-1001");
        data.put("date", "2024-04-27");
        data.put("amount", 5000);
    }
    return data;
}

Word 模板 (report.ftl)

<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
    <w:body>
        <w:p>
            <w:r>
                <w:t>${title}</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:t>${content}</w:t>
            </w:r>
        </w:p>
    </w:body>
</w:document>

Word 模板 (invoice.ftl)

<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
    <w:body>
        <w:p>
            <w:r>
                <w:t>发票号码:${invoiceNumber}</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:t>日期:${date}</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:t>金额:${amount} 元</w:t>
            </w:r>
        </w:p>
    </w:body>
</w:document>

代码实现

public void generateDynamicDocument(String docType) {
    try {
        Configuration cfg = getFreemarkerConfiguration();
        String templateName;
        String outputPath;

        if ("report".equals(docType)) {
            templateName = "report.ftl";
            outputPath = "project_report.docx";
        } else if ("invoice".equals(docType)) {
            templateName = "invoice.ftl";
            outputPath = "invoice.docx";
        } else {
            System.out.println("未知的文档类型!");
            return;
        }

        Template template = cfg.getTemplate(templateName);
        Map<String, Object> data = getDataModelForDynamicTemplate(docType);

        // 渲染模板到输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Writer out = new OutputStreamWriter(baos);
        template.process(data, out);
        out.flush();

        // 将渲染后的内容写入 Word 文档
        InputStream is = new ByteArrayInputStream(baos.toByteArray());
        XWPFDocument document = new XWPFDocument(is);
        FileOutputStream fos = new FileOutputStream(outputPath);
        document.write(fos);
        fos.close();
        document.close();

        System.out.println(docType + " 文档生成成功!");
    } catch (IOException | TemplateException e) {
        e.printStackTrace();
    }
}

解释

  • 动态选择模板:根据传入的 docType 参数,选择不同的模板文件和输出路径。
  • 通用生成逻辑:使用相同的方法渲染不同模板,生成对应的 Word 文件。

示例 4:生成带有图片的文档

在文档中嵌入图片,可以通过 Freemarker 和 Apache POI 实现。

数据模型

private Map<String, Object> getDataModelWithImage() {
    Map<String, Object> data = new HashMap<>();
    data.put("name", "张三");
    data.put("age", 30);
    data.put("position", "软件工程师");
    data.put("photoPath", "src/main/resources/images/photo.jpg"); // 图片路径
    return data;
}

Word 模板 (document_with_image.ftl)

<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
    <w:body>
        <w:p>
            <w:r>
                <w:t>姓名:${name}</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:t>年龄:${age}</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:t>职位:${position}</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:t>照片:</w:t>
            </w:r>
        </w:p>
        <w:p>
            <w:r>
                <w:drawing>
                    <wp:inline>
                        <a:graphic xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main">
                            <a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/picture">
                                <pic:pic xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
                                    <pic:nvPicPr>
                                        <pic:cNvPr id="0" name=""/>
                                        <pic:cNvPicPr/>
                                    </pic:nvPicPr>
                                    <pic:blipFill>
                                        <a:blip r:embed="rId1"/>
                                        <a:stretch>
                                            <a:fillRect/>
                                        </a:stretch>
                                    </pic:blipFill>
                                    <pic:spPr>
                                        <a:xfrm>
                                            <a:off x="0" y="0"/>
                                            <a:ext cx="990000" cy="792000"/>
                                        </a:xfrm>
                                        <a:prstGeom prst="rect">
                                            <a:avLst/>
                                        </a:prstGeom>
                                    </pic:spPr>
                                </pic:pic>
                            </a:graphicData>
                        </a:graphic>
                    </wp:inline>
                </w:drawing>
            </w:r>
        </w:p>
    </w:body>
</w:document>

解释

  • 图片嵌入:使用 Word 的 XML 结构,将图片嵌入到文档中。
  • 占位符rId1 是图片的引用标识符,在后续操作中需要关联实际的图片文件。

代码实现

public void generateWordWithImage() {
    try {
        Configuration cfg = getFreemarkerConfiguration();
        Template template = cfg.getTemplate("document_with_image.ftl");
        Map<String, Object> data = getDataModelWithImage();

        // 渲染模板到输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Writer out = new OutputStreamWriter(baos);
        template.process(data, out);
        out.flush();

        // 读取渲染后的内容
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        XWPFDocument document = new XWPFDocument(bais);

        // 添加图片
        String imgFile = (String) data.get("photoPath");
        InputStream pic = new FileInputStream(imgFile);
        document.addPictureData(pic, Document.PICTURE_TYPE_JPEG);
        pic.close();

        // 获取图片关系 ID
        String picId = document.getAllPictures().get(0).getPackageRelationship().getId();

        // 替换模板中的图片引用 ID
        for (XWPFParagraph para : document.getParagraphs()) {
            for (XWPFRun run : para.getRuns()) {
                String text = run.getText(0);
                if (text != null && text.contains("rId1")) {
                    text = text.replace("rId1", picId);
                    run.setText(text, 0);
                }
            }
        }

        // 保存 Word 文件
        FileOutputStream fos = new FileOutputStream("output_with_image.docx");
        document.write(fos);
        fos.close();
        document.close();

        System.out.println("带图片的 Word 文件生成成功!");
    } catch (IOException | TemplateException | org.apache.poi.openxml4j.exceptions.InvalidFormatException e) {
        e.printStackTrace();
    }
}

解释

  1. 渲染模板:将数据模型填充到模板中,生成包含图片引用的 Word 文档。
  2. 添加图片:使用 Apache POI 将实际的图片文件添加到文档中。
  3. 替换图片引用 ID:将模板中的图片引用 rId1 替换为实际的关系 ID,确保图片正确显示。
  4. 保存文件:将生成的 Word 文件保存为 output_with_image.docx

示例 5:自动化批量生成报告

结合上述功能,可以实现批量生成多个用户的报告,并保存为不同的文件。

代码实现

public void generateBatchReports() {
    try {
        Configuration cfg = getFreemarkerConfiguration();
        Template template = cfg.getTemplate("document.ftl");
        List<Map<String, Object>> users = Arrays.asList(
            createUser("张三", 30, "软件工程师"),
            createUser("李四", 28, "产品经理"),
            createUser("王五", 35, "测试工程师")
        );

        for (Map<String, Object> user : users) {
            // 设置当前用户数据
            Map<String, Object> data = new HashMap<>();
            data.putAll(user);

            // 渲染模板到输出流
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            Writer out = new OutputStreamWriter(baos);
            template.process(data, out);
            out.flush();

            // 将渲染后的内容写入 Word 文档
            InputStream is = new ByteArrayInputStream(baos.toByteArray());
            XWPFDocument document = new XWPFDocument(is);
            String outputPath = user.get("name") + "_report.docx";
            FileOutputStream fos = new FileOutputStream(outputPath);
            document.write(fos);
            fos.close();
            document.close();

            System.out.println(outputPath + " 生成成功!");
        }
    } catch (IOException | TemplateException e) {
        e.printStackTrace();
    }
}

解释

  • 循环生成:遍历用户列表,为每个用户生成单独的 Word 报告文件。
  • 动态输出路径:根据用户姓名,生成对应的报告文件名,避免文件覆盖。

十二、图表与流程图辅助理解

1. 文档生成详细流程图 📊

graph TD;
    A[开始] --> B[准备数据模型]
    B --> C[加载 Freemarker 模板]
    C --> D{选择生成类型}
    D -->|Word| E[渲染模板]
    D -->|PDF| F[渲染 HTML 模板]
    E --> G[使用 Apache POI 写入 .docx]
    F --> H[使用 iText 生成 .pdf]
    G --> I[保存 Word 文件]
    H --> I[保存 PDF 文件]
    I --> J[结束]

解释

  • 选择生成类型:根据需求选择生成 Word 或 PDF 文件。
  • 不同路径处理:分别使用 Apache POI 和 iText 处理不同格式的文档生成。

3. 依赖关系图表 📋

组件功能依赖
Freemarker模板渲染,生成文档内容Java
Apache POI操作 Word 文件,生成 .docxFreemarker, Java IO
iText生成 PDF 文件Freemarker, Java IO, XMLWorker
Java IO文件读写操作Java
数据模型提供动态填充的数据Java 集合框架
模板文件定义文档的结构和样式Freemarker
XMLWorker解析 HTML,支持复杂样式iText

解释

  • 组件关系:展示各组件之间的依赖和功能分工,帮助理解整体架构。

十三、总结 🎯

通过本文的详细指南,您已经掌握了如何使用 Java 与 Freemarker 生成 Word 和 PDF 文件的全流程。从环境配置、模板设计,到代码实现和优化技巧,每一步都进行了深入解析。以下是关键要点总结:

  • Freemarker 模板引擎:灵活高效地填充数据模型,生成动态文档内容。
  • Apache POI:强大的 Word 操作库,支持复杂的文档生成需求。
  • iText:功能丰富的 PDF 生成库,通过 XMLWorker 支持复杂的样式和布局。
  • 数据模型设计:结构化、灵活的数据模型是成功生成文档的基础。
  • 模板设计规范:简洁、可复用的模板设计提升开发效率和文档质量。
  • 优化与最佳实践:合理的错误处理、性能优化和安全性考虑,确保文档生成过程稳定可靠。

通过持续应用和优化这些方法,您可以高效地在 Java 项目中实现自动化的 Word 和 PDF 文档生成,满足各种业务需求,提升开发与运维效率。🚀🔍

参考图表

1. 文档生成详细流程图

graph TD;
    A[开始] --> B[准备数据模型]
    B --> C[加载 Freemarker 模板]
    C --> D{选择生成类型}
    D -->|Word| E[渲染模板]
    D -->|PDF| F[渲染 HTML 模板]
    E --> G[使用 Apache POI 写入 .docx]
    F --> H[使用 iText 生成 .pdf]
    G --> I[保存 Word 文件]
    H --> I[保存 PDF 文件]
    I --> J[结束]

3. 依赖关系图表

组件功能依赖
Freemarker模板渲染,生成文档内容Java
Apache POI操作 Word 文件,生成 .docxFreemarker, Java IO
iText生成 PDF 文件Freemarker, Java IO, XMLWorker
Java IO文件读写操作Java
数据模型提供动态填充的数据Java 集合框架
模板文件定义文档的结构和样式Freemarker
XMLWorker解析 HTML,支持复杂样式iText

解释

  • 组件关系:展示各组件之间的依赖和功能分工,帮助理解整体架构。

4. 模板与数据驱动测试优势图解

flowchart TD
    A[数据驱动测试] --> B[使用外部数据文件]
    A --> C[简化循环逻辑]
    D[脚本控制循环] --> E[根据响应动态控制]
    D --> F[灵活应对复杂条件]
    B --> G[批量处理数据]
    E --> G

解释

  • 数据驱动测试:通过外部数据文件批量处理数据,简化循环逻辑。
  • 脚本控制循环:根据响应动态控制循环,灵活应对复杂条件。
  • 共同优势:高效批量处理数据,提升测试效率。

结束语

Freemarker 结合 Java 的强大生态系统,为文档生成提供了高效、灵活的解决方案。无论是生成简单的文本报告,还是复杂的带有图表和图片的 PDF 文件,Freemarker 都能满足各种需求。通过本文的详细解析,您已经掌握了从环境配置、模板设计到代码实现的全流程。持续学习和实践,将助您在自动化文档生成的道路上不断前进,提升工作效率和文档质量。💪🚀

结束

通过全面解析 Java 使用 Freemarker 生成 Word 和 PDF 文件的方法,结合实际示例和图表说明,您可以更加高效地实现自动化文档生成,满足复杂的业务需求,提升开发与运维效率。📈🔧


Viewing all articles
Browse latest Browse all 3145

Trending Articles