解析Excel数据如虎添翼:Excel数据监听器助你快速解析数据,轻松驾驭业务需求,一键解析,风云再起,数据处理从未如此简单,事半功倍

以下代码是一个Excel数据监听器,用于监听和处理Excel数据的读取事件。它实现了AnalysisEventListener接口,并重写了其中的方法。以下是代码中的主要部分:

1、整体解读

在使用EasyExcel读取Excel文件时,如果没有指定表头所在的行数,EasyExcel会默认根据内容进行智能识别,尝试找到表头所在的行

  • invokeHead方法:在解析Excel表格的表头时触发的回调方法。通过比较模板表头和当前上传文件的表头,检查表头的合法性。
  • invoke方法:在解析Excel表格的数据行时触发的回调方法。在这个方法中可以处理每一行的数据逻辑。
  • onException方法:当解析过程中发生异常时触发的回调方法。如果是Excel数据转换异常,则会记录日志并抛出自定义的BizException异常。其他异常会直接抛出。
  • doAfterAllAnalysed方法:当整个Excel表格解析完成时触发的回调方法。在这个方法中可以进行数据处理、数据存储、数据校验等业务逻辑的处理。
  • getDataList方法:用于获取解析过程中的数据行信息。
  • readTemplateHeader方法:用于读取Excel模板的表头。

这段代码的作用是根据传入的Excel模板路径和文件名,解析Excel文件,并进行表头校验和数据行处理,文章中只处理了按照模板校验表头是否合法,针对动态传入List形式的表头可自行扩展

2、添加依赖


<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>easyexcelartifactId>
    <version>2.2.6version>
dependency>

<dependency>
	<groupId>org.dromara.hutoolgroupId>
	<artifactId>hutool-allartifactId>
	<version>6.0.0-M10version>
dependency>

3、完整代码

【注意】:这里推荐EasyExcel升级到3.3.1以上版本,2.X已经不维护了

/**
 * Excel数据监听器,用户监听和处理Excel数据的读取事件
 * 实现了EasyExcel库的AnalysisEventListener接口,实现了相应的回掉方法
 *
 * @author 管理员
 * @date 2024/2/27
 * @param  要解析的Excel数据对象的类型
 */
@Slf4j
public class ExcelListener < T > extends AnalysisEventListener < T > {
    /**
     * 文件特殊字符规则
     */
    private static final String FILE_SPECIAL_REGEX = "[\\/:*?\"<>|;.]";

    /**
     * 用于存储解析过程中数据行信息
     */
    private final List < T > dataList = Lists.newArrayList();

    /**
     * Excel模板路径(此实现是resource路径)
     */
    private String templatePath;

    /**
     * 模板表头集合
     */
    private List < String > templateHeaderList;

    /**
     * Excel数据监听器构造方法
     *
     * @author 管理员
     * @date 2024/3/4
     */
    public ExcelListener() {
    }

    /**
     * Excel数据监听器构造方法,此构造方法会校验表头是否合法【注意】:此方法默认为第一行为表头,如不是请重写构造方法传入表头对应行数
     *
     * @author 管理员
     * @date 2024/3/4
     * @param templatePath 模板路径(resource路径)
     */
    public ExcelListener(String templatePath) {
        this.templatePath = templatePath;
        this.templateHeaderList = readTemplateHeader();
    }

    /**
     * Excel数据监听器构造方法,此构造方法会校验表头是否合法,文件名称非法
     * 【注意】:此方法默认为第一行为表头,如不是请重写构造方法传入表头对应行数
     *
     * @author 管理员
     * @date 2024/3/4
     * @param templatePath 模板路径(resource路径)
     * @param excelFileName 上传Excel文件名称
     * @throws BizException 业务异常
     */
    public ExcelListener(String templatePath, String excelFileName) {
        // 文件名不能包含特殊字符
        if (ReUtil.contains(FILE_SPECIAL_REGEX, FileNameUtil.mainName(excelFileName))) {
            throw new BizException("业务错误码","误码提示");
        }

        // 判断文件后缀是否是Excel 文件格式错误
        if (!ReUtil.isMatch("(xlsx|xls)$", FileNameUtil.extName(excelFileName).toLowerCase())) {
            throw new new BizException("业务错误码","误码提示");
        }

        this.templatePath = templatePath;
        this.templateHeaderList = readTemplateHeader();
    }

