使用Apache POI和POI-OOXML实现word模板文档自动填充功能

最近接到一个新的需求,用户创建好模板文件保存到模板库,然后使用在线文档编辑器打开模板时,将系统数据填充到模板文件并生成新的word文件,然后在线编辑,研究使用Apache POI和POI-OOXML实现了这个功能。

Maven依赖

XML 复制代码
        <!-- Apache POI 和 POI-OOXML -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.2</version>
        </dependency>

自动填充的接口

java 复制代码
import org.apache.poi.xwpf.usermodel.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/fill")
public class WordFillController {

    @PostMapping("/wordFill")
    public ResponseEntity<?> wordFill() {
        String templatePath = "E:\\word\\template.docx";
        String outputPath = "E:\\word\\output.docx";

        // 待填充进模板的数据
        Map<String, String> data = new HashMap<>();
        data.put("name", "张三");
        data.put("sex", "男");
        data.put("age", "18");

        Map<String, String> data1 = new HashMap<>();
        data1.put("name", "张三1");
        data1.put("sex", "男1");
        data1.put("age", "20");

        Map<String, String> data2 = new HashMap<>();
        data2.put("name", "李四");
        data2.put("sex", "男");
        data2.put("age", "21");

        Map<String, String> data3 = new HashMap<>();
        data3.put("name", "王五");
        data3.put("sex", "女");
        data3.put("age", "45");

        List<Map<String, String>> dataList = Arrays.asList(data1, data2, data3);

        try (
                // 从模板文件创建word文档对象
                XWPFDocument doc = new XWPFDocument(Files.newInputStream(Paths.get(templatePath)));
                // 文件输出流
                FileOutputStream fos = new FileOutputStream(outputPath)
        ) {

            replacePlaceholders(doc, data);// 替换占位符
            fillTable(doc, dataList);// 填充表格
            // 将文档写入输出流
            doc.write(fos);
            // 刷新输出流
            fos.flush();

            return ResponseEntity.ok().build();
        } catch (IOException e) {
            e.printStackTrace();
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
        }
    }

    private void replacePlaceholders(XWPFDocument doc, Map<String, String> data) {
        // 遍历文档中的段落,替换占位符
        doc.getParagraphs().forEach(paragraph -> {
            // 遍历段落中的文本,替换占位符
            paragraph.getRuns().forEach(run -> {
                // 获取文本内容
                String text = run.getText(0);
                // 判断文本是否包含占位符,如果包含,则替换占位符
                if (text != null && text.contains("${")) {
                    for (Map.Entry<String, String> entry : data.entrySet()) {
                        text = text.replace("${" + entry.getKey() + "}", entry.getValue());
                    }
                    // 设置替换后的文本
                    run.setText(text, 0);
                }
            });
        });
    }

    private void fillTable(XWPFDocument doc, List<Map<String, String>> dataList) {
        XWPFTable table = doc.getTables().get(0); // 获取第一个表格

        // 获取表格的模板行(假设表格的第一行是标题行,第二行为模板行)
        XWPFTableRow templateRow = table.getRow(1);

        // 添加新的行,并删除模板行
        for (Map<String, String> data : dataList) {
            XWPFTableRow newRow = table.createRow();

            // 手动创建每个单元格,并复制模板行的样式
            for (int i = 0; i < templateRow.getTableCells().size(); i++) {
                XWPFTableCell templateCell = templateRow.getCell(i);
                XWPFTableCell newCell = newRow.getCell(i);
                if (newCell == null) {
                    newCell = newRow.createCell();
                }

                // 复制模板行单元格的样式,不复制内容
                copyCellStyleWithoutText(templateCell, newCell);

                // 填充数据
                switch (i) {
                    case 0:
                        newCell.setText(data.get("name"));
                        break;
                    case 1:
                        newCell.setText(data.get("sex"));
                        break;
                    case 2:
                        newCell.setText(data.get("age"));
                        break;
                }
            }
        }

        // 删除模板行
        table.removeRow(1);
    }

    private void copyCellStyleWithoutText(XWPFTableCell sourceCell, XWPFTableCell targetCell) {
        // 复制单元格的样式,但不复制文本内容
        targetCell.getParagraphs().forEach(paragraph -> {
            XWPFParagraph sourceParagraph = sourceCell.getParagraphs().get(0);
            paragraph.setAlignment(sourceParagraph.getAlignment());
            paragraph.setVerticalAlignment(sourceParagraph.getVerticalAlignment());

            if (!sourceParagraph.getRuns().isEmpty()) {
                XWPFRun sourceRun = sourceParagraph.getRuns().get(0);
                XWPFRun targetRun = paragraph.createRun();

                targetRun.setBold(sourceRun.isBold());
                targetRun.setItalic(sourceRun.isItalic());
                targetRun.setFontFamily(sourceRun.getFontFamily());
                targetRun.setFontSize(sourceRun.getFontSize());
            }
        });

        // 复制单元格背景色等样式
        targetCell.setColor(sourceCell.getColor());
        targetCell.setVerticalAlignment(sourceCell.getVerticalAlignment());
    }
}

word模板

调用接口后生成的新文件

相关推荐
todoitbo1 天前
把 XAMPP 搬到鸿蒙 PC:在鸿蒙 PC 上跑 Apache httpd 2.4.69实战项目
华为·apache·harmonyos·鸿蒙·开源鸿蒙·鸿蒙pc
AI行业应用研究1 天前
会务系统的查询路径设计:从菜单导航到意图检索,多大规模的活动该配 AI 助理?
人工智能·apache
超龄超能程序猿1 天前
Apache Guacamole 入门指南
apache
豆角焖肉2 天前
Apache POI 实现 Excel 上传解析、导出
apache·poi·multipartfile·excel 导入导出·批量导入
nvd113 天前
深入剖析:GCP Pub/Sub 与 Apache Kafka 对消息处理延迟与消费者卡死的处理机制异同
分布式·kafka·apache
白帽攻防录3 天前
SRC 挖洞:Apache APISIX 非规范路径授权绕过深度复盘,CVE-2026-94269 怎么绕过网关访问后端
网络安全·apache·src漏洞挖掘
Knight_AL4 天前
从 EasyExcel 到 Apache Fesod:Java Excel 处理生态的一次重构
java·apache·excel
心易行者5 天前
Agent应用+API端点商业化进阶实战:从单体智能体到可付费调用的API全流程
运维·服务器·人工智能·python·apache
文慧的科技江湖10 天前
2026年10月4日充电桩行业晚报:当“只充80%“成为通行规则,行业开始管单车占桩时长 | 慧知开源充电桩平台
开源·apache·新能源·虚拟电厂·充电桩·ocpp·v2g
nvd1112 天前
# 从 192 秒到 13 秒:Apache Flink 批处理极速调优实战记录
大数据·flink·apache