Spring Boot 一行代码生成各种复杂的Word文档

1. 简介

在项目开发中,生成Word文档常用于自动化生成合同、报告、数据汇总等场景,可大幅提升效率并减少人工错误。通过模板引擎(如poi-tl)结合动态数据,能快速生成格式统一、内容灵活的文档,满足业务对标准化和个性化的双重需求。

本篇文章我们将介绍一个强劲的开源组件poi-tl,该组件能超级超级简单的根据模板生成word文档。

什么是POI-TL?

poi-tl 是一款 Word 模板引擎,可根据 Word 模板和数据生成新文档。Word 模板具有丰富的样式。poi-tl 会在生成的文档中完美保留模板中的样式。

poi-tl 是一款“无逻辑”模板引擎。它没有复杂的控制结构和变量赋值,只有标签。某些标签可替换为文本、图片、表格等,某些标签会隐藏部分文档内容,而其他标签则会对一系列文档内容进行循环。

Spring Boot 一行代码生成各种复杂的Word文档

2.实战案例

2.1 环境准备

引入依赖

<dependency>
  <groupId>com.deepoove</groupId>
  <artifactId>poi-tl</artifactId>
  <version>1.12.2</version>
</dependency>

准备Word模板如下

Spring Boot 一行代码生成各种复杂的Word文档

准备数据模型

public class PersonalInfo {
  // 学院
  private String college;
  // 专业
  private String major;
  // 姓名
  private String name;
  // 性别
  private String gender;
  // 民族
  private String nation;
  // 出生年月
  private String birthDate;
  // 籍贯
  private String nativePlace;
  // 现所在地
  private String currentLocation;
  // 学历
  private String education;
  // 政治面貌
  private String politicalStatus;
  // 婚姻状况
  private String maritalStatus;
  // 就业意向
  private String employmentIntention;
  // 兴趣爱好
  private String hobbies;
  // 自我评价
  private String selfEvaluation;
  // 住址
  private String address;
  // 联系电话
  private String phoneNumber;
  // 电子邮箱
  private String email;
  // 头像
  private String img ;
}

2.2 根据模板生成文档

PersonalInfo person = new PersonalInfo(
    "计算机学院",
    "软件工程",
    "张三",
    "男",
    "汉族",
    "1995年08月",
    "湖南长沙",
    "北京",
    "本科",
    "共青团员",
    "未婚",
    "希望从事软件开发相关工作",
    "编程、阅读、篮球",
    "本人学习能力强,有良好的团队合作精神和沟通能力",
    "北京市海淀区XX街道XX号",
    "13812345678",
    "zhangsan@example.com",
    "https://img1.baidu.com/it/u=2709364071,2311129161&fm=253&app=138&f=JPEG?w=800&h=800") ;
ClassPathResource resource = new ClassPathResource("templates/person_template.docx") ;
XWPFTemplate.compile(resource.getInputStream())
  .render(person)
  .writeToFile("e:/person.docx") ;

注意,我们上面的模板中对于图片使用的占位符使用了 '@'。

最终生成的文档如下:

Spring Boot 一行代码生成各种复杂的Word文档

2.3 生成表格数据

表格标签以#开头,例如{{#table}},它将被渲染为一个Word表格,具有N行N列。N的值取决于表格标签的数据。如下示例:

模板定义

Spring Boot 一行代码生成各种复杂的Word文档

代码实现

Map<String, Object> dataModal = new HashMap<>() ;
dataModal.put("hobbies", Tables.of(new String[][] {
  new String[] {"烹饪", "绘画", "电影", "徒步", "编程"}
}).border(BorderStyle.DEFAULT).create());
XWPFTemplate.compile(resource.getInputStream())
  .render(dataModal)
  .writeToFile("e:/person.docx") ;

生成文档

Spring Boot 一行代码生成各种复杂的Word文档

2.4 生成列表

列表标签对应于Word的符号列表或编号列表,以*开头,例如{{*list}}。

模板定义

Spring Boot 一行代码生成各种复杂的Word文档

代码实现

ClassPathResource resource = new ClassPathResource("templates/person_template.docx") ;
Map<String, Object> dataModal = new HashMap<>() ;
dataModal.put("webflux_advantage", Numberings.create(
    "非阻塞异步处理,提升并发能力", "支持背压(Backpressure)机制", 
    "活的运行环境:支持 Netty 和 Servlet 容器", " 天然适合实时和流式应用")) ;
XWPFTemplate.compile(resource.getInputStream())
  .render(dataModal)
  .writeToFile("e:/person.docx") ;

生成文档

Spring Boot 一行代码生成各种复杂的Word文档

2.4 区块

一个区块(section)由前后两个标签组成,起始标签以 ? 标识,结束标签以 / 标识,例如,{{?section}} 是 sections 区块的起始标签,{{/section}} 是结束标签,其中 section 是这个区块的名称。

在处理一系列文档元素时,区块超级有用。位于区块内的文档元素(文本、图片、表格等)可以根据区块的取值被渲染零次、一次或 N 次。

  • 假值或空集合

如果区块的值为 null、false 或空集合,则位于该区块内的所有文档元素都将不会被显示,这与 if 语句条件为假的情况类似。

模板定义

Spring Boot 一行代码生成各种复杂的Word文档

代码实现

ClassPathResource resource = new ClassPathResource("templates/person_template.docx") ;
Map<String, Object> dataModal = new HashMap<>() ;
dataModal.put("isVIP", false) ;
dataModal.put("hasDiscount", true) ;
dataModal.put("discountAmount", 666) ;
XWPFTemplate.compile(resource.getInputStream())
  .render(dataModal)
  .writeToFile("e:/person.docx") ;

