最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot集成freemarker導(dǎo)出Word模板的實戰(zhàn)步驟

 更新時間:2026年05月09日 09:17:21   作者:勿忘初心1221  
本文聚焦 SpringBoot 集成 spring-boot-starter-freemarker 實現(xiàn) Word 模板導(dǎo)出實戰(zhàn),全程實際開發(fā)場景,包含 doc 模板轉(zhuǎn) ftl 格式、模板數(shù)據(jù)填充、代碼實現(xiàn)、測試驗證,附詳細步驟+代碼+截圖,新手也能直接上手復(fù)用,適合 Java 后端開發(fā)學習和項目落地

一、前言

1.1 應(yīng)用場景

在后端開發(fā)中,Word 導(dǎo)出是高頻需求(如報表導(dǎo)出、合同導(dǎo)出、單據(jù)導(dǎo)出、數(shù)據(jù)統(tǒng)計報告等),而 freemarker 作為一款模板引擎,能快速實現(xiàn) Word 模板的動態(tài)數(shù)據(jù)填充,搭配 SpringBoot 可高效落地到項目中,相比其他方式更簡潔、易維護。

1.2 本文目標

  • 掌握 SpringBoot 集成 spring-boot-starter-freemarker 的核心配置
  • 學會將 doc 格式 Word 模板轉(zhuǎn)換為 freemarker 支持的 ftl 格式
  • 實現(xiàn) Word 模板數(shù)據(jù)動態(tài)填充、文件導(dǎo)出功能
  • 完成測試驗證,解決導(dǎo)出過程中的常見問題

1.3 環(huán)境說明

本文實戰(zhàn)環(huán)境,可直接對應(yīng)你的本地環(huán)境,無需額外修改:

  • JDK:8 及以上
  • SpringBoot:2.7.x(適配多數(shù)項目,其他版本可微調(diào)配置)
  • freemarker:spring-boot-starter-freemarker 內(nèi)置版本(無需額外指定版本)
  • 工具:IDEA、WPS/Office(編輯 Word 模板)、瀏覽器(測試接口)
  • 測試文件:doc 格式模板文件、轉(zhuǎn)換后的 ftl 模板文件

二、核心原理簡述

freemarker 導(dǎo)出 Word 的核心是:

1.先制作 Word 模板(doc 格式),標記需要動態(tài)填充的占位符(如:${name});
2.再將其轉(zhuǎn)換為 freemarker 支持的 ftl 模板文件;
3.SpringBoot 集成 freemarker 后,需要讀取 ftl 模板,我通過代碼封裝需要填充的數(shù)據(jù)(Map/實體類),再結(jié)合 freemarker 引擎渲染模板,最終轉(zhuǎn)換為 Word 文件并響應(yīng)給前端進行下載。

關(guān)鍵要點:ftl 模板的占位符語法、doc 轉(zhuǎn) ftl 的格式兼容、數(shù)據(jù)填充的語法規(guī)范、文件流的正確處理。

三、實戰(zhàn)步驟

3.1 第一步:引入 Maven 依賴

在 SpringBoot 項目的 pom.xml 中,引入 spring-boot-starter-freemarker 依賴,無需額外引入 freemarker 核心包(starter 已集成),同時引入文件處理相關(guān)依賴。

<!-- 核心: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 導(dǎo)入導(dǎo)出工具類  -->
    <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 格式)并轉(zhuǎn)換為 ftl 格式

這是核心步驟之一,模板的制作直接影響導(dǎo)出效果,重點是正確設(shè)置占位符,避免格式錯亂。

3.2.1 制作 doc 模板

  1. 用 WPS/Office 新建 Word 文檔(保存為 doc 格式,注意:不要保存為 docx 格式,避免轉(zhuǎn)換后格式異常);
  2. 在需要動態(tài)填充數(shù)據(jù)的位置,設(shè)置占位符,占位符語法: 變量名,例如:姓名: {變量名},例如:姓名: 變量名,例如:姓名:{name}、年齡:${age};
  3. 模板中可保留固定內(nèi)容(如標題、表格表頭、落款等),僅將動態(tài)數(shù)據(jù)替換為占位符;

具體如下圖所示:

3.2.2 doc 轉(zhuǎn) ftl 格式

