excel 动态表头与合并列
零、希望Springboot-java导出excel文件,包括动态表头与下边合并的列
使用 org.apache.poi 与自己封装工具类实现相关功能。代码如下

一、代码
1、依赖
implementation(group: 'org.apache.poi',name: 'poi-ooxml',version: '4.1.0')implementation(group: 'org.apache.poi',name: 'poi',version: '4.1.0')implementation(group: 'cn.hutool', name: 'hutool-all', version: '5.8.3')
2、工具类 ExcelMergeUtil.java
import cn.hutool.json.JSONUtil;
import com.longze.fengqx.HeaderNode;
import com.longze.fengqx.PoiModel;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.RegionUtil;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;/*** @author Fengqx* @version 1.0* @description: excel文件合并* @date 2023/8/20 13:13*/
public class ExcelMergeUtil {public static SXSSFSheet createExcelHead(SXSSFWorkbook book, String sheetName, String headJson){List<HeaderNode> headerNodes = JSONUtil.toList(headJson, HeaderNode.class);SXSSFSheet sxssfSheet = book.createSheet(sheetName);CellStyle headStyle = book.createCellStyle();defaultHeadStyle(headStyle);//表头层级int deep = headerNodes.stream().map(HeaderNode::getRow).reduce(Integer::max).orElse(1);for (int i = 0; i < deep; i++) {sxssfSheet.createRow(i);}//创建单元格for (HeaderNode headerNode : headerNodes) {int row = headerNode.getRow();int col = headerNode.getColumn();SXSSFCell sxssfCell = sxssfSheet.getRow(row).createCell(col);sxssfSheet.setColumnWidth(col, headerNode.getWidth() * 256);sxssfCell.setCellStyle(headStyle);sxssfCell.setCellValue(headerNode.getHeaderName());CellRangeAddress region;//是否跨列if (headerNode.isOverNode()) {region = new CellRangeAddress(row, deep, col, col);} else {region = new CellRangeAddress(row, row, col, (col + headerNode.getOverNodeCount() - 1));}if (region.getNumberOfCells() > 1) {sxssfSheet.addMergedRegionUnsafe(region);//合并后设置下边框RegionUtil.setBorderTop(BorderStyle.THIN, region, sxssfSheet);RegionUtil.setBorderLeft(BorderStyle.THIN, region, sxssfSheet);RegionUtil.setBorderBottom(BorderStyle.THIN, region, sxssfSheet);RegionUtil.setBorderRight(BorderStyle.THIN, region, sxssfSheet);}}return sxssfSheet;}public static void mergeCellFunc(Sheet sheet, String[] title, String[] field, List<Map<String, String>> list, Integer deep,List<Integer> mergeIndex){Map<String, List<Map<String, String>>> map = Maps.newHashMap();map.put("测试合并数据", list);// 模拟大数据量情况下,任务中心可分页查询接口,分批返回数据List<List<Map<String, String>>> listList =excelPageByNum(list, 5);// 数据总数 + 表头int size = listList.stream().mapToInt(List::size).sum() + deep;List<PoiModel> poiModels = Lists.newArrayList();for (List<Map<String, String>> listmid : listList) {for (Map<String, String> mapMid : listmid) {int index = sheet.getLastRowNum()+1;Row row = sheet.createRow(index);for (int i = 0; i < title.length; i++) {String titleField = field[i];String old = null;if (index > deep+1) {old = poiModels.get(i) == null ? null : poiModels.get(i).getContent();}for (int k : mergeIndex) {if (index == deep+1) {PoiModel poiModel =new PoiModel(mapMid.get(titleField),mapMid.get(titleField),null,deep+1,i);poiModels.add(poiModel);break;}PoiModel poiModel = poiModels.get(i);String content = mapMid.get(titleField);// 当前行的当前列与上一行的当前列的内容不一致时,则把当前行以上的合并if (i > 0 && k == i) {// 如果不需要考虑当前行与上一行内容相同,但是它们的前一列内容不一样则不合并的情况,把或条件删除if (!StringUtils.equalsIgnoreCase(content, poiModel.getContent())
// || (StringUtils.equalsIgnoreCase(content, poiModel.getContent()) && !StringUtils.equalsIgnoreCase(poiModels.get(i - 1).getOldContent(),mapMid.get(field[i - 1])))) {get(poiModel, content, index, i, sheet);}}// 处理第一列的情况if (k == i && i == 0 && !StringUtils.equalsIgnoreCase(content,poiModel.getContent())) {get(poiModel, content, index, i, sheet);}// 最后一行没有后续的行与之比较,所有当到最后一行时则直接合并对应列的相同内容if (k == i && index == size && poiModels.get(i).getRowIndex() != index) {CellRangeAddress cra = new CellRangeAddress(poiModels.get(i).getRowIndex(), index, poiModels.get(i).getCellIndex(), poiModels.get(i).getCellIndex());sheet.addMergedRegion(cra);}}Cell cell = row.createCell(i);cell.setCellValue(mapMid.get(titleField));poiModels.get(i).setOldContent(old);}}}}/*** 表头样式** @param headStyle*/private static void defaultHeadStyle(CellStyle headStyle) {headStyle.setBorderTop(BorderStyle.THIN);headStyle.setBorderLeft(BorderStyle.THIN);headStyle.setBorderBottom(BorderStyle.THIN);headStyle.setBorderRight(BorderStyle.THIN);headStyle.setAlignment(HorizontalAlignment.CENTER);headStyle.setVerticalAlignment(VerticalAlignment.CENTER);headStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);headStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex());}//合并单元格private static void get(PoiModel poiModel, String content, int index, int i, Sheet sheet) {if (poiModel.getRowIndex() != index - 1) {CellRangeAddress cra = new CellRangeAddress(poiModel.getRowIndex(), index - 1, poiModel.getCellIndex(), poiModel.getCellIndex());//在sheet里增加合并单元格sheet.addMergedRegion(cra);}/*重新记录该列的内容为当前内容,行标记改为当前行标记,列标记则为当前列*/poiModel.setContent(content);poiModel.setRowIndex(index);poiModel.setCellIndex(i);}public static <T> List<List<T>> excelPageByNum(List<T> list, int pageSize) {return IntStream.range(0, list.size()).boxed().filter(t -> t % pageSize == 0).map(t -> list.stream().skip(t).limit(pageSize).collect(Collectors.toList())).collect(Collectors.toList());}
}
3、实体对象
HeaderNode.java 和 PoiModel.java
public class PoiModel {private String content;private String oldContent;private String primaryKey;private int rowIndex;private int cellIndex;public PoiModel() {}public PoiModel(String content, String oldContent, String primaryKey, int rowIndex, int cellIndex) {this.content = content;this.oldContent = oldContent;this.primaryKey = primaryKey;this.rowIndex = rowIndex;this.cellIndex = cellIndex;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}public String getOldContent() {return oldContent;}public void setOldContent(String oldContent) {this.oldContent = oldContent;}public String getPrimaryKey() {return primaryKey;}public void setPrimaryKey(String primaryKey) {this.primaryKey = primaryKey;}public int getRowIndex() {return rowIndex;}public void setRowIndex(int rowIndex) {this.rowIndex = rowIndex;}public int getCellIndex() {return cellIndex;}public void setCellIndex(int cellIndex) {this.cellIndex = cellIndex;}
}public class HeaderNode {/*** 标题头*/private String headerName;/*** 层级*/private int row;/*** 非叶子节点列跨度*/private int overNodeCount;/*** 当前列没有子节点*/private boolean overNode = true;/*** 列*/private int column;/*** 宽度*/private int width = 13;public String getHeaderName() {return headerName;}public void setHeaderName(String headerName) {this.headerName = headerName;}public int getRow() {return row;}public void setRow(int row) {this.row = row;}public int getOverNodeCount() {return overNodeCount;}public void setOverNodeCount(int overNodeCount) {this.overNodeCount = overNodeCount;}public boolean isOverNode() {return overNode;}public void setOverNode(boolean overNode) {this.overNode = overNode;}public int getColumn() {return column;}public void setColumn(int column) {this.column = column;}public int getWidth() {return width;}public void setWidth(int width) {this.width = width;}
}
4、下载Controller
@GetMapping(value = "/downExcel")@ResponseBodypublic void downExcel(HttpServletResponse response,@RequestParam(required = true) String type) throws Exception {try {tengxunService.downExcel(response, type);} catch (Exception ex) {throw ex;}}
5、下载service
@Overridepublic void downloadExcel(HttpServletResponse response, String type)throws Exception {response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码String fileName = URLEncoder.encode("腾讯充值文件", "UTF-8");response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");OutputStream os = response.getOutputStream();
//加工数据List<Map<String, String>> list = Lists.newArrayList();for(int i=0;i<chongzhiList.size();i++){Chongzhi dto=new Chongzhi ();list.add(JSONObject.parseObject(JSON.toJSONString(dto),Map.class));}String weekStart ="08.01";String weekEnd ="08.07";String nextWeekStart = "08.08";String nextWeekEnd ="08.15";//合并单元格方法try {String customizeLabel = "[{\"headerName\":\"区域\",\"column\":0,\"row\":0}," +"{\"headerName\":\"用户姓名\",\"column\":1,\"row\":0}," +"{\"headerName\":\"本周("+weekStart+"-"+weekEnd+")充值记录\",\"column\":2,\"row\":0}," +"{\"headerName\":\"本周("+weekStart+"-"+weekEnd+")充值详情\",\"column\":3,\"row\":0,\"overNodeCount\":4,\"overNode\":false},{\"headerName\":\"充值时间\",\"column\":3,\"row\":1}," +"{\"headerName\":\"充值项目\",\"column\":4,\"row\":\"1\"}," +"{\"headerName\":\"充值方式\",\"column\":5,\"row\":1}," +"{\"headerName\":\"充值金额\",\"column\":6,\"row\":1}," +"{\"headerName\":\"下周("+nextWeekStart+"-"+nextWeekEnd+")充值计划\",\"column\":7,\"row\":0}]";//1、生bookSXSSFWorkbook book = new SXSSFWorkbook();//2、生成动态标题SXSSFSheet sxssfSheet = ExcelMerge.createExcelHead(book,"Sheet1", customizeLabel);//3、取数据对应字段值 汇总String[] title = {"区域", "用户姓名", "本周("+weekStart+"-"+weekEnd+")充值记录", "充值时间", "充值项目", "充值方式", "充值金额", "下周("+nextWeekStart+"-"+nextWeekEnd+")充值计划"};//4、取数据对应属性的字段值 汇总String[] field = {"areaName", "name", "chongzjilu", "chongzTime", "chongzProject", "chongzMethod", "chongzMoney", "nextChongz"};//5、需要合并的列List<Integer> mergeIndex = Arrays.asList(0,1,7);//6、合并ExcelMerge.mergeCellFunc(sxssfSheet,title,field, list, sxssfSheet.getLastRowNum(),mergeIndex);//创建excel文件 下载book.write(os);} catch (IOException e){logger.error("文件导出失败,错误信息{}",e);// 重置responseresponse.reset();response.setContentType("application/json");response.setCharacterEncoding("utf-8");Map<String, String> map = new HashMap<>();map.put("statusCode", "500");map.put("message", "下载文件失败" + e.getMessage());response.getWriter().println(JSON.toJSONString(map));}finally {try {os.close();} catch (IOException e) {e.printStackTrace();}}}
三、下载
完事通过controller调用下载接口,直接可以下载出文件
可以任意改表头,与选择是否要合并的字段,当做参数传入,将需要合并的列顺序传入即可完成合并,一步到位,十分方便
//5、需要合并的列 List<Integer> mergeIndex = Arrays.asList(0,1,7);ExcelMerge.mergeCellFunc(sxssfSheet,title,field, list, sxssfSheet.getLastRowNum(),mergeIndex);
截图如下

荆轲刺秦王!
相关文章:
excel 动态表头与合并列
零、希望Springboot-java导出excel文件,包括动态表头与下边合并的列 使用 org.apache.poi 与自己封装工具类实现相关功能。代码如下 一、代码 1、依赖 implementation(group: org.apache.poi,name: poi-ooxml,version: 4.1.0)implementation(group: org.apache.po…...
jenkins自动部署微服务到docker
1、代码上传到git; 2、jenkins拉取git的代码,maven打包,使用插件生成镜像,自动上传docker; 两个插件,一个打包插件,一个创建镜像上传docker仓库.(将dockerfile内容搬到插件配置&…...
【蔚来汽车】蔚来20220713第三题-旅游规划 <模拟、滑动窗口>
【蔚来汽车】蔚来20220713第三题-旅游规划 牛牛对 n 个城市旅游情况进行了规划,已知每个城市有两种属性 x 和 y ,其中 x 表示去第 i 号城市的花费,y 表示在第 i 号城市游玩后会得到的开心值。 现在牛牛希望从中挑选出一些城市去游玩&…...
[解决方案]Antd TreeSelect/Select placeholder失效
🔎嘿,这里是慰慰👩🏻🎓,会发各种类型的文章,智能专业,从事前端🐾 🎉如果有帮助的话,就点个赞叭,让我开心一下!…...
微人事 部门管理 模块 (十五)
部门管理的树展示和搜索 数据展示页是个树,我们一次性把数据加载出来也可以通过点一次id加载查询出来出来子部门,我们用一次拿到说有json数据加载出来 数据不多可以用递归,数据很多就用懒加载的方式 由于子部门比较深就不适合,权…...
【Terraform学习】使用 Terraform 从 EC2 实例访问 S3 存储桶(Terraform-AWS最佳实战学习)
使用 Terraform 从 EC2 实例访问 S3 存储桶 实验步骤 前提条件 安装 Terraform: 地址 下载仓库代码模版 本实验代码位于 task_ec2_s3connet 文件夹中。 变量文件 variables.tf 在上面的代码中,您将声明,aws_access_key,aws_…...
ZDRE6VP4-1X/50MG24K4V比例压力阀放大器
DRE 6-11/100MG24K4M比、DRE 10-6X/200YMG24K4M、DRE 20-52/200YMG24K4M、DRE 20-6X/200YMG24K4M、ZDRE6VP1-1X/315MG24N9K4M、ZDRE6VP4-1X/50MG24K4V、Z3DRE6VP2-2X/50G24K4M、Z3DRE6VP1-2X/100G24K4M、Z3DRE10VP2-1X/100XYG24K4M、Z3DRE10VP1-1X/315XLG24K4V 功能: 设定值通…...
纠缠辅助的量子网络:原理、技术、发展与挑战
7月11日,中国科大网络空间安全学院和陆军院士工作室李忠辉博士为第一作者、薛开平教授为通讯作者的量子网络综述论文“Entanglement-Assisted Quantum Networks: Mechanics, Enabling Technologies, Challenges, and Research Directions”在通信领域知名期刊《IEEE…...
React Native 可触摸组件基础知识
在 React Native 中要实现可触摸的组件方式有三种,第一种方式就是使用TouchableHighlight组件,第二种方式就是使用TouchableOpacity组件,最后一种方式就是使用TouchableWithoutFeedback组件。 TouchableHighlight TouchableHighlight组件主…...
用户、权限和Vim编辑器
用户 用户分类 超级管理员:可以登录,拥有所有权限,用户Id为0 普通用户:可以登录,但只能操作家目录,用户Id为1000 程序用户:不能登录,用于管理程序,用户Id为1~999 添…...
git版本管理加合并笔记
目录 1.创建空文件夹,右键Bash here打开 2.打开链接,点击克隆下载,复制SSH链接 3.输入git SSH链接 回车 4.换成https在桌面上进行克隆仓库就正常了 5.去vscode里改东西 6.提交 7.创建dev分支 8.在dev里修改内容,提交&…...
Failed to load property source from location ‘classpath:/application.yml‘
项目场景: 今天到公司启动项目,突然发现项目起不起来了 问题描述 出现 Failed to load property source from location ‘classpath:/application.yml’ 错误 原因分析: 刚开始以为是 application.yml 中格式错误,但是发现同…...
Ajax复习
Ajax复习 一、简介 AJAX 全称为 Asynchronous JavaScript And XML,就是异步的 JS 和 XML。 一句话总结:无刷新通信。 二、 特点 优点 无刷新通信 允许你根据用户事件来更新部分页面内容 缺点 没有浏览历史,不能回退 存在跨域问题…...
里式替换原则(LSP)
目录 简介: 作用: 过程: 总结: 简介: 里式替换原则(Liskov Substitution Principle,简称LSP)的提出者是美国计算机科学家Barbara Liskov。Barbara Liskov是一位计算机科学家,麻省理工学院教授,也是美国第一个计算机…...
mysql------做主从复制,读写分离
1.为什么要做主从复制(主从复制的作用) 做数据的热备,作为后备数据库,主数据库服务器故障后,可切换到从数据库继续工作,避免数据丢失。 架构的扩展。业务量越来越大,I/O访问频率过高,单机无法满…...
Anaconda虚拟环境跨系统迁移
旧设备运行代码 conda activate name conda env export > environment.yml注意:如果当前路径已经有了 environment.yml 文件,conda 会重写这个文件 新设备运行代码 # 将environment.yml 拷贝到新设备中conda env create -f environment.yml...
第四章 IRIS 编程简介 - Macros
文章目录 第四章 IRIS 编程简介 - MacrosMacrosInclude Files这些代码元素如何协同工作 第四章 IRIS 编程简介 - Macros Macros ObjectScript 还支持定义替换的宏。定义可以是一个值、整行代码或(使用 ##continue 指令)多行。使用宏来确保一致性。例如…...
大厂考核重点:mysql索引面试题
很多同学面对Mysql索引相关的面试题都是死记硬背的,这肯定是不行的,也不容易记住,所以大家还是要循循渐进,从理解开始,慢慢掌握,当然对于想要准备面试题的同学,这几个问题是需要记住并理解的&am…...
MySQL使用binlog日志做数据恢复
MySQL的binlog日志是MySQL日志中非常重要的一种日志,记录了数据库所有的DML操作。通过binlog日志我们可以进行数据库的读写分离、数据增量备份以及服务器宕机时的数据恢复。 定期备份固然可以在服务器发生宕机的时候快速的恢复数据,但传统的全量备份不可…...
USB Type-C端口集成式ESD静电保护方案 安全低成本
Type-C端口是根据USB3.x和USB4协议传输数据的,很容易受到电气过载(EOS)和静电放电(ESD)事件的影响。由于Type-C支持随意热插拔功能,其内部高集成度的芯片,更容易受到人体静电放电的伤害和损坏。…...
centos 7 部署awstats 网站访问检测
一、基础环境准备(两种安装方式都要做) bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats࿰…...
2021-03-15 iview一些问题
1.iview 在使用tree组件时,发现没有set类的方法,只有get,那么要改变tree值,只能遍历treeData,递归修改treeData的checked,发现无法更改,原因在于check模式下,子元素的勾选状态跟父节…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
基于江科大stm32屏幕驱动,实现OLED多级菜单(动画效果),结构体链表实现(独创源码)
引言 在嵌入式系统中,用户界面的设计往往直接影响到用户体验。本文将以STM32微控制器和OLED显示屏为例,介绍如何实现一个多级菜单系统。该系统支持用户通过按键导航菜单,执行相应操作,并提供平滑的滚动动画效果。 本文设计了一个…...
机器学习的数学基础:线性模型
线性模型 线性模型的基本形式为: f ( x ) ω T x b f\left(\boldsymbol{x}\right)\boldsymbol{\omega}^\text{T}\boldsymbol{x}b f(x)ωTxb 回归问题 利用最小二乘法,得到 ω \boldsymbol{\omega} ω和 b b b的参数估计$ \boldsymbol{\hat{\omega}}…...
从零手写Java版本的LSM Tree (一):LSM Tree 概述
🔥 推荐一个高质量的Java LSM Tree开源项目! https://github.com/brianxiadong/java-lsm-tree java-lsm-tree 是一个从零实现的Log-Structured Merge Tree,专为高并发写入场景设计。 核心亮点: ⚡ 极致性能:写入速度超…...
嵌入式面试常问问题
以下内容面向嵌入式/系统方向的初学者与面试备考者,全面梳理了以下几大板块,并在每个板块末尾列出常见的面试问答思路,帮助你既能夯实基础,又能应对面试挑战。 一、TCP/IP 协议 1.1 TCP/IP 五层模型概述 链路层(Link Layer) 包括网卡驱动、以太网、Wi‑Fi、PPP 等。负责…...
【题解-洛谷】P10480 可达性统计
题目:P10480 可达性统计 题目描述 给定一张 N N N 个点 M M M 条边的有向无环图,分别统计从每个点出发能够到达的点的数量。 输入格式 第一行两个整数 N , M N,M N,M,接下来 M M M 行每行两个整数 x , y x,y x,y,表示从 …...
Git 命令全流程总结
以下是从初始化到版本控制、查看记录、撤回操作的 Git 命令全流程总结,按操作场景分类整理: 一、初始化与基础操作 操作命令初始化仓库git init添加所有文件到暂存区git add .提交到本地仓库git commit -m "提交描述"首次提交需配置身份git c…...