生成文档

Spring Boot 一行代码生成各种复杂的Word文档

  • 非空集合

如果区块的值为非空集合,则该区块内的文档元素将根据集合的大小循环一次或 N 次,这与 foreach 语法类似。

模板定义

Spring Boot 一行代码生成各种复杂的Word文档

代码实现

// 准备数据模型
public record Tech(Long id, String tech, String version) {}


dataModal.put("techStack", List.of(
    new Tech(1L, "Spring Boot", "4.0.0"), 
    new Tech(2L, "Spring Cloud", "2025.0.1"), 
    new Tech(3L, "Spring MVC", "3.0.1"))) ;
XWPFTemplate.compile(resource.getInputStream())
  .render(dataModal)
  .writeToFile("e:/person.docx") ;

生成文档

Spring Boot 一行代码生成各种复杂的Word文档

2.5 生成图表

通过在模板中添加各种图表后,进行简单的标签文本设置可以动态的生成各种图表(Word中支持的)。

模板定义

Spring Boot 一行代码生成各种复杂的Word文档

选择了你想要的图表后来,接下来最重大的一步设置标签:

Spring Boot 一行代码生成各种复杂的Word文档

代码实现

ChartMultiSeriesRenderData chart = Charts
    .ofMultiSeries("ChartTitle", new String[] { "中文", "English" })
    .addSeries("地区", new Double[] { 15.0, 6.0 })
    .addSeries("语言", new Double[] { 223.0, 119.0 })
    .create();
dataModal.put("barChart", chart);
XWPFTemplate.compile(resource.getInputStream())
  .render(dataModal)
  .writeToFile("e:/person.docx") ;

生成文档

Spring Boot 一行代码生成各种复杂的Word文档

2.6 Spring表达式支持

SpEL表达式,支持在运行时查询和操作对象图,可作为独立组件使用,接下来,我们基于Spring Boot环境使用。

模板定义

Spring Boot 一行代码生成各种复杂的Word文档

代码实现

dataModal.put("name", "pack_xg") ;
dataModal.put("empty", false) ;
dataModal.put("sex", true) ;
dataModal.put("time", new Date()) ;
dataModal.put("price", 548378389) ;
dataModal.put("dogs", new Dog[] {new Dog("汪汪", 2), new Dog("狗蛋", 10)}) ;
dataModal.put("localDate", LocalDate.now()) ;


ConfigureBuilder builder = Configure.builder();
builder.useSpringEL() ;
XWPFTemplate.compile(resource.getInputStream(), builder.build())
  .render(dataModal)
  .writeToFile("e:/person.docx") ;

代码中,我们通过builder#useSpringEL方法开启SpEL表达式的支持。

生成文档

Spring Boot 一行代码生成各种复杂的Word文档

2.7 生成文档并下载

接下来,我们通过Controller接口生成文档并提供下载功能。

private final HttpServletResponse response ;
@GetMapping("/download")
public void download() throws Exception {
  PersonalInfo person = new PersonalInfo(
      "计算机学院",
      "软件工程",
      "张三",
      "男",
      "汉族",
      "1995年08月",
      "湖南长沙",
      "北京",
      "本科",
      "共青团员",
      "未婚",
      "希望从事软件开发相关工作",
      "编程、阅读、篮球",
      "本人学习能力强,有良好的团队合作精神和沟通能力",
      "北京市海淀区XX街道XX号",
      "13812345678",
      "zhangsan@example.com",
      "https://img1.baidu.com/it/u=2709364071,2311129161&fm=253&app=138&f=JPEG?w=800&h=800") ;


  ClassPathResource resource = new ClassPathResource("templates/person_template.docx") ;
  Map<String, Object> dataModal = new HashMap<>() ;
  dataModal.put("person", person) ;
  dataModal.put("hobbies", Tables.of(new String[][] {
    new String[] {"烹饪", "绘画", "电影", "徒步", "编程"}
  }).border(BorderStyle.DEFAULT).create());
  dataModal.put("webflux_advantage", Numberings.create(
      "非阻塞异步处理,提升并发能力", "支持背压(Backpressure)机制", 
      "活的运行环境:支持 Netty 和 Servlet 容器", " 天然适合实时和流式应用")) ;
  dataModal.put("isVIP", false) ;
  dataModal.put("hasDiscount", true) ;
  dataModal.put("discountAmount", 666) ;


  dataModal.put("techStack", List.of(
      new Tech(1L, "Spring Boot", "4.0.0"), 
      new Tech(2L, "Spring Cloud", "2025.0.1"), 
      new Tech(3L, "Spring MVC", "3.0.1"))) ;


  ChartMultiSeriesRenderData chart = Charts
      .ofMultiSeries("ChartTitle", new String[] { "中文", "English" })
      .addSeries("地区", new Double[] { 15.0, 6.0 })
      .addSeries("语言", new Double[] { 223.0, 119.0 })
      .create();
  dataModal.put("barChart", chart);


  response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
  response.setCharacterEncoding("utf-8");
  String fileName = URLEncoder.encode("个人简历.docx", "UTF-8").replaceAll("\+", "%20");
  response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=utf-8''" + fileName);


  XWPFTemplate.compile(resource.getInputStream())
    .render(dataModal)
    .write(response.getOutputStream()) ;


  response.flushBuffer() ;
}
© 版权声明

相关文章

6 条评论

您必须登录才能参与评论!
立即登录