SpringBoot利用模板實現(xiàn)自動生成Word合同的功能
前言
想像一下,我們做了一個Sass模式下的人事合同管理系統(tǒng),每公司的合同都不是統(tǒng)一的,所以每家公司都有一個合同模板;要求實現(xiàn)下載員工合同的功能。
基于以上需求有兩個解決方案:1.我博客中提到過《Spring Boot 手把手實現(xiàn)PDF功能》
2.就是通過poi-tl實現(xiàn)word內(nèi)容填充,而本文就重點介紹它的基本實現(xiàn)。
一、目標
兩個表:一個是用戶表、一個用戶識別表,通過用戶ID,輸入word文件中包含用戶的基本信息、用戶的識別記錄。

二、數(shù)據(jù)庫表




三、POM依賴
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>org.example</groupId>
<artifactId>word</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>word</name>
<description>word</description>
<url/>
<licenses>
<license/>
</licenses>
<developers>
<developer/>
</developers>
<scm>
<connection/>
<developerConnection/>
<tag/>
<url/>
</scm>
<properties>
<java.version>17</java.version>
<mybatis-plus.version>3.5.7</mybatis-plus.version>
<poi-tl.version>1.12.1</poi-tl.version>
<springdoc.version>1.6.15</springdoc.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- OpenAPI/Swagger UI -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.1.0</version>
</dependency>
<!-- MyBatis-Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- POI-TL for Word template -->
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>${poi-tl.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
四、配置
YML
server:
port: 2025
spring:
application:
name: word
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: 密碼
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
mapper-locations: classpath*:/mapper/**/*.xml
springdoc:
api-docs:
enabled: true
path: /v3/api-docs
swagger-ui:
enabled: true
path: /swagger-ui.html
tags-sorter: alpha
operations-sorter: alpha
packages-to-scan: org.example.word.controller
default-consumes-media-type: application/json
default-produces-media-type: application/json配置類
MyBatisPlusConfig
@Configuration
public class MyBatisPlusConfig {
/**
* MyBatis Plus Interceptor for pagination
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}OpenApiConfig
@Configuration
public class OpenApiConfig {
/**
* 配置 OpenAPI 信息
*/
@Bean
public OpenAPI customOpenAPI() {
return new OpenAPI()
.info(new Info()
.title("Word 模板導(dǎo)入系統(tǒng) API")
.description("基于 Spring Boot + POI-TL 的 Word 報告生成系統(tǒng)")
.version("1.0.0")
.contact(new Contact()
.name("開發(fā)團隊")
.email("developer@example.com")
.url("https://example.com"))
.license(new License()
.name("Apache 2.0")
.url("https://www.apache.org/licenses/LICENSE-2.0.html")));
}
}五、實現(xiàn)
模板文件內(nèi)容

主程序
@MapperScan("org.example.word.mapper")
@SpringBootApplication
public class WordApplication {
public static void main(String[] args) {
SpringApplication.run(WordApplication.class, args);
}
}實體類
**
* User Entity
*/
@Data
@TableName("user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String username;
private String email;
private String phone;
private Integer age;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Integer deleted;
}@Data
@TableName("user_reg")
public class UserReg implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
private Long userId;
private LocalDateTime regTime;
}mapper
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
@Mapper
public interface UserRegMapper extends BaseMapper<UserReg> {
}
service
public interface UserService extends IService<User> {
void generateUserReport(Long userId, HttpServletResponse response);
}
package org.example.word.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.data.Rows;
import com.deepoove.poi.data.Tables;
import jakarta.servlet.http.HttpServletResponse;
import org.example.word.entity.User;
import org.example.word.entity.UserReg;
import org.example.word.mapper.UserMapper;
import org.example.word.service.UserRegService;
import org.example.word.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* User Service Implementation
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
@Autowired
private UserRegService userRegService;
@Autowired
private UserMapper userMapper;
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
@Override
public void generateUserReport(Long userId, HttpServletResponse response) {
try {
// 查詢用戶信息
User user = userMapper.selectById(userId);
// 判斷用戶是否存在
if (user == null) {
throw new RuntimeException("用戶不存在,ID: " + userId);
}
// 查詢用戶識別信息
List<UserReg> userRegs= userRegService.list(new QueryWrapper<UserReg>().
eq("user_id", userId).orderByDesc("reg_time"));
Map<String, Object> data = buildReportData(user, userRegs);
//重點代碼
// 構(gòu)建模板數(shù)據(jù)
ClassPathResource resource = new ClassPathResource("templates/user_registration_template_simple.docx");
// 判斷模板文件是否存在
if (!resource.exists()) {
throw new RuntimeException("Word 模板文件不存在");
}
XWPFTemplate template = XWPFTemplate.compile(resource.getInputStream()).render(data);
// 設(shè)置響應(yīng)內(nèi)容類型為Word文檔
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
// 設(shè)置字符編碼為UTF-8(支持中文文件名)
response.setCharacterEncoding("UTF-8");
// 設(shè)置下載文件名(URL編碼,防止中文亂碼)
String fileName = URLEncoder.encode("用戶識別報告_" + user.getUsername() + ".docx", "UTF-8");
// 設(shè)置Content-Disposition響應(yīng)頭,告訴瀏覽器以附件形式下載文件
response.setHeader("Content-Disposition", "attachment; filename=" + fileName);
// 獲取HTTP響應(yīng)的輸出流
OutputStream out = response.getOutputStream();
// 將生成的Word文檔寫入輸出流
template.write(out);
// 刷新輸出流,確保所有數(shù)據(jù)都發(fā)送到客戶端
out.flush();
// 關(guān)閉輸出流
out.close();
// 關(guān)閉模板對象,釋放資源
template.close();
} catch (Exception e) {
throw new RuntimeException("生成用戶報告失敗", e);
}
}
private Map<String, Object> buildReportData(User user, List<UserReg> userRegs) {
// 創(chuàng)建數(shù)據(jù)映射容器
Map<String, Object> data = new HashMap<>();
// ==================== 填充用戶基本信息 ====================
// 這些數(shù)據(jù)對應(yīng)模板中的 {{username}}、{{email}} 等占位符
// 用戶名(必填)
data.put("username", user.getUsername());
// 郵箱(可能為空,提供默認值)
data.put("email", user.getEmail() != null ? user.getEmail() : "未填寫");
// 電話(可能為空,提供默認值)
data.put("phone", user.getPhone() != null ? user.getPhone() : "未填寫");
// 年齡(可能為空,需要轉(zhuǎn)換為字符串)
data.put("age", user.getAge() != null ? user.getAge().toString() : "未填寫");
// 創(chuàng)建時間(格式化為字符串)
data.put("createTime", user.getCreateTime() != null ?
user.getCreateTime().format(DATE_FORMATTER) : "未知");
// ==================== 構(gòu)建識別記錄表格 ====================
// 使用 POI-TL 的 TableRenderData 創(chuàng)建真實的Word表格
// 對應(yīng)模板中的 {{#regTable}} 占位符
// 創(chuàng)建行數(shù)據(jù)列表(包括表頭和數(shù)據(jù)行)
java.util.List<com.deepoove.poi.data.RowRenderData> allRows = new java.util.ArrayList<>();
// --- 第1步:創(chuàng)建表頭行 ---
// Rows.of(): 創(chuàng)建一行,參數(shù)為各列的內(nèi)容
// .center(): 設(shè)置單元格內(nèi)容居中對齊
// .bgColor("CCCCCC"): 設(shè)置背景色為灰色(十六進制顏色代碼)
// .create(): 生成 RowRenderData 對象
allRows.add(Rows.of("序號", "用戶ID", "識別時間")
.center() // 表頭文字居中
.bgColor("CCCCCC") // 表頭灰色背景
.create());
// --- 第2步:創(chuàng)建數(shù)據(jù)行 ---
// 遍歷所有識別記錄,每條記錄生成一行
for (int i = 0; i < userRegs.size(); i++) {
UserReg reg = userRegs.get(i);
// 創(chuàng)建一行數(shù)據(jù),包含3列:序號、用戶ID、識別時間
allRows.add(Rows.of(
String.valueOf(i + 1), // 第1列:序號(從1開始)
String.valueOf(reg.getUserId()), // 第2列:用戶ID
reg.getRegTime() != null ? // 第3列:識別時間
reg.getRegTime().format(DATE_FORMATTER) : "未知"
).create());
}
// --- 第3步:創(chuàng)建表格對象 ---
// Tables.of(): 將所有行數(shù)據(jù)組裝成表格
// allRows.toArray(): 將List轉(zhuǎn)換為數(shù)組
// .create(): 生成 TableRenderData 對象
com.deepoove.poi.data.TableRenderData table = Tables.of(
allRows.toArray(new com.deepoove.poi.data.RowRenderData[0])
).create();
// 將表格對象放入數(shù)據(jù)映射,鍵名為 "regTable"
// 對應(yīng)模板中的 {{#regTable}} 占位符
data.put("regTable", table);
// ==================== 填充統(tǒng)計信息 ====================
// 總識別次數(shù)(識別記錄的數(shù)量)
data.put("totalRegCount", userRegs.size());
// 報告生成時間(當前時間)
data.put("reportTime", LocalDateTime.now().format(DATE_FORMATTER));
// 返回完整的數(shù)據(jù)映射
return data;
}
}
public interface UserRegService extends IService<UserReg> {
}@Service
public class UserRegServiceImpl extends ServiceImpl<UserRegMapper, UserReg> implements UserRegService {
}
conntroller
@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/generate/{userId}")
public void generateReport(@PathVariable Long userId, HttpServletResponse response) {
userService.generateUserReport(userId, response);
}
}六、效果展示

