Spring boot easyexcel 实现复合数据导出、按模块导出

场景:

  • 导出数据为1对多的复合数据
  • 一个模块是一条数据,直接填充数据无法实现

如图:

  • 红框内为一条数据(1对多),下方箭头指向为第二条数据
  • 如果直接填充,只能填充第一条,第二条就没办法了。
  • 由于多行都包含许多,固定表头,只能走填充路线,怎么实现呢

在这里插入图片描述

实现思路流程:

准备一个导出基础填充模板,默认填充key

在这里插入图片描述

计算,复制起始行、复制结束行、复制行数;用poi的 复制行方式生成新模块,也就是一条新的 1对多数据。
sheet.copyRows(startRows.get(i), endRows.get(i), copyStartRows.get(i), policy);
在这里插入图片描述

  • 复制后

在这里插入图片描述

根据填充fillKey 规律,生成填充key集合;然后进行填充key替换

在这里插入图片描述

并返回待填充的 fillKeys,与数据对齐,进行数据填充。

如果数据过大,经测试一般一个 sheet 最好 100个复合数据,多的再进行sheet复制
xssfWorkbook.cloneSheet(0,"sheet" + (i+1));

参考代码:

    @ApiOperation(value = "数据-excel导出",notes = "首次调用会返回一个processId标识,查询进度携带标识")@GetMapping("/export")public ResultData exportHtMeta(String processId){HtMetaExcelProcessVo htMetaExcelProcessVo;if (!StringUtils.hasLength(processId)){try {htMetaExcelProcessVo=htMetaInfoService.exportHtMetaCopyModule(processId);} catch (Exception e) {throw new ExcelHandlerException("导入失败:"+e.getMessage());}}else {Cache cache = cacheManager.getCache(HtMetaConstants.EXPORT_PREFIX);htMetaExcelProcessVo=cache.get(processId,HtMetaExcelProcessVo.class);if (htMetaExcelProcessVo==null){return new ResultData(ErrorCodeEnum.NOT_FOUND_DATA.getCode(),"该导入uid,没有对应数据");}if (htMetaExcelProcessVo.getCurProcess().equals(htMetaExcelProcessVo.getTotalProcess())){htMetaExcelProcessVo.setImportStatus(HtMetaConstants.EXCEL_PROCESS_SUCCESS);htMetaExcelProcessVo.setMsg("导出成功");}}return new ResultData(htMetaExcelProcessVo);}
