easyExcel 不规则模板导入数据
文章目录
- 前言
- 一、需求和效果
- 二、难点和思路
- 三、全部代码
- 踩坑
前言
之前分享的 EasyExcel 批量导入并校验数据,仅支持规则excel,即首行表头,下面对应数据,无合并单元格情况。
本篇主要解决问题:
- 模板excel 表头不在首行
- 数据项有合并单元格情况
esayexcel版本2.2.7
一、需求和效果


二、难点和思路
-
跳过表头前的说明
设置headRowNumber指定表头位置,第三行
EasyExcel.read(inputStream, EvalTemplateReq.class,listener).extraRead(CellExtraTypeEnum.MERGE).sheet().headRowNumber(3).doRead();
- 合并单元格数据获取放入list
合并单元格的数据默认是在合并表格的左上第一个格子,其他为null,获取到这个格子的数据,赋值给其他被合并单元格对应的字段中
3.1 开启合并单元格识别
extraRead(CellExtraTypeEnum.MERGE)开启合并单元格识别
3.2 获取合并的单元格数据
EvalExcelDataListener 重写extra方法,获取到被合并的数据,后续处理。extra方法在invoke方法执行,先不要在意要导入的list部分字段是null,后续重新赋值即可。
extra会从excel头读取,所以要把前2行的数据过滤掉,这里用extra.getFirstRowIndex()>2判断一下
// 合并单元格private final List<CellExtra> extraMergeInfoList = new ArrayList<>();@Overridepublic void extra(CellExtra extra, AnalysisContext context) {if (extra.getType() == CellExtraTypeEnum.MERGE) {// 处理合并单元格的情况int firstRowIndex = extra.getFirstRowIndex();if(firstRowIndex>2) {extraMergeInfoList.add(extra);}}}
3.3 doAfterAllAnalysed中补全list字段值
@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {//所有数据解析完毕执行该方法// 防止导入空的Excelif (context.readRowHolder().getRowIndex() <= 0) {throw new ExcelAnalysisException("当前excel无数据!");}//处理合并单元格list = EasyExcelMergeUtil.explainMergeData(list, extraMergeInfoList, 3);saveData();}
3.4 explainMergeData 补全字段值
根据单元格左上角标通过反射获取字段并赋值给其他被合并的单元格,角标对应实体类index属性
三、全部代码
- 接收数据实体
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;import javax.validation.constraints.NotBlank;
import java.util.List;/*** 评估标准*/
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EvalTemplateReq {@ExcelIgnoreprivate String id;/*** 评估项*/@ExcelProperty(value = "评估名称",index = 1)@NotBlank(message = "评估名称不能为空")private String item;/*** 指标简称*/@ExcelProperty(value = "指标简称",index =2)@NotBlank(message = "指标简称不能为空")private String itemShortName;/*** 指标编码*/@ExcelProperty(value = "指标编码",index = 3)@NotBlank(message = "指标编码不能为空")private String itemCode;/*** 指标释义*/@ExcelProperty(value = "指标释义",index = 4)@NotBlank(message = "指标释义不能为空")private String itemExplain;/*** 评价要求/分数 json*/@ExcelIgnoreprivate List<Requirements> requirementList;/*** 系统监测类/参演部门上报类*/@ExcelProperty(value = "分类",index = 0)@NotBlank(message = "分类不能为空")private String type;@ExcelProperty(value = "评价要求",index = 5)@JsonIgnoreprivate String require;@ExcelProperty(value = "分数",index = 6)@JsonIgnoreprivate Integer score;@Builder@Data@AllArgsConstructor@NoArgsConstructorpublic static class Requirements {@ExcelIgnoreprivate String id;/*** 评价内容*/@NotBlank(message = "评价要求不能为空")private String require;/*** 分数*/@NotBlank(message = "分数不能为空")private Integer score;}
}
- service调用
/*** 导入评估标准*/@Overridepublic String importEval(MultipartFile file) {EvalExcelDataListener listener = new EvalExcelDataListener(evalTemplateDao);InputStream inputStream;try {inputStream = file.getInputStream();EasyExcel.read(inputStream, EvalTemplateReq.class,listener).extraRead(CellExtraTypeEnum.MERGE).sheet().headRowNumber(3).doRead();return "全部导入成功!";} catch (IOException e) {throw new BusinessCheckException("Excel 文件流读取失败");} catch (ExcelAnalysisException e) {return e.getMessage();} catch (Exception e) {throw new BusinessException("数据导入失败", e);}}
- 合并单元格数据处理util
package com.gsafety.bg.pd.service.excel;import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.CellExtra;
import lombok.extern.slf4j.Slf4j;import java.lang.reflect.Field;
import java.util.List;@Slf4j
public class EasyExcelMergeUtil {/*** 处理合并单元格* @param data 解析数据* @param extraMergeInfoList 合并单元格信息* @param headRowNumber 起始行* @return 填充好的解析数据*/public static <T> List<T> explainMergeData(List<T> data, List<CellExtra> extraMergeInfoList, Integer headRowNumber) {// 循环所有合并单元格信息extraMergeInfoList.forEach(cellExtra -> {int firstRowIndex = cellExtra.getFirstRowIndex() - headRowNumber;int lastRowIndex = cellExtra.getLastRowIndex() - headRowNumber;int firstColumnIndex = cellExtra.getFirstColumnIndex();int lastColumnIndex = cellExtra.getLastColumnIndex();// 获取初始值Object initValue = getInitValueFromList(firstRowIndex, firstColumnIndex, data);// 设置值for (int i = firstRowIndex; i <= lastRowIndex; i++) {for (int j = firstColumnIndex; j <= lastColumnIndex; j++) {setInitValueToList(initValue, i, j, data);}}});return data;}/*** 设置合并单元格的值** @param filedValue 值* @param rowIndex 行* @param columnIndex 列* @param data 解析数据*/private static <T> void setInitValueToList(Object filedValue, Integer rowIndex, Integer columnIndex, List<T> data) {if (rowIndex >= data.size()) return;T object = data.get(rowIndex);for (Field field : object.getClass().getDeclaredFields()) {// 提升反射性能,关闭安全检查field.setAccessible(true);ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);if (annotation != null) {if (annotation.index() == columnIndex) {try {field.set(object, filedValue);break;} catch (IllegalAccessException e) {log.error("设置合并单元格的值异常:{}", e.getMessage());}}}}}/*** 获取合并单元格的初始值* rowIndex对应list的索引* columnIndex对应实体内的字段** @param firstRowIndex 起始行* @param firstColumnIndex 起始列* @param data 列数据* @return 初始值*/private static <T> Object getInitValueFromList(Integer firstRowIndex, Integer firstColumnIndex, List<T> data) {Object filedValue = null;T object = data.get(firstRowIndex);for (Field field : object.getClass().getDeclaredFields()) {// 提升反射性能,关闭安全检查field.setAccessible(true);ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);if (annotation != null) {if (annotation.index() == firstColumnIndex) {try {filedValue = field.get(object);break;} catch (IllegalAccessException e) {log.error("设置合并单元格的初始值异常:{}", e.getMessage());}}}}return filedValue;}}
- easyexcel数据监听类
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellExtraTypeEnum;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.metadata.CellExtra;
import com.gsafety.bg.gsdss.common.utils.json.JsonUtil;
import com.gsafety.bg.pd.dao.EvalTemplateDao;
import com.gsafety.bg.pd.model.dto.req.EvalTemplateReq;
import com.gsafety.bg.pd.model.po.EvalTemplatePO;import java.util.*;
import java.util.stream.Collectors;public class EvalExcelDataListener extends AnalysisEventListener<EvalTemplateReq> {private final Integer LIST_COUNT = 19;List<EvalTemplateReq> list = new ArrayList<>(LIST_COUNT);// 合并单元格private final List<CellExtra> extraMergeInfoList = new ArrayList<>();// 由于监听器只能通过new的方式创建,所以可以通过构造器传入dao层对象private final EvalTemplateDao dao;public EvalExcelDataListener(EvalTemplateDao dao) {this.dao = dao;}@Overridepublic void invoke(EvalTemplateReq req, AnalysisContext context) {list.add(req);if (list.size() > LIST_COUNT) {throw new ExcelAnalysisException("当前excel数据量不得大于" + LIST_COUNT + "条!");}}@Overridepublic void extra(CellExtra extra, AnalysisContext context) {if (extra.getType() == CellExtraTypeEnum.MERGE) {// 处理合并单元格的情况int firstRowIndex = extra.getFirstRowIndex();if(firstRowIndex>2) {extraMergeInfoList.add(extra);}}}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {//所有数据解析完毕执行该方法// 防止导入空的Excelif (context.readRowHolder().getRowIndex() <= 0) {throw new ExcelAnalysisException("当前excel无数据!");}//处理合并单元格list = EasyExcelMergeUtil.explainMergeData(list, extraMergeInfoList, 3);saveData();}protected void saveData() {Map<String, List<EvalTemplateReq>> collect = list.stream().collect(Collectors.groupingBy(EvalTemplateReq::getItem));List<EvalTemplatePO> pos = new ArrayList<>();collect.forEach((k, v) -> {List<EvalTemplateReq.Requirements> requirements = v.stream().map(l -> EvalTemplateReq.Requirements.builder().id(v.indexOf(l) + "").require(l.getRequire()).score(l.getScore()).build()).collect(Collectors.toList());pos.add(EvalTemplatePO.builder().item(k).itemCode(collect.get(k).get(0).getItemCode()).itemExplain(collect.get(k).get(0).getItemExplain()).itemShortName(collect.get(k).get(0).getItemShortName()).type(collect.get(k).get(0).getType()).requirements(JsonUtil.of(requirements)).build());});dao.deleteAll();dao.saveAll(pos);}
}
踩坑
- extra方法不生效
extraRead(CellExtraTypeEnum.MERGE)显式指定识别合并单元格数据
invoke中原校验数据,因为为null导致拦截,不走extra方法。
执行顺序:invoke->extra->doAfterAllAnalysed - 指定表头位置后extra获取的第一个数据还是首行的合并单元格数据
headRowNumber(3)只是指定了读取表头的位置,extra是获取整表的所有合并单元格数据,根据excel模板,要跳过2行,获取从第三行后的合并单元格数据。if(firstRowIndex>2)
参考:https://blog.csdn.net/xhmico/article/details/136905419

相关文章:
easyExcel 不规则模板导入数据
文章目录 前言一、需求和效果二、难点和思路三、全部代码踩坑 前言 之前分享的 EasyExcel 批量导入并校验数据,仅支持规则excel,即首行表头,下面对应数据,无合并单元格情况。 本篇主要解决问题: 模板excel 表头不在首…...
前端调试技巧(npm Link,vscode调试,浏览器调试等)
Npm Link 功能: 在本地开发npm模块的时候,我们可以使用npm link命令,将npm 模块链接到对应的运行项目中去,方便地对模块进行调试和测试 断点调试 vscode调试 Debug Vue2 Project 目标:在VSCode中调试项目代码…...
SSL证书到期自动巡检脚本-推送钉钉告警
1. 编写SSL证书巡检脚本 cat /hao/batch_check_ssl_expire.sh #!/bin/bash# 域名列表文件绝对路径 domains_file"/hao/domains.txt"#钉钉webhook webhook_url"https://oapi.dingtalk.com/robot/send?access_token99999999999999999999999999999999999999999…...
Winform打印编程基础
1、目的 进行打印设置、打印预览、及实现打印功能 2、代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Printing; using System.IO; using System.Linq; using System.Te…...
Python编程实例-Python的隐藏特性
Python的隐藏特性 文章目录 Python的隐藏特性1、Python中的下划线(_)2、通过解析树进行正则表达式调试3、省略号(...)4、dir()函数5、Lambda 函数6、链式比较运算符7、zip()函数8、修饰器9、上下文管理器和with语句10、生成器和yield语句11、元类(Metaclass)12、小结Python…...
防火墙安全策略利用
拓扑图: 办公区为10.0.1.0/24 生产部为:10.0.2.0/24 办公区为vlan2,生产区为vlan3 DMZ区域为10.0.3.0/24、10.0.3.10为HTTP服务器 游客区:210.0.0./24 ISP:12.0.0.0/24 要求3: 添加安全策略…...
SystemUIService启动-Android13
SystemUIService启动-Android13 1、SystemUIService启动2、其他SystemUI services启动2.1 Dagger依赖注入2.2 Recents为例 1、SystemUIService启动 SystemUI启动,及其SystemUIService启动 <!-- SystemUi service component --><string name"config_s…...
linux权限深度解析——探索原理
前言:本节内容主要讲述的是linux权限相关的内容, linux的权限如果使用root账号是感受不到的, 所以我们要使用普通账号对本节相关内容进行学习,以及一些实验的测试。 然后, 通过linux权限的学习我们可以知道为什么有时候…...
Qt学生管理系统(付源码)
Qt学生管理系统 一、前言1.1 项目介绍1.2 项目目标 2、需求说明2.1 功能性说明2.2 非功能性说明 三、UX设计3.1 登录界面3.2 学生数据展示3.3 信息插入和更新 三、架构说明3.1 客户端结构如下3.2 数据流程图3.2.1 数据管理3.2.2 管理员登录 四、 设计说明3.1 数据库设计3.2 结构…...
重磅!新公司法正式实施,这些变化你必须知道! ️
新公司法来了!企业设立和经营必知的关键变动 🏛️🚀 大家好,我是猫头虎,科技自媒体博主。今天我们来聊聊一件大事——新公司法的实施,这对企业设立和经营带来了哪些重大影响?跟着我,…...
[Flask笔记]一个完整的Flask程序
前面讲过Flask是一个轻量级Web开发框架,为什么说是轻量级的呢,因为它用短短几行代码就能运行起来,我们一起来看看最简单的flask框架。 安装Flask 在看Flask框架之前我们需要先安装flask模块,学过python的肯定都知道,…...
企业专利布局怎么弄
企业专利布局策略与实施 在当今竞争激烈的商业环境中,企业的专利布局已成为保护创新成果、提升市场竞争力的重要手段。专利布局不仅是技术创新的体现,更是企业战略布局的重要一环。 一、企业专利布局的策略 多维度布局 企业专利布局应结合市场、技术、…...
ArduPilot开源飞控之AP_Mount_Topotek
ArduPilot开源飞控之AP_Mount_Topotek 1. 源由2. 框架设计3. 重要函数3.1 动态过程3.1.1 AP_Mount_Topotek::update3.1.2 AP_Mount_Backend::calculate_poi 3.2 基础能力3.2.1 AP_Mount_Topotek::healthy3.2.2 AP_Mount_Topotek::has_pan_control 3.3 设备功能3.3.1 AP_Mount_T…...
React组件间通信的几种方式
一、Props向下传递(Top-Down Propagation) 父组件通过props将其状态或数据传递给子组件。 父组件: class ParentComponent extends React.Component {state { message: Hello World };render() {return <ChildComponent message{this.…...
2024最新国际版抖音TikTok安装教程,免root免拔卡安卓+iOS,附全套安装工具!
我是阿星,今天给大家带来是2024年最新TikTok国际版抖音的下载和安装教程,而且还是免root免拔卡的那种,安卓和iOS都能用哦!由于某些原因,国内用户并不能使用TikTok。今天阿星就教一下大家怎么安装TikTok。 TikTok在全球…...
kafka与zookeeper的SSL认证教程
作者 乐维社区(forum.lwops.cn)许远 在构建现代的分布式系统时,确保数据传输的安全性至关重要。Apache Kafka 和 Zookeeper 作为流行的分布式消息队列和协调服务,提供了SSL(Secure Sockets Layer)认证机制&…...
为何数字化转型项目会夭折?深入分析失败的关键因素
数字化转型是一个复杂的过程,涉及企业运营的各个方面。根据麦肯锡的报告,尽管数字化转型对企业至关重要,但根据数据显示70%的数字化转型尝试未能成功。本文总结了六大常见失败原因:转型准备不足、组织文化障碍、技术实施问题、人才…...
数据结构(其二)--线性表
目录 1. 基本概念 2.线性表的基本操作 3.顺序表 (1).静态分配 (2).动态分配 (3).顺序表的插入与删除(以静态分配为例)(示例代码中包含了一下必要的基本函数…...
软链接node_modules
公司项目很多微应用的子项目公用同一套模板,也就会使用同一个node_modules 1.先创建3个同样的项目,并安装一个其中的一个node_modules给他丢到外边 2.win r -------> cmd --------> ctrlshift enter(已管理员身份打开cmd) 3.在窗口分别执行以下代码…...
Apache中使用SSI设置
先停服务在修改httpd.conf,备份下 Apache\Apache24\conf 设置httpd.conf LoadModule ssl_module modules/mod_ssl.so 取消该命令前的注释符# AddType text/html .shtml AddOutputFilter INCLUDES .shtml 取消该命令前的注释符# 加入html AddType text/html .s…...
2026毕业答辩PPT制作,高效出稿不踩雷
临近毕业答辩,不少毕业生都陷入PPT制作困境:熬夜排版耗时费力,内容逻辑混乱、格式不规范,担心视觉效果拉低答辩印象分,零基础设计更是无从下手,越赶稿越出错。这不仅耽误答辩准备时间,还会影响现…...
springboot党员之家服务系统 微信小程序毕业论文
目录摘要第一章 绪论第二章 系统需求分析第三章 系统设计第四章 系统实现第五章 系统测试第六章 总结与展望参考文献致谢项目技术支持源码LW获取详细视频演示 :文章底部获取博主联系方式!同行可合作摘要 简要介绍系统开发背景、目标、技术栈(…...
Qwen3-ForcedAligner-0.6B快速部署:ARM架构服务器兼容性验证报告
Qwen3-ForcedAligner-0.6B快速部署:ARM架构服务器兼容性验证报告 1. 引言 最近在给一个视频制作团队做技术选型,他们有个很具体的需求:给大量已有的采访视频配上精准的字幕。传统方法是人工听打,再一帧一帧地对时间轴࿰…...
Gemma-3 Pixel Studio快速上手:靛蓝像素UI+视觉理解零基础图文对话指南
Gemma-3 Pixel Studio快速上手:靛蓝像素UI视觉理解零基础图文对话指南 1. 认识Gemma-3 Pixel Studio Gemma-3 Pixel Studio是一款基于Google最新开源Gemma-3-12b-it模型构建的高性能对话终端。它不仅具备强大的逻辑推理能力,更集成了卓越的视觉理解功能…...
FFmpeg实战:如何用faststart和empty_moov优化MP4视频流媒体播放
FFmpeg实战:如何用faststart和empty_moov优化MP4视频流媒体播放 在视频流媒体服务中,MP4文件的播放体验往往取决于一个关键因素——moov box的位置。想象一下,当用户点击播放按钮时,是立即看到画面还是等待漫长的缓冲?…...
DeepSeek+豆包+Kimi降AI指令怎么写?保姆级教程来了
DeepSeek豆包Kimi降AI指令怎么写?保姆级教程来了 很多同学私信问我降AI到底怎么操作,今天出一个保姆级教程,照着做就行。 核心工具推荐嘎嘎降AI(www.aigcleaner.com),4.8元/千字,操作简单效果…...
如何用Mitt打造高效的Node.js后端事件驱动架构
如何用Mitt打造高效的Node.js后端事件驱动架构 【免费下载链接】mitt 🥊 Tiny 200 byte functional event emitter / pubsub. 项目地址: https://gitcode.com/gh_mirrors/mi/mitt Mitt是一个轻量级的事件发射器(Event Emitter)库&…...
【JDK17-HttpClient】异步请求的线程模型是什么?使用什么线程池?如何自定义?
深度解密 JDK17 HttpClient 异步请求线程模型:线程池选择、自定义与生产调优全指南 发布时间:2023-10-31 当前聚焦技术问题:异步请求的线程模型是什么?使用什么线程池?如何自定义? 一、问题引入:异步调用背后的线程陷阱 在高并发微服务架构中,HTTP 客户端的异步调用能…...
单片机—STM32中:关于寄存器
首先需了解:计算机系统五大组成部分:运算器,控制器,存储器,输入设备,输出设备。其中存储器分为内存(ROM),外设(RAM)。寄存器是连接软件和硬件的桥梁;软件读写…...
Figo义商本体论AI人格测评问卷的技术构建与工程化实践
义商本体论AI人格测评问卷的技术构建与工程化实践 作者:Figo Cheung, Figo AI Team 一、引言:从"规则约束"到"人格培育"的AI伦理转向 当前AI伦理研究多聚焦于"价值对齐"的外部规则设计,通过预设禁忌清单实现行为合规&…...
