赞
踩
基于模板生成 Word 文档是一种常见的需求,特别是在需要生成带有固定格式和动态数据的报表或文档时,常见的使用场景有:
(1)生成标准化的报告,如业务报告等;
(2)合同生成,根据不同客户信息自动生成合同文档;
(3)证书制作,如制作证书或奖状等;
(4)表单填充,将表单数据填充到预定义格式的 Word 模板中,以生成个性化的表单;
(5)自定义文档,根据用户输入或者系统数据生成自定义格式的文档。
(1)准备模板文件:首先需要创建模板文件,其中包含文档的结构、样式和占位符(如变量或标记);
(2)技术选型:使用 Java 中的一些库来处理模板文件,比如 Apache POI、Docx4j、Freemarker、Thymeleaf 等。本篇文章以使用 Freemarker 模板引擎为例;
(3)填充模板数据:准备好需要填充的数据,在 Java 中读取模板文件,将需要填充的数据替换掉模板中的占位符;
(4)生成新文档:将填充完数据的文件保存或输出为新的 Word 文档。
上述操作得到的 xml 文件中的数据基本都糅合在一行里,不方便阅读和更改,可以通过在线工具进行格式化。
注:如果只是需要查看 xml 文件内容,可以直接选择浏览器打开,主流的浏览器基本都会对 xml 进行格式化显示
在 Word 中使用变量时需要确保在生成 xml 文件后变量是完整的。因为在 Word 的 Open XML 格式设计中,为了提供对文档内容和样式的细致控制,即使在视觉上看起来连续的文本也可能由多个元素构成。例如:
Word 拼写检查
Word 拼写错误时会有下划曲线,如果放任不管的话,生成的 xml 里变量就会被拆分开,可以右键忽略拼写检查解决。
Word 修订标识
Word 文档有修订标识符(Revision Identifier,RSID)用来标识每个修订片段。例如我在某次编辑中之改动了变量 ${cardNum} 的一部分,如下图所示,生成的 xml 里的变量就会被修订标识给拆分开。所以修改变量时需要整个一起改,避免在 xml 中变量被拆分。
除了上述例子之外,Word 文档还有其他情况也会将变量给拆分开,因此建议 xml 模板生成后,检查一下各个变量的格式是否正确,不正确的也可以直接参照 xml 格式手动编辑一下,总之需要确保变量名在 xml 模板文件中是一个整体,例如:${name}
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.33</version>
</dependency>
xml 文件需要放在项目能读取到的地方,这里直接放到项目中的 resources 目录下的 templates 文件夹下,确保加载模板时能正常读取到即可
package com.demo.wordex.controller; import freemarker.cache.ClassTemplateLoader; import freemarker.template.Configuration; import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import java.io.BufferedWriter; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @Slf4j @RestController @RequestMapping("/test") public class TestController { /** * 基于模板生成 Word 文档 * * @param response response */ @GetMapping("/generate_word") public void generateWord(HttpServletResponse response){ Map<String, String> templateParam = new HashMap<>(5); templateParam.put("name", "小明"); templateParam.put("sex", "男"); templateParam.put("age", "18"); templateParam.put("birthday", "2024-07-22"); templateParam.put("cardNum", "440000000000001111"); try { // 设置 HTTP 响应的内容类型为 Microsoft Word 文档 response.setContentType("application/msword"); // 设置响应字符编码为 UTF-8 response.setCharacterEncoding("utf-8"); // 使用 URLEncoder 对文件名进行编码,以防止中文文件名在不同浏览器和操作系统下出现乱码问题 String filename = URLEncoder.encode("测试Word_" + System.currentTimeMillis(), "utf-8"); // 设置响应头,指定文档将以附件的形式下载,并定义文件名 response.setHeader("Content-disposition", "attachment;filename=" + filename + ".doc"); // 创建 Freemarker 的 Configuration 对象,设置默认的不兼容改进选项 Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); configuration.setDefaultEncoding("utf-8"); // 设置模板加载器,加载模板文件 configuration.setTemplateLoader(new ClassTemplateLoader(getClass(), "/templates")); Template t = configuration.getTemplate("generate_word_test.xml", "utf-8"); // 创建 Writer 对象,用于将生成的文档写到输出流中,缓存区大小设为 10KB Writer out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8), 10240); // 将模型数据与模板结合生成 Word 文档,写入到 Writer 对象中 t.process(templateParam, out); out.close(); } catch (Exception e) { log.info("生成Word文档异常,异常原因:{}", e.getMessage(), e); throw new RuntimeException("生成Word文档异常,异常原因:" + e.getMessage()); } } }
测试结果
localhost:8090/test/generate_word
下载 Word 文档package com.demo.wordex.utils; import freemarker.cache.ClassTemplateLoader; import freemarker.template.Configuration; import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletResponse; import java.io.BufferedWriter; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @Slf4j @Component public class WordUtil { /** * 基于模板生成 Word 文档 * * @param response response * @param basePackagePath resources 目录下模板所在包路径 * @param templateFileName 模板文件名 * @param templateParam 模板参数 * @param fileName 文件名 */ public void generate(HttpServletResponse response, String basePackagePath, String templateFileName, Object templateParam, String fileName) { try { // 设置 HTTP 响应的内容类型为 Microsoft Word 文档 response.setContentType("application/msword"); // 设置响应字符编码为 UTF-8 response.setCharacterEncoding("utf-8"); // 使用 URLEncoder 对文件名进行编码,以防止中文文件名在不同浏览器和操作系统下出现乱码问题 String filename = URLEncoder.encode(fileName + "_" + System.currentTimeMillis(), "utf-8"); // 设置响应头,指定文档将以附件的形式下载,并定义文件名 response.setHeader("Content-disposition", "attachment;filename=" + filename + ".doc"); // 创建 Freemarker 的 Configuration 对象,设置默认的不兼容改进选项 Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS); configuration.setDefaultEncoding("utf-8"); // 设置模板加载器,加载模板文件 configuration.setTemplateLoader(new ClassTemplateLoader(getClass(), basePackagePath)); Template t = configuration.getTemplate(templateFileName, "utf-8"); // 创建 Writer 对象,用于将生成的文档写到输出流中,缓存区大小设为 10KB Writer out = new BufferedWriter(new OutputStreamWriter(response.getOutputStream(), StandardCharsets.UTF_8), 10240); // 将模型数据与模板结合生成 Word 文档,写入到 Writer 对象中 t.process(templateParam, out); out.close(); } catch (Exception e) { log.info("基于模板{}生成Word文档异常,异常原因:{}", templateFileName, e.getMessage(), e); throw new RuntimeException("生成Word文档异常,异常原因:" + e.getMessage()); } } }
package com.demo.wordex.controller; import com.demo.wordex.utils.WordUtil; import freemarker.cache.ClassTemplateLoader; import freemarker.template.Configuration; import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.BufferedWriter; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @Slf4j @RestController @RequestMapping("/test") public class TestController { @Resource private WordUtil wordUtil; /** * 基于模板生成 Word 文档(基于示例 1 提取 Word 工具类) * * @param response response */ @GetMapping("/generate_word_2") public void generateWord2(HttpServletResponse response) { Map<String, String> templateParam = new HashMap<>(5); templateParam.put("name", "小明"); templateParam.put("sex", "男"); templateParam.put("age", "18"); templateParam.put("birthday", "2024-07-22"); templateParam.put("cardNum", "440000000000001111"); wordUtil.generate(response, "/templates", "generate_word_test.xml", templateParam, "测试Word"); } }
创建用户信息类
package com.demo.wordex.model; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @NoArgsConstructor @AllArgsConstructor public class UserInfo { private String name; private String sex; private String age; private String birthday; private String cardNum; }
使用对象入参
package com.demo.wordex.controller; import com.demo.wordex.model.UserInfo; import com.demo.wordex.utils.WordUtil; import freemarker.cache.ClassTemplateLoader; import freemarker.template.Configuration; import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.BufferedWriter; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @Slf4j @RestController @RequestMapping("/test") public class TestController { @Resource private WordUtil wordUtil; /** * 基于模板生成 Word 文档(使用对象入参) * * @param response response */ @GetMapping("/generate_word_3") public void generateWord3(HttpServletResponse response) { UserInfo userInfo = new UserInfo(); userInfo.setName("小明"); userInfo.setSex("男"); userInfo.setAge("18"); userInfo.setBirthday("2024-07-22"); userInfo.setCardNum("440000000000001111"); wordUtil.generate(response, "/templates", "generate_word_test.xml", userInfo, "测试Word"); } }
示例
<#list userList as user>
...
${user.name}
${user.sex}
${user.age}
...
</#list>
xml 节选
<#list userList as user> <w:tr w:rsidR="00D062FB" w14:paraId="49CA5E91" w14:textId="77777777" w:rsidTr="00827A33"> <w:tc> <w:tcPr> <w:tcW w:w="1405" w:type="dxa"/> </w:tcPr> <w:p w14:paraId="0CBA5021" w14:textId="4EA7152F" w:rsidR="00D062FB" w:rsidRDefault="00886BBD" w:rsidP="00D062FB"> <w:pPr> <w:jc w:val="center"/> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> </w:pPr> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> <w:t>${user.name}</w:t> </w:r> </w:p> </w:tc> <w:tc> <w:tcPr> <w:tcW w:w="1161" w:type="dxa"/> </w:tcPr> <w:p w14:paraId="62A0C0C7" w14:textId="18F50C90" w:rsidR="00D062FB" w:rsidRDefault="00827A33" w:rsidP="00D062FB"> <w:pPr> <w:jc w:val="center"/> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> </w:pPr> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> <w:t>${user.sex}</w:t> </w:r> </w:p> </w:tc> <w:tc> <w:tcPr> <w:tcW w:w="1209" w:type="dxa"/> </w:tcPr> <w:p w14:paraId="4C107460" w14:textId="5F88AA2B" w:rsidR="00D062FB" w:rsidRDefault="00827A33" w:rsidP="00D062FB"> <w:pPr> <w:jc w:val="center"/> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> </w:pPr> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> <w:t>${user.age}</w:t> </w:r> </w:p> </w:tc> <w:tc> <w:tcPr> <w:tcW w:w="1465" w:type="dxa"/> </w:tcPr> <w:p w14:paraId="2E4D57CD" w14:textId="160F2EF2" w:rsidR="00D062FB" w:rsidRDefault="00827A33" w:rsidP="00D062FB"> <w:pPr> <w:jc w:val="center"/> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> </w:pPr> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> <w:t>${user.birthday}</w:t> </w:r> </w:p> </w:tc> <w:tc> <w:tcPr> <w:tcW w:w="3056" w:type="dxa"/> </w:tcPr> <w:p w14:paraId="682E0D8A" w14:textId="075D0180" w:rsidR="00D062FB" w:rsidRDefault="00827A33" w:rsidP="00D062FB"> <w:pPr> <w:jc w:val="center"/> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> </w:pPr> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> <w:t>${user.cardNum}</w:t> </w:r> </w:p> </w:tc> </w:tr> </#list>
package com.demo.wordex.controller; import com.demo.wordex.model.UserInfo; import com.demo.wordex.utils.WordUtil; import freemarker.cache.ClassTemplateLoader; import freemarker.template.Configuration; import freemarker.template.Template; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.BufferedWriter; import java.io.OutputStreamWriter; import java.io.Writer; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @Slf4j @RestController @RequestMapping("/test") public class TestController { @Resource private WordUtil wordUtil; /** * 基于模板生成 Word 文档(生成动态列表) * * @param response response */ @GetMapping("/generate_dynamic_word") public void generateDynamicWord(HttpServletResponse response) { List<UserInfo> userList = new ArrayList<>(); userList.add(new UserInfo("小明", "男", "18", "2024-07-22", "440000000000001111")); userList.add(new UserInfo("小红", "女", "20", "2024-07-26", "440000000000002222")); userList.add(new UserInfo("小白", "男", "17", "2024-07-24", "440000000000003333")); userList.add(new UserInfo("小蓝", "女", "19", "2024-07-23", "440000000000004444")); userList.add(new UserInfo("小黑", "男", "18", "2024-07-21", "440000000000005555")); Map<String, Object> templateParam = new HashMap<>(5); templateParam.put("date", LocalDate.now()); templateParam.put("total", userList.size()); templateParam.put("userList", userList); wordUtil.generate(response, "/templates", "dynamic_userlist.xml", templateParam, "测试动态列表Word"); } }
localhost:8090/test/generate_dynamic_word
下载 Word 文档Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。