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文档

复制代码
 
复制代码
相关推荐
陈小桔1 小时前
idea中重新加载所有maven项目失败,但maven compile成功
java·maven
小学鸡!1 小时前
Spring Boot实现日志链路追踪
java·spring boot·后端
xiaogg36781 小时前
阿里云k8s1.33部署yaml和dockerfile配置文件
java·linux·kubernetes
逆光的July1 小时前
Hikari连接池
java
微风粼粼1 小时前
eclipse 导入javaweb项目,以及配置教程(傻瓜式教学)
java·ide·eclipse
番茄Salad2 小时前
Spring Boot临时解决循环依赖注入问题
java·spring boot·spring cloud
天若有情6732 小时前
Spring MVC文件上传与下载全面详解:从原理到实战
java·spring·mvc·springmvc·javaee·multipart
祈祷苍天赐我java之术2 小时前
Redis 数据类型与使用场景
java·开发语言·前端·redis·分布式·spring·bootstrap
Olrookie3 小时前
若依前后端分离版学习笔记(二十)——实现滑块验证码(vue3)
java·前端·笔记·后端·学习·vue·ruoyi
倚栏听风雨4 小时前
java.lang.SecurityException异常
java