欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

EasyExcel實現(xiàn)讀取excel中的日期單元格并自動判定終止讀取

 更新時間:2024年11月08日 10:20:16   作者:weixin_45614626  
這篇文章主要為大家詳細介紹了EasyExcel如何實現(xiàn)讀取excel中的日期單元格并自動判定終止讀取,感興趣的小伙伴可以跟隨小編一起學習一下

個人在工作中遇到一個需求,讀取第三方的對賬文件,但是對賬文件中的日期的單元格格式不是文本,這樣讀出來就會是一個數字,表示的是1990年1月1日距今的天數。而且這個excel中分成了兩個表格,第一個表格是我需要的,第二個表格不需要讀取,同時還有說明行,表頭不是第一行等約束。

最初樸素的想法是讀取出來數字,然后用Date接受,這樣就知道需要excel對應的這串數字其實是日期,然后再自定義的listener里邊定義converter做日期轉換。大致代碼如下:

public class CustomModelBuildListener<T> extends AnalysisEventListener<Object> {

    /**
     * head 所在行,用于多head行的情況下, 只想要獲取某行數據作為head
     */
    private final int headRow;

    /**
     * data 起始行, 可以跳過某些不想讀取的數據,比如示例數據
     */
    private final int dataRow;

    /**
     * 最終數據存放List
     */
    @Getter
    private final List<T> result = new ArrayList<>();

    /**
     * 用于將原始結構轉為指定類型
     */
    private final Class<T> clazz;

    /**
     * 用來對應字段關系
     */
    private Map<Integer, ReadCellData<?>> headMap;

    public CustomModelBuildListener(int headRow, int dataRow, Class<T> clazz) {
        this.headRow = headRow;
        this.dataRow = dataRow;
        this.clazz = clazz;
    }

    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        if (getCurrentRow(context) != headRow) {
            return;
        }
        this.headMap = headMap;
        //log.info("Invoke head of listener: {}", JsonUtils.writeValue(headMap));
    }

    @Override
    public void invoke(Object data, AnalysisContext context) {
        int currentRow = getCurrentRow(context);
        if (currentRow < dataRow) {
            return;
        }
        this.result.add(converter(data));
        //log.info("Invoke data of listener, data:{}, row: {}", JsonUtils.writeValue(data), currentRow);
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        log.info("All analysed done of listener, row: {}", getCurrentRow(context));
    }

    private int getCurrentRow(AnalysisContext context) {
        return context.readRowHolder().getRowIndex() + 1;
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    private T converter(Object data) {
        try {
            Map<Integer, ReadCellData> dataMap = (Map<Integer, ReadCellData>) data;
            Map<String, Object> name2Value = new HashMap<>();
            headMap.forEach((key, value) -> {
                if (!dataMap.containsKey(key)) {
                    return;
                }
                ReadCellData<?> cellData = dataMap.get(key);
                if (cellData.getType() == CellDataTypeEnum.NUMBER) {
                    name2Value.put(value.getStringValue(), cellData.getNumberValue());
                } else if (cellData.getType() == CellDataTypeEnum.STRING) {
                    name2Value.put(value.getStringValue(), cellData.getStringValue());
                }
            });
            T instance = clazz.newInstance();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(ExcelProperty.class)) {
                    ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
                    String desc = excelProperty.value()[0];
                    if (name2Value.containsKey(desc)) {
                        field.setAccessible(true);
                        if (field.getType() == Integer.class) {
                            Object value = name2Value.get(desc);
                            if (value instanceof BigDecimal) {
                                field.set(instance, ((BigDecimal) value).intValue());
                            } else if (value instanceof String) {
                                field.set(instance, Integer.parseInt((String) value));
                            } else {
                                log.warn("Unknown type for integer convert, type:{}", value.getClass());
                                throw new RuntimeException("Unknown type for converter, type:" + field.getType());
                            }
                        } else if (field.getType() == Long.class) {
                            Object value = name2Value.get(desc);
                            if (value instanceof BigDecimal) {
                                field.set(instance, ((BigDecimal)value).longValue());
                            } else if (value instanceof String) {
                                field.set(instance, Long.parseLong((String)value));
                            } else {
                                log.warn("Unknown type for long convert, type:{}", value.getClass());
                                throw new RuntimeException("Unknown type for converter, type:" + field.getType());
                            }
                        } else if (field.getType() == Double.class) {
                            Object value = name2Value.get(desc);
                            if (value instanceof BigDecimal) {
                                field.set(instance, ((BigDecimal)value).doubleValue());
                            } else if (value instanceof String) {
                                field.set(instance, Double.parseDouble((String)value));
                            } else {
                                log.warn("Unknown type for double convert, type:{}", value.getClass());
                                throw new RuntimeException("Unknown type for converter, type:" + field.getType());
                            }
                        } else if (field.getType() == String.class) {
                            field.set(instance, name2Value.get(desc));
                        } else if (field.getType() == Date.class) {
                            field.set(instance, TimeUtils.formatExcelDate((Integer) name2Value.get(desc)));
                        } else {
                            //走到這個邏輯的話,說明轉換的某些類型沒有做適配,補齊下就好
                            log.warn("Unknown type for converter, type:{}", field.getType());
                            throw new RuntimeException("Unknown type for converter, type:" + field.getType());
                        }
                    }
                }
            }
            return instance;
        } catch (Exception e) {
            log.error("Convert data error, data:{}", JsonUtils.writeValue(data), e);
            throw new RuntimeException(e);
        }
    }
}

