2024 java easyexcel poi word模板填充数据,多个word合成一个word

先看效果

一、准备工作

1.word模版


2.文件路径

二、pom依赖

   <!--   easyexcel     -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.1.7</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
        </dependency>
        <!--   word export     -->
        <dependency>
            <groupId>com.deepoove</groupId>
            <artifactId>poi-tl</artifactId>
            <version>1.12.0</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.2.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.2.2</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>4.1.2</version>
        </dependency>


三、两个工具类+自己的实体类(这里是用户作为示例)

1.自己的实体类

import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;

import java.io.Serializable;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 用户信息 实体类
 *
 * @author zhaoyan
 * @since 2024-04-19
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table(value = "user")
public class User implements Serializable {
    /**
     * 用户id
     */
    @Id(keyType = KeyType.Auto)
    private Integer id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 身份证号
     */
    private String cardId;


    /**
     * 性别
     */
    private String sex;


}


2.合并word工具类


import java.io.*;
import java.util.*;

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlOptions;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody;

/**
 * 合并word工具类
 */
public class MergeWordUtil {

    /**
     * 将多个Word文档文件合并成一个新的Word文档文件,
     * 并将合并后的内容保存到指定的目标路径中。
     *
     * @param files      多个word文件
     * @param targetPath 目标存放地址
     * @throws Exception
     */
    public static void mergeMroeWord(List<File> files, String targetPath) throws Exception {
        // 1. 打开目标路径对应的文件输出流 `dest`,并使用 try-with-resources 语句确保流在使用完毕后会被正确关闭。
        try (OutputStream dest = new FileOutputStream(targetPath);) {
            // 2. 创建一个 `ArrayList` 类型的 `documentList`,用于存储读取的多个Word文档对象。
            ArrayList<XWPFDocument> documentList = new ArrayList<>();
            XWPFDocument doc = null;

            // 3. 遍历传入的文件列表 `files`,对每个文件执行以下操作:
            for (File file : files) {
                try (FileInputStream in = new FileInputStream(file.getAbsoluteFile())) {
                    // - 使用 `FileInputStream` 读取文件内容,并通过 `OPCPackage` 打开文件流,创建一个新的XWPFDocument对象 `document`。
                    OPCPackage open = OPCPackage.open(in);
                    XWPFDocument document = new XWPFDocument(open);
                    // - 将读取的文档对象 `document` 添加到 `documentList` 中。
                    documentList.add(document);
                } catch (FileNotFoundException e) {
                    // - 如果读取文件时发生 `FileNotFoundException` 异常,则捕获并打印异常信息。
                    e.printStackTrace();
                }
            }
            // 4. 遍历 `documentList` 中的文档对象:
            for (int i = 0; i < documentList.size(); i++) {
                doc = documentList.get(0);
                // - 如果是第一个文档(`i == 0`),在文档中创建一个新段落,并在新段落中添加一个换页符。
                if (i == 0) {
                    documentList.get(i).createParagraph().createRun().addBreak(BreakType.PAGE);
                    // appendBody(doc,documentList.get(i));


                    // - 如果是最后一个文档(`i == documentList.size() - 1`),调用 `appendBody` 方法将当前文档内容追加到 `doc` 文档中。
                } else if (i == documentList.size() - 1) {
                    appendBody(doc, documentList.get(i));

                    // - 如果既不是第一个文档也不是最后一个文档,分别在文档中创建一个新段落并添加换页符,然后调用 `appendBody` 方法将当前文档内容追加到 `doc` 文档中。
                } else {
                    documentList.get(i).createParagraph().createRun().addBreak(BreakType.PAGE);
                    appendBody(doc, documentList.get(i));
                }
            }
            // 5. 最终将合并后的文档内容写入到目标路径对应的文件中,并确保 `doc` 不为null。
            assert doc != null;

            // 6. 如果在写入过程中发生异常,则会抛出异常。
            doc.write(dest);
        }
    }


