SpringBoot3實(shí)現(xiàn)Word文檔動(dòng)態(tài)生成與下載
引言
日常開(kāi)發(fā)中,經(jīng)常會(huì)遇到這樣的需求:根據(jù)業(yè)務(wù)數(shù)據(jù)動(dòng)態(tài)生成Word文檔,比如合同導(dǎo)出、報(bào)表生成、用戶(hù)證明材料等。如果直接用原生API操作Word,代碼繁瑣且容易出現(xiàn)格式錯(cuò)亂,后期維護(hù)成本極高。
最近在SpringBoot3項(xiàng)目中,通過(guò)集成 poi-tl 工具,摸索出一套極簡(jiǎn)高效的Word動(dòng)態(tài)生成方案,無(wú)需復(fù)雜的樣式編碼配置,僅需簡(jiǎn)單幾步,即可快速實(shí)現(xiàn)Word模板占位符填充、動(dòng)態(tài)表格渲染及圖片插入等核心需求,大幅提升開(kāi)發(fā)效率。
項(xiàng)目代碼結(jié)構(gòu)
先附上完整的項(xiàng)目代碼結(jié)構(gòu),方便大家對(duì)照搭建,后續(xù)所有代碼都將對(duì)應(yīng)此結(jié)構(gòu),避免路徑錯(cuò)亂、類(lèi)找不到等問(wèn)題:
src
└── main
├── java
│ └── com.example.demo
│ ├── controller
│ │ └── ContractController.java <-- 接口類(lèi)(接收請(qǐng)求、調(diào)用工具類(lèi))
│ ├── dto
│ │ ├── ContractDTO.java <-- 主數(shù)據(jù)模型(對(duì)應(yīng)模板占位符)
│ │ └── ContractDetailDTO.java <-- 明細(xì)數(shù)據(jù)模型(對(duì)應(yīng)表格占位符)
│ ├── util
│ │ └── WordGenerateUtil.java <-- 通用工具類(lèi)(封裝生成、下載邏輯)
│ └── DemoApplication.java <-- 項(xiàng)目啟動(dòng)類(lèi)
└── resources
├── static
│ └── img
│ └── attachment.jpg <-- 測(cè)試圖片(用于圖片渲染驗(yàn)證)
├── templates
│ └── contractTemplate.docx <-- Word模板(存放占位符)
└── application.yml <-- 項(xiàng)目配置文件(默認(rèn)配置即可)一、環(huán)境準(zhǔn)備
- JDK 17+;
- Spring Boot 3.0+(本文用 3.2.5);
- poi-tl 1.12.2;
1.1 引入Maven依賴(lài)
直接在pom.xml中添加以下依賴(lài):
<!-- 核心依賴(lài):實(shí)現(xiàn)Word模板渲染與生成 -->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.12.2</version>
</dependency>
<!-- Apache POI: 處理Office文檔的核心庫(kù) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.5.1</version>
</dependency>
<!-- 可選但推薦:文件操作工具 -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.21.0</version>
</dependency>1.2 模板與圖片準(zhǔn)備
1. 模板準(zhǔn)備:在src/main/resources目錄下,新建templates文件夾,放入Word模板文件(后綴必須是.docx,不能是.doc,否則會(huì)報(bào)格式錯(cuò)誤),命名為contractTemplate.docx;
2. 圖片準(zhǔn)備:在src/main/resources/static目錄下,新建img文件夾,放入一張測(cè)試圖片(命名為attachment.jpg),用于后續(xù)圖片渲染驗(yàn)證。
二、實(shí)現(xiàn)Word動(dòng)態(tài)生成與下載
整體流程:制作Word模板(設(shè)置占位符)→ 編寫(xiě)數(shù)據(jù)模型(與占位符對(duì)應(yīng))→ 編寫(xiě)工具類(lèi)與接口(實(shí)現(xiàn)生成與下載)。
2.1 第一步:制作Word模板
模板制作的核心是設(shè)置“占位符”,后續(xù)代碼將數(shù)據(jù)替換到占位符中,且會(huì)完全繼承模板的原有樣式(字體、顏色、行距等),無(wú)需額外編寫(xiě)樣式代碼。
制作規(guī)則(簡(jiǎn)單好記,無(wú)需死記硬背):
- 普通文本占位符:用 {{變量名}} 表示,比如 {{contractNo}}(合同編號(hào))、{{customerName}}(客戶(hù)姓名);
- 動(dòng)態(tài)表格占位符:用 {{#表格變量名}} 開(kāi)頭;
- 圖片占位符:用 {{@圖片變量名}} 表示,后續(xù)通過(guò)代碼傳入圖片流即可正常渲染;
- 占位符可以放在Word的任何位置(正文、表格、頁(yè)眉頁(yè)腳)。
實(shí)戰(zhàn)示例(以客戶(hù)合同模板為例):
打開(kāi)WPS/Word,新建文檔,輸入以下內(nèi)容并插入占位符,保存為contractTemplate.docx,放入templates目錄:
客戶(hù)合同
合同編號(hào):{{contractNo}}
客戶(hù)姓名:{{customerName}}
聯(lián)系電話(huà):{{phone}}
簽訂日期:{{signDate}}
合同明細(xì):
{{#detailList}}
合同附件:{{@attachmentImg}}2.2 第二步:編寫(xiě)數(shù)據(jù)模型
數(shù)據(jù)模型的作用是封裝需要填充到模板中的數(shù)據(jù),變量名必須和模板中的占位符完全一致(大小寫(xiě)敏感)。
實(shí)戰(zhàn)代碼(兩個(gè)核心類(lèi),放在dto包下):
import lombok.Data;
import java.util.List;
import java.math.BigDecimal;
/**
* 合同主數(shù)據(jù)模型(對(duì)應(yīng)模板中的普通文本占位符)
*/
@Data
public class ContractDTO {
// 合同編號(hào)(對(duì)應(yīng){{contractNo}})
private String contractNo;
// 客戶(hù)姓名(對(duì)應(yīng){{customerName}})
private String customerName;
// 聯(lián)系電話(huà)(對(duì)應(yīng){{phone}})
private String phone;
// 簽訂日期(對(duì)應(yīng){{signDate}})
private String signDate;
// 合同明細(xì)(對(duì)應(yīng){{#detailList}}循環(huán)表格)
private List<ContractDetailDTO> detailList;
// 附件圖片(對(duì)應(yīng){{@attachmentImg}},無(wú)需賦值,接口中單獨(dú)處理)
private String attachmentImg;
}
/**
* 合同明細(xì)數(shù)據(jù)模型(對(duì)應(yīng)表格中的占位符)
*/
@Data
public class ContractDetailDTO {
// 商品名稱(chēng)(對(duì)應(yīng){{productName}})
private String productName;
// 單價(jià)(對(duì)應(yīng){{price}})
private BigDecimal price;
// 數(shù)量(對(duì)應(yīng){{num}})
private Integer num;
// 小計(jì)(對(duì)應(yīng){{total}})
private BigDecimal total;
}2.3 編寫(xiě)通用Word工具類(lèi)
工具類(lèi)封裝了“生成Word并下載”“生成Word保存到本地”兩個(gè)核心方法。
import com.deepoove.poi.XWPFTemplate;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.Map;
/**
* Word文檔生成工具類(lèi)(通用,可直接復(fù)用)
*/
@Component
public class WordGenerateUtil {
/**
* 生成Word并通過(guò)瀏覽器下載
* @param templateName 模板文件名(放在resources/templates目錄下)
* @param data 填充到模板的數(shù)據(jù)(Map格式,key對(duì)應(yīng)模板占位符)
* @param response 響應(yīng)對(duì)象(用于返回下載流)
* @param downloadFileName 下載時(shí)的文件名(如:張三的合同.docx)
* @throws IOException 異常(可在調(diào)用處統(tǒng)一處理)
*/
public void generateAndDownload(String templateName, Map<String, Object> data,
HttpServletResponse response, String downloadFileName) throws IOException {
// 1. 讀取templates目錄下的Word模板
ClassPathResource resource = new ClassPathResource("templates/" + templateName);
// 2. 編譯模板并填充數(shù)據(jù)
XWPFTemplate template = XWPFTemplate.compile(resource.getInputStream()).render(data);
// 3. 設(shè)置響應(yīng)頭,實(shí)現(xiàn)瀏覽器下載(解決中文文件名亂碼問(wèn)題)
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(downloadFileName, "UTF-8"));
response.setCharacterEncoding("UTF-8");
// 4. 寫(xiě)入響應(yīng)流,完成下載
try (OutputStream outputStream = response.getOutputStream()) {
template.write(outputStream);
outputStream.flush();
} finally {
// 5. 關(guān)閉資源,避免內(nèi)存泄漏
template.close();
}
}
/**
* 生成Word并保存到本地(可選,根據(jù)需求使用)
* @param templateName 模板文件名
* @param data 填充數(shù)據(jù)
* @param localPath 本地保存路徑(如:D:/contract/張三的合同.docx)
* @throws IOException 異常
*/
public void generateToLocal(String templateName, Map<String, Object> data, String localPath) throws IOException {
ClassPathResource resource = new ClassPathResource("templates/" + templateName);
XWPFTemplate template = XWPFTemplate.compile(resource.getInputStream()).render(data);
// 寫(xiě)入本地文件
template.writeToFile(localPath);
template.close();
}
}2.4 編寫(xiě)接口類(lèi)
編寫(xiě)REST接口,模擬從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)(實(shí)際開(kāi)發(fā)中替換為真實(shí)DAO查詢(xún)),調(diào)用工具類(lèi)實(shí)現(xiàn)Word下載。
import cn.iocoder.boot.entity.ContractDTO;
import cn.iocoder.boot.entity.ContractDetailDTO;
import cn.iocoder.boot.utils.WordGenerateUtil;
import com.deepoove.poi.data.*;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
/**
* 合同導(dǎo)出接口(實(shí)戰(zhàn)示例)
*/
@RestController
@RequestMapping("/contract")
public class ContractController {
@Resource
private WordGenerateUtil wordGenerateUtil;
/**
* 導(dǎo)出單個(gè)客戶(hù)合同
* @param customerId 客戶(hù)ID(實(shí)際開(kāi)發(fā)中用于查詢(xún)客戶(hù)數(shù)據(jù))
* @param response 響應(yīng)對(duì)象(返回下載流)
* @throws IOException 異常
*/
@GetMapping("/export/{customerId}")
public void exportContract(@PathVariable String customerId, HttpServletResponse response) throws IOException {
// 1. 模擬從數(shù)據(jù)庫(kù)查詢(xún)客戶(hù)合同數(shù)據(jù)(實(shí)際開(kāi)發(fā)中替換為真實(shí)DAO查詢(xún))
ContractDTO contractDTO = getContractData(customerId);
// 2. 組裝數(shù)據(jù)(key必須和模板占位符完全一致)
Map<String, Object> data = new HashMap<>();
data.put("contractNo", contractDTO.getContractNo());
data.put("customerName", contractDTO.getCustomerName());
data.put("phone", contractDTO.getPhone());
data.put("signDate", contractDTO.getSignDate());
// 3. 創(chuàng)建表格數(shù)據(jù)
RowRenderData row0 = Rows.of("商品名稱(chēng)", "單價(jià)(元)","數(shù)量","小計(jì)(元)").textColor("FFFFFF")
.bgColor("4472C4").center().create();
Tables.TableBuilder tableBuilder = Tables.of(row0);
contractDTO.getDetailList().forEach(detail -> {
RowRenderData row = Rows.create(detail.getProductName(), detail.getPrice().toString(), detail.getNum().toString(), detail.getTotal().toString());
tableBuilder.addRow(row);
});
data.put("detailList", tableBuilder.create());
// 4. 填充圖片,圖片路徑:項(xiàng)目resources/static/img目錄下的圖片(實(shí)際可從數(shù)據(jù)庫(kù)獲取圖片路徑)
data.put("attachmentImg", Pictures.ofStream(
new ClassPathResource("static/img/attachment.jpg").getInputStream(), // 圖片流
PictureType.JPEG) // 圖片格式,無(wú)需手動(dòng)寫(xiě)后綴
.size(200, 100) // 圖片寬高(單位:像素)
.create()
);
// 5. 調(diào)用工具類(lèi),生成并下載Word
wordGenerateUtil.generateAndDownload(
"contractTemplate.docx", // 模板文件名
data, // 填充數(shù)據(jù)
response, // 響應(yīng)對(duì)象
contractDTO.getCustomerName() + "的合同.docx" // 下載文件名
);
}
/**
* 模擬查詢(xún)合同數(shù)據(jù)(實(shí)際開(kāi)發(fā)中替換為真實(shí)業(yè)務(wù)邏輯/DAO查詢(xún))
*/
private ContractDTO getContractData(String customerId) {
ContractDTO contract = new ContractDTO();
// 模擬主數(shù)據(jù)(實(shí)際從數(shù)據(jù)庫(kù)查詢(xún))
contract.setContractNo("HT-" + System.currentTimeMillis());
contract.setCustomerName("張三");
contract.setPhone("13800138000");
contract.setSignDate("2026-03-25");
// 模擬合同明細(xì)數(shù)據(jù)(對(duì)應(yīng)表格循環(huán))
List<ContractDetailDTO> detailList = new ArrayList<>();
ContractDetailDTO detail1 = new ContractDetailDTO();
detail1.setProductName("Java開(kāi)發(fā)服務(wù)");
detail1.setPrice(new BigDecimal("5000.00"));
detail1.setNum(1);
detail1.setTotal(new BigDecimal("5000.00"));
ContractDetailDTO detail2 = new ContractDetailDTO();
detail2.setProductName("系統(tǒng)維護(hù)服務(wù)");
detail2.setPrice(new BigDecimal("2000.00"));
detail2.setNum(1);
detail2.setTotal(new BigDecimal("2000.00"));
detailList.add(detail1);
detailList.add(detail2);
contract.setDetailList(detailList);
return contract;
}
}三、測(cè)試驗(yàn)證
測(cè)試步驟簡(jiǎn)單,無(wú)需復(fù)雜配置,啟動(dòng)SpringBoot項(xiàng)目后,直接訪(fǎng)問(wèn)接口即可驗(yàn)證功能是否正常。
3.1 前置準(zhǔn)備
1. 確認(rèn)templates目錄下有contractTemplate.docx模板,static/img目錄下有attachment.jpg圖片;
2. 確保項(xiàng)目啟動(dòng)無(wú)報(bào)錯(cuò)(JDK17環(huán)境,依賴(lài)正常引入);
3. 無(wú)需修改application.yml,默認(rèn)配置即可。
3.2 接口訪(fǎng)問(wèn)與驗(yàn)證
訪(fǎng)問(wèn)接口地址:http://localhost:8080/contract/export/1(customerId隨便傳,此處僅為模擬),瀏覽器會(huì)自動(dòng)下載Word文件。
四、常見(jiàn)問(wèn)題
- 模板后綴必須是.docx,不能是.doc,否則會(huì)報(bào)“不支持的格式”異常;
- 占位符大小寫(xiě)敏感,比如模板中是{{contractNo}},代碼中寫(xiě)contractno會(huì)導(dǎo)致填充失??;
- 圖片渲染需通過(guò)Pictures工具類(lèi)創(chuàng)建渲染對(duì)象,僅傳字符串路徑會(huì)導(dǎo)致圖片無(wú)法顯示;項(xiàng)目?jī)?nèi)圖片用ClassPathResource獲取流,本地圖片用FileInputStream獲取流;
- 批量生成Word時(shí),必須循環(huán)關(guān)閉template資源,否則會(huì)導(dǎo)致內(nèi)存溢出;
五、擴(kuò)展場(chǎng)景
實(shí)際開(kāi)發(fā)中,除了基礎(chǔ)的文本、表格、圖片填充,還可能遇到以下場(chǎng)景,簡(jiǎn)單補(bǔ)充實(shí)現(xiàn)思路:
- 條件渲染:某些字段為空時(shí)不顯示,可使用{{?變量名}} 占位符(如{{?remark}} 備注:{{remark}} {{/?remark}});
- 動(dòng)態(tài)圖片:從數(shù)據(jù)庫(kù)獲取圖片流(無(wú)需保存到本地),直接傳入Pictures.ofStream()方法即可渲染;
- 批量導(dǎo)出:循環(huán)調(diào)用工具類(lèi)的generateToLocal方法,生成多個(gè)Word文件,再通過(guò)ZipOutputStream打包成zip,返回給前端下載。
六、總結(jié)
本次實(shí)戰(zhàn)用SpringBoot3實(shí)現(xiàn)Word動(dòng)態(tài)生成與下載,核心邏輯是“模板占位符+數(shù)據(jù)模型+通用工具類(lèi)”,無(wú)需復(fù)雜的樣式配置,所有代碼均可直接復(fù)制復(fù)用。
對(duì)比原生API,這種方式不僅代碼簡(jiǎn)潔,而且后期維護(hù)方便——修改模板無(wú)需改代碼,只需調(diào)整Word文件中的占位符和樣式即可,極大降低維護(hù)成本。
以上就是SpringBoot3實(shí)現(xiàn)Word文檔動(dòng)態(tài)生成與下載的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot3 Word動(dòng)態(tài)生成與下載的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot中使用Filter和Interceptor的示例代碼
這篇文章主要介紹了SpringBoot中使用Filter和Interceptor的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06
Java設(shè)計(jì)模式之策略模式詳細(xì)解析
這篇文章主要介紹了Java設(shè)計(jì)模式之策略模式詳細(xì)解析,策略模式中,定義算法族,分別封裝起來(lái),讓他們之間可以相互轉(zhuǎn)化,此模式讓算法的變化獨(dú)立于使用算法的客戶(hù),需要的朋友可以參考下2023-11-11
java實(shí)現(xiàn)學(xué)生信息錄入界面
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)學(xué)生信息錄入界面,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
解決使用json-lib包實(shí)現(xiàn)xml轉(zhuǎn)json時(shí)空值被轉(zhuǎn)為空中括號(hào)的問(wèn)題
網(wǎng)上能查到的xml轉(zhuǎn)json的jar包大部分是net.sf.json-lib,但是JSON json =xmlSerializer.read(xml); 方法會(huì)出現(xiàn)將空值轉(zhuǎn)化為[]的問(wèn)題,下面為大家提供兩種解決方法2018-03-03
SpringBoot 使用 OpenAPI3 規(guī)范整合 knife4j的詳細(xì)過(guò)程
Swagger工具集使用OpenAPI規(guī)范,可以生成、展示和測(cè)試基于OpenAPI規(guī)范的API文檔,并提供了生成客戶(hù)端代碼的功能,本文給大家介紹SpringBoot使用OpenAPI3規(guī)范整合knife4j的詳細(xì)過(guò)程,感興趣的朋友跟隨小編一起看看吧2023-12-12
使用Cloud Toolkit在IDEA中極速創(chuàng)建dubbo工程
這篇文章主要介紹了使用Cloud Toolkit在IDEA中極速創(chuàng)建dubbo工程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11