關鍵就在于最后的field.getType() == Date.class表示了需要把數字轉為日期,但是如果excel里邊本來就是文本格式,你又用Date接受,也會走到這個邏輯里邊,然后就會報錯,因為轉換方法如下:

public static Date formatExcelDate(int day) {
        LocalDate localDate = LocalDate.ofYearDay(1990, 1);
        localDate = localDate.plusDays(day);
        return Date.from(localDate.atStartOfDay().atZone(ZoneId.systemDefault()).toInstant());
    }

所以這種寫法只適用于excel是非文本格式的日期,并且接受類屬性是Date。

于是發(fā)現(xiàn)了第二種方法。

點進去@ExcelProperty注解,發(fā)現(xiàn)有以下的注釋

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ExcelProperty {

    /**
     * The name of the sheet header.
     *
     * <p>
     * write: It automatically merges when you have more than one head
     * <p>
     * read: When you have multiple heads, take the last one
     *
     * @return The name of the sheet header
     */
    String[] value() default {""};

    /**
     * Index of column
     *
     * Read or write it on the index of column, If it's equal to -1, it's sorted by Java class.
     *
     * priority: index &gt; order &gt; default sort
     *
     * @return Index of column
     */
    int index() default -1;

    /**
     * Defines the sort order for an column.
     *
     * priority: index &gt; order &gt; default sort
     *
     * @return Order of column
     */
    int order() default Integer.MAX_VALUE;

    /**
     * Force the current field to use this converter.
     *
     * @return Converter
     */
    Class<? extends Converter<?>> converter() default AutoConverter.class;

    /**
     *
     * default @see com.alibaba.excel.util.TypeUtil if default is not meet you can set format
     *
     * @return Format string
     * @deprecated please use {@link com.alibaba.excel.annotation.format.DateTimeFormat}
     */
    @Deprecated
    String format() default "";
}

最后一個format被廢棄了,但是指向了一個新的注解,是用來解析時間轉換成Date類型的,

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface DateTimeFormat {

    /**
     *
     * Specific format reference {@link java.text.SimpleDateFormat}
     *
     * @return Format pattern
     */
    String value() default "";

    /**
     * True if date uses 1904 windowing, or false if using 1900 date windowing.
     *
     * @return True if date uses 1904 windowing, or false if using 1900 date windowing.
     */
    BooleanEnum use1904windowing() default BooleanEnum.DEFAULT;
}

