当前位置: 代码网 > it编程>编程语言>Java > SpringBoot集成freemarker导出Word模板的实战步骤

SpringBoot集成freemarker导出Word模板的实战步骤

2026年05月09日 Java 我要评论
一、前言1.1 应用场景在后端开发中,word 导出是高频需求(如报表导出、合同导出、单据导出、数据统计报告等),而 freemarker 作为一款模板引擎,能快速实现 word 模板的动态数据填充,

一、前言

1.1 应用场景

在后端开发中,word 导出是高频需求(如报表导出、合同导出、单据导出、数据统计报告等),而 freemarker 作为一款模板引擎,能快速实现 word 模板的动态数据填充,搭配 springboot 可高效落地到项目中,相比其他方式更简洁、易维护。

1.2 本文目标

  • 掌握 springboot 集成 spring-boot-starter-freemarker 的核心配置
  • 学会将 doc 格式 word 模板转换为 freemarker 支持的 ftl 格式
  • 实现 word 模板数据动态填充、文件导出功能
  • 完成测试验证,解决导出过程中的常见问题

1.3 环境说明

本文实战环境,可直接对应你的本地环境,无需额外修改:

  • jdk:8 及以上
  • springboot:2.7.x(适配多数项目,其他版本可微调配置)
  • freemarker:spring-boot-starter-freemarker 内置版本(无需额外指定版本)
  • 工具:idea、wps/office(编辑 word 模板)、浏览器(测试接口)
  • 测试文件:doc 格式模板文件、转换后的 ftl 模板文件

二、核心原理简述

freemarker 导出 word 的核心是:

1.先制作 word 模板(doc 格式),标记需要动态填充的占位符(如:${name});
2.再将其转换为 freemarker 支持的 ftl 模板文件;
3.springboot 集成 freemarker 后,需要读取 ftl 模板,我通过代码封装需要填充的数据(map/实体类),再结合 freemarker 引擎渲染模板,最终转换为 word 文件并响应给前端进行下载。

关键要点:ftl 模板的占位符语法、doc 转 ftl 的格式兼容、数据填充的语法规范、文件流的正确处理。

三、实战步骤

3.1 第一步:引入 maven 依赖

在 springboot 项目的 pom.xml 中,引入 spring-boot-starter-freemarker 依赖,无需额外引入 freemarker 核心包(starter 已集成),同时引入文件处理相关依赖。

<!-- 核心:freemarker 依赖 -->
<dependency>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-starter-freemarker</artifactid>
</dependency>

以下是其他所需的依赖:

    <!-- commons-io 依赖 -->
    <dependency>
      <groupid>commons-io</groupid>
      <artifactid>commons-io</artifactid>
      <version>2.11.0</version>
    </dependency>
    <!-- hutool 工具类 -->
    <dependency>
      <groupid>cn.hutool</groupid>
      <artifactid>hutool-all</artifactid>
      <version>5.7.16</version>
    </dependency>
    <!-- easyexcel 导入导出工具类  -->
    <dependency>
      <groupid>com.alibaba</groupid>
      <artifactid>easyexcel</artifactid>
      <version>3.3.2</version>
    </dependency>
    <dependency>
      <groupid>cn.afterturn</groupid>
      <artifactid>easypoi-base</artifactid>
      <version>4.1.3</version>
    </dependency>
    <dependency>
      <groupid>cn.afterturn</groupid>
      <artifactid>easypoi-web</artifactid>
      <version>4.1.3</version>
    </dependency>
    <dependency>
      <groupid>cn.afterturn</groupid>
      <artifactid>easypoi-annotation</artifactid>
      <version>4.1.3</version>
    </dependency>

3.2 第二步:制作 word 模板(doc 格式)并转换为 ftl 格式

这是核心步骤之一,模板的制作直接影响导出效果,重点是正确设置占位符,避免格式错乱。

3.2.1 制作 doc 模板

  1. 用 wps/office 新建 word 文档(保存为 doc 格式,注意:不要保存为 docx 格式,避免转换后格式异常);
  2. 在需要动态填充数据的位置,设置占位符,占位符语法: 变量名,例如:姓名: {变量名},例如:姓名: 变量名,例如:姓名:{name}、年龄:${age};
  3. 模板中可保留固定内容(如标题、表格表头、落款等),仅将动态数据替换为占位符;