七、擴展:顯示圖片
用戶表增加字段 與數(shù)據(jù)
ALTER TABLE user ADD COLUMN photo_path VARCHAR(500) COMMENT '用戶照片網(wǎng)絡(luò)路徑'; UPDATE user SET photo_path = 'https://example.com/photo.jpg';
為實體類增加成員
@Data
@TableName("user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(value = "id", type = IdType.AUTO)
private Long id;
private String username;
private String email;
private String phone;
private Integer age;
private String photoPath;
private LocalDateTime createTime;
private LocalDateTime updateTime;
private Integer deleted;
}創(chuàng)建模板標簽

實現(xiàn)核心方法代碼
private Map<String, Object> buildReportData(User user, List<UserReg> userRegs) {
// 創(chuàng)建數(shù)據(jù)映射容器
Map<String, Object> data = new HashMap<>();
// ==================== 填充用戶基本信息 ====================
// 這些數(shù)據(jù)對應(yīng)模板中的 {{username}}、{{email}} 等占位符
// 用戶名(必填)
data.put("username", user.getUsername());
// 郵箱(可能為空,提供默認值)
data.put("email", user.getEmail() != null ? user.getEmail() : "未填寫");
// 電話(可能為空,提供默認值)
data.put("phone", user.getPhone() != null ? user.getPhone() : "未填寫");
// 年齡(可能為空,需要轉(zhuǎn)換為字符串)
data.put("age", user.getAge() != null ? user.getAge().toString() : "未填寫");
// 創(chuàng)建時間(格式化為字符串)
data.put("createTime", user.getCreateTime() != null ?
user.getCreateTime().format(DATE_FORMATTER) : "未知");
// ==================== 處理用戶照片 ====================
// 如果用戶有照片路徑,則添加照片到報告中
if (user.getPhotoPath() != null && !user.getPhotoPath().isEmpty()) {
try {
// 使用 Pictures.ofUrl() 從網(wǎng)絡(luò)路徑加載圖片
// 參數(shù):圖片URL, 寬度(像素), 高度(像素)
PictureRenderData picture = Pictures.ofUrl(user.getPhotoPath())
.size(120, 160) // 設(shè)置照片尺寸:寬120px,高160px
.create();
data.put("userPhoto", picture);
} catch (Exception e) {
// 如果加載照片失敗,使用默認文本
data.put("userPhoto", "照片加載失敗");
}
} else {
// 如果沒有照片路徑,顯示默認文本
data.put("userPhoto", "暫無照片");
}
// ==================== 構(gòu)建識別記錄表格 ====================
// 使用 POI-TL 的 TableRenderData 創(chuàng)建真實的Word表格
// 對應(yīng)模板中的 {{#regTable}} 占位符
// 創(chuàng)建行數(shù)據(jù)列表(包括表頭和數(shù)據(jù)行)
java.util.List<com.deepoove.poi.data.RowRenderData> allRows = new java.util.ArrayList<>();
// --- 第1步:創(chuàng)建表頭行 ---
// Rows.of(): 創(chuàng)建一行,參數(shù)為各列的內(nèi)容
// .center(): 設(shè)置單元格內(nèi)容居中對齊
// .bgColor("CCCCCC"): 設(shè)置背景色為灰色(十六進制顏色代碼)
// .create(): 生成 RowRenderData 對象
allRows.add(Rows.of("序號", "用戶ID", "識別時間")
.center() // 表頭文字居中
.bgColor("CCCCCC") // 表頭灰色背景
.create());
// --- 第2步:創(chuàng)建數(shù)據(jù)行 ---
// 遍歷所有識別記錄,每條記錄生成一行
for (int i = 0; i < userRegs.size(); i++) {
UserReg reg = userRegs.get(i);
// 創(chuàng)建一行數(shù)據(jù),包含3列:序號、用戶ID、識別時間
allRows.add(Rows.of(
String.valueOf(i + 1), // 第1列:序號(從1開始)
String.valueOf(reg.getUserId()), // 第2列:用戶ID
reg.getRegTime() != null ? // 第3列:識別時間
reg.getRegTime().format(DATE_FORMATTER) : "未知"
).create());
}
// --- 第3步:創(chuàng)建表格對象 ---
// Tables.of(): 將所有行數(shù)據(jù)組裝成表格
// allRows.toArray(): 將List轉(zhuǎn)換為數(shù)組
// .create(): 生成 TableRenderData 對象
com.deepoove.poi.data.TableRenderData table = Tables.of(
allRows.toArray(new com.deepoove.poi.data.RowRenderData[0])
).create();
// 將表格對象放入數(shù)據(jù)映射,鍵名為 "regTable"
// 對應(yīng)模板中的 {{#regTable}} 占位符
data.put("regTable", table);
// ==================== 填充統(tǒng)計信息 ====================
// 總識別次數(shù)(識別記錄的數(shù)量)
data.put("totalRegCount", userRegs.size());
// 報告生成時間(當前時間)
data.put("reportTime", LocalDateTime.now().format(DATE_FORMATTER));
// 返回完整的數(shù)據(jù)映射
return data;
}實現(xiàn)效果