    /**
     * 将一个文档对象的内容追加到另一个文档对象中,
     * 同时处理了文档中的图片数据,
     * 确保图片在合并后的文档中能够正确显示
     *
     * @param src
     * @param append
     * @throws Exception
     */
    public static void appendBody(XWPFDocument src, XWPFDocument append) throws Exception {

        // 1. 通过 `src.getDocument().getBody()` 和 `append.getDocument().getBody()` 分别获取源文档和追加文档的主体内容(CTBody对象)。
        CTBody src1Body = src.getDocument().getBody();
        CTBody src2Body = append.getDocument().getBody();

        // 2. 调用 `append.getAllPictures()` 方法获取追加文档中的所有图片数据,并将其存储在 `allPictures` 列表中。
        // 记录图片合并前及合并后的ID
        List<XWPFPictureData> allPictures = append.getAllPictures();
        // 3. 创建一个 `HashMap` 对象 `map`,用于记录图片在合并前和合并后的关系。
        Map<String, String> map = new HashMap<String, String>();

        // 4. 遍历追加文档中的所有图片数据 `allPictures`,对每个图片执行以下操作:
        for (XWPFPictureData picture : allPictures) {
            // - 获取图片在追加文档中的关系ID,并将其存储在 `before` 变量中。
            String before = append.getRelationId(picture);
            // - 将图片数据添加到源文档中,并指定图片类型为PNG,获取添加后的图片关系ID,并将其存储在 `after` 变量中。
            String after = src.addPictureData(picture.getData(), Document.PICTURE_TYPE_PNG);
            // - 将 `before` 和 `after` 的对应关系存储在 `map` 中。
            map.put(before, after);
        }
        // 5. 调用另一个方法 `appendBody`,传入源文档的主体内容、追加文档的主体内容和图片关系ID的映射,将追加文档的内容合并到源文档中。
        appendBody(src1Body, src2Body, map);
    }


    /**
     * 将两个文档对象的主体内容进行合并,
     * 并在合并过程中替换图片ID,
     * 最终更新源文档对象的主体内容
     *
     * @param src    源文档的主体内容,类型为 `CTBody`。
     * @param append 追加文档的主体内容,类型为 `CTBody`。
     * @param map    存储图片ID替换关系的映射,类型为 `Map<String, String>`。
     * @throws Exception
     */
    private static void appendBody(CTBody src, CTBody append, Map<String, String> map) throws Exception {
        // 1. 创建一个 `XmlOptions` 对象 `optionsOuter`,并设置其保存外部内容的选项。
        XmlOptions optionsOuter = new XmlOptions();
        optionsOuter.setSaveOuter();
        // 2.将追加文档对象 append 转换为XML字符串,并将结果存储在 appendString 变量中。
        String appendString = append.xmlText(optionsOuter);
        // 3.将源文档对象 src 转换为XML字符串,并将结果存储在 srcString 变量中。
        String srcString = src.xmlText();
        // 4.通过字符串操作,将源文档的XML内容分为四部分:prefix、mainPart、sufix 和 addPart,具体操作和含义与前述相同。
        String prefix = srcString.substring(0, srcString.indexOf(">") + 1);
        String mainPart = srcString.substring(srcString.indexOf(">") + 1, srcString.lastIndexOf("<"));
        String sufix = srcString.substring(srcString.lastIndexOf("<"));
        String addPart = appendString.substring(appendString.indexOf(">") + 1, appendString.lastIndexOf("<"));
        // 5.如果 map 不为null且不为空,遍历 map 中的键值对,将 addPart 中的图片ID替换为对应的新ID。
        if (map != null && !map.isEmpty()) {
            // 对xml字符串中图片ID进行替换
            for (Map.Entry<String, String> set : map.entrySet()) {
                addPart = addPart.replace(set.getKey(), set.getValue());
            }
        }
        // 6.将 prefix、mainPart、addPart 和 sufix 拼接为一个完整的XML内容字符串,并通过 CTBody.Factory.parse() 方法将其解析为 CTBody 对象 makeBody。
        CTBody makeBody = CTBody.Factory.parse(prefix + mainPart + addPart + sufix);
        // 7.最后将合并后的 makeBody 设置为源文档对象 src 的内容。
        src.set(makeBody);
    }
}

3.下载工具类


import com.deepoove.poi.XWPFTemplate;
import com.test.entity.User;
import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.io.FileUtils;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;


/**下载工具类
 * 
 */
public class ExportToWrodUtils {