將制作好的 doc 模板文件轉(zhuǎn)換為 freemarker 支持的 ftl 格式,步驟如下:

  1. 將 doc 模板文件另存為「Word 2003 XML 文檔」(后綴為 .xml);
  2. 找到保存后的 .xml 文件,將文件后綴名改為 .ftl;
  3. 用 IDEA 打開 ftl 文件,檢查占位符是否正常(若有亂碼,調(diào)整文件編碼為 UTF-8)。

注:轉(zhuǎn)換后不要隨意修改 ftl 中的標簽結(jié)構(gòu),僅修改占位符相關(guān)內(nèi)容,否則會導(dǎo)致導(dǎo)出的 Word 格式錯亂。

3.3 第三步:編寫核心代碼

核心代碼分為 3 部分:

1.實體類 / Map(封裝填充數(shù)據(jù),目前我測試使用,直接使用Map類型封裝)
2.工具類(freemarker 模板渲染、文件流處理)
3.接口層(提供導(dǎo)出接口,供前端調(diào)用)

以下是核心代碼:

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);

/**
     * 導(dǎo)出word(基于FreeMarker)
     *
     * @param dataMap      數(shù)據(jù)集
     * @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, "數(shù)據(jù)集不能為空");
        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("導(dǎo)出Word文檔失敗,模板: {}", templateName, e);
            if (!response.isCommitted()) {
                response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }
        } finally {
            IOUtils.closeQuietly(writer);
        }
    }
    /**
     * 獲取模板文件路徑
     * 兼容 Windows 和 Linux 系統(tǒng)
     *
     * @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)

編寫接口,封裝需要填充的數(shù)據(jù),調(diào)用工具類實現(xiàn)導(dǎo)出功能,前端可通過瀏覽器或接口工具(Postman)調(diào)用:

@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", "北京市東城區(qū)長安街北側(cè)");
        dataMap.put("hobby", "學習");

        String templatePath = ExportWordUtils.getTemplatePath();
        LOGGER.info("導(dǎo)出測試單,模板路徑: {}, 文件名: {}", templatePath, fileName);
				
		// 調(diào)用工具類導(dǎo)出 Word
        ExportWordUtils.exportDoc(dataMap, "test.ftl", templatePath, fileName, request, response);
    }
    
}

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

將轉(zhuǎn)換好的 ftl 模板文件,放到項目 /resources/templates/word/ 路徑下,確保模板路徑正確,否則會報「模板找不到」異常。

四、測試驗證

驗證1:接口調(diào)用測試
驗證2:導(dǎo)出文件驗證

4.1 接口調(diào)用測試

  1. 啟動 SpringBoot 項目,確保項目無報錯;
  2. 用瀏覽器或 Postman 調(diào)用導(dǎo)出接口(如:http://localhost:9995/exportWord);
  3. 觀察是否自動下載 Word 文件,無報錯即接口調(diào)用成功。

4.2 導(dǎo)出文件驗證

  1. 打開下載的 Word 文件,檢查占位符是否被正確替換為填充的數(shù)據(jù);
  2. 檢查 Word 格式是否正常(無亂碼、表格對齊、字體樣式一致);
  3. 驗證數(shù)據(jù)是否正?;靥铒@示。

五、常見問題

結(jié)合實際開發(fā)中遇到的問題,整理如下:

  • 問題1:模板找不到(Template not found)
    解決方案:檢查 ftl 模板路徑配置是否正確,模板文件名是否正確,路徑是否有拼寫錯誤。
  • 問題2:導(dǎo)出的 Word 文件亂碼
    解決方案:確保 ftl 模板編碼為 UTF-8,接口響應(yīng)頭設(shè)置正確的 charset=UTF-8,工具類中文件流編碼統(tǒng)一為 UTF-8。
  • 問題3:doc 轉(zhuǎn) ftl 后格式錯亂
    解決方案:制作 doc 模板時盡量簡化格式,避免復(fù)雜排版;轉(zhuǎn)換后不要修改 ftl 中的 XML 標簽結(jié)構(gòu),僅調(diào)整占位符。
  • 問題4:導(dǎo)出文件無法打開
    解決方案:確保模板是 doc 格式轉(zhuǎn)換的(不要用 docx),ftl 模板無語法錯誤,占位符語法是否格式正確(有時候doc轉(zhuǎn)成xml格式后,表達式會被樣式代碼拆分,需要手動刪除多余樣式,調(diào)整為正確的表達式${變量名}),數(shù)據(jù)填充時避免出現(xiàn)null,會導(dǎo)致模板渲染失敗。

六、總結(jié)

本文完成了 SpringBoot 集成 spring-boot-starter-freemarker 導(dǎo)出 Word 模板的完整實戰(zhàn),從依賴引入、模板制作(doc 轉(zhuǎn) ftl)、代碼實現(xiàn),到測試驗證、避坑指南,所有代碼可直接復(fù)制復(fù)用。
核心要點:

  • doc 模板轉(zhuǎn) ftl 是關(guān)鍵,需要注意格式兼容和占位符語法正確。
  • 獲取模板文件路徑要正確,文件編碼要正確,避免模板找不到、亂碼問題。
  • 數(shù)據(jù)填充時,變量名需與 ftl 模板占位符完全一致。
  • ftl中模板占位符要對應(yīng)有數(shù)據(jù),如果存在null值或沒有配置數(shù)據(jù),會導(dǎo)致報錯。

以上就是SpringBoot集成freemarker導(dǎo)出Word模板的實戰(zhàn)步驟的詳細內(nèi)容,更多關(guān)于SpringBoot freemarker導(dǎo)出Word模板的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 淺談java中守護線程與用戶線程

    淺談java中守護線程與用戶線程

    本篇文章主要介紹了淺談java中守護線程與用戶線程,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-09-09
  • RestTemplate使用Proxy代理作為跳板發(fā)送請求

    RestTemplate使用Proxy代理作為跳板發(fā)送請求

    這篇文章主要為大家介紹了RestTemplate使用代理proxy作為跳板發(fā)送請求的方法示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步
    2022-03-03
  • Java如何讀取XML文件 具體實現(xiàn)

    Java如何讀取XML文件 具體實現(xiàn)

    這篇文章主要介紹了Java如何讀取XML文件 具體實現(xiàn),有需要的朋友可以參考一下
    2013-12-12
  • Javaweb EL自定義函數(shù)開發(fā)及代碼實例

    Javaweb EL自定義函數(shù)開發(fā)及代碼實例

    這篇文章主要介紹了Javaweb EL自定義函數(shù)開發(fā)及代碼實例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-06-06
  • Java將List轉(zhuǎn)換為String的幾種方式

    Java將List轉(zhuǎn)換為String的幾種方式

    我們大家在實際開發(fā)中經(jīng)常遇到List轉(zhuǎn)為String字符串的情況,下面這篇文章主要給大家介紹了關(guān)于Java將List轉(zhuǎn)換為String的幾種方式,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-05-05
  • SpringMVC中解決@ResponseBody注解返回中文亂碼問題

    SpringMVC中解決@ResponseBody注解返回中文亂碼問題

    這篇文章主要介紹了SpringMVC中解決@ResponseBody注解返回中文亂碼問題, 小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-04-04
  • Java中callable的實現(xiàn)原理

    Java中callable的實現(xiàn)原理

    本文主要介紹了Java里的callable的實現(xiàn)原理,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-03-03
  • Spring中的事件監(jiān)聽器使用學習記錄

    Spring中的事件監(jiān)聽器使用學習記錄

    Spring框架中的事件監(jiān)聽機制是一種設(shè)計模式,它允許你定義和觸發(fā)事件,同時允許其他組件監(jiān)聽這些事件并在事件發(fā)生時作出響應(yīng),這篇文章主要介紹了Spring中的事件監(jiān)聽器使用學習,需要的朋友可以參考下
    2024-07-07
  • springboot中@Async默認線程池導(dǎo)致OOM問題

    springboot中@Async默認線程池導(dǎo)致OOM問題

    這篇文章主要介紹了springboot中@Async默認線程池導(dǎo)致OOM問題,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-06-06
  • 聊聊ResourceBundle和properties讀取配置文件的區(qū)別

    聊聊ResourceBundle和properties讀取配置文件的區(qū)別

    這篇文章主要介紹了ResourceBundle和properties讀取配置文件的區(qū)別,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07

最新評論

谢通门县| 苍梧县| 桂阳县| 龙游县| 璧山县| 东山县| 绵竹市| 永清县| 巨野县| 阿拉善右旗| 保德县| 禄劝| 枣庄市| 惠来县| 洛阳市| 来宾市| 南部县| 外汇| 土默特右旗| 荥经县| 台湾省| 泽州县| 古交市| 麻城市| 曲松县| 阿克陶县| 南安市| 滨州市| 揭阳市| 鹤岗市| 余庆县| 安宁市| 开化县| 方城县| 宝清县| 梁山县| 图们市| 罗定市| 红河县| 普兰县| 勐海县|