⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 网络 「网络」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

在当今数据驱动的世界中,有效地管理和处理大量数据已成为企业成功的关键因素之一。特别是对于那些依赖于Excel文件进行数据交换和报告的企业来说,能够快速准确地将Excel数据转换为可操作的信息变得尤为重要。

然而,传统的数据导入方法往往面临着性能瓶颈、格式不兼容以及缺乏灵活性等问题。

本文介绍了一种基于Java的解决方案,它利用了阿里巴巴开源的EasyExcel库来实现高效且灵活的Excel数据导入功能。我们将探讨如何通过自定义监听器和工具类来处理复杂的Excel表结构,并展示如何轻松应对各种数据验证需求。

期间有些数据转换之类的方法,参考:

https://blog.csdn.net/LuChangQiu/article/details/143117324

编写的逻辑是在狮子大佬的开源框架 Ruoyi-Plus 基础上丰富的内容:

https://gitee.com/dromara/RuoYi-Vue-Plus

一. 特殊 Excel 表格例子

二. 使用示例

1. bo 示例(部分字段)

bo 和 vo 注解一样

2. 自定义监听器

3. 使用方法

三. 实现

1. 工具类

/**
* Excel相关处理
*
* @author 鲁子狄
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
publicclassExcelUtil{

/**
* 同步导入(适用于小数据量)
*
* @param is 输入流
* @return 转换后集合
*/
publicstatic <T> List<T> importExcel(InputStream is, Class<T> clazz){
return EasyExcel.read(is).head(clazz).autoCloseStream(false).sheet().doReadSync();
}


/**
* 使用校验监听器 异步导入 同步返回
*
* @param is 输入流
* @param clazz 对象类型
* @param isValidate 是否 Validator 检验 默认为是
* @return 转换后集合
*/
publicstatic <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, boolean isValidate){
DefaultExcelListener<T> listener = new DefaultExcelListener<>(isValidate);
EasyExcel.read(is, clazz, listener).sheet().doRead();
return listener.getExcelResult();
}

/**
* 使用自定义监听器 异步导入 自定义返回
*
* @param is 输入流
* @param clazz 对象类型
* @param listener 自定义监听器
* @return 转换后集合
*/
publicstatic <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener){
EasyExcel.read(is, clazz, listener).sheet().doRead();
return listener.getExcelResult();
}

/**
* 使用自定义监听器 异步导入 自定义返回
*
* @param is 输入流
* @param clazz 对象类型
* @param targetCells 获取表头对应的单元格
* @param headRowNum 表头行
* @param listener 自定义监听器
* @return 转换后集合
*/
publicstatic <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, Set<CellPosition> targetCells, Integer headRowNum, ExcelListener<T> listener){
byte[] inputStreamBytes;
try {
inputStreamBytes = IOUtils.toByteArray(is);
} catch (IOException e) {
thrownew RuntimeException("读取输入流异常", e);
}

if (targetCells != null) {
// 同步读取预表头数据
PreHeaderListener preHeaderListener = new PreHeaderListener(targetCells);
// 注册监听器前先设置读取模式为无模型读取
try (InputStream preHeaderInputStream = new ByteArrayInputStream(inputStreamBytes)) {
EasyExcel.read(preHeaderInputStream)
.sheet()
.headRowNumber(0)
.registerReadListener(preHeaderListener)
.doReadSync();
} catch (IOException e) {
thrownew RuntimeException("读取预表头数据异常", e);
}
// 获取预表头数据
Map<String, String> preHeaderData = preHeaderListener.getPreHeaderData();
listener.setPreHeaderData(preHeaderData);
}

// 使用新的输入流重新读取文件,这次是为了读取实际数据
try (InputStream inputStream = new ByteArrayInputStream(inputStreamBytes)) {
EasyExcel.read(inputStream)
.sheet()
.head(clazz)
.headRowNumber(headRowNum)
.registerReadListener(listener)
.doRead();
} catch (IOException e) {
thrownew RuntimeException("导出Excel异常", e);
}

return listener.getExcelResult();
}
}

2. 默认基础导入(特殊 Excel 建立在这个基础上)

特殊 Excel 建立在这个基础上,这块的代码也要有

2.1 Excel 导入监听

publicinterfaceExcelListener<T> extendsReadListener<T> {

ExcelResult<T> getExcelResult();

/**
* 设置预表头数据
*
* @param preHeaderData 预表头数据
*/
voidsetPreHeaderData(Map<String, String> preHeaderData);
}

2.2 默认监听

@Slf4j
@NoArgsConstructor
publicclassDefaultExcelListener<T> extendsAnalysisEventListener<T> implementsExcelListener<T> {

/**
* 是否Validator检验,默认为是
*/
private Boolean isValidate = Boolean.TRUE;

/**
* excel 表头数据
*/
private Map<Integer, String> headMap;

/**
* 导入回执
*/
private ExcelResult<T> excelResult;

publicDefaultExcelListener(boolean isValidate){
this.excelResult = new DefaultExcelResult<>();
this.isValidate = isValidate;
}

/**
* 处理异常
*
* @param exception ExcelDataConvertException
* @param context Excel 上下文
*/
@Override
publicvoidonException(Exception exception, AnalysisContext context)throws Exception {
String errMsg = null;
if (exception instanceof ExcelDataConvertException excelDataConvertException) {
// 如果是某一个单元格的转换异常 能获取到具体行号
Integer rowIndex = excelDataConvertException.getRowIndex();
Integer columnIndex = excelDataConvertException.getColumnIndex();
errMsg = StrUtil.format("第{}行-第{}列-表头{}: 解析异常<br/>",
rowIndex + 1, columnIndex + 1, headMap.get(columnIndex));
if (log.isDebugEnabled()) {
log.error(errMsg);
}
}
if (exception instanceof ConstraintViolationException constraintViolationException) {
Set<ConstraintViolation<?>> constraintViolations = constraintViolationException.getConstraintViolations();
String constraintViolationsMsg = StreamUtils.join(constraintViolations, ConstraintViolation::getMessage, ", ");
errMsg = StrUtil.format("第{}行数据校验异常: {}", context.readRowHolder().getRowIndex() + 1, constraintViolationsMsg);
if (log.isDebugEnabled()) {
log.error(errMsg);
}
}
excelResult.getErrorList().add(errMsg);
thrownew ExcelAnalysisException(errMsg);
}

@Override
publicvoidinvokeHeadMap(Map<Integer, String> headMap, AnalysisContext context){
this.headMap = headMap;
log.debug("解析到一条表头数据: {}", JsonUtils.toJsonString(headMap));
}

@Override
publicvoidinvoke(T data, AnalysisContext context){
if (isValidate) {
ValidatorUtils.validate(data);
}
excelResult.getList().add(data);
}

@Override
publicvoiddoAfterAllAnalysed(AnalysisContext context){
log.debug("所有数据解析完成!");
}

@Override
public ExcelResult<T> getExcelResult(){
return excelResult;
}

@Override
publicvoidsetPreHeaderData(Map<String, String> preHeaderData){

}

2.3 Excel 返回对象

publicinterfaceExcelResult<T> {

/**
* 对象列表
*/
List<T> getList();

/**
* 错误列表
*/
List<String> getErrorList();

/**
* 导入回执
*/
String getAnalysis();
}

2.4 默认excel返回对象实现

publicclassDefaultExcelResult<T> implementsExcelResult<T> {

/**
* 数据对象list
*/
@Setter
private List<T> list;

/**
* 错误信息列表
*/
@Setter
private List<String> errorList;

publicDefaultExcelResult(){
this.list = new ArrayList<>();
this.errorList = new ArrayList<>();
}

publicDefaultExcelResult(List<T> list, List<String> errorList){
this.list = list;
this.errorList = errorList;
}

publicDefaultExcelResult(ExcelResult<T> excelResult){
this.list = excelResult.getList();
this.errorList = excelResult.getErrorList();
}

@Override
public List<T> getList(){
return list;
}

@Override
public List<String> getErrorList(){
return errorList;
}

/**
* 获取导入回执
*
* @return 导入回执
*/
@Override
public String getAnalysis(){
int successCount = list.size();
int errorCount = errorList.size();
if (successCount == 0) {
return"读取失败,未解析到数据";
} else {
if (errorCount == 0) {
return StrUtil.format("恭喜您,全部读取成功!共{}条", successCount);
} else {
return"";
}
}
}
}

3. 特殊 Excel

3.1 单元格位置类

/**
* 单元格位置类
* 用于表示Excel中的单元格位置
*
* @author 鲁子狄
* @since 2025/03/19 11:30
**/
@Data
publicclassCellPosition{

/**
* 行索引
*/
privatefinalint row;

/**
* 列索引
*/
privatefinalint column;


/**
* 构造函数
* 将列字母转换为列索引
*
* @param row 行索引
* @param columnLetter 列字母
*/
publicCellPosition(int row, String columnLetter){
this.row = row - 1;
column = getColumnIndex(columnLetter);
}

/**
* 将列字母转换为列索引
*
* @param columnLetter 列字母
* @return 列索引
*/
privatestaticintgetColumnIndex(String columnLetter){
int columnIndex = 0;
for (int i = 0; i < columnLetter.length(); i++) {
columnIndex = columnIndex * 26 + (columnLetter.charAt(i) - 'A' + 1);
}
return columnIndex - 1;
}
}

3.2 预表头监听器

/**
* 预表头监听器
* 用于在读取Excel文件时处理预表头数据
*
* @author 鲁子狄
* @since 2025/03/18 14:55
**/
@Slf4j
@Getter
publicclassPreHeaderListenerextendsAnalysisEventListener<Map<Integer, String>> {

/**
* 存储目标单元格的位置
*/
privatefinal Set<CellPosition> targetCells;
/**
* 存储预表头数据
*/
privatefinal Map<String, String> preHeaderData = new HashMap<>();

/**
* 构造函数
*
* @param targetCells 目标单元格的位置集合
*/
publicPreHeaderListener(Set<CellPosition> targetCells){
this.targetCells = targetCells;
}

/**
* 将列索引转换为列字母
*
* @param columnIndex 列索引
* @return 列字母
*/
privatestatic String getColumnLetter(int columnIndex){
StringBuilder columnLetter = new StringBuilder();
while (columnIndex >= 0) {
columnLetter.insert(0, (char) ('A' + columnIndex % 26));
columnIndex = columnIndex / 26 - 1;
}
return columnLetter.toString();
}

/**
* 处理每一行数据
*
* @param map 当前行的数据
* @param context 分析上下文
*/
@Override
publicvoidinvoke(Map<Integer, String> map, AnalysisContext context){
int rowIndex = context.readRowHolder().getRowIndex();
for (CellPosition position : targetCells) {
if (position.getRow() == rowIndex) {
int columnIndex = position.getColumn();
if (map.containsKey(columnIndex)) {
String cellValue = map.get(columnIndex);
preHeaderData.put("行" + (rowIndex + 1) + "列" + getColumnLetter(columnIndex), cellValue);
}
}
}
}

/**
* 所有数据解析完成后调用
*
* @param analysisContext 分析上下文
*/
@Override
publicvoiddoAfterAllAnalysed(AnalysisContext analysisContext){
}
}
文章目录
  1. 1. 一. 特殊 Excel 表格例子
  2. 2. 二. 使用示例
    1. 2.1. 1. bo 示例(部分字段)
    2. 2.2. 2. 自定义监听器
    3. 2.3. 3. 使用方法
  3. 3. 三. 实现
    1. 3.1. 1. 工具类
    2. 3.2. 2. 默认基础导入(特殊 Excel 建立在这个基础上)
      1. 3.2.1. 2.1 Excel 导入监听
      2. 3.2.2. 2.2 默认监听
      3. 3.2.3. 2.3 Excel 返回对象
      4. 3.2.4. 2.4 默认excel返回对象实现
    3. 3.3. 3. 特殊 Excel
      1. 3.3.1. 3.1 单元格位置类
      2. 3.3.2. 3.2 预表头监听器