    @Override
    public void invokeHead(Map < Integer, CellData > headMap, AnalysisContext context) {
        // 当解析Excel表格的表头时触发的回调方法,此处可在类中添加成员变量,校验表头合法性
        // invokeHead抛出异常只会终止当前invokeHead方法的执行,可在OnException中处理并终止程序
        // headMap存储所有表头信息,可与成员变量对比,不一致则不合法
        // 未传入模板路劲则表示无须对比模板表头,直接调用默认父类方法
        if (CharSequenceUtil.isBlank(templatePath)) {
            super.invokeHead(headMap, context);
            return;
        }

        // 模板表头无内容表示模板错误
        if (CollUtil.isEmpty(templateHeaderList)) {
            throw new BizException("业务错误码","误码提示");
        }

        List < String > currentHeaderList = Lists.newArrayListWithCapacity(headMap.size());
        headMap.forEach((columnIndex, cellData) -> currentHeaderList.add(cellData.getStringValue()));

        // 比较模板的表头与当前上传文件的表头,如果两个表头不一致则表示模板错误,抛出异常
        if (!CollUtil.isEqualList(templateHeaderList, currentHeaderList)) {
            throw new BizException("业务错误码","误码提示");
        }

        // 无任何异常执行直接调用默认父类方法
        super.invokeHead(headMap, context);
    }

    @Override
    public void invoke(T rowData, AnalysisContext analysisContext) {
        // 当解析Excel表格的数据行时触发的回调方法,在这个方法中可以处理每一行的数据逻辑
        dataList.add(rowData);
    }

    @Override
    public void onException(Exception exception, AnalysisContext context) {
        // onException抛出异常会终止解析,并在最外层可捕获该异常
        // 解析过程中发生异常的逻辑,一般记录日志
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            StringBuilder stringBuffer = new StringBuilder();
            stringBuffer.append("第").append(excelDataConvertException.getRowIndex()).append("行,")
                    .append(excelDataConvertException.getColumnIndex()).append("列解析异常").append("异常数据为:")
                    .append(excelDataConvertException.getCellData()).append("异常列属性为:")
                    .append(excelDataConvertException.getExcelContentProperty().getHead());
            log.error(stringBuffer.toString());
            throw new BizException("业务错误码",stringBuffer.toString());
        }
        else if (exception instanceof BizException) {
            throw (BizException) exception;
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // 当整个Excel表格解析完成时触发的回调方法,解析完毕之后一般有以下逻辑处理
        // 数据处理、数据存储、数据校验、业务通知

        // 逻辑1:当dataList为空,标识文件中没有数据行,可以认为是一个人空模板
        if (CollUtil.isEmpty(dataList)) {
            throw new BizException("业务错误码","误码提示");
        }
    }

    /**
     * 获取解析过程中的数据行信息
     *
     * @author 管理员
     * @date 2024/2/27
     * @return 数据行信息
     */
    public List < T > getDataList() {
        return dataList;
    }

    /**
     * 读取Excel模板表头
     *
     * @author 管理员
     * @date 2024/3/4
     * @return Excel模板表头
     * @throws BizException 业务异常
     */
    private List < String > readTemplateHeader() {
        // 无模板路径不解析模板表头
        if (CharSequenceUtil.isBlank(templatePath)) {
            return Lists.newArrayList();
        }

        try (InputStream inputStream = ResourceUtil.getStreamSafe(templatePath)) {
            List < Map < Integer, String > > headList = EasyExcelFactory.read(inputStream).headRowNumber(0).sheet()
                    .doReadSync();

            // 读取表头不为空,只要模板没问题,肯定不会空
            if (CollUtil.isNotEmpty(headList)) {
                Map < Integer, String > headMap = headList.get(0);
                List < String > headerList = Lists.newArrayListWithExpectedSize(headMap.size());
                headMap.forEach((key, value) -> headerList.add(value));
                return headerList;
            }
        }
        catch (IOException ex) {
            log.error("读取Excel模板表头异常", ex);
            throw new BizException("业务错误码","误码提示");
        }
        return Lists.newArrayList();
    }
}

4、导入导出支持LocalDateTime

【说明】:下面代码是使用EasyExcel版本为:3.3.1,自定义转换器支持LocalDateTime,试用了官方的LocalDateTimeDateConverter发现效果并不理想,所以自行编写了一个。