具体如下图所示:

3.2.2 doc 转 ftl 格式

将制作好的 doc 模板文件转换为 freemarker 支持的 ftl 格式,步骤如下:

  1. 将 doc 模板文件另存为「word 2003 xml 文档」(后缀为 .xml);
  2. 找到保存后的 .xml 文件,将文件后缀名改为 .ftl;
  3. 用 idea 打开 ftl 文件,检查占位符是否正常(若有乱码,调整文件编码为 utf-8)。

注:转换后不要随意修改 ftl 中的标签结构,仅修改占位符相关内容,否则会导致导出的 word 格式错乱。

3.3 第三步:编写核心代码

核心代码分为 3 部分:

1.实体类 / map(封装填充数据,目前我测试使用,直接使用map类型封装)
2.工具类(freemarker 模板渲染、文件流处理)
3.接口层(提供导出接口,供前端调用)

以下是核心代码:

3.3.1 freemarker 工具类

import cn.afterturn.easypoi.word.wordexportutil;
import cn.hutool.core.lang.assert;
import freemarker.template.configuration;
import freemarker.template.template;
import org.apache.commons.io.ioutils;
import org.apache.poi.xwpf.usermodel.xwpfdocument;
import org.slf4j.logger;
import org.slf4j.loggerfactory;

import javax.servlet.http.httpservletrequest;
import javax.servlet.http.httpservletresponse;
import java.io.*;
import java.net.urlencoder;
import java.nio.charset.standardcharsets;
import java.nio.file.files;
import java.util.base64;
import java.util.map;
import java.util.objects;

public class exportwordutils {

    private static final logger logger = loggerfactory.getlogger(exportwordutils.class);

/**
     * 导出word(基于freemarker)
     *
     * @param datamap      数据集
     * @param templatename 模板名称
     * @param filepath     模板路径
     * @param filename     文件名
     * @param request      httpservletrequest
     * @param response     httpservletresponse
     */
    public static void exportdoc(map<string, object> datamap, string templatename,
                                 string filepath, string filename,
                                 httpservletrequest request, httpservletresponse response) {
        assert.notnull(datamap, "数据集不能为空");
        assert.notnull(templatename, "模板名称不能为空");
        assert.notnull(filepath, "模板路径不能为空");
        assert.notnull(filename, "文件名不能为空");

        writer writer = null;
        try {
            configuration config = new configuration(configuration.version_2_3_30);
            config.setdefaultencoding(standardcharsets.utf_8.name());
            config.setdirectoryfortemplateloading(new file(filepath));

            template template = config.gettemplate(templatename, standardcharsets.utf_8.name());

            string useragent = getuseragent(request);
            string encodedfilename = encodefilename(filename + ".doc", useragent);

			response.setcontenttype("application/xml");
            response.setcharacterencoding(standardcharsets.utf_8.name());
        	response.addheader("content-disposition", "attachment;filename=" + filename);

            writer = new bufferedwriter(new outputstreamwriter(response.getoutputstream(), standardcharsets.utf_8));
            template.process(datamap, writer);
            writer.flush();
        } catch (exception e) {
            logger.error("导出word文档失败,模板: {}", templatename, e);
            if (!response.iscommitted()) {
                response.setstatus(httpservletresponse.sc_internal_server_error);
            }
        } finally {
            ioutils.closequietly(writer);
        }
    }
    /**
     * 获取模板文件路径
     * 兼容 windows 和 linux 系统
     *
     * @return 模板所在目录的绝对路径
     */
    public static string gettemplatepath() {
        try {
            string path = objects.requirenonnull(
                            thread.currentthread().getcontextclassloader().getresource("templates/word/"))
                    .getpath();
            return java.net.urldecoder.decode(path, "utf-8");
        } catch (exception e) {
            logger.error("获取模板路径失败", e);
            throw new runtimeexception("获取模板路径失败", e);
        }
    }

	private static string getuseragent(httpservletrequest request) {
        return request.getheader("user-agent");
    }