八、關(guān)于模板標簽說明
{{變量名}}- 用于文本{{@變量名}}- 用于圖片{{#變量名}}- 用于表格{{*變量名}}- 用于列表*
到此這篇關(guān)于SpringBoot利用模板實現(xiàn)自動生成Word合同的功能的文章就介紹到這了,更多相關(guān)SpringBoot模板生成Word合同內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis-Plus使用updateById()、update()將字段更新為null
本文主要介紹了Mybatis-Plus使用updateById()、update()將字段更新為null,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
一步步教你如何使用Java實現(xiàn)WebSocket
websocket協(xié)議是基于TCP的一種新的網(wǎng)絡(luò)協(xié)議,它實現(xiàn)了瀏覽器與服務(wù)器的全雙工通訊-允許服務(wù)器主動發(fā)起信息個客戶端,websocket是一種持久協(xié)議,http是非持久協(xié)議,下面這篇文章主要給大家介紹了關(guān)于如何使用Java實現(xiàn)WebSocket的相關(guān)資料,需要的朋友可以參考下2023-05-05
java多線程編程之使用thread類創(chuàng)建線程
在Java中創(chuàng)建線程有兩種方法:使用Thread類和使用Runnable接口。在使用Runnable接口時需要建立一個Thread實例2014-01-01
SpringMVC接收java.util.Date類型數(shù)據(jù)的2種方式小結(jié)
這篇文章主要介紹了使用SpringMVC接收java.util.Date類型數(shù)據(jù)的2種方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
SpringBoot實現(xiàn)連接nacos并支持多環(huán)境部署
這篇文章主要介紹了SpringBoot實現(xiàn)連接nacos并支持多環(huán)境部署方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06

