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

news2024/10/6 6:45:21

先看效果

一、准备工作

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请求

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

STM32、GD32等驱动AMG8833热成像传感器源码分享

一、AMG8833介绍 1简介 AMG8833是一种红外热像传感器&#xff0c;也被称为热感传感器。它可以用来检测和测量物体的热辐射&#xff0c;并将其转换为数字图像。AMG8833传感器可以感知的热源范围为-20C到100C&#xff0c;并能提供8x8的像素分辨率。它通过I2C接口与微控制器或单…

DS进阶:AVL树和红黑树

一、AVL树 1.1 AVL树的概念 二叉搜索树&#xff08;BST&#xff09;虽可以缩短查找的效率&#xff0c;但如果数据有序或接近有序二叉搜索树将退化为单支树&#xff0c;查找元素相当于在顺序表中搜索元素&#xff0c;效率低下。因此&#xff0c;两位俄罗斯的数学家G.M.Adelson-…

为什么深度学习模型在 GPU 上运行得更快:CUDA 编程简介

如今,当我们谈论深度学习时,通常会将其实现与利用 GPU 来提高性能联系起来。 GPU(图形处理单元)最初设计用于加速图像、2D 和 3D 图形的渲染。然而,由于它们能够执行许多并行操作,因此它们的实用性超出了深度学习等应用程序。 GPU 在深度学习模型中的使用始于 2000 年代…

Unity读书系列《Unity高级编程:主程手记》——架构

文章目录 前言一、架构的意义1、承载力2、可扩展性3、易用性4、可伸缩性5、容错性以及错误的感知力 二、软件架构的思维方式二、构建Unity项目1、前端和后端架构之间2、培养架构设计思路3、Unity项目的分层设计 总结 前言 这篇文章是《Unity高级编程&#xff1a;主程手记》的第…

【源码】WHMCS 虚拟主机计费系统 易支付插件 USDT收款插件 支付宝 微信收款

【源码介绍】 WHMCS 虚拟主机计费系统 易支付插件 USDT收款插件 支付宝 微信收款 【源码说明】 WHMCS是一个国外的专业虚拟主机计费系统&#xff0c;功能很强大&#xff0c;这里分享一个7、8版本都可用的易支付 需要对接USDT可以谷歌下载易支付USDT插件&#xff0c;主机对接…

【R语言实战】——kNN和朴素贝叶斯方法实战

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

wifi可以连接但是上不了网该怎么解决?

上网的过程中&#xff0c;我们有时候会遇到wifi可以连接但是上不了网的情况&#xff0c;打开电脑浏览器&#xff0c;显示域名解析错误。遇到这种情况&#xff0c;一般说明IP与站点的解析过程出现了错误。 在网络中的主机都是IP地址来标识的&#xff0c;如果在浏览器输入此IP地…

美国言语听力学会(ASHA)关于非处方 (OTC) 助听器的媒体声明(翻译稿)

美国国会于 2021 年 4 月 13 日批准美国听力学会积极提供建议&#xff0c;并一直积极参与制定FDA关于非处方助听器销售的拟议法规。根据2017年通过的立法授权。学院积极参与帮助塑造授权立法&#xff0c;并就即将出台的条例分享了建议。 根据美国卫生与公众服务部NIH / NIDCD的…

数据分析:扩增子-16s rRNA分析snakemake流程

介绍 扩增子测序是分析环境微生物的常见手段&#xff0c;通常使用的是16s rRNA片段。16srRNA分析主要有质控、去冗余、聚类OTU、去嵌合体、生成OTU表和物种注释等步骤。更多知识分享请到 https://zouhua.top/。 先看看前期数据处理的可视化图。 数据 18份来自宏基因组公众号…

C# WinForm —— 08 Form初始化、布局、注册事件

Form 初始化 Form初始化的时候会调用 Designer.cs 里的 InitializeComponent(); 函数&#xff0c;在InitializeComponent(); 函数里面有Load Form语句时会调用 FrmLogin_Load()函数 Form布局 两种方式&#xff1a; 拖控件到窗体&#xff0c;设置属性在Load事件中写代码添加…

线性神经网络示例