    /**
     * 下载
     *
     * @param response
     * @param sourceFilePath   源文件
     * @param moreFilePath     多个文件路径文件夹,template\\morelist\\
     * @param mergeFilePath    合并后文件路径,template\\res\\
     * @param finalFileName    合并后文件名字
     * @param downloadFileName 下载时的文件名字
     * @param userList
     * @throws Exception
     */
    public static void downloadWord(HttpServletResponse response, String sourceFilePath, String moreFilePath, String mergeFilePath,
                                    String finalFileName, String downloadFileName, List<User> userList) throws Exception {
        // 1.获取文件流
        InputStream stream = new FileInputStream(sourceFilePath);

        // 2.数据列表
        XWPFTemplate template = XWPFTemplate.compile(stream);
        List<File> fileList = new ArrayList<>();
        for (User user : userList) {
            //可以改成自己的业务逻辑↓↓↓↓↓↓↓↓↓↓↓
            // 填充数据
            Map<String, String> data = new HashMap<>();
            data.put("name", user.getName());
            data.put("sex", user.getSex());
            // 根据身份证号或去年月日
            data.put("year", extractYearMonthDayOfIdCard(user.getCardId()).split("-")[0]);
            data.put("month", extractYearMonthDayOfIdCard(user.getCardId()).split("-")[1]);
            data.put("day", extractYearMonthDayOfIdCard(user.getCardId()).split("-")[2]);
            template.render(data);
            File file = new File(moreFilePath + user.getCardId() + ".docx");
            //可以改成自己的业务逻辑↑↑↑↑↑↑↑↑↑↑↑
            fileList.add(file);
            // 保存为单个Word文档
            FileOutputStream out = new FileOutputStream(file);
            template.write(out);
            out.close();
        }

        // 3.合并多个word,为一个word
        MergeWordUtil.mergeMroeWord(fileList, mergeFilePath + finalFileName);

        // 4.删除合并后之前用到的多个单独word文件
        // 创建一个File对象,表示文件夹路径
        File folder = new File(moreFilePath);
        // 获取文件夹中的所有文件
        File[] files = folder.listFiles();
        // 遍历文件数组,删除Word文件
        for (File file : files) {
            if (file.isFile() && file.getName().endsWith(".docx")) {
                try {
                    FileUtils.forceDelete(file);
                    System.out.println("删除文件 " + file.getName());
                } catch (Exception e) {
                    System.out.println("删除文件失败: " + file.getName());
                    e.printStackTrace();
                }
            }
        }

        // 5.下载操作
        File file = null;
        FileInputStream is = null;
        try {
            response.setContentType("text/html;charset=utf-8");
            response.setCharacterEncoding("UTF-8");
            // 下载文件时的文件名字
            response.setHeader("content-disposition", "attachment;filename=\"" + URLEncoder.encode(downloadFileName + ".docx", "utf-8") + "\"");
            // 要下载的目标文件
            file = new File(mergeFilePath + finalFileName);
            is = new FileInputStream(file);
            ServletOutputStream os = response.getOutputStream();
            IOUtils.copy(is, os);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                is.close();
            }
            // if (file != null) {
            //     file.delete();
            // }
        }
    }

    /**
     * 省份证的正则表达式^(\d{15}|\d{17}[\dx])$ 方法类
     *
     * @param id 省份证号
     * @return 生日(yyyy - MM - dd)
     */
    public static String extractYearMonthDayOfIdCard(String id) {
        String year = null;
        String month = null;
        String day = null;
        // 正则匹配身份证号是否是正确的,15位或者17位数字+数字/x/X
        if (id.matches("^\\d{15}|\\d{17}[\\dxX]$")) {
            year = id.substring(6, 10);
            month = id.substring(10, 12);
            day = id.substring(12, 14);
        } else {
            System.out.println("身份证号码不匹配!");
            return null;
        }
        return year + "-" + month + "-" + day;
    }

}

四、业务逻辑使用工具类

 // 1.controller层
    /**
     * 导出为word数据列表
     *
     * @throws IOException
     */
    @GetMapping("/downloadWord")
    public void downloadWord(HttpServletResponse response) throws Exception {
        userService.downloadWord(response);
    }



    // 2.service层
    
    /**
     * 导出为word数据列表
     *
     * @return
     * @throws IOException
     */
    void downloadWord(HttpServletResponse response) throws Exception;



    // 3.serviceImpl层
    /**
     * 导出为word数据列表
     *
     * @return
     * @throws IOException
     */
    @Override
    public void downloadWord(HttpServletResponse response) throws Exception {
        // 数据列表
        QueryWrapper queryWrapper = QueryWrapper.create().where(USER.POWER.eq("用户"));
        List<User> userList = userMapper.selectListWithRelationsByQuery(queryWrapper);
        ExportToWrodUtils.downloadWord(response, "template\\template2.docx", "template\\morelist\\", "template\\res\\", "合并后数据", "word数据列表", userList);
    }


