当遇到要生成一个word文档(证明文件等)的需求时,就可以考虑使用word模板生成.doc和.wps文件
一、需求
1、生成如下这样的订单数据.doc文件,红框部分是变化的,其余部分是固定的
2、生成如下这样的书籍列表,书的个数不固定是动态的。
二、使用Docx4j实现
1、引入依赖
<!-- Docx4j 核心依赖 --> <dependency><groupId>org.docx4j</groupId><artifactId>docx4j-core</artifactId><version>8.3.0</version> <!-- 兼容 JDK 1.8 的稳定版本 --> </dependency><!-- 处理变量替换所需依赖 --> <dependency><groupId>org.docx4j</groupId><artifactId>docx4j-JAXB-ReferenceImpl</artifactId><version>8.3.0</version> </dependency><!-- Velocity模板引擎 --> <dependency><groupId>org.apache.velocity</groupId><artifactId>velocity-engine-core</artifactId><version>2.3</version> </dependency><dependency><groupId>org.docx4j</groupId><artifactId>docx4j-JAXB-Internal</artifactId><version>8.3.0</version> </dependency>
注: 前两个完成第一个需求就够了,后两个使用模板完成后一个需求
2、制作模板
变量的地方用${}
模板contract_template.docx内容如下:
模板book_template.docx内容如下:
使用foreach进行循环
3、代码
Docx4jService类
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);}}}}
Book类
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;} }
WordController类
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 {@Autowiredprivate Docx4jService docx4jService;/*** 生成并下载 Word 文档*/@GetMapping("/generate")public ResponseEntity<byte[]> generateWord() throws Exception {// 1. 准备模板变量数据Map<String, String> 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<Book> books = new ArrayList<>();for (int i = 1; i <= 3; i++) {books.add(new Book("Java开发指南" + i, "作者" + i));}VelocityContext context = new VelocityContext();context.put("books", books);String xmlContent = mainPart.getXML();StringWriter writer = new StringWriter();Velocity.evaluate(context, writer, "bookTemplate", xmlContent);String processedXml = writer.toString();// 过滤非法标签String cleanedXml = cleanSdtElements(processedXml);// 替换主文档内容(使用docx4j的JAXB工具类)replaceMainDocumentContent(mainPart, cleanedXml);try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {Docx4J.save(template, baos);response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");String fileName = new String("1.docx".getBytes("UTF-8"), "ISO-8859-1");response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");response.setContentLength(baos.size());baos.writeTo(response.getOutputStream());response.flushBuffer();}} catch (Exception e) {e.printStackTrace();}}// 清理内容控件相关标签private String cleanSdtElements(String xml) {xml = xml.replaceAll("<w:sdt[^>]*>", "");xml = xml.replaceAll("<w:sdtPr[^>]*>", "");xml = xml.replaceAll("<w:dataBinding[^>]*>", "");xml = xml.replaceAll("<w:sdtContent[^>]*>", "");xml = xml.replaceAll("</w:sdt>", "");xml = xml.replaceAll("</w:sdtPr>", "");xml = xml.replaceAll("</w:dataBinding>", "");xml = xml.replaceAll("</w:sdtContent>", "");return xml;}// 关键修复:使用docx4j提供的Context获取JAXBContextprivate void replaceMainDocumentContent(MainDocumentPart mainPart, String xmlContent)throws JAXBException, XMLStreamException, UnsupportedEncodingException {// 使用docx4j的Context工具类获取预配置的JAXBContextUnmarshaller u = Context.jc.createUnmarshaller();XMLInputFactory xif = XMLInputFactory.newFactory();xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);try (ByteArrayInputStream bais = new ByteArrayInputStream(xmlContent.getBytes("UTF-8"))) {XMLStreamReader xsr = xif.createXMLStreamReader(new StreamSource(bais));Document doc = (Document) u.unmarshal(xsr);mainPart.setJaxbElement(doc);} catch (IOException e) {throw new RuntimeException("XML内容转换失败", e);}}}
4、整体代码结构
5、使用postman 调用controller的接口就能下载到从模板生成的word文档