java从word模板生成.doc和.wps文件

当遇到要生成一个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 {

    @Autowired
    private 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获取JAXBContext
    private void replaceMainDocumentContent(MainDocumentPart mainPart, String xmlContent)
            throws JAXBException, XMLStreamException, UnsupportedEncodingException {
        // 使用docx4j的Context工具类获取预配置的JAXBContext
        Unmarshaller 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文档

复制代码
 
复制代码
相关推荐
大厂码农老A2 小时前
P10老板一句‘搞不定就P0’,15分钟我用Arthas捞回1000万资损
java·前端·后端
nlog3n2 小时前
分布式任务事务框架设计与实现方案
java·分布式
熙客2 小时前
分布式调度问题:定时任务
java·分布式·spring cloud
星海穿梭者3 小时前
SQL SERVER 查看锁表
java·服务器·前端
muxin-始终如一3 小时前
Spring框架面试问题及详细回答
java·spring·面试
Fency咖啡3 小时前
Spring Boot 3.x 开发 Starter 快速上手体验,通过实践理解自动装配原理
java·spring boot·后端
悟能不能悟3 小时前
什么是反应式编程
java
南方者4 小时前
【JAVA】【BUG】Java 开发中常见问题的具体示例,结合代码片段说明问题场景及原因
java·后端·debug