SpringBoot利用模板实现自动生成Word合同的功能

 更新时间:2025年12月01日 09:29:23   作者:众纳  
这篇文章主要为大家详细介绍了SpringBoot如何利用模板实现自动生成Word合同的功能,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下

前言

想像一下,我们做了一个Sass模式下的人事合同管理系统,每公司的合同都不是统一的,所以每家公司都有一个合同模板;要求实现下载员工合同的功能。

基于以上需求有两个解决方案:1.我博客中提到过《Spring Boot 手把手实现PDF功能

2.就是通过poi-tl实现word内容填充,而本文就重点介绍它的基本实现。

一、目标

两个表:一个是用户表、一个用户识别表,通过用户ID,输入word文件中包含用户的基本信息、用户的识别记录。

二、数据库表

三、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 模板导入系统 API")
                        .description("基于 Spring Boot + POI-TL 的 Word 报告生成系统")
                        .version("1.0.0")
                        .contact(new Contact()
                                .name("开发团队")
                                .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")));
    }
}

五、实现

模板文件内容

主程序

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

            //重点代码
            // 构建模板数据
            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);
            // 设置响应内容类型为Word文档
            response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
            // 设置字符编码为UTF-8(支持中文文件名)
            response.setCharacterEncoding("UTF-8");
            // 设置下载文件名(URL编码,防止中文乱码)
            String fileName = URLEncoder.encode("用户识别报告_" + user.getUsername() + ".docx", "UTF-8");
            // 设置Content-Disposition响应头,告诉浏览器以附件形式下载文件
            response.setHeader("Content-Disposition", "attachment; filename=" + fileName);

            // 获取HTTP响应的输出流
            OutputStream out = response.getOutputStream();
            // 将生成的Word文档写入输出流
            template.write(out);
            // 刷新输出流,确保所有数据都发送到客户端
            out.flush();
            // 关闭输出流
            out.close();
            // 关闭模板对象,释放资源
            template.close();

        } catch (Exception e) {
            throw new RuntimeException("生成用户报告失败", e);
        }
    }
    private Map<String, Object> buildReportData(User user, List<UserReg> userRegs) {
        // 创建数据映射容器
        Map<String, Object> data = new HashMap<>();

        // ==================== 填充用户基本信息 ====================
        // 这些数据对应模板中的 {{username}}、{{email}} 等占位符

        // 用户名(必填)
        data.put("username", user.getUsername());

        // 邮箱(可能为空,提供默认值)
        data.put("email", user.getEmail() != null ? user.getEmail() : "未填写");

        // 电话(可能为空,提供默认值)
        data.put("phone", user.getPhone() != null ? user.getPhone() : "未填写");

        // 年龄(可能为空,需要转换为字符串)
        data.put("age", user.getAge() != null ? user.getAge().toString() : "未填写");

        // 创建时间(格式化为字符串)
        data.put("createTime", user.getCreateTime() != null ?
                user.getCreateTime().format(DATE_FORMATTER) : "未知");

        // ==================== 构建识别记录表格 ====================
        // 使用 POI-TL 的 TableRenderData 创建真实的Word表格
        // 对应模板中的 {{#regTable}} 占位符

        // 创建行数据列表(包括表头和数据行)
        java.util.List<com.deepoove.poi.data.RowRenderData> allRows = new java.util.ArrayList<>();

        // --- 第1步:创建表头行 ---
        // Rows.of(): 创建一行,参数为各列的内容
        // .center(): 设置单元格内容居中对齐
        // .bgColor("CCCCCC"): 设置背景色为灰色(十六进制颜色代码)
        // .create(): 生成 RowRenderData 对象
        allRows.add(Rows.of("序号", "用户ID", "识别时间")
                .center()           // 表头文字居中
                .bgColor("CCCCCC")  // 表头灰色背景
                .create());

        // --- 第2步:创建数据行 ---
        // 遍历所有识别记录,每条记录生成一行
        for (int i = 0; i < userRegs.size(); i++) {
            UserReg reg = userRegs.get(i);

            // 创建一行数据,包含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步:创建表格对象 ---
        // Tables.of(): 将所有行数据组装成表格
        // allRows.toArray(): 将List转换为数组
        // .create(): 生成 TableRenderData 对象
        com.deepoove.poi.data.TableRenderData table = Tables.of(
                allRows.toArray(new com.deepoove.poi.data.RowRenderData[0])
        ).create();

        // 将表格对象放入数据映射,键名为 "regTable"
        // 对应模板中的 {{#regTable}} 占位符
        data.put("regTable", table);

        // ==================== 填充统计信息 ====================
        // 总识别次数(识别记录的数量)
        data.put("totalRegCount", userRegs.size());

        // 报告生成时间(当前时间)
        data.put("reportTime", LocalDateTime.now().format(DATE_FORMATTER));

        // 返回完整的数据映射
        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);
    }


}

