java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot freemarker导出Word模板

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

作者:勿忘初心1221

本文聚焦 SpringBoot 集成 spring-boot-starter-freemarker 实现 Word 模板导出实战,全程实际开发场景,包含 doc 模板转 ftl 格式、模板数据填充、代码实现、测试验证,附详细步骤+代码+截图,新手也能直接上手复用,适合 Java 后端开发学习和项目落地

一、前言

1.1 应用场景

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

1.2 本文目标

1.3 环境说明

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

二、核心原理简述

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. 验证数据是否正常回填显示。

五、常见问题

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

六、总结

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

以上就是SpringBoot集成freemarker导出Word模板的实战步骤的详细内容,更多关于SpringBoot freemarker导出Word模板的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文