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` 列表中。// 记录图片合并前及合并后的IDList<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,为一个wordMergeWordUtil.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/Xif (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*/@Overridepublic 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.hqwc.cn/news/650408.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

固态继电器:推进可再生能源系统

随着可再生能源系统的发展&#xff0c;太阳能系统日益成为现代能源解决方案的先锋。在这种背景下&#xff0c;固态继电器&#xff08;SSR&#xff09;&#xff0c;特别是光耦固态继电器的利用变得日益突出。本文旨在深入探讨SSR在可再生能源系统中的多方位应用&#xff0c;重点…

Selenium-Webdriver的原理与机制

一&#xff0c;Selenium有关理论知识 1&#xff0c;简介 Selenium是一个用于Web应用程序自动化测试工具。Selenium测试直接运行在浏览器中&#xff0c;就像真正的用户在操作一样。支持的浏览器包括IE&#xff08;7, 8, 9, 10, 11&#xff09;&#xff0c;Mozilla Firefox&…

Vue3+vite优化基础架构(1)--- 使用unplugin-vue-components

Vue3vite优化基础架构&#xff08;1&#xff09;--- 使用unplugin-vue-components 说明安装unplugin-vue-componentsvite.config.js中使用unplugin-vue-components/vite 说明 这里记录下自己在Vue3vite的项目使用unplugin-vue-components/vite来自定义组件自动全局引入svg雪碧…

树莓派也能用于心脏病数据安全管理!

树莓派&#xff08;Raspberry Pi&#xff09;是 EKORA 用于病人数据安全管理的用户友好型系统的核心。 为心脏起搏器和除颤器患者提供的纸质记录效率低下&#xff0c;给有效护理带来了障碍。两位英国国家医疗服务系统&#xff08;NHS&#xff09;的心脏病专家建立了 EKORA&…

Python3.11修改并运行oneforall

遇到的问题 使用python3.11默认无法运行oneforall脚本&#xff0c;出现如下报错 # 解决方案 修改 /usr/local/lib/python3.11/dist-packages/exrex.py exrex.py具体文件路径报错中会显示 vim /usr/local/lib/python3.11/dist-packages/exrex.py# 修改前 from re import sre…

【软件测试】终于有人讲明白:bug的分类和定级了!

01、bug的定义 一般是指不满足用户需求的则可以认为是bug&#xff0c;狭义指软件程序的漏洞或缺陷&#xff0c;广义指测试工程师或用户提出的软件可改进的细节、或与需求文档存在差异的功能实现等 对应三个测试目的&#xff1a; 为了发现程序的代码或业务逻辑错误 为了检查产…

数字藏品:重塑艺术与科技的新媒介

数字藏品&#xff0c;这个新兴的词汇&#xff0c;正在逐渐渗透到我们的日常生活中。它不仅是一种新的艺术表达方式&#xff0c;更是一种科技与艺术相结合的全新媒介。那么&#xff0c;数字藏品究竟是什么呢&#xff1f; 首先&#xff0c;我们需要明确一点&#xff0c;数字藏品并…

Redis底层数据结构之quicklist

目录 一、概述二、quicklist结构三、quicklistNode结构四、优缺点 redis底层数据结构已完结&#x1f44f;&#x1f44f;&#x1f44f;&#xff1a; ☑️redis底层数据结构之SDS☑️redis底层数据结构之ziplist☑️redis底层数据结构之quicklist☑️redis底层数据结构之Dict☑️…

jmeter安装和简单使用

jmeter安装和简单使用 1. 安装 jmeter是基于Java开发的测试应用&#xff0c;其运行依赖于java环境&#xff0c;所以在下载运行jmeter前&#xff0c;先确保本机已经安装jdk或者jre&#xff0c;安装jdk步骤此处不描述。 下载jmeter&#xff1a; jmeter是Apache旗下的产品&…

获取boss直聘城市地区josn数据

获取boss直聘城市地区josn数据 当我需要爬取多个城市的地区的时候&#xff0c;只能手动点击&#xff0c;然后一个一个看 结果&#xff1a; 能看到所有区域所有子地区的地区代码 解析该JSON数据 import pandas as pd import requests code[] area[] 城市代码101210100 res…

广工电工与电子技术实验报告-8路彩灯循环控制电路

实验代码 module LED_water (clk,led); input clk; output [7:0] led; reg [7:0] led; integer p; reg clk_1Hz; reg [7:0] current_state, next_state; always (posedge clk) begin if(p25000000-1)begin …

详解23种设计模式——工厂模式

工厂模式 | CoderMast编程桅杆工厂模式 设计思想 工厂模式是最常用的设计模式之一&#xff0c;属于创建型模式&#xff0c;将创建对象的权利交给了一个工厂类&#xff0c;从而提供了一种不使用构造方法的情况下创建对象的途径&#xff0c;无需指定要创建的具体类&#xff0c;将…