当遇到要生成一个word文档(证明文件等)的需求时,就可以考虑使用word模板生成.doc和.wps文件
一、需求
1、生成如下这样的订单数据.doc文件,红框部分是变化的,其余部分是固定的
2、生成如下这样的书籍列表,书的个数不固定是动态的。
二、使用Docx4j实现
1、引入依赖- <dependency>
- <groupId>org.docx4j</groupId>
- docx4j-core</artifactId>
- <version>8.3.0</version>
- </dependency>
- <dependency>
- <groupId>org.docx4j</groupId>
- docx4j-JAXB-ReferenceImpl</artifactId>
- <version>8.3.0</version>
- </dependency>
- <dependency>
- <groupId>org.apache.velocity</groupId>
- velocity-engine-core</artifactId>
- <version>2.3</version>
- </dependency>
- <dependency>
- <groupId>org.docx4j</groupId>
- docx4j-JAXB-Internal</artifactId>
- <version>8.3.0</version>
- </dependency>
复制代码 注: 前两个完成第一个需求就够了,后两个使用模板完成后一个需求
2、制作模板
变量的地方用${}
模板contract_template.docx内容如下:
模板book_template.docx内容如下:
使用foreach进行循环
3、代码- package com.example.demo.demos;
- import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
- import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
- import org.docx4j.wml.Document;
- import org.springframework.core.io.ClassPathResource;
- import org.springframework.stereotype.Service;
- import javax.xml.bind.JAXBElement;
- import java.io.ByteArrayOutputStream;
- import java.util.List;
- import java.util.Map;
- @Service
- public class Docx4jService {
- /**
- * 根据模板生成Word文档(修复没有write方法的问题)
- */
- public byte[] generateWord(String templatePath, Map<String, String> data) throws Exception {
- // 1. 加载模板
- ClassPathResource resource = new ClassPathResource(templatePath);
- WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(resource.getInputStream());
- MainDocumentPart mainDocumentPart = wordMLPackage.getMainDocumentPart();
- // 2. 获取文档XML内容
- Document document = mainDocumentPart.getJaxbElement();
- List<Object> content = document.getBody().getContent();
- // 3. 手动替换变量
- replaceVariables(content, data);
- // 4. 写回并生成字节数组(使用save方法替代write方法)
- try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
- // 关键修复:用save方法替代write方法
- wordMLPackage.save(out);
- return out.toByteArray();
- }
- }
- /**
- * 递归替换文档中的变量(修复Text节点无法识别问题)
- */
- @SuppressWarnings("unchecked")
- private void replaceVariables(List<Object> content, Map<String, String> data) {
- for (int i = 0; i < content.size(); i++) {
- Object obj = content.get(i);
- // 处理JAXB包装的元素(解开外层包装)
- if (obj instanceof JAXBElement) {
- obj = ((JAXBElement<?>) obj).getValue();
- }
- // 关键修复:处理运行元素(R),Text节点一定在R里面
- if (obj instanceof org.docx4j.wml.R) {
- org.docx4j.wml.R run = (org.docx4j.wml.R) obj;
- // 遍历R元素中的内容,寻找Text节点
- replaceVariables(run.getContent(), data);
- }
- // 现在可以找到Text节点了
- else if (obj instanceof org.docx4j.wml.Text) {
- org.docx4j.wml.Text text = (org.docx4j.wml.Text) obj;
- String value = text.getValue();
- // 遍历所有变量进行替换
- for (Map.Entry<String, String> entry : data.entrySet()) {
- String key = "${" + entry.getKey() + "}";
- if (value.contains(key)) {
- value = value.replace(key, entry.getValue());
- text.setValue(value);
- }
- }
- }
- // 处理段落(P):继续遍历段落中的内容(会包含R元素)
- else if (obj instanceof org.docx4j.wml.P) {
- org.docx4j.wml.P p = (org.docx4j.wml.P) obj;
- replaceVariables(p.getContent(), data);
- }
- // 处理表格(Tbl):继续遍历表格中的内容
- else if (obj instanceof org.docx4j.wml.Tbl) {
- org.docx4j.wml.Tbl tbl = (org.docx4j.wml.Tbl) obj;
- replaceVariables(tbl.getContent(), data);
- }
- }
- }
- }
复制代码 - package com.example.demo.demos;
- public class Book {
- private String name;
- private String author;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getAuthor() {
- return author;
- }
- public void setAuthor(String author) {
- this.author = author;
- }
- public Book(String name, String author) {
- this.name = name;
- this.author = author;
- }
- }
复制代码 [code][/code] WordController类
[code]package com.example.demo.demos;import org.docx4j.jaxb.Context;import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.core.io.ClassPathResource;import org.springframework.http.HttpHeaders;import org.springframework.http.HttpStatus;import org.springframework.http.ResponseEntity;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 javax.xml.bind.JAXBException;import javax.xml.bind.Unmarshaller;import javax.xml.stream.XMLInputFactory;import javax.xml.stream.XMLStreamException;import javax.xml.stream.XMLStreamReader;import javax.xml.transform.stream.StreamSource;import java.io.*;import java.net.URLEncoder;import java.util.*;import org.apache.velocity.VelocityContext;import org.apache.velocity.app.Velocity;import org.docx4j.Docx4J;import org.docx4j.openpackaging.packages.WordprocessingMLPackage;import org.docx4j.wml.Document;@RestController@RequestMapping("/word")public class WordController { @Autowired private Docx4jService docx4jService; /** * 生成并下载 Word 文档 */ @GetMapping("/generate") public ResponseEntity generateWord() throws Exception { // 1. 准备模板变量数据 Map data = new HashMap(); data.put("username", "张三"); data.put("orderNo", "ORD20250911001"); data.put("amount", "1000.00"); data.put("date", "2025-09-11"); // 2. 调用服务生成 Word 字节数组 byte[] wordBytes = docx4jService.generateWord("templates/contract_template.docx", data); // 3. 配置响应头,实现文件下载 String fileName = URLEncoder.encode("订单详情.docx", "UTF-8"); HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", "attachment; filename=" + fileName); headers.add("Content-Type", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); return new ResponseEntity(wordBytes, headers, HttpStatus.OK); } static { try { Properties props = new Properties(); props.setProperty("resource.loader", "class"); props.setProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); Velocity.init(props); } catch (Exception e) { throw new RuntimeException("Velocity引擎初始化失败", e); } } @GetMapping("/export") public void exportBooks(HttpServletResponse response) { try (InputStream templateStream = new ClassPathResource("templates/book_template.docx").getInputStream()) { WordprocessingMLPackage template = WordprocessingMLPackage.load(templateStream); MainDocumentPart mainPart = template.getMainDocumentPart(); //生成books内容 List books = new ArrayList(); for (int i = 1; i |