/*** 导出批次大小,每个sheet导出模块大小*/private static final Integer SPLIT_SIZE=100;@Overridepublic HtMetaExcelProcessVo exportHtMetaCopyModule(String exportKey) throws Exception{HtMetaExcelProcessVo excelProcessVo;Cache cache = cacheManager.getCache(HtMetaConstants.EXPORT_PREFIX);if (cache==null){throw new ExcelHandlerException("ehcahe 缓存配置异常");}if (StringUtils.hasLength(exportKey)){//检查是否存在已导出if (cache.get(exportKey)!=null){return cache.get(exportKey,HtMetaExcelProcessVo.class);}}else {exportKey = UUID.randomUUID().toString().replace("-", "");}ClassPathResource resource = new ClassPathResource("excel-template/导入模板.xlsx");String exportPath = new File("").getAbsolutePath() + File.separator + "ht-meta-export";if (!new File(exportPath).exists()){boolean mkdir = new File(exportPath).mkdir();log.info("导出目录创建:{}",mkdir);}File exportFile = new File(exportPath+File.separator+exportKey+".xlsx");log.info("华泰-元数据,导出文件:{}",exportFile.getAbsolutePath());//按数据生成-临时导入模板File tmpExportTemplate = null;ExcelWriter excelWriter =null;try {tmpExportTemplate = File.createTempFile("temp", ".xlsx");List<HtMetaClusterInfoVo> list = htMetaClusterInfoMapper.clusterList(new HtMetaClusterQo());log.info("导出数据条数:{}",list.size());int sheetSize = (list.size() / SPLIT_SIZE);if (sheetSize==1){excelProcessVo = new HtMetaExcelProcessVo(HtMetaConstants.EXCEL_PROCESS_ING, 0, 4, "正在导出");}else {excelProcessVo = new HtMetaExcelProcessVo(HtMetaConstants.EXCEL_PROCESS_ING, 0, 4+sheetSize, "正在导出");}excelProcessVo.setProcessId(exportKey);cache.put(exportKey,excelProcessVo);//阶段1refreshProcess(cache,exportKey);//单条导出if (list.size()==1){FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();excelWriter = EasyExcel.write(exportFile).withTemplate(resource.getInputStream()).build();WriteSheet writeSheet = EasyExcel.writerSheet(0).build();HtMetaClusterInfoVo e = list.get(0);excelWriter.fill(new FillWrapper("data0", Collections.singletonList(e)), fillConfig, writeSheet);List<HtMetaNodeInfoVo> nodeInfoVos = e.getNodeInfoVos();List<HtMetaBsInfoVo> bsInfoVos = e.getBsInfoVos();excelWriter.fill(new FillWrapper("data1", nodeInfoVos), fillConfig, writeSheet);excelWriter.fill(new FillWrapper("data2", bsInfoVos), fillConfig, writeSheet);excelWriter.finish();excelProcessVo = new HtMetaExcelProcessVo(HtMetaConstants.EXCEL_PROCESS_SUCCESS, 4, 4, "导出成功");excelProcessVo.setProcessId(exportKey);cache.put(exportKey,excelProcessVo);return excelProcessVo;}int overSize;if (sheetSize>1){//剩余数量overSize = list.size() - (sheetSize * SPLIT_SIZE);log.info("剩余数据条数:{}",overSize);} else {overSize = 0;}log.info("开始生成数据导出模板");List<List<String>> fillKeys = HtMetaExcelUtil.copyMultiRow(6, 17,12,list.size() - 1,resource.getInputStream(), tmpExportTemplate);log.info("生成结束");//阶段2refreshProcess(cache,exportKey);FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();excelWriter = EasyExcel.write(exportFile).withTemplate(tmpExportTemplate).build();//阶段3refreshProcess(cache,exportKey);String finalExportKey = exportKey;ExcelWriter finalExcelWriter = excelWriter;File finalTmpExportTemplate = tmpExportTemplate;CompletableFuture.runAsync(()->{try {fiilTemplateExcel(finalExportKey, cache, finalExcelWriter, list, sheetSize, overSize, fillKeys, fillConfig);log.info("填充结束");}finally {if (finalExcelWriter!=null){finalExcelWriter.finish();}boolean delete = finalTmpExportTemplate.delete();log.info("临时导入模板删除: {}",delete);}});} catch (IOException e) {log.info("导出失败");throw e;}return excelProcessVo;}

HtMetaExcelUtil

import lombok.extern.slf4j.Slf4j;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;/*** @author xiaoshu* @description* @date 2023年09月01日 23:34*/
@Slf4j
public class HtMetaExcelUtil {/*** 导出批次大小,每个sheet导出模块大小*/private static final Integer SPLIT_SIZE=100;/*** poi对excel进行 多行模块复制,并替换 Fill填充前缀** @param startRowIndex    模块-起始行index,excel标记行号-1* @param endRowIndex      模块-结束行* @param moduleRowSize    模块行数* @param copyCount        复制次数* @param sourceFileStream 源文件流* @param outFile          输出文件* @return List<List<String>> 填充fillKey列表*/public static List<List<String>> copyMultiRow(int startRowIndex, int endRowIndex,int moduleRowSize, int copyCount,InputStream sourceFileStream,File outFile) {File tempFile =null;int sheetSize=0;if (copyCount>SPLIT_SIZE){sheetSize= (copyCount + 1) / SPLIT_SIZE;log.info("复制sheet数量:{}",sheetSize);copyCount=SPLIT_SIZE-1;}//填充key列表List<List<String>> fillKeys = new LinkedList<>();//添加填充模板,默认keyfillKeys.add(Arrays.asList("data0","data0","data1","data2"));//复制起始行int startRow = startRowIndex;//复制结束行int endRow = endRowIndex;//目标起始行int targetRow = endRow + 1;List<Integer> startRows = new LinkedList<>();startRows.add(startRow);List<Integer> endRows = new LinkedList<>();endRows.add(endRow);List<Integer> copyStartRows = new LinkedList<>();copyStartRows.add(targetRow);XSSFWorkbook workbook = null;XSSFWorkbook xssfWorkbook = null;try {workbook = new XSSFWorkbook(sourceFileStream);for (int i = 1; i < copyCount; i++) {startRow = startRow + moduleRowSize;startRows.add(startRow);endRow = endRow + moduleRowSize;endRows.add(endRow);targetRow = endRow + 1;copyStartRows.add(targetRow);}XSSFSheet sheet = workbook.getSheetAt(0);CellCopyPolicy policy = new CellCopyPolicy();policy.setCopyCellFormula(false);policy.setMergeHyperlink(false);policy.setMergeHyperlink(false);for (int i = 0; i < copyCount; i++) {sheet.copyRows(startRows.get(i), endRows.get(i), copyStartRows.get(i), policy);setRowsBorder(workbook,sheet,copyStartRows.get(i)+5,copyStartRows.get(i)+7);setRowsBorder(workbook,sheet,copyStartRows.get(i)+9,copyStartRows.get(i)+12);}//生成临时模板文件tempFile = File.createTempFile("temp", ".xlsx");//写入复制模块后的文件workbook.write(Files.newOutputStream(tempFile.toPath()));//移除模板本身索引startRows.remove(0);//添加最后一列索引if (copyCount!=1){Integer lastRow = startRows.get(startRows.size() - 1);startRows.add(lastRow + moduleRowSize);}//替换填充前缀xssfWorkbook = new XSSFWorkbook(tempFile);XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0);int initIndex = 3;if (copyCount!=1){for (Integer row : startRows) {//每行对应填充keyList<String> fillKey = new LinkedList<>();XSSFRow row1 = xssfSheet.getRow(row);replaceRowValue(row1, "data0", "data" + initIndex);fillKey.add("data" + initIndex);XSSFRow row2 = xssfSheet.getRow(row + 2);replaceRowValue(row2, "data0", "data" + initIndex);fillKey.add("data" + initIndex);XSSFRow row3 = xssfSheet.getRow(row + 4);replaceRowValue(row3, "data1", "data" + (initIndex + 1));fillKey.add("data" + (initIndex + 1));XSSFRow row4 = xssfSheet.getRow(row + 8);replaceRowValue(row4, "data2", "data" + (initIndex + 2));fillKey.add("data" + (initIndex + 2));initIndex = initIndex + 3;fillKeys.add(fillKey);}}else {//每行对应填充keyList<String> fillKey = new LinkedList<>();int row=endRowIndex+1;XSSFRow row1 = xssfSheet.getRow(row);replaceRowValue(row1, "data0", "data" + initIndex);fillKey.add("data" + initIndex);XSSFRow row2 = xssfSheet.getRow(row + 2);replaceRowValue(row2, "data0", "data" + initIndex);fillKey.add("data" + initIndex);XSSFRow row3 = xssfSheet.getRow(row + 4);replaceRowValue(row3, "data1", "data" + (initIndex + 1));fillKey.add("data" + (initIndex + 1));XSSFRow row4 = xssfSheet.getRow(row + 8);replaceRowValue(row4, "data2", "data" + (initIndex + 2));fillKey.add("data" + (initIndex + 2));fillKeys.add(fillKey);}if (sheetSize>=1){for (int i = 0; i < sheetSize; i++) {xssfWorkbook.cloneSheet(0,"sheet" + (i+1));}}//替换填充前缀->输出文件xssfWorkbook.write(Files.newOutputStream(outFile.toPath()));return fillKeys;} catch (IOException | InvalidFormatException e) {throw new RuntimeException(e);} finally {try {if (xssfWorkbook != null) {xssfWorkbook.close();}if (workbook != null) {workbook.close();}if (sourceFileStream != null) {sourceFileStream.close();}} catch (IOException e) {throw new RuntimeException(e);}if (tempFile!=null){boolean delete = tempFile.delete();log.info("临时模板删除: {}",delete);}}}//添加边框public static void setRowsBorder(XSSFWorkbook xssfWorkbook,XSSFSheet sheet,int startRow,int endRow){// 创建单元格样式XSSFCellStyle style = xssfWorkbook.createCellStyle();//上下左右边框// 设置边框样式为实线style.setBorderTop(BorderStyle.THIN);style.setBorderBottom(BorderStyle.THIN);style.setBorderLeft(BorderStyle.THIN);style.setBorderRight(BorderStyle.THIN);// 设置边框颜色为黑色style.setTopBorderColor(IndexedColors.BLACK.getIndex());style.setBottomBorderColor(IndexedColors.BLACK.getIndex());style.setLeftBorderColor(IndexedColors.BLACK.getIndex());style.setRightBorderColor(IndexedColors.BLACK.getIndex());for (int i = startRow; i < endRow; i++) {XSSFRow row = sheet.getRow(i);row.setRowStyle(style);}}/*** 行值替换* @param row      替换行* @param oldValue 过去值* @param newValue 替换值*/public static void replaceRowValue(XSSFRow row, String oldValue, String newValue) {Iterator<Cell> cellIterator = row.cellIterator();cellIterator.forEachRemaining(e -> {if (StringUtils.hasLength(e.getStringCellValue())) {String cellValue = e.getStringCellValue();cellValue = cellValue.replace(oldValue, newValue);e.setCellValue(cellValue);}});}/*** 获取导出文件* @param processId 进度id* @return String - 文件路径*/public static String getHtExportFile(String processId) {File file = new File("");return  file.getAbsolutePath() + File.separator + "ht-meta-export" + File.separator + processId + ".xlsx";}/*** 浏览器文件下载* @param targetFile  目标文件* @param response    response*/public static void browserDownLoad(File targetFile, String downLoadName, HttpServletResponse response){OutputStream out = null;InputStream in = null;try {response.reset();response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(downLoadName, "UTF-8"));response.addHeader("Content-Length", "" + targetFile.length());response.setContentType("application/vnd.ms-excel");out = new BufferedOutputStream(response.getOutputStream());in = new BufferedInputStream(new FileInputStream(targetFile));IOUtils.copy(in, out);out.flush();} catch (Exception e) {} finally {IOUtils.closeQuietly(in);IOUtils.closeQuietly(out);}}}

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

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

相关文章

安卓玩机搞机----不用刷第三方官改固件即可享受“高级设置”的操作 ChiMi安装使用步骤

很多玩友特别喜欢第三方作者修改的带有高级设置的官改包。因为他可以随意修改系统里面的有关设置选项。包括但不限于修改状态栏 显示日期 秒等等的操作。 第三方带高级设置的官改 一般官改带高级设置的类似与 今天给大家分享下不用刷这些官改包即可享受高级设置的操作。 红米…

2023上海工博会,正运动展位现场直击(二)

9月21日&#xff0c;上海工博会已经成功开展了2天&#xff0c;热度仍旧不减&#xff0c;正运动技术展位6.1H-E261不仅吸引了大量工业自动化专业人士&#xff0c;而且也为他们呈现了一系列令人印象深刻的产品和运动控制解决方案。其中&#xff0c;高性能软硬件产品引发了广泛关注…

数据结构入门-14-排序

一、选择排序 1.1 选择排序思想 先把最小的元素拿出来 剩下的&#xff0c;再把最小的拿出来 剩下的&#xff0c;再把最小的拿出来 但是这样 空间复杂度是O(n) 优化一下&#xff0c;希望原地排序 1.1.2 选择原地排序 索引i指向0的位置 索引j指向i1的元素 j 后面的元素遍历&…

使用香橙派学习Linux udev的rules 并实现U盘的自动挂载

在之前编程首先语音刷抖音的博文里提到过udev&#xff0c;现在回顾一下&#xff1a; 什么是udev&#xff1f; udev是一个设备管理工具&#xff0c;udev以守护进程的形式运行&#xff0c;通过侦听内核发出来的uevent来管理/dev目录下的设备文件。udev在用户空间运行&#xff0c;…

macOS Sonoma 14 RC2(23A344)/Ventura13.6/Monterey 12.7 三版系统同时更新

macOS Sonoma 14 RC2&#xff08;23A344&#xff09;/macOS13.6/macOS 12.7 同时更新

冯诺伊曼体系结构和操作系统

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析3 目录 &#x1f449;&#x1f3fb;一、冯诺依曼体系结构概念常见的输入设备和输出设备内…

【数据结构】二叉树之堆的实现

&#x1f525;博客主页&#xff1a;小王又困了 &#x1f4da;系列专栏&#xff1a;数据结构 &#x1f31f;人之为学&#xff0c;不日近则日退 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、二叉树的顺序结构 &#x1f4d2;1.1顺序存储 &#x1f4d2;1.2堆的性质…

MySQL查询表结构方法

MySQL查询数据库单个表结构代码 – 查询数据库表信息 SELECT​ COLUMN_NAME 列名,​ DATA_TYPE 字段类型,​ CHARACTER_MAXIMUM_LENGTH 长度,​ IS_NULLABLE 是否为空,​ IF(column_key PRI,Y,) 是否为主键,​ COLUMN_DEFAULT 默认值,​ COLUMN_COMMENT 备注FROM​ INFORMAT…

【数据结构】图的基本概念,图的存储结构(邻接矩阵;邻接表;十字链表;邻接多重表)

欢~迎~光~临~^_^ 目录 1、图的基本概念 2、图的存储结构 2.1邻接矩阵 2.2邻接表 2.3十字链表 2.4邻接多重表 2.5图的四种存储结构的对比 1、图的基本概念 图是由一组节点&#xff08;通常称为顶点&#xff09;和一组连接这些节点的边&#xff08;通常称为边&#xff0…

Linux中sudo命令的添加和操作

使用 sudo分配权限 &#xff08;1&#xff09;修改/etc/sudoers 文件分配文件 # chmod 740 /etc/sudoers # vi /etc/sudoers 找到这行&#xff1a;root ALL(ALL) ALL, 在这行下面添加 xxx ALL(ALL) ALL (这里的xxx就是你的普通用户&#xff0c;而ruice就是我的普通用户 ) 编…

外汇天眼:外汇交易市场与股票交易市场优势对比!

在纽约证券交易所上市的股票大约有2800多只。纳斯达克证券交易所还列出了另外3,300多家股票。您将交易哪一个&#xff1f;有时间留在这么多公司的头上吗&#xff1f;在外汇交易中&#xff0c;有数十种货币交易&#xff0c;但是大多数市场参与者交易了七种主要货币对。难道七个主…

微信开放平台第三方开发,实现代小程序备案申请

大家好&#xff0c;我是小悟 微信小程序备案整体流程总共分为五个环节&#xff1a;备案信息填写、平台初审、工信部短信核验、通管局审核和备案成功。 服务商可以代小程序发起备案申请。在申请小程序备案之前&#xff0c;需要确保小程序基本信息已填写完成、小程序至少存在一个…

如何利用播放器节省20%点播成本

点播成本节省的点其实涉及诸多部分&#xff0c;例如&#xff1a;CDN、转码、存储等&#xff0c;而利用播放器降本却是很多客户比较陌生的部分。火山引擎基于内部支撑抖音集团相关业务的实践&#xff0c;播放器恰恰是成本优化中最重要和最为依赖的部分。 火山引擎的视频团队做了…

华为云云耀云服务器L实例评测|Docker版的Minio安装 Springboot项目中的使用 结合vue进行图片的存取

前言 最近华为云云耀云服务器L实例上新&#xff0c;也搞了一台来玩&#xff0c;期间遇到过MySQL数据库被攻击的情况&#xff0c;Redis被攻击的情况&#xff0c;教训是密码不能太简单。在使用服务器时&#xff0c;学习到很多运维相关的知识。 本篇博客介绍如何在Linux中安装mi…

IP协议的相关特性

文章目录 一.IP协议二. IP地址不够用了?1. 动态分配IP(DHCP)2. NAT机制(网络地址转换)(理解网络结构的关键要点)3. IPv64. 为什么IPv6不如NAT受用? 二. IP组成三. 路由转发(了解) 一.IP协议 概念 IP地址&#xff08;Internet Protocol Address&#xff09;是指互联网协议地…

FL Studio21水果编曲软件怎么下载中文版?

FL Studio21这款软件在国内被广泛使用&#xff0c;因此又被称为"水果"。它提供音符编辑器&#xff0c;可以针对作曲者的要求编辑出不同音律的节奏&#xff0c;例如鼓、镲、锣、钢琴、笛、大提琴、筝、扬琴等等任何乐器的节奏律动。此外&#xff0c;它还提供了方便快捷…

代码随想录算法训练营第57天| 647. 回文子串,516.最长回文子序列,动态规划总结

链接: 647. 回文子串 链接: 516.最长回文子序列 链接: 动态规划总结 647. 回文子串 理解dp数组的含义很重 class Solution {public int countSubstrings(String s) {char[] chars s.toCharArray();boolean[][] dp new boolean[s.length()][s.length()];int res 0;// 遍…

目标检测:Edge Based Oriented Object Detection

论文作者&#xff1a;Jianghu Shen,Xiaojun Wu 作者单位&#xff1a;Harbin Institute of Technology Shenzhen 论文链接&#xff1a;http://arxiv.org/abs/2309.08265v1 内容简介&#xff1a; 1&#xff09;方向&#xff1a;遥感领域中的目标检测技术 2&#xff09;应用&…

购物H5商城架构运维之路

一、引言 公司属于旅游行业&#xff0c;需要将旅游&#xff0c;酒店&#xff0c;购物&#xff0c;聚合到线上商城。通过对会员数据进行聚合&#xff0c;形成大会员系统&#xff0c;从而提供统一的对客窗口。 二、业务场景 围绕更加有效地获取用户&#xff0c;提升用户的LTV&a…

Python线程和进程

1、深度解析Python线程和进程 一篇文章带你深度解析Python线程和进程 - 知乎使用Python中的线程模块&#xff0c;能够同时运行程序的不同部分&#xff0c;并简化设计。如果你已经入门Python&#xff0c;并且想用线程来提升程序运行速度的话&#xff0c;希望这篇教程会对你有所帮…