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…...
K8S认证|CKS题库+答案| 11. AppArmor
目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作: 1)、切换集群 2)、切换节点 3)、切换到 apparmor 的目录 4)、执行 apparmor 策略模块 5)、修改 pod 文件 6)、…...
Leetcode 3577. Count the Number of Computer Unlocking Permutations
Leetcode 3577. Count the Number of Computer Unlocking Permutations 1. 解题思路2. 代码实现 题目链接:3577. Count the Number of Computer Unlocking Permutations 1. 解题思路 这一题其实就是一个脑筋急转弯,要想要能够将所有的电脑解锁&#x…...
Linux简单的操作
ls ls 查看当前目录 ll 查看详细内容 ls -a 查看所有的内容 ls --help 查看方法文档 pwd pwd 查看当前路径 cd cd 转路径 cd .. 转上一级路径 cd 名 转换路径 …...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...
Caliper 负载(Workload)详细解析
Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
Vue 模板语句的数据来源
🧩 Vue 模板语句的数据来源:全方位解析 Vue 模板(<template> 部分)中的表达式、指令绑定(如 v-bind, v-on)和插值({{ }})都在一个特定的作用域内求值。这个作用域由当前 组件…...
数据结构:递归的种类(Types of Recursion)
目录 尾递归(Tail Recursion) 什么是 Loop(循环)? 复杂度分析 头递归(Head Recursion) 树形递归(Tree Recursion) 线性递归(Linear Recursion)…...
2025-05-08-deepseek本地化部署
title: 2025-05-08-deepseek 本地化部署 tags: 深度学习 程序开发 2025-05-08-deepseek 本地化部署 参考博客 本地部署 DeepSeek:小白也能轻松搞定! 如何给本地部署的 DeepSeek 投喂数据,让他更懂你 [实验目的]:理解系统架构与原…...