    private static string encodefilename(string filename, string useragent) throws unsupportedencodingexception {
        if (useragent.contains("msie") || useragent.contains("trident")) {
            return urlencoder.encode(filename, "utf-8");
        } else if (useragent.contains("firefox")) {
            return new string(filename.getbytes(standardcharsets.utf_8), standardcharsets.iso_8859_1);
        } else {
            return urlencoder.encode(filename, "utf-8");
        }
    }
}

3.3.2 接口层(controller)

编写接口,封装需要填充的数据,调用工具类实现导出功能,前端可通过浏览器或接口工具(postman)调用:

@slf4j
@restcontroller
public class testcontroller {

    private static final logger logger = loggerfactory.getlogger(testcontroller.class);


	@getmapping("/exportword")
    public void exportword(httpservletresponse response, httpservletrequest request) {

        string filename = "测试单";
        map<string, object> datamap = new hashmap<>();

        datamap.put("year", string.valueof(dateutils.getcurrentyear()));
        datamap.put("month", string.valueof(dateutils.getcurrentmonth()));
        datamap.put("day", string.valueof(dateutils.getcurrentday()));

        datamap.put("name", "张三");
        datamap.put("age", "20");
        datamap.put("phone", "123456789");
        datamap.put("address", "北京市东城区长安街北侧");
        datamap.put("hobby", "学习");

        string templatepath = exportwordutils.gettemplatepath();
        logger.info("导出测试单,模板路径: {}, 文件名: {}", templatepath, filename);
				
		// 调用工具类导出 word
        exportwordutils.exportdoc(datamap, "test.ftl", templatepath, filename, request, response);
    }
    
}

3.4 第四步:放置 ftl 模板文件

将转换好的 ftl 模板文件,放到项目 /resources/templates/word/ 路径下,确保模板路径正确,否则会报「模板找不到」异常。

四、测试验证

验证1:接口调用测试
验证2:导出文件验证

4.1 接口调用测试

  1. 启动 springboot 项目,确保项目无报错;
  2. 用浏览器或 postman 调用导出接口(如:http://localhost:9995/exportword);
  3. 观察是否自动下载 word 文件,无报错即接口调用成功。

4.2 导出文件验证

  1. 打开下载的 word 文件,检查占位符是否被正确替换为填充的数据;
  2. 检查 word 格式是否正常(无乱码、表格对齐、字体样式一致);
  3. 验证数据是否正常回填显示。

五、常见问题

结合实际开发中遇到的问题,整理如下:

  • 问题1:模板找不到(template not found)
    解决方案:检查 ftl 模板路径配置是否正确,模板文件名是否正确,路径是否有拼写错误。
  • 问题2:导出的 word 文件乱码
    解决方案:确保 ftl 模板编码为 utf-8,接口响应头设置正确的 charset=utf-8,工具类中文件流编码统一为 utf-8。
  • 问题3:doc 转 ftl 后格式错乱
    解决方案:制作 doc 模板时尽量简化格式,避免复杂排版;转换后不要修改 ftl 中的 xml 标签结构,仅调整占位符。
  • 问题4:导出文件无法打开
    解决方案:确保模板是 doc 格式转换的(不要用 docx),ftl 模板无语法错误,占位符语法是否格式正确(有时候doc转成xml格式后,表达式会被样式代码拆分,需要手动删除多余样式,调整为正确的表达式${变量名}),数据填充时避免出现null,会导致模板渲染失败。

六、总结

本文完成了 springboot 集成 spring-boot-starter-freemarker 导出 word 模板的完整实战,从依赖引入、模板制作(doc 转 ftl)、代码实现,到测试验证、避坑指南,所有代码可直接复制复用。
核心要点:

  • doc 模板转 ftl 是关键,需要注意格式兼容和占位符语法正确。
  • 获取模板文件路径要正确,文件编码要正确,避免模板找不到、乱码问题。
  • 数据填充时,变量名需与 ftl 模板占位符完全一致。
  • ftl中模板占位符要对应有数据,如果存在null值或没有配置数据,会导致报错。

以上就是springboot集成freemarker导出word模板的实战步骤的详细内容,更多关于springboot freemarker导出word模板的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com