六、效果展示

七、扩展:显示图片

用户表增加字段 与数据

ALTER TABLE user ADD COLUMN photo_path VARCHAR(500) COMMENT '用户照片网络路径';


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;

}

创建模板标签

实现核心方法代码

private Map<String, Object> buildReportData(User user, List<UserReg> userRegs) {
        // 创建数据映射容器
        Map<String, Object> data = new HashMap<>();

        // ==================== 填充用户基本信息 ====================
        // 这些数据对应模板中的 {{username}}、{{email}} 等占位符

        // 用户名(必填)
        data.put("username", user.getUsername());

        // 邮箱(可能为空,提供默认值)
        data.put("email", user.getEmail() != null ? user.getEmail() : "未填写");

        // 电话(可能为空,提供默认值)
        data.put("phone", user.getPhone() != null ? user.getPhone() : "未填写");

        // 年龄(可能为空,需要转换为字符串)
        data.put("age", user.getAge() != null ? user.getAge().toString() : "未填写");

        // 创建时间(格式化为字符串)
        data.put("createTime", user.getCreateTime() != null ?
                user.getCreateTime().format(DATE_FORMATTER) : "未知");

        // ==================== 处理用户照片 ====================
        // 如果用户有照片路径,则添加照片到报告中
        if (user.getPhotoPath() != null && !user.getPhotoPath().isEmpty()) {
            try {
                // 使用 Pictures.ofUrl() 从网络路径加载图片
                // 参数:图片URL, 宽度(像素), 高度(像素)
                PictureRenderData picture = Pictures.ofUrl(user.getPhotoPath())
                        .size(120, 160)  // 设置照片尺寸:宽120px,高160px
                        .create();
                data.put("userPhoto", picture);
            } catch (Exception e) {
                // 如果加载照片失败,使用默认文本
                data.put("userPhoto", "照片加载失败");
            }
        } else {
            // 如果没有照片路径,显示默认文本
            data.put("userPhoto", "暂无照片");
        }

        // ==================== 构建识别记录表格 ====================
        // 使用 POI-TL 的 TableRenderData 创建真实的Word表格
        // 对应模板中的 {{#regTable}} 占位符

        // 创建行数据列表(包括表头和数据行)
        java.util.List<com.deepoove.poi.data.RowRenderData> allRows = new java.util.ArrayList<>();

        // --- 第1步:创建表头行 ---
        // Rows.of(): 创建一行,参数为各列的内容
        // .center(): 设置单元格内容居中对齐
        // .bgColor("CCCCCC"): 设置背景色为灰色(十六进制颜色代码)
        // .create(): 生成 RowRenderData 对象
        allRows.add(Rows.of("序号", "用户ID", "识别时间")
                .center()           // 表头文字居中
                .bgColor("CCCCCC")  // 表头灰色背景
                .create());

        // --- 第2步:创建数据行 ---
        // 遍历所有识别记录,每条记录生成一行
        for (int i = 0; i < userRegs.size(); i++) {
            UserReg reg = userRegs.get(i);

            // 创建一行数据,包含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步:创建表格对象 ---
        // Tables.of(): 将所有行数据组装成表格
        // allRows.toArray(): 将List转换为数组
        // .create(): 生成 TableRenderData 对象
        com.deepoove.poi.data.TableRenderData table = Tables.of(
                allRows.toArray(new com.deepoove.poi.data.RowRenderData[0])
        ).create();

        // 将表格对象放入数据映射,键名为 "regTable"
        // 对应模板中的 {{#regTable}} 占位符
        data.put("regTable", table);

        // ==================== 填充统计信息 ====================
        // 总识别次数(识别记录的数量)
        data.put("totalRegCount", userRegs.size());

        // 报告生成时间(当前时间)
        data.put("reportTime", LocalDateTime.now().format(DATE_FORMATTER));

        // 返回完整的数据映射
        return data;
    }

实现效果

八、关于模板标签说明

  • {{变量名}} - 用于文本
  • {{@变量名}} - 用于图片
  • {{#变量名}} - 用于表格
  • {{*变量名}} - 用于列表*

到此这篇关于SpringBoot利用模板实现自动生成Word合同的功能的文章就介绍到这了,更多相关SpringBoot模板生成Word合同内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • idea每次修改代码都需要重新install的问题

    idea每次修改代码都需要重新install的问题

    这篇文章主要介绍了idea每次修改代码都需要重新install的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-08-08
  • spring-boot-starter-thymeleaf加载外部html文件方式

    spring-boot-starter-thymeleaf加载外部html文件方式

    本文介绍了在SpringMVC中使用Thymeleaf模板引擎加载外部HTML文件的方法,以及在Spring Boot中使用Thymeleaf的基本步骤,包括引入依赖、创建Controller、创建HTML文件、参数化访问、热加载和热更新文件
    2025-02-02
  • java 对象实例化过程中的多态特性解析

    java 对象实例化过程中的多态特性解析

    这篇文章主要介绍了java 对象实例化过程中的多态特性解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • JAVA 格式化日期、时间的方法

    JAVA 格式化日期、时间的方法

    这篇文章主要介绍了JAVA 格式化日期、时间的方法,文中讲解非常细致,代码帮助大家更好的理解和学习,感兴趣的朋友可以了解下
    2020-06-06
  • java面试LruCache 和 LinkedHashMap及算法实现

    java面试LruCache 和 LinkedHashMap及算法实现

    这篇文章主要为大家介绍了java面试LruCache 和 LinkedHashMap及算法实现示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-02-02
  • springboot连接neo4j报错的解决方案

    springboot连接neo4j报错的解决方案

    这篇文章主要介绍了springboot连接neo4j报错的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-02-02
  • PowerShell用户认证Function实例代码

    PowerShell用户认证Function实例代码

    这篇文章主要介绍了PowerShell用户认证Function的资料,并附实例代码,帮助大家学习理解,有需要的小伙伴可以参考下
    2016-09-09
  • Java上转型和下转型对象

    Java上转型和下转型对象

    这篇文章给大家讲述了Java上转型和下转型对象的详细用法以及相关的代码分享,有兴趣的朋友可以学习下。
    2018-03-03
  • 详解SpringBoot使用RedisTemplate操作Redis的5种数据类型

    详解SpringBoot使用RedisTemplate操作Redis的5种数据类型

    本文主要介绍了SpringBoot使用RedisTemplate操作Redis的5种数据类型,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-03-03
  • Java中遍历数组使用foreach循环还是for循环?

    Java中遍历数组使用foreach循环还是for循环?

    这篇文章主要介绍了Java中遍历数组使用foreach循环还是for循环?本文着重讲解for语句的语法并给出使用实例,同时总结出尽量使用foreach语句遍历数组,需要的朋友可以参考下
    2015-06-06

最新评论