Java使用FreeMarker實現動態(tài)生成Word文檔
引言
在電纜行業(yè),生成供貨清單是一項常見但繁瑣的任務。本教程將介紹如何使用現代Java技術棧自動化這一過程,大幅提高工作效率和準確性。我們將使用SpringBoot作為框架,Apache POI處理Word文檔,以及FreeMarker作為模板引擎來實現這一功能!
讓我們先了解一下這個問題的背景:
- 在電纜行業(yè),手動創(chuàng)建供貨清單是一個復雜且重復的過程。
- 這個過程不僅耗時,還容易出錯,影響工作效率和數據準確性。
為了解決這個問題,我們提出了一個技術方案,結合了以下幾個關鍵技術:
- SpringBoot: 作為我們的主要開發(fā)框架
- Apache POI: 用于生成和操作Word文檔
- FreeMarker模板引擎: 用于生成Word文件的內容
這個方案的主要優(yōu)勢包括:
- 靈活性: 使用FreeMarker模板可以輕松調整文檔格式,而無需修改程序代碼。
- 效率: 自動化生成過程大大減少了人工操作,提高了辦公效率。
- 準確性: 自動化處理確保了數據的準確性和一致性。
- 適用性: 特別適合電纜行業(yè)的業(yè)務需求,生成符合要求的.doc文件。
通過閱讀這篇博客,您將學習如何實現這個解決方案,從而幫助您或您的團隊簡化工作流程,提高生產效率。
效果圖:

項目結構
src/
├── main/
│ ├── java/
│ │ └── com/
│ │ └── pw/
│ │ ├── WordController.java #負責生成測試數據并調用WordUtil工具類來生成Word文檔
│ │ └── utils/
│ │ └── WordUtil.java #這個工具類封裝了使用FreeMarker生成Word文檔的核心功能
│ └── resources/
│ └── templates/
│ └── template.ftl #模版定義了Word文檔的結構和樣式,使用HTML和CSS來格式化內容
1.WordController類:這個類是我們應用的入口點,負責生成測試數據并調用WordUtil來生成Word文檔。
2.WordUtil類:這個工具類封裝了使用FreeMarker生成Word文檔的核心邏輯。
3.FreeMarker模版(template.ftl):這個模版定義了Word文檔的結構和樣式,使用HTML和CSS來格式化內容。
源代碼展示
1.WordController
import com.pw.utils.WordUtil;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class WordController {
public static void main(String[] args) throws IOException {
// 指定保存Word文件的目錄
String filePath = "F:\\Poi2Word\\src\\main\\resources\\output"; // 更改為您希望的目錄
new WordController().generateWordFile(filePath);
}
public void generateWordFile(String directory) throws IOException {
List<Map<String, Object>> listMap = new ArrayList<>();
//測試數據
addTestData(listMap, "4600025747", "絕緣導線", "AC10kV,JKLYJ,300", 1500, "米", "盤號:A1");
addTestData(listMap, "4600025748", "絕緣導線", "AC10kV,JKLGYJ,150/30", 2500, "米", "盤號:A2");
addTestData(listMap, "4600025749", "絕緣導線", "AC10kV,JKLGYJ,150/30", 3500, "米", "盤號:A3");
addTestData(listMap, "4600025750", "絕緣導線", "AC10kV,JKLGYJ,150/30", 4500, "米", "盤號:A4");
addTestData(listMap, "4600025751", "絕緣導線", "AC10kV,JKLGYJ,150/30", 3800, "米", "盤號:A5");
addTestData(listMap, "4600025752", "絕緣導線", "AC10kV,JKLYJ,180", 2000, "米", "盤號:A6");
addTestData(listMap, "4600025753", "絕緣導線", "AC10kV,JKLYJ,120", 4200, "米", "盤號:A7");
addTestData(listMap, "4600025754", "絕緣導線", "AC10kV,JKLYJ,120", 3700, "米", "盤號:A8");
addTestData(listMap, "4600025755", "絕緣導線", "AC10kV,JKLYJ,120", 4300, "米", "盤號:A9");
addTestData(listMap, "4600025756", "絕緣導線", "AC10kV,JKLGYJ,100/20", 2800, "米", "盤號:A10");
addTestData(listMap, "4600025757", "絕緣導線", "AC10kV,JKLGYJ,100/20", 2400, "米", "盤號:A11");
addTestData(listMap, "4600025758", "絕緣導線", "AC10kV,JKLGYJ,100/20", 2600, "米", "盤號:A12");
HashMap<String, Object> map = new HashMap<>();
map.put("qdList", listMap); // 添加供貨清單數據
map.put("contacts", "張三"); // 聯系人
map.put("contactsPhone", "13988887777"); // 聯系電話
map.put("date", "2025年01月18日"); // 日期
map.put("company", "新電纜科技有限公司"); // 公司名稱
map.put("customer", "國網北京市電力公司"); // 客戶
String wordName = "template.ftl"; // FreeMarker模板文件名
String fileName = "供貨清單" + System.currentTimeMillis() + ".doc"; // 帶時間戳的文件名
String name = "name"; // 臨時文件名
// 確保輸出目錄存在
File directoryFile = new File(directory);
if (!directoryFile.exists()) {
directoryFile.mkdirs(); // 如果目錄不存在則創(chuàng)建
}
// 生成Word文件
WordUtil.exportMillCertificateWord(directory, map, wordName, fileName, name);
System.out.println("文件成功生成在:" + directory + fileName);
}
private void addTestData(List<Map<String, Object>> listMap, String danhao, String name, String model, int num, String unit, String remark) {
Map<String, Object> item = new HashMap<>();
item.put("serNo", listMap.size() + 1); // 序號
item.put("danhao", danhao); // 單號
item.put("name", name); // 產品名稱
item.put("model", model); // 規(guī)格型號
item.put("num", String.valueOf(num)); // 數量,轉換為字符串
item.put("unit", unit); // 單位
item.put("remark", remark); // 備注
listMap.add(item); // 將數據添加到列表
}
}2.WordUtil工具類
package com.pw.utils;
import freemarker.template.Configuration;
import freemarker.template.Template;
import java.io.*;
import java.util.Map;
public class WordUtil {
private static Configuration configuration = null;
// 模板文件夾路徑
private static final String templateFolder = WordUtil.class.getResource("/templates").getPath();
static {
configuration = new Configuration();
configuration.setDefaultEncoding("utf-8");
try {
System.out.println(templateFolder);
configuration.setDirectoryForTemplateLoading(new File(templateFolder)); // 設置模板加載路徑
} catch (IOException e) {
e.printStackTrace();
}
}
private WordUtil() {
throw new AssertionError(); // 防止實例化
}
/**
* 導出Word文檔
* @param map Word文檔中參數
* @param wordName 模板的名字,例如xxx.ftl
* @param fileName Word文件的名字 格式為:"xxxx.doc"
* @param outputDirectory 輸出文件的目錄路徑
* @param name 臨時的文件夾名稱,作為Word文件生成的標識
* @throws IOException
*/
public static void exportMillCertificateWord(String outputDirectory, Map map, String wordName, String fileName, String name) throws IOException {
Template freemarkerTemplate = configuration.getTemplate(wordName); // 獲取模板文件
File file = null;
try {
// 調用工具類的createDoc方法生成Word文檔
file = createDoc(map, freemarkerTemplate, name);
// 確保輸出目錄存在
File dir = new File(outputDirectory);
if (!dir.exists()) {
dir.mkdirs(); // 如果目錄不存在則創(chuàng)建
}
// 定義完整的文件路徑
File outputFile = new File(outputDirectory, fileName);
// 重命名并移動文件到指定目錄
file.renameTo(outputFile);
System.out.println("文件成功生成在: " + outputFile.getAbsolutePath());
} finally {
if (file != null && file.exists()) {
file.delete(); // 刪除臨時文件
}
}
}
private static File createDoc(Map<?, ?> dataMap, Template template, String name) {
File f = new File(name);
try {
// 使用OutputStreamWriter來指定編碼,防止特殊字符出問題
Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
template.process(dataMap, w); // 使用FreeMarker處理模板
w.close();
} catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
return f; // 返回生成的文件
}
}3.FreeMarker模版
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${company}送貨清單</title>
<style>
body { font-family: SimSun, serif; } <!-- 設置字體 -->
table { border-collapse: collapse; width: 100%; } <!-- 設置表格樣式 -->
th, td { border: 1px solid black; padding: 5px; text-align: center; } <!-- 設置表格的單元格樣式 -->
th { background-color: #f2f2f2; } <!-- 設置表頭背景色 -->
.subtotal { font-weight: bold; } <!-- 小計行加粗 -->
.total { font-weight: bold; font-size: 1.1em; } <!-- 總計行加粗并設置字體大小 -->
</style>
</head>
<body>
<h1 style="text-align: center;">${company}送貨清單</h1> <!-- 頂部公司名稱 -->
<table>
<tr>
<th>序號</th> <!-- 表頭:序號 -->
<th>供貨單號</th> <!-- 表頭:供貨單號 -->
<th>產品名稱</th> <!-- 表頭:產品名稱 -->
<th>規(guī)格型號</th> <!-- 表頭:規(guī)格型號 -->
<th>數量</th> <!-- 表頭:數量 -->
<th>單位</th> <!-- 表頭:單位 -->
<th>備注</th> <!-- 表頭:備注 -->
</tr>
<#assign totalQuantity = 0> <!-- 總數量初始化 -->
<#assign totalItems = 0> <!-- 總項數初始化 -->
<#assign sortedList = qdList?sort_by("model")> <!-- 按照規(guī)格型號排序 -->
<#assign currentModel = ""> <!-- 當前型號初始化 -->
<#assign subtotalQuantity = 0> <!-- 小計數量初始化 -->
<#assign subtotalItems = 0> <!-- 小計項數初始化 -->
<#list sortedList as item> <!-- 遍歷排序后的列表 -->
<#if item.model != currentModel> <!-- 如果規(guī)格型號變了 -->
<#if currentModel != ""> <!-- 如果當前規(guī)格型號不是空 -->
<tr class="subtotal">
<td colspan="4">小計:${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}軸</td>
<td>${subtotalQuantity}</td>
<td>${sortedList[0].unit}</td>
<td></td>
</tr>
</#if>
<#assign currentModel = item.model> <!-- 更新當前型號 -->
<#assign subtotalQuantity = 0> <!-- 重置小計數量 -->
<#assign subtotalItems = 0> <!-- 重置小計項數 -->
</#if>
<tr>
<td>${item?counter}</td> <!-- 序號 -->
<td>${item.danhao}</td> <!-- 單號 -->
<td>${item.name}</td> <!-- 產品名稱 -->
<td>${item.model}</td> <!-- 規(guī)格型號 -->
<td>${item.num}</td> <!-- 數量 -->
<td>${item.unit}</td> <!-- 單位 -->
<td>${item.remark}</td> <!-- 備注 -->
</tr>
<#assign itemNum = item.num?replace(",", "")?number> <!-- 將數量轉為數字并處理逗號 -->
<#assign subtotalQuantity = subtotalQuantity + itemNum> <!-- 累加小計數量 -->
<#assign subtotalItems = subtotalItems + 1> <!-- 累加小計項數 -->
<#assign totalQuantity = totalQuantity + itemNum> <!-- 累加總數量 -->
<#assign totalItems = totalItems + 1> <!-- 累加總項數 -->
</#list>
<#if currentModel != ""> <!-- 如果當前規(guī)格型號不是空 -->
<tr class="subtotal">
<td colspan="4">小計:${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}軸</td>
<td>${subtotalQuantity}</td>
<td>${sortedList[0].unit}</td>
<td></td>
</tr>
</#if>
<tr class="total">
<td colspan="4">合計:${totalQuantity}${qdList[0].unit} ${totalItems}軸</td>
<td>${totalQuantity}</td>
<td>${qdList[0].unit}</td>
<td></td>
</tr>
</table>
<p>發(fā)貨聯系人:${contacts}</p> <!-- 發(fā)貨聯系人 -->
<p>聯系電話:${contactsPhone}</p> <!-- 聯系電話 -->
<p>日期:${date}</p> <!-- 日期 -->
<p style="text-align: right;">收貨人(簽字):_______________</p> <!-- 收貨人簽字 -->
<p style="text-align: right;">聯系電話:_______________</p> <!-- 收貨人聯系電話 -->
<p style="text-align: right;">${customer}</p> <!-- 客戶 -->
</body>
</html>4.POM依賴
<!-- freemarker依賴,用于模板引擎,方便進行頁面的渲染和數據的展示等操作 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>
<!-- Apache POI 的核心依賴,用于操作 Microsoft Office 格式的文檔,如 Excel、Word 等文件 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.0.0</version>
</dependency>
<!-- Apache POI 的 OOXML 擴展依賴,主要用于處理 Office 2007 及以后版本的 OOXML 格式的文件,例如.xlsx 等 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.0.0</version>
</dependency>
<!-- OOXML 模式相關的依賴,提供了對 OOXML 文檔結構和內容模式的支持,有助于 Apache POI 更好地操作 OOXML 格式文件 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>1.4</version>
</dependency>WordController類深度解析
WordController類是整個應用的核心控制器,負責協調數據生成和文檔創(chuàng)建的過程。讓我們逐步分析它的主要組成部分:
1.類結構
public class WordController {
// 方法定義...
}
這個類沒有繼承任何其他類,也沒有實現任何接口,是一個獨立的控制器類。
2.main方法
public static void main(String[] args) throws IOException {
String filePath = "F:\\Poi2Word\\src\\main\\resources\\output";
new WordController().generateWordFile(filePath);
}
這是應用的入口點。
它設置了輸出文件的路徑,然后調用generateWordFile方法。
請注意:在常規(guī)的 Spring Boot 實際應用場景下,我們一般不會直接在控制器類中使用 main 方法。此處之所以將 main 方法置于控制器中,純粹是出于演示目的,旨在讓相關流程更加直觀易懂。
而當進入到正式開發(fā)環(huán)節(jié)時,有幾個關鍵要點務必落實:
其一,需要引入數據庫集成功能,將當前所使用的測試數據全面替換為從數據庫中精準查詢獲取的真實數據,以此確保數據的準確性與時效性;
其二,要對控制器進行優(yōu)化改造,摒棄現有的演示模式,將其轉換為遵循標準規(guī)范的請求接口實現方式,進而滿足實際業(yè)務需求,提升系統的穩(wěn)定性與可擴展性。
3.generateWordFile方法
此方法的只要目的是生成Word文件,首先需要先收集和存儲測試數據,存儲表格數據是將一條數據存儲在Map集合中,再將每一條數據存儲到List集合中。將其他數據存儲到單獨的一個Map集合中。然后確保輸出目錄存在,最后調用WordUtil中的exportMillCertificateWord方法生成文件,并輸出文件的生成位置。
// 生成 Word 文件的方法
public void generateWordFile(String directory) throws IOException {
// 存儲測試數據的列表,每個元素都是一個 Map,存儲了具體的信息
List<Map<String, Object>> listMap = new ArrayList<>();
// 添加測試數據,調用 addTestData 方法添加一條記錄
addTestData(listMap, "4600025747", "絕緣導線", "AC10kV,JKLYJ,300", 1500, "米", "盤號:A1");
//... 可以繼續(xù)調用 addTestData 方法添加更多測試數據...
// 存儲最終要填充到 Word 模板的數據的 Map,包含各種信息
HashMap<String, Object> map = new HashMap<>();
// 將測試數據列表添加到 map 中,鍵為 "qdList"
map.put("qdList", listMap);
// 聯系人信息
map.put("contacts", "張三");
// 聯系人電話
map.put("contactsPhone", "13988887777");
// 日期信息
map.put("date", "2025年01月18日");
// 公司名稱
map.put("company", "新電纜科技有限公司");
// 客戶名稱
map.put("customer", "國網北京市電力公司");
// Word 模板文件的名稱
String wordName = "template.ftl";
// 生成的 Word 文件的名稱,使用當前時間戳保證文件名的唯一性
String fileName = "供貨清單" + System.currentTimeMillis() + ".doc";
// 名稱信息,具體含義可能根據實際情況而定
String name = "name";
// 創(chuàng)建一個文件對象,用于表示輸出目錄
File directoryFile = new File(directory);
// 檢查輸出目錄是否存在,如果不存在則創(chuàng)建目錄
if (!directoryFile.exists()) {
directoryFile.mkdirs();
}
// 調用 WordUtil 的 exportMillCertificateWord 方法生成 Word 文件
// 傳入目錄、數據 Map、模板名稱、生成的文件名稱和名稱信息
WordUtil.exportMillCertificateWord(directory, map, wordName, fileName, name);
// 打印生成文件的成功信息
System.out.println("文件成功生成在:" + directory + fileName);
}這個方法完成以下任務:
- 創(chuàng)建一個一個List<Map<String,Object>>集合來存儲供貨清單數據
- 使用addTestData方法添加多條測試數據
- 創(chuàng)建一個Map集合來存儲企業(yè)名稱,發(fā)貨聯系人,聯系電話等信息
- 確保輸出目錄存在
- 調用WordUtil.exportMillCertificateWord方法來生成Word文檔
4.addTestData方法
這個方法用于創(chuàng)建單個供貨項目的數據
// 添加一條測試數據到 listMap 中
private void addTestData(List<Map<String, Object>> listMap, String danhao, String name, String model, int num, String unit, String remark) {
// 創(chuàng)建一個新的 HashMap,用于存儲每一條數據
Map<String, Object> item = new HashMap<>();
// 將數據項依次放入 HashMap 中,"serNo" 表示序號,使用 listMap 的大小+1 生成序號
item.put("serNo", listMap.size() + 1); // 序號是當前列表的大小 + 1
item.put("danhao", danhao); // 供貨單號
item.put("name", name); // 產品名稱
item.put("model", model); // 規(guī)格型號
item.put("num", String.valueOf(num)); // 數量,將整數轉為字符串
item.put("unit", unit); // 單位
item.put("remark", remark); // 備注
// 將該條數據項添加到 listMap 列表中
listMap.add(item);
}這個方法完成以下任務:
- 它接收多個參數,代表一個供貨項目的各個屬性。
- 創(chuàng)建一個新的Map來存儲這個項目的數據。
- 自動計算序號(serNo)基于當前列表的大小。
- 將所有數據添加到Map中。
- 將這個Map添加到供貨清單列表中。
WordUtil類深度解析
WordUtil類是整個文檔生成過程的核心,它封裝了FreeMarker模板引擎的配置和使用邏輯。讓我們逐步分析它的主要組成部分:
1.類結構和靜態(tài)成員
public class WordUtil {
private static Configuration configuration = null;
private static final String templateFolder = WordUtil.class.getResource("/templates").getPath();
// 其他方法...
}configuration:這是FreeMarker的核心配置對象,用于設置模版加載路徑。
templateFolder:定義了模版文件的存儲路徑。使用getResource()方法確保在不同環(huán)境下都能正確找到模版文件。
2.靜態(tài)初始化塊
這段代碼的作用是初始化FreeMarker的Configuration對象,設置模版加載目錄以及編碼格式,以便FreeMarker后續(xù)能夠正確加載和處理模版文件。
// 靜態(tài)初始化塊,用于初始化 FreeMarker 配置
static {
// 創(chuàng)建一個 FreeMarker 配置對象,用于后續(xù)模板處理
configuration = new Configuration();
// 設置 FreeMarker 配置對象的默認編碼為 "utf-8"
configuration.setDefaultEncoding("utf-8");
try {
// 輸出模板文件夾路徑,幫助調試
System.out.println(templateFolder);
// 設置模板加載目錄為 templateFolder 指定的路徑,模板文件會從該目錄加載
configuration.setDirectoryForTemplateLoading(new File(templateFolder));
} catch (IOException e) {
// 如果加載模板目錄時出現異常,打印錯誤堆棧信息
e.printStackTrace();
}
}這個靜態(tài)初始化塊在類加載時執(zhí)行,主要完成以下任務:
- 創(chuàng)建FreeMarker的Configuration對象
- 設置默認編碼為UTF-8,確保正確處理中文等字符
- 設置模版加載目錄,這樣FreeMarker就知道從哪里查找加載模版文件了
- 錯誤處理:如果執(zhí)行過程中出現了IO異常,就會打印堆棧跟蹤
3.私有構造函數
這個構造函數防止類被實例化,確保WordUtil只能通過其靜態(tài)方法使用。
private WordUtil() {
throw new AssertionError();
}
私有構造函數的好處包括:
防止類被實例化
當類的構造函數被聲明為private時,外部代碼無法直接創(chuàng)建該類的實例。這就意味著該類只能公國靜態(tài)方法訪問,確保類的功能是全局共享的。
實現單例模式的基礎
在一些設計模式中,例如單例模式,類只允許有一個實例,私有構造函數確保了這一點。通過private構造函數,我們可以控制類的實例化過程,并確保只有一個實例被創(chuàng)建。
封裝類的內部實現
私有構造函數可以幫助隱藏類的具體實現細節(jié),外部代碼不需要關心如何創(chuàng)建類的實例,只需要使用類提供的靜態(tài)方法即可。這增加了類的封裝性,降低了與外部代碼的耦合度。
避免多余的對象創(chuàng)建
由于無法實例化類,每次調用靜態(tài)方法時,都會使用已有的類實例,這可以避免無意義的對象創(chuàng)建,節(jié)省內存和資源。
4.exportMillCertificateWord方法
這個方法的主要功能是通過加載指定的 FreeMarker 模板生成一個臨時的 Word 文檔,確保輸出目錄存在后,將臨時文件重命名并保存到指定的位置,同時在過程結束后清理臨時文件,并打印文件生成的成功消息。
// 導出 Word 文檔的方法
public static void exportMillCertificateWord(String outputDirectory, Map map, String wordName, String fileName, String name) throws IOException {
// 獲取 FreeMarker 模板文件
Template freemarkerTemplate = configuration.getTemplate(wordName);
// 初始化一個 File 對象,用于存儲生成的臨時文件
File file = null;
try {
// 使用模板和數據創(chuàng)建 Word 文檔,返回臨時文件
file = createDoc(map, freemarkerTemplate, name);
// 創(chuàng)建目標目錄的 File 對象
File dir = new File(outputDirectory);
// 如果目錄不存在,則創(chuàng)建該目錄
if (!dir.exists()) {
dir.mkdirs(); // 創(chuàng)建目錄及其父目錄
}
// 定義最終輸出文件的完整路徑(包括目錄和文件名)
File outputFile = new File(outputDirectory, fileName);
// 將臨時生成的文件重命名為目標文件,并將其移動到指定目錄
file.renameTo(outputFile);
// 打印輸出文件的絕對路徑,a通知文件生成成功
System.out.println("文件成功生成在: " + outputFile.getAbsolutePath());
} finally {
// 最后,無論是否成功生成文件,都確保臨時文件被刪除
if (file != null && file.exists()) {
file.delete(); // 刪除臨時文件
}
}
}這個方法是文檔導出的主要入口,主要實現了以下功能:
- 加載指定的FreeMarker模版
- 調用createDoc方法生成臨時文檔文件
- 確保輸出目錄存在
- 將臨時文件重命名并移動到指定的輸出位置
- 使用finally塊確保臨時文件被刪除,無論過程是否成功
5.createDoc方法
這個方法是創(chuàng)建文檔的核心方法,主要是通過創(chuàng)建一個臨時文件,使用指定的FreeMarker模版和數據模型將內容填充到文件中,并確保文件使用UTF-8編碼進行寫入。該方法在執(zhí)行過程中捕獲異常并打印堆棧信息,確保發(fā)生錯誤時能夠正確處理。最后。方法返回生成的文件對象,以便后續(xù)操作或保存。
// 創(chuàng)建文檔的方法,使用 FreeMarker 模板生成內容并寫入文件
private static File createDoc(Map<?, ?> dataMap, Template template, String name) {
// 創(chuàng)建一個新的 File 對象,表示生成的文檔文件,文件名由參數 "name" 提供
File f = new File(name);
try {
// 使用 OutputStreamWriter 創(chuàng)建一個寫入文件的 Writer 對象,設置編碼為 "utf-8"
Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
// 使用 FreeMarker 模板將數據填充到文件中
template.process(dataMap, w);
// 關閉 Writer,確保所有內容寫入文件
w.close();
} catch (Exception ex) {
// 捕獲異常并打印錯誤堆棧信息
ex.printStackTrace();
// 拋出 RuntimeException,確保錯誤被傳播到調用者
throw new RuntimeException(ex);
}
// 返回生成的文件對象
return f;
}這個方法是實際創(chuàng)建文檔的核心,主要實現以下功能:
創(chuàng)建一個臨時文件。
使用OutputStreamWriter設置UTF-8編碼,確保正確處理所有字符。
調用FreeMarker的template.process()方法,將數據模型(dataMap)應用到模板上。
關閉寫入器。
如果過程中發(fā)生異常,打印堆棧跟蹤并拋出RuntimeException。
返回生成的文件對象。
6.WordUtil類總結
WordUtil 類通過封裝 FreeMarker 模板引擎的配置和文件操作,提供了一個簡潔的文檔生成工具。它加載指定模板,使用數據模型填充內容,創(chuàng)建臨時文件,并確保文件按照指定路徑保存。該類通過靜態(tài)方法確保全局共享功能,使用 UTF-8 編碼處理字符,捕獲異常并清理臨時文件,確保文檔生成過程的穩(wěn)定性和高效性。
FreeMarker模板深度解析
FreeMarker模板是整個文檔生成過程的核心,它定義了最終Word文檔的結構和樣式。讓我們來逐步分析模板的主要組成部分
1.文檔結構和樣式
<!DOCTYPE html> <!-- 聲明文檔類型為 HTML5 -->
<html>
<head>
<!-- 設置文檔字符編碼為 UTF-8,支持中文和其他字符集 -->
<meta charset="UTF-8">
<!-- 設置頁面標題,動態(tài)插入公司名稱 -->
<title>${company}送貨清單</title>
<style>
/* 設置頁面正文的字體為 SimSun(宋體),如果沒有則使用 serif */
body { font-family: SimSun, serif; }
/* 設置表格樣式:表格邊框合并,寬度100% */
table { border-collapse: collapse; width: 100%; }
/* 設置表格頭部和單元格的邊框、內邊距和文本居中對齊 */
th, td { border: 1px solid black; padding: 5px; text-align: center; }
/* 設置表頭背景色為淺灰色 */
th { background-color: #f2f2f2; }
/* 設置小計行字體加粗 */
.subtotal { font-weight: bold; }
/* 設置合計行字體加粗,字體大小稍大 */
.total { font-weight: bold; font-size: 1.1em; }
</style>
</head>
<body>
<!-- 頁面標題,居中顯示公司名稱和送貨清單 -->
<h1 style="text-align: center;">${company}送貨清單</h1>
<!-- 表格內容將在這里生成,動態(tài)插入數據 -->
</body>
</html>這段代碼通過HTML和內嵌CSS定義了頁面布局和樣式:
動態(tài)公司名稱:<title>標簽使用${company}插入動態(tài)的公司名稱,顯示在瀏覽器標簽中。
字體和表格樣式:
- 設置頁面字體為宋體(Simsun)
- 定義表格邊框合并、100%寬度,并使單元格內容居中
小計和總計行樣式:為小計行加粗字體,并為總計行加粗且增大字體,突出顯示重要數據。
2.表格結構和動態(tài)數據插入
<table>
<!-- 表頭,定義表格的列名 -->
<tr>
<th>序號</th> <!-- 序號 -->
<th>供貨單號</th> <!-- 供貨單號 -->
<th>產品名稱</th> <!-- 產品名稱 -->
<th>規(guī)格型號</th> <!-- 規(guī)格型號 -->
<th>數量</th> <!-- 數量 -->
<th>單位</th> <!-- 單位 -->
<th>備注</th> <!-- 備注 -->
</tr>
<!-- 初始化總計和小計相關變量 -->
<#assign totalQuantity = 0> <!-- 總數量 -->
<#assign totalItems = 0> <!-- 總項數 -->
<#assign sortedList = qdList?sort_by("model")> <!-- 按照規(guī)格型號對數據進行排序 -->
<#assign currentModel = ""> <!-- 當前規(guī)格型號 -->
<#assign subtotalQuantity = 0> <!-- 小計數量 -->
<#assign subtotalItems = 0> <!-- 小計項數 -->
<!-- 遍歷排序后的列表 -->
<#list sortedList as item>
<!-- 如果當前項的規(guī)格型號與上一項不同,則輸出上一項的小計 -->
<#if item.model != currentModel>
<#if currentModel != "">
<!-- 輸出上一規(guī)格型號的小計行 -->
<tr class="subtotal">
<td colspan="4">小計:${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}軸</td>
<td>${subtotalQuantity}</td>
<td>${sortedList[0].unit}</td>
<td></td>
</tr>
</#if>
<!-- 更新當前規(guī)格型號為當前項的規(guī)格型號,并重置小計 -->
<#assign currentModel = item.model>
<#assign subtotalQuantity = 0>
<#assign subtotalItems = 0>
</#if>
<!-- 輸出當前行數據 -->
<tr>
<td>${item?counter}</td> <!-- 序號,使用 FreeMarker 的 counter 計數 -->
<td>${item.danhao}</td> <!-- 供貨單號 -->
<td>${item.name}</td> <!-- 產品名稱 -->
<td>${item.model}</td> <!-- 規(guī)格型號 -->
<td>${item.num}</td> <!-- 數量 -->
<td>${item.unit}</td> <!-- 單位 -->
<td>${item.remark}</td> <!-- 備注 -->
</tr>
<!-- 更新小計和總計的數量和項數 -->
<#assign itemNum = item.num?replace(",", "")?number> <!-- 將數量轉為數字并處理逗號 -->
<#assign subtotalQuantity = subtotalQuantity + itemNum> <!-- 累加小計數量 -->
<#assign subtotalItems = subtotalItems + 1> <!-- 累加小計項數 -->
<#assign totalQuantity = totalQuantity + itemNum> <!-- 累加總數量 -->
<#assign totalItems = totalItems + 1> <!-- 累加總項數 -->
</#list>
<!-- 如果最后一項有數據,輸出最后的規(guī)格型號小計 -->
<#if currentModel != "">
<tr class="subtotal">
<td colspan="4">小計:${subtotalQuantity}${sortedList[0].unit} ${subtotalItems}軸</td>
<td>${subtotalQuantity}</td>
<td>${sortedList[0].unit}</td>
<td></td>
</tr>
</#if>
<!-- 輸出最終的合計行 -->
<tr class="total">
<td colspan="4">合計:${totalQuantity}${qdList[0].unit} ${totalItems}軸</td> <!-- 顯示合計的數量和項數 -->
<td>${totalQuantity}</td> <!-- 合計數量 -->
<td>${qdList[0].unit}</td> <!-- 單位 -->
<td></td>
</tr>
</table>表格結構:使用 <table> 標簽創(chuàng)建表格,并通過 <th> 定義表頭,包含7列:序號、供貨單號、產品名稱等。
動態(tài)數據插入:使用 FreeMarker <#list> 遍歷排序后的清單數據,并通過 ${item.屬性名} 動態(tài)插入每項數據,如 ${item.danhao} 插入供貨單號。
小計和總計計算:
- 通過 <#assign> 定義變量如 totalQuantity 和 subtotalQuantity,在循環(huán)中累加數量。
- 使用 <#if> 判斷條件,插入小計行,并在循環(huán)結束后插入總計行。
數據處理:
- 使用 sortedList = qdList?sort_by("model") 按型號對清單數據進行排序。
- 處理數量 itemNum = item.num?replace(",", "")?number,移除逗號并轉換為數字,確保計算正確。
格式化輸出:
- 小計和總計行使用 colspan 屬性合并單元格,確保表格顯示整潔。
- 使用 CSS 類 subtotal 和 total 為小計和總計行應用加粗和突出顯示的樣式。
總結:此表格通過 FreeMarker 動態(tài)插入數據、計算小計和總計,并通過合適的排序和格式化樣式,確保清單展示清晰且易于閱讀。
最后,模板還包括了一些額外信息:
<p>發(fā)貨聯系人:${contacts}</p>
<p>聯系電話:${contactsPhone}</p>
<p>日期:${date}</p>
<p style="text-align: right;">收貨人(簽字):_______________</p>
<p style="text-align: right;">聯系電話:_______________</p>
<p style="text-align: right;">${customer}</p>
這部分添加了額外的聯系信息和簽名區(qū)域,進一步完善了文檔的實用性。
總的來,這個FreeMarker模板展示了如何結合HTML、CSS和FreeMarker的模板語法來創(chuàng)建一個復雜、動態(tài)且格式良好的文檔。它不僅能夠準確地呈現數據,還能執(zhí)行必要的計算和格式化,從而生成一個專業(yè)的供貨清單文檔。
總結
通過使用SpingBoot、Apache POI和FreeMarker,我們成功自動化了電纜供貨清單的生成過程。這不僅提高了效率,還減少了人為錯誤。本解決方案的模塊化設計使其易于維護和擴展。
以上就是Java使用FreeMarker實現動態(tài)生成Word文檔的詳細內容,更多關于Java FreeMarker生成Word的資料請關注腳本之家其它相關文章!
相關文章
Java調用Creo?2.0讀取/修改模型參數的實戰(zhàn)指南
本文詳細講解了使用Java調用Creo2.0實現.prt模型參數讀取與修改的方法,強調方向大于努力,不可完全依賴AI;提供實測可用代碼、操作步驟及避坑要點;涵蓋環(huán)境配置、關鍵注意事項、執(zhí)行步驟與常見問題排查等內容,適合Creel.0二次開發(fā)新手參考,需要的朋友可以參考下2026-05-05
Mybatis-Plus中and()和or()的使用與原理詳解
最近發(fā)現MyBatisPlus還是挺好用的,下面這篇文章主要給大家介紹了關于Mybatis-Plus中and()和or()的使用與原理的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-09-09
Redisson分布式信號量RSemaphore的使用超詳細講解
這篇文章主要介紹了Redisson分布式信號量RSemaphore的使用,基于Redis的Redisson的分布式信號量RSemaphore采用了與java.util.concurrent.Semaphore相似的接口和用法2023-02-02
Maven和IntelliJ IDEA搭建多模塊微服務的實現
本文主要介紹了Maven和IntelliJ IDEA搭建多模塊微服務的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-05-05