于是使用這個注解,指定format,例如-MM-dd。

但是因為需要指定表頭行,并且手動中斷讀取進程,所以還是自定義了一個listener

public class DefaultModelBuildListener<T> extends AnalysisEventListener<T> {

    /**
     * 最終數據存放List
     */
    @Getter
    private List<T> result = new ArrayList<>();

    private boolean continueRead = true;



    @Override
    public void invoke(T data, AnalysisContext context) {
        if (Objects.isNull(data) || isFieldNull(data)) {
            // 停止讀取
            continueRead = false;
            return;
        }
        // 將讀到的數據添加到列表中
        result.add(data);
    }

    public static boolean isFieldNull(Object object) {
        if (object == null) {
            return true;
        }

        Field[] fields = object.getClass().getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                if (field.get(object) == null) {
                    return true;
                } else {
                    return false;
                }
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        }
        return false;
    }


    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        log.info("All analysed done of listener");
    }

    @Override
    public boolean hasNext(AnalysisContext context) {
        return continueRead;
    }
}

最重要的是判斷終止條件的地方,因為第一個表和第二個表不一樣,所以第一個表的列不在第二個表格中,讀取到第二個表對應實體類屬性就是null,所以在invoke方法里加了條件。最初發(fā)現(xiàn)null用的是context.interupt。但是發(fā)現(xiàn)報異常,點進去發(fā)現(xiàn)又是一個廢棄的方法,提示用hasNext來終止,進一步深入,發(fā)現(xiàn)讀取的處理過程是:

for (ReadListener readListener : analysisContext.currentReadHolder().readListenerList()) {
            try {
                if (isData) {
                    readListener.invoke(readRowHolder.getCurrentRowAnalysisResult(), analysisContext);
                } else {
                    readListener.invokeHead(cellDataMap, analysisContext);
                }
            } catch (Exception e) {
                onException(analysisContext, e);
                break;
            }
            if (!readListener.hasNext(analysisContext)) {
                throw new ExcelAnalysisStopException();
            }
        }

每次讀取完一行,會用hasNext判斷繼不繼續(xù),所以重寫hasNext方法來無異常終止即可。

測試下來沒問題。

最終兩種讀取方法如下

/**
     * Excel解析方法, 自定義屬性較多,建議優(yōu)先使用parse(InputStream inputStream, Class<T> clazz)
     * @param inputStream 輸入流
     * @param clazz       數據模型類的類型
     * @param <T>         數據模型的泛型
     * @param headRowNumber 表頭行一共有幾行,這里邊的行都是不會讀取的
     * @param headRow 表頭行行號
     * @param dataRow 數據行開始行數
     * @return 數據列表
     */
    public static <T> List<T> parse(InputStream inputStream, Class<T> clazz, int headRowNumber, int headRow, int dataRow) {
        CustomModelBuildListener<T> dataListener = new CustomModelBuildListener<>(headRow,dataRow, clazz);
        try (InputStream in = inputStream) {
            ExcelReaderBuilder excelReaderBuilder = EasyExcel
                    .read(in, clazz, dataListener)
                    .useDefaultListener(false); //很重要,將不使用ModelBuildEventListener轉換對象
            ExcelReaderSheetBuilder sheetBuilder = excelReaderBuilder.sheet().headRowNumber(headRowNumber);
            sheetBuilder.doRead();
            return dataListener.getResult();
        } catch (Exception e) {
            log.error("Failed to parse excel file", e);
            throw new ServiceException(SystemCode.PARAM_VALID_ERROR, "Failed to parse Excel file "+ e.getMessage());
        }
    }

    /**
     * Excel解析方法, 自定義屬性較多,建議優(yōu)先使用parse(InputStream inputStream, Class<T> clazz)
     * @param inputStream 輸入流
     * @param clazz       數據模型類的類型
     * @param <T>         數據模型的泛型
     * @param headRowNumber 表頭是第幾行,默認下一行就是數據,如果表頭和數據行中間還有不希望讀取的行,需要在listener自定義處理
     * @return 數據列表
     */
    public static <T> List<T> parseDefault(InputStream inputStream, Class<T> clazz, int headRowNumber) {
        DefaultModelBuildListener excelDataListener = new DefaultModelBuildListener();
        try (InputStream in = inputStream) {
            EasyExcel.read(in, clazz, excelDataListener)
                    .sheet()
                    .headRowNumber(headRowNumber)
                    .doRead();
            return excelDataListener.getResult();
        } catch (Exception e) {
            log.error("Failed to parse excel file", e);
            throw new ServiceException(SystemCode.PARAM_VALID_ERROR, "Failed to parse Excel file "+ e.getMessage());
        }
    }