五、调用接口,查看效果

浏览器直接get请求

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/577259.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于微信小程序云开发实现考研题库小程序V2.0

不久之前&#xff0c;基于云开发的微信答题小程序搭建题库小程序V1.0&#xff0c;软件架构是微信原生小程序云开发。现在来回顾一下&#xff0c;已经实现的功能。 一、V1.0项目预览 1、页面结构 首页 答题页 结果页 我的页 排行榜页 答题历史页 登录页 使用指引页 2…

制造型企业 如何实现便捷的机台文件统一管理?

机台文件统一管理&#xff0c;这是生产制造型企业都需要去做的&#xff0c;机台文件需要统一管理的原因主要包括以下几点&#xff1a; 1、提高效率&#xff1a;统一管理可以简化文件的访问和使用过程&#xff0c;提高工作效率&#xff0c;尤其是在需要频繁访问或更新机台文件的…

【AIGC调研系列】大型语言模型如何减少幻觉生成

在解读大型语言模型&#xff08;LLMs&#xff09;中的长格式事实性问题时&#xff0c;我们首先需要认识到这些模型在生成内容时可能会产生与既定事实不一致的情况&#xff0c;这种情况通常被称为“幻觉”[2][3]。这种现象不仅可能导致信息的误传&#xff0c;还可能对社会造成误…

FORM调用标准AP\AR\GL\FA界面

EBS FORM客户化界面有时候数据需要追溯打开AP\AR\GL\FA等界面&#xff1a; 一种打开日记账的方式&#xff1a; PROCEDURE SHOW_JOURNAL ISparent_form_id FormModule;child_form_id FormModule; BEGINclose_jrn;parent_form_id : FIND_FORM(:SYSTEM.CURRENT_FORM);COPY(TO…

HWOD:蛇形矩阵

一、知识点 忽略蛇形矩阵本身的定义1,2,3,4,5,6&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c;&#xff0c; 而是从它的输出图形上找规律 二、题目 1、描述 蛇形矩阵是由1开始的自然数依次排列成的一个矩阵上三角形。 例如&#xff0c;当输入5时&#xf…

Magnet for Mac:高效窗口管理工具

Magnet for Mac是一款专为Mac用户设计的窗口管理工具&#xff0c;旨在帮助用户更高效地管理和布局多个应用程序窗口&#xff0c;提升工作效率。 Magnet for Mac v2.14.0中文免激活版下载 这款软件拥有直观易用的界面和丰富的功能&#xff0c;支持用户将屏幕分割成多个区域&…

【UE5】蓝图通信方式

目录 1、直接通信 2、getAllActorsOfClass 3、getAllActorsOfClassWithTag 4、通过射线检测 5、接口 6、事件分发器 7、SpawnActor 8、调用控制台命令 9、关卡蓝图中直接调用 创建两个Actor蓝图 1、直接通信 场景中 2、getAllActorsOfClass 3、getAllActorsOfClassWit…

如何实现直播声卡反向给手机充电功能呢?

在数字化时代的浪潮中&#xff0c;声卡作为多媒体系统的核心组件&#xff0c;扮演着声波与数字信号相互转换的关键角色。它不仅能够将来自各类音源的原始声音信号转换为数字信号&#xff0c;进而输出到各类声响设备&#xff0c;更能够通过音乐设备数字接口(MIDI)发出合成乐器的…

MySQL 数据库远程访问问题

在默认的情况下&#xff0c;MySQL 是不能远程访问的&#xff0c;当我们修改了用户名可以接受远程访问后&#xff0c;还是没有办法接受远程访问。 还有一个配置的地方需要验证。 mysqld.cnf 配置文件 mysqld.cnf 配置文件对访问的地址可能会有限制。 配置文件的地址为&#…

杰发科技AC7840——ADC简介(1)_双路ADC同时使用