/**
 * 阿里EasyExcel时间转换器
 *
 * @author 虾酱
 * @since 2024/3/12 17:15
 */
public class LocalDateTimeConverter implements Converter < LocalDateTime > {
    /**
     * Excel时间转换格式
     */
    private final String dateFormat;

    public LocalDateTimeConverter(String dateFormat) {
        this.dateFormat = dateFormat;
    }

    @Override
    public Class < LocalDateTime > supportJavaTypeKey() {
        return LocalDateTime.class;
    }

    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }

    @Override
    public LocalDateTime convertToJavaData(ReadCellData < ? > cellData, ExcelContentProperty contentProperty,
            GlobalConfiguration globalConfiguration) {
        // 应用场景:导入
        return TimeUtil.parse(cellData.getStringValue(), dateFormat);
    }

    @Override
    public WriteCellData < String > convertToExcelData(LocalDateTime localDateTime,
            ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
        // 应用场景:导出
        return new WriteCellData <>(TimeUtil.format(localDateTime, dateFormat));
    }
}

方式一:表头文件什么的都不做任何校验

ExcelListener < T > excelListener = new ExcelListener <>();

方式二:此种方式只校验表头

ExcelListener < T > excelListener = new ExcelListener <>(templatePath);

方式三:最后这种方式校验表头与文件的合法性

ExcelListener < T > excelListener = new ExcelListener <>(templatePath,fileName);

excelListener 传入EasyExcelFactory中实现文件解析并转换为对应实体对象

EasyExcelFactory.read(inputStream, clazz, excelListener).sheet().doRead();

总而言之,该Excel数据监听器可以帮助用户高效、准确地解析和处理Excel数据。它提供了表头校验、数据处理和异常处理等功能,使得数据的导入和处理变得更加简单和可靠。无论是处理大规模数据还是进行数据校验,该监听器都能够帮助用户提升工作效率,减少错误率。

【扩展内容】:保留每一行数据的行号
这种情况适用于需要在处理Excel数据时保留每一行数据的行号的场景。例如:

  1. 数据校验:在处理Excel数据时,你可能需要对每一行数据进行校验。如果校验失败,你可能希望知道是哪一行数据出错了。通过在ExcelRow对象中保存行号,你可以方便地定位到出错的行。

  2. 数据处理:在处理Excel数据时,你可能需要根据每一行的行号进行特定的数据处理操作。例如,你可能需要根据行号将数据存储到不同的数据库表中,或者根据行号做一些特定的逻辑处理。

  3. 错误处理:如果在处理Excel数据时发生错误,你可能希望进行相应的错误处理,并记录下出错的行号。通过ExcelRow对象,你可以方便地获取到出错行的行号,并进行错误处理。

总之,通过在ExcelRow对象中添加行号字段,你可以在处理Excel数据时方便地获取到每一行数据及其对应的行号,这样能够更加灵活地进行后续的数据处理和错误处理操作。

在T对象中添加一个行数字段,可以通过创建一个新的包含行数字段的类来实现。这个新的类可以包含两个字段:rowData表示Excel的每一行数据,rowNumber表示行数。

public class ExcelRow<T> {
    private T rowData; // Excel每一行的数据
    private int rowNumber; // 行数

    public ExcelRow(T rowData, int rowNumber) {
        this.rowData = rowData;
        this.rowNumber = rowNumber;
    }

    public T getRowData() {
        return rowData;
    }

    public int getRowNumber() {
        return rowNumber;
    }
}
public class ExcelListener<T> extends AnalysisEventListener<T> {
    private List<ExcelRow<T>> dataList = new ArrayList<>();

    // ...

    @Override
    public void invoke(T rowData, AnalysisContext analysisContext) {
        int rowNumber = analysisContext.readRowHolder().getRowIndex();
        ExcelRow<T> excelRow = new ExcelRow<>(rowData, rowNumber);
        dataList.add(excelRow);
    }

    // ...

    public List<ExcelRow<T>> getDataList() {
        return dataList;
    }
}

在上述代码中,invoke方法通过analysisContext.readRowHolder().getRowIndex()获取到当前行的行数,然后创建ExcelRow对象并添加到dataList中。

通过使用ExcelRow对象,你可以获取到每一行数据及其对应的行数。

你可能感兴趣的:(技术问题,excel,linux,java,maven,guava,spring,cloud,intellij-idea)