第一種方法對應的是parse方法,第二種方案對應的是parseDefault方法。

到此這篇關于EasyExcel實現(xiàn)讀取excel中的日期單元格并自動判定終止讀取的文章就介紹到這了,更多相關EasyExcel讀取excel日期單元格內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • maven如何使用profiles多環(huán)境配置

    maven如何使用profiles多環(huán)境配置

    在軟件開發(fā)過程中,我們經常需要在不同的環(huán)境中部署和運行我們的應用程序,例如開發(fā)環(huán)境、測試環(huán)境和生產環(huán)境,為了方便管理和配置不同環(huán)境下的參數,我們可以使用Maven的profiles功能,本文給大家介紹maven如何使用profiles多環(huán)境配置,感興趣的的朋友一起看看吧
    2024-02-02
  • 教你快速學會JPA中所有findBy語法規(guī)則

    教你快速學會JPA中所有findBy語法規(guī)則

    這篇文章主要介紹了教你快速學會JPA中所有findBy語法規(guī)則,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • SpringBoot接口加密與解密的實現(xiàn)

    SpringBoot接口加密與解密的實現(xiàn)

    這篇文章主要介紹了SpringBoot接口加密與解密的實現(xiàn)
    2023-10-10
  • java實現(xiàn)二維碼生成功能詳細示例

    java實現(xiàn)二維碼生成功能詳細示例

    這篇文章主要給大家介紹了關于java實現(xiàn)二維碼生成功能的相關資料,隨著信息化時代的到來,二維碼作為一種信息傳遞的工具,越來越受到人們的歡迎,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2023-07-07
  • spring+springmvc+mybatis 開發(fā)JAVA單體應用

    spring+springmvc+mybatis 開發(fā)JAVA單體應用

    這篇文章主要介紹了spring+springmvc+mybatis 開發(fā)JAVA單體應用的相關知識,本文通過圖文實例代碼的形式給大家介紹的非常詳細 ,需要的朋友可以參考下
    2018-11-11
  • 基于Beanutils.copyProperties()的用法及重寫提高效率

    基于Beanutils.copyProperties()的用法及重寫提高效率

    這篇文章主要介紹了Beanutils.copyProperties( )的用法及重寫提高效率的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • 根據ID填充文本框的實例代碼

    根據ID填充文本框的實例代碼

    這篇文章介紹了根據ID填充文本框的小例子,有需要的朋友可以參考一下
    2013-07-07
  • springBoot詳細講解使用mybaties案例

    springBoot詳細講解使用mybaties案例

    MyBatis本是apache的一個開源項目iBatis,2010年這個項目由apache software foundation遷移到了google code,并且改名為MyBatis。2013年11月遷移到Github。iBATIS一詞來源于“internet”和“abatis”的組合,是一個基于Java的持久層框架
    2022-05-05
  • Java應用CPU使用率過高排查方式

    Java應用CPU使用率過高排查方式

    這篇文章主要介紹了Java應用CPU使用率過高排查方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-07-07
  • SpringCloud配置客戶端ConfigClient接入服務端

    SpringCloud配置客戶端ConfigClient接入服務端

    這篇文章主要為大家介紹了SpringCloud配置客戶端ConfigClient接入服務端,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-08-08

最新評論