通过5个条件判定一件事情是否会发生&#xff0c;5个条件对这件事情是否发生的影响力不同&#xff0c;计算每个条件对这件事情发生的影响力多大&#xff0c;写一个线性神经网络模型pytorch程序,最后打印5个条件分别的影响力。 一 在这个场景中&#xff0c;一个线性神经网络&…

knife4j swagger 使用笔记

1.接口访问的端口跟后台设置的不一致&#xff0c;接口请求无反应 处理办法 2.响应参数不显示问题 &#xff08;1&#xff09;返回的参数里面一定要有响应的参数对象&#xff0c;如下&#xff1a; &#xff08;2&#xff09;TableDataInfo 定义成泛型类 TableDataInfo package…

Int4:Lucene 中的更多标量量化

作者&#xff1a;来自 Elastic Benjamin Trent, Thomas Veasey 在 Lucene 中引入 Int4 量化 在之前的博客中&#xff0c;我们全面介绍了 Lucene 中标量量化的实现。 我们还探索了两种具体的量化优化。 现在我们遇到了一个问题&#xff1a;int4 量化在 Lucene 中是如何工作的以…

软件需求管理规程(Word原件2024)

软件开发人员及用户往往容易忽略信息沟通&#xff0c;这导致软件开发出来后不能很好地满足用户的需要&#xff0c;从而造成返工。而返工不仅在技术上给开发人员带来巨大的麻烦&#xff0c;造成人力、物力的浪费&#xff0c;而且软件的性能也深受影响。所以在软件项目开发周期的…

单片机为什么有多组VDD?

以前我在画尺寸小的PCB时&#xff0c;比较头痛&#xff0c;特别是芯片引脚又多的&#xff0c;芯片底下&#xff0c;又不能打太多过孔。 可能有些老铁也比较好奇&#xff0c;为什么一个单片机芯片&#xff0c;有这么多组VDD和VSS。 比如下面这个100个引脚的STM32单片机。 有5组…

前端实现将当前页面内容下载成图片(图片可做到高清画质)

插件背景&#xff1a; html2canvas可以把你想要转变的元素变为图片&#xff0c;使用file-saver下载图片。 1、安装html2canvas、file-saver npm install html2canvasnpm install file-saver --save 2、在Vue组件中引入并使用html2canvas、file-saver import html2canvas fro…

智慧旅游开启智慧出行新时代,科技引领旅行新风尚:以科技为引领,推动旅游业智慧化升级,为旅行者提供更加便捷、高效的旅行服务

一、引言 随着信息技术的飞速发展&#xff0c;智慧旅游作为一种全新的旅游形态&#xff0c;正逐渐改变着人们的出行方式。它利用现代科技手段&#xff0c;实现旅游资源的智能化管理、旅游信息的智能化传播和旅游服务的智能化提供&#xff0c;为旅行者带来更加便捷、高效的旅行…

Qt下使用OpenCV截取图像并在QtableWidget表格上显示

文章目录 前言一、在QLabel上显示图片并绘制矩形框二、保存矩形框数据为CSV文件三、保存截取图像四、将截取图像填充到表格五、图形视图框架显示图像六、示例完整代码总结 前言 本文主要讲述了在Qt下使用OpenCV截取绘制的矩形框图像&#xff0c;并将矩形框数据保存为CSV文件&a…

气膜仓库:现代化仓储新选择—轻空间

气膜仓库&#xff0c;作为现代化仓储的新选择&#xff0c;越来越受到人们的青睐。相比传统料仓&#xff0c;气膜仓库具有诸多优势&#xff0c;使其成为各行各业的首选储存解决方案。 1. 高效节能 气膜仓库的建设周期短&#xff0c;基础简单&#xff0c;安装快捷&#xff0c;能耗…

C#命名空间常用函数

在C#中&#xff0c;不同命名空间下有各种常用函数&#xff0c;下面列举一些常见的函数及其对应的命名空间&#xff1a; System命名空间&#xff1a; Console.WriteLine()&#xff1a;用于向控制台输出信息。Convert.ToInt32()&#xff1a;用于将其他数据类型转换为整数类型。 S…