0. 简介 1. 特性 2. 双路ADC Sample里面没有双路的&#xff0c;以为那个规则组只有一个通道&#xff0c;看了外设寄存器才发现&#xff0c;原来他的通道是双路的。 注意1: ADC硬件引脚的配置 注意2: 规则组长度设置和 RSEQ序列号和CH通道号组合应该就对应了转换顺序&#xff0…

服务器防护哪家好

在当前的网络安全环境中&#xff0c;服务器防护已经成为企业和个人防御网络威胁的重要一环。选择一个高效且可靠的服务器防护方案是至关重要的。今天我们来看一下为什么安全狗的服务器防护哪家好呢&#xff0c;一起来看看安全狗服务器防护的介绍吧。 首先&#xff0c;安全狗提供…

读天才与算法:人脑与AI的数学思维笔记11_算法如何思考

1. 创造力 1.1. 创建一种算法&#xff0c;其首要任务是放弃已知的所有艺术风格&#xff0c;然后判断由算法自己所产生的艺术品是否具有与所有艺术风格都截然不同的特性&#xff0c;即真正独树一帜的艺术风格 1.2. 抗性模型同样适用于人类创造力代码的引导 1.3. 神经科学家的…

网盘——进入文件夹

本文主要讲解网盘的文件操作中进入文件夹的部分&#xff0c;具体实现步骤如下&#xff1a; 1、具体步骤如下&#xff1a; A、客户端发送进入文件夹的请求&#xff08;该请求包含目录信息以及要进入的文件夹名字&#xff09; B、服务器收到该请求之后&#xff0c;服务器首先判…

SpringBoot---------整合Redis

目录 第一步&#xff1a;引入依赖 第二步&#xff1a;配置Redis信息 第三步&#xff1a;选择Spring Data Redis进行操作Redis数据库 ①操作String类型数据&#xff08;用的少&#xff09; ②操作Object类型数据&#xff08;重要&#xff01;&#xff01;&#xff01;&#x…

mac安装java

在 macOS 上配置 Java 环境变量是相对简单的。你需要做的是设置 JAVA_HOME 环境变量&#xff0c;并将 bin 目录添加到 PATH 变量中。本篇是最详细的教程&#xff0c;细化每个步骤过程&#xff0c;保姆级的教程&#xff01; 1. 下载JDK安装包 到oracle官网下载适合的JDK安装包…

阿里巴巴瓴羊基于 Flink 实时计算的优化和实践

摘要&#xff1a;本⽂整理⾃阿里云智能集团技术专家王柳焮⽼师在 Flink Forward Asia 2023 中平台建设专场的分享。内容主要为以下四部分&#xff1a; 阿里巴巴瓴羊基于 Flink 实时计算的平台演进Flink 能力优化与建设基于 Flink 的最佳实践未来规划 1. 阿里巴巴瓴羊基于 Flink…

低代码信创开发核心技术(四)动态元数据系统设计

一、概述 在当今快速发展的信息技术领域&#xff0c;动态元数据系统扮演着至关重要的角色。它不仅能够提供数据的描述信息&#xff0c;还能动态地适应业务需求的变化&#xff0c;从而提高系统的灵活性和可扩展性。构建一个动态元数据系统意味着我们可以在不重启系统的情况下&a…

代码随想录算法训练营第三十八天|动态规划理论基础、509.斐波那契数、70.爬楼梯、746.使用最小花费爬楼梯

动态规划理论基础 1.什么是动态规划&#xff1a; 动态规划&#xff0c;英文&#xff1a;Dynamic Programming&#xff0c;简称DP&#xff0c;如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的。 所以动态规划中每一个状态一定是由上一个状态推导出来的&…

buuctf re 45-48

[BJDCTF2020]BJD hamburger competition 参考&#xff1a;http://t.csdnimg.cn/cJOSi 参考&#xff1a;http://t.csdnimg.cn/g9dA7 这是一堆&#xff0c;仔细看&#xff0c;是个游戏 安装ILSPY 下载失败 思路还是比较简单的 对所给进行sha1解密&#xff0c;在进行md5加密…

Vuforia AR篇(四)— AR虚拟按钮

目录 前言一、创建虚拟按钮二、创建脚本三、效果 前言 在当今互联网和移动设备普及的背景下&#xff0c;**增强现实&#xff08;AR&#xff09;**技术正迅速成为连接现实世界与数字信息的重要桥梁。AR虚拟按钮作为这一技术的创新应用&#xff0c;不仅提供了一种全新的用户交互…