在当今数据驱动的世界中,有效地管理和处理大量数据已成为企业成功的关键因素之一。特别是对于那些依赖于Excel文件进行数据交换和报告的企业来说,能够快速准确地将Excel数据转换为可操作的信息变得尤为重要。然而,传统的数据导入方法往往面临着性能瓶颈、格式不兼容以及缺乏灵活性等问题。本文介绍了一种基于Java的解决方案,它利用了阿里巴巴开源的EasyExcel库来实现高效且灵活的Excel数据导入功能。我们将探讨如何通过自定义监听器和工具类来处理复杂的Excel表结构,并展示如何轻松应对各种数据验证需求。
期间有些数据转换之类的方法,参考 :
[笔记] SpringBoot3 使用 EasyExcel 封装工具类实现,自定义表头导出,并实现数据格式化转换与添加下拉框操作. - 掘金编写的逻辑是在狮子大佬的开源框架 Ruoyi-Plus 基础上丰富的内容
Ruoyi-Plus 地址 : Ruoyi-Vue-Plus
一. 特殊 Excel 表格例子
二. 使用示例
1. bo 示例(部分字段)
bo 和 vo 注解一样
2. 自定义监听器
3. 使用方法
三. 实现
1. 工具类
java
/**
* Excel相关处理
*
* @author 鲁子狄
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ExcelUtil {
/**
* 同步导入(适用于小数据量)
*
* @param is 输入流
* @return 转换后集合
*/
public static <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 转换后集合
*/
public static <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 转换后集合
*/
public static <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 转换后集合
*/
public static <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) {
throw new 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) {
throw new 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) {
throw new RuntimeException("导出Excel异常", e);
}
return listener.getExcelResult();
}
}
2. 默认基础导入(特殊 Excel 建立在这个基础上)
特殊 Excel 建立在这个基础上,这块的代码也要有
2.1 Excel 导入监听
java
public interface ExcelListener<T> extends ReadListener<T> {
ExcelResult<T> getExcelResult();
/**
* 设置预表头数据
*
* @param preHeaderData 预表头数据
*/
void setPreHeaderData(Map<String, String> preHeaderData);
}
2.2 默认监听
java
@Slf4j
@NoArgsConstructor
public class DefaultExcelListener<T> extends AnalysisEventListener<T> implements ExcelListener<T> {
/**
* 是否Validator检验,默认为是
*/
private Boolean isValidate = Boolean.TRUE;
/**
* excel 表头数据
*/
private Map<Integer, String> headMap;
/**
* 导入回执
*/
private ExcelResult<T> excelResult;
public DefaultExcelListener(boolean isValidate) {
this.excelResult = new DefaultExcelResult<>();
this.isValidate = isValidate;
}
/**
* 处理异常
*
* @param exception ExcelDataConvertException
* @param context Excel 上下文
*/
@Override
public void onException(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);
throw new ExcelAnalysisException(errMsg);
}
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
this.headMap = headMap;
log.debug("解析到一条表头数据: {}", JsonUtils.toJsonString(headMap));
}
@Override
public void invoke(T data, AnalysisContext context) {
if (isValidate) {
ValidatorUtils.validate(data);
}
excelResult.getList().add(data);
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
log.debug("所有数据解析完成!");
}
@Override
public ExcelResult<T> getExcelResult() {
return excelResult;
}
@Override
public void setPreHeaderData(Map<String, String> preHeaderData) {
}
2.3 Excel 返回对象
java
public interface ExcelResult<T> {
/**
* 对象列表
*/
List<T> getList();
/**
* 错误列表
*/
List<String> getErrorList();
/**
* 导入回执
*/
String getAnalysis();
}
2.3 默认excel返回对象实现
java
public class DefaultExcelResult<T> implements ExcelResult<T> {
/**
* 数据对象list
*/
@Setter
private List<T> list;
/**
* 错误信息列表
*/
@Setter
private List<String> errorList;
public DefaultExcelResult() {
this.list = new ArrayList<>();
this.errorList = new ArrayList<>();
}
public DefaultExcelResult(List<T> list, List<String> errorList) {
this.list = list;
this.errorList = errorList;
}
public DefaultExcelResult(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 单元格位置类
java
/**
* 单元格位置类
* 用于表示Excel中的单元格位置
*
* @author 鲁子狄
* @since 2025/03/19 11:30
**/
@Data
public class CellPosition {
/**
* 行索引
*/
private final int row;
/**
* 列索引
*/
private final int column;
/**
* 构造函数
* 将列字母转换为列索引
*
* @param row 行索引
* @param columnLetter 列字母
*/
public CellPosition(int row, String columnLetter) {
this.row = row - 1;
column = getColumnIndex(columnLetter);
}
/**
* 将列字母转换为列索引
*
* @param columnLetter 列字母
* @return 列索引
*/
private static int getColumnIndex(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 预表头监听器
java
/**
* 预表头监听器
* 用于在读取Excel文件时处理预表头数据
*
* @author 鲁子狄
* @since 2025/03/18 14:55
**/
@Slf4j
@Getter
public class PreHeaderListener extends AnalysisEventListener<Map<Integer, String>> {
/**
* 存储目标单元格的位置
*/
private final Set<CellPosition> targetCells;
/**
* 存储预表头数据
*/
private final Map<String, String> preHeaderData = new HashMap<>();
/**
* 构造函数
*
* @param targetCells 目标单元格的位置集合
*/
public PreHeaderListener(Set<CellPosition> targetCells) {
this.targetCells = targetCells;
}
/**
* 将列索引转换为列字母
*
* @param columnIndex 列索引
* @return 列字母
*/
private static 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
public void invoke(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
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
}