亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Java如何基于EasyExcel實(shí)現(xiàn)導(dǎo)入數(shù)據(jù)校驗(yàn)并生成錯(cuò)誤信息Excel

 更新時(shí)間:2024年09月11日 11:30:56   作者:有答案直接發(fā)給我  
這篇文章主要介紹了Java如何基于EasyExcel實(shí)現(xiàn)導(dǎo)入數(shù)據(jù)校驗(yàn)并生成錯(cuò)誤信息Excel,為了優(yōu)化項(xiàng)目中的文件導(dǎo)入功能,考慮構(gòu)建一個(gè)基于EasyExcel的通用Excel導(dǎo)入框架,主要解決導(dǎo)入數(shù)據(jù)的校驗(yàn)問題,避免業(yè)務(wù)代碼中堆積大量校驗(yàn)邏輯,需要的朋友可以參考下

功能設(shè)計(jì)

由于項(xiàng)目中涉及到大量的文件導(dǎo)入功能,故考慮設(shè)計(jì)一個(gè)excel導(dǎo)入的通用框架,解決以下問題

  • 導(dǎo)入的數(shù)據(jù)不可信任,可能出現(xiàn)空值校驗(yàn)的許多判斷,如果將這些判斷加入業(yè)務(wù)代碼可能會(huì)造成大量代碼的堆積,如下情況:
if(name==null){
	throw new RuntimeException("名稱不能為空");
}
if(age==null){
	throw new RuntimeException("年齡不能為空");
}
if(sex==null){
	throw new RuntimeException("性別不能為空");
}
if(order.size()>10){
	throw new RuntimeException("訂單號(hào)長度不能大于10");
}

  • EasyExcel幫我處理導(dǎo)入文件時(shí),只是簡單的根據(jù)列名把內(nèi)容set到字段上,如果字段類型不符是會(huì)直接報(bào)錯(cuò)的!而我們需要將數(shù)據(jù)的錯(cuò)誤內(nèi)容提交給用戶,所以如下的報(bào)錯(cuò)是不可取的

  • 針對(duì)文件中的問題,需要清晰地呈現(xiàn)給用戶,每一行具體出現(xiàn)了哪種類型的錯(cuò)誤,例如如下:

  • 基于EasyExcel封裝,由于項(xiàng)目中本身使用的EasyExcel,考慮到不改動(dòng)項(xiàng)目的技術(shù)組成,還是基于EasyExcel開發(fā)。

設(shè)計(jì)思路

EasyExcel做的工作其實(shí)很簡單,就是把文件中的內(nèi)容映射到我們實(shí)體類的字段上,我們要做的就是在映射前和映射后做校驗(yàn)

代碼解析

我先把完整代碼貼上,下面再詳細(xì)分析

注解類

/**
 * 導(dǎo)入校驗(yàn)注解
 *
 * @author wangmeng
 * @since 2024/5/25
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelCheck {

    /**
     * 是否可以為空,默認(rèn)是
     */
    boolean canEmpty() default true;

    /**
     * 是否可以重復(fù),默認(rèn)是
     */
    boolean canRepeat() default true;

    /**
     * 長度校驗(yàn),只對(duì)String生效
     */
    int length() default -1;
}

錯(cuò)誤信息實(shí)體類

/**
 * excel導(dǎo)入錯(cuò)誤信息
 *
 * @author wangmeng
 * @since 2024/5/25
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class ExcelErrorMessage {

    /**
     * 行號(hào)
     */
    private Integer rowNum;

    /**
     * 列名
     */
    private String colHeaderName;

    /**
     * 錯(cuò)誤信息
     */
    private String message;

}

導(dǎo)入通用的listener

/**
 * excel導(dǎo)入共通監(jiān)聽類
 *
 * @author wangmeng
 * @since 2024/5/25
 */
@Slf4j
public class CheckableImportListener<T> extends AnalysisEventListener<T> {


    /**
     * check注解對(duì)象
     */
    protected List<Object[]> filedList;
    /**
     * excel數(shù)據(jù)
     */
    protected final List<T> list = new ArrayList<>();
    /**
     * 錯(cuò)誤信息集合
     */
    @Getter
    private final List<ExcelErrorMessage> errorList = new ArrayList<>();


    private Boolean isEmpty = false;


    public CheckableImportListener() {
        super();
    }


    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        log.error("解析單元格失敗,", exception);
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException) exception;
            log.error("第{}行,第{}列解析異常,數(shù)據(jù)為:{}", excelDataConvertException.getRowIndex(),
                excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
        }
    }

    @Override
    public void invoke(T data, AnalysisContext context) {
        if (CollectionUtils.isEmpty(list)) {
            Class<?> clazz = data.getClass();
            //含check注解的字段
            filedList = Arrays.stream(clazz.getDeclaredFields())
                .filter(o -> null != o.getAnnotation(ExcelCheck.class))
                .map(o -> new Object[]{o, o.getAnnotation(ExcelCheck.class), o.getAnnotation(ExcelProperty.class)}).collect(Collectors.toList());
        }
        log.info("data:{}", JSON.toJSONString(data));
        list.add(data);
        if (CollectionUtils.isNotEmpty(filedList)) {
            checkEmpty(data);
            //存在空值則不進(jìn)行其他校驗(yàn)
            if (isEmpty) {
                return;
            }
            // 校驗(yàn)長度
            checkLength(data);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        if (isEmpty) {
            return;
        }
        errorList.sort(Comparator.comparing(ExcelErrorMessage::getRowNum));
    }

    /**
     * 檢驗(yàn)非空
     *
     * @param data
     */
    public void checkEmpty(T data) {
        for (Object[] os : filedList) {
            Field filed = (Field) os[0];
            filed.setAccessible(true);
            ExcelCheck excelCheck = (ExcelCheck) os[1];
            ExcelProperty excelProperty = (ExcelProperty) os[2];
            try {
                //校驗(yàn)非空
                if (!excelCheck.canEmpty()) {
                    if (filed.get(data) == null ||
                        (filed.getType() == String.class && StringUtils.isEmpty((String) filed.get(data)))) {
                        errorList.add(new ExcelErrorMessage()
                            .setRowNum(list.size() + 1)
                            .setColHeaderName(excelProperty.value()[0])
                            .setMessage(excelProperty.value()[0] + "字段不能為空!"));
                        isEmpty = true;
                    }
                }
            } catch (IllegalAccessException e) {
                log.error("校驗(yàn)excel信息失敗,", e);
                e.printStackTrace();
            }
        }
    }


    /**
     * 校驗(yàn)長度
     *
     * @param data
     */
    public void checkLength(T data) {
        for (Object[] os : filedList) {
            Field filed = (Field) os[0];
            filed.setAccessible(true);
            ExcelCheck excelCheck = (ExcelCheck) os[1];
            ExcelProperty excelProperty = (ExcelProperty) os[2];
            try {
                //校驗(yàn)非空
                if (excelCheck.length() > 0 && filed.getType() == String.class) {
                    String value = (String) filed.get(data);
                    if (value.length() > excelCheck.length()) {
                        errorList.add(new ExcelErrorMessage()
                            .setRowNum(list.size() + 1)
                            .setColHeaderName(excelProperty.value()[0])
                            .setMessage(excelProperty.value()[0] + "字段長度大于" + excelCheck.length() + "!"));
                    }
                }
            } catch (IllegalAccessException e) {
                log.error("校驗(yàn)字段長度失敗,", e);
                throw new RuntimeException(e);
            }
        }


    }


    /**
     * 檢驗(yàn)重復(fù)
     */
    public void checkRepeat() {
        List<Object[]> repeatAnnotation = filedList.stream().filter(o -> {
            ExcelCheck excelCheck = (ExcelCheck) o[1];
            return !excelCheck.canRepeat();
        }).collect(Collectors.toList());
        for (Object[] objects : repeatAnnotation) {
            ExcelProperty property = (ExcelProperty) objects[2];
            //使用iterate方式構(gòu)建流以獲取行號(hào)
            Stream.iterate(0, i -> i + 1).limit(list.size()).collect(Collectors.groupingBy(i -> {
                    Field field = (Field) objects[0];
                    String result = "";
                    try {
                        field.setAccessible(true);
                        result = JSON.toJSONString(field.get(list.get(i)));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                    return result;
                }, LinkedHashMap::new, Collectors.mapping(i -> i + 2, Collectors.toList())))
                .forEach((k, v) -> {
                    if (v.size() > 1) {
                        for (int i = 0; i < v.size(); i++) {
                            if (i == 0) {
                                continue;
                            }
                            errorList.add(new ExcelErrorMessage()
                                .setRowNum(v.get(i))
                                .setColHeaderName(property.value()[0])
                                .setMessage(property.value()[0] + "字段重復(fù)!"));
                        }
                    }
                });
        }
    }

    public void addError(Integer index, String errorMessage) {
        ExcelErrorMessage excelErrorMessage = new ExcelErrorMessage().setRowNum(index).setMessage(errorMessage);
        errorList.add(excelErrorMessage);
    }

}

導(dǎo)入處理器類

/**
 * excel導(dǎo)入處理器,在easyExcel基礎(chǔ)封裝,增加通用讀取、校驗(yàn)功能
 *
 * @author wangmeng
 * @since 2024/6/7
 */
@Setter
@Getter
@Accessors(chain = true)
@Slf4j
public class ExcelImportProcessor {

    /**
     * 默認(rèn)校驗(yàn)類型listener
     */
    private CheckableImportListener<?> listener = new CheckableImportListener<>();


    private Consumer<ExcelReaderBuilder> readerBuilderConsumer;

    /**
     * 默認(rèn)第一個(gè)sheet
     */
    private Integer sheetNo = 0;

    /**
     * 錯(cuò)誤列名
     */
    private final static String ERROR_COLUMN_NAME = "錯(cuò)誤信息";


    public ExcelImportProcessor() {

    }

    public ExcelImportProcessor(CheckableImportListener<?> listener) {
        this.listener = listener;
    }


    public <R> List<R> importData(MultipartFile file, Class<R> clazz) {
        // 校驗(yàn)文件
        validateExcel(file);
        List<R> dataList = null;
        try (InputStream inputStream = file.getInputStream()) {
            ExcelReaderBuilder readerBuilder = EasyExcel.read(inputStream, clazz, listener);
            if (readerBuilderConsumer != null) {
                readerBuilderConsumer.accept(readerBuilder);
            }
            dataList = readerBuilder.sheet(sheetNo).doReadSync();
        } catch (ExcelAnalysisException e) {
            ExcelDataConvertException exception = (ExcelDataConvertException) e.getCause();
            List<ExcelErrorMessage> errorList = listener.getErrorList();
            String headerName = exception.getExcelContentProperty().getField().getAnnotation(ExcelProperty.class).value()[0];
            errorList.add(new ExcelErrorMessage().setRowNum(exception.getRowIndex() + 1)
                .setColHeaderName(headerName)
                .setMessage("'" + headerName + "'類型轉(zhuǎn)換失敗,請(qǐng)輸入正確格式"));
        } catch (IOException ioe) {
            log.info("導(dǎo)入失敗,異常,", ioe);
            throw new RuntimeException("導(dǎo)入失敗!");
        }
        if (CollectionUtils.isEmpty(dataList)) {
            throw new RuntimeException("解析數(shù)據(jù)為空!");
        }
        return dataList;
    }


    public List<ExcelErrorMessage> getErrorList() {
        return listener.getErrorList();
    }


    /**
     * 手動(dòng)添加錯(cuò)誤
     *
     * @param index        data的下標(biāo)(從0開始)
     * @param errorMessage 錯(cuò)誤信息
     */
    public void addError(Integer index, String errorMessage) {
        // 下標(biāo)從0開始+1,標(biāo)題占一行+1,總計(jì)+2
        Integer row = index + 2;
        listener.addError(row, errorMessage);
    }

    /**
     * 生成錯(cuò)誤信息excel,在原excel文件追加錯(cuò)誤列
     *
     * @param filePath 源文件路徑
     */
    public Boolean generateErrorSheet(String filePath) {
        List<ExcelErrorMessage> errorList = listener.getErrorList();
        if (CollectionUtils.isEmpty(errorList)) {
            return false;
        }
        Map<Integer, String> errorMap = errorList.stream().collect(Collectors.groupingBy(ExcelErrorMessage::getRowNum,
            Collectors.mapping(ExcelErrorMessage::getMessage, Collectors.joining(";"))));

        Workbook workbook = null;
        // 打開原excel文件
        try (
            FileInputStream inputStream = new FileInputStream(filePath)) {
            workbook = new XSSFWorkbook(inputStream);
            Sheet sheet = workbook.getSheetAt(sheetNo);

            // 添加錯(cuò)誤列
            Row headerRow = sheet.getRow(0);
            short lastCellNum = headerRow.getLastCellNum();
            // 檢查是否已經(jīng)存在錯(cuò)誤列
            Cell lastValidCell = headerRow.getCell(lastCellNum - 1);
            if (lastValidCell != null) {
                if (!ERROR_COLUMN_NAME.equals(lastValidCell.getStringCellValue())) {
                    Cell errorHeaderCell = headerRow.createCell(lastCellNum);
                    errorHeaderCell.setCellValue(ERROR_COLUMN_NAME);
                    errorMap.forEach((rowNum, msg) -> {
                        Row row = sheet.getRow(rowNum - 1);
                        if (row != null) {
                            Cell errorCell = row.createCell(lastCellNum);
                            errorCell.setCellValue(msg);
                        }
                    });

                } else {
                    int lastRowNum = sheet.getLastRowNum();
                    for (int rowNum = 1; rowNum <= lastRowNum; rowNum++) {
                        Row row = sheet.getRow(rowNum);
                        String setErrorMsg = errorMap.get(rowNum + 1);
                        // 如果沒有需要設(shè)置的錯(cuò)誤信息,要把舊的錯(cuò)誤信息清除
                        Cell errorCell = row.getCell(lastCellNum - 1);
                        if (setErrorMsg == null) {
                            if (errorCell != null) {
                                errorCell.setCellValue((String) null);
                            }
                        } else {
                            if (errorCell == null) {
                                errorCell = row.createCell(lastCellNum - 1);
                            }
                            errorCell.setCellValue(setErrorMsg);
                        }
                    }
                }
            }


        } catch (IOException e) {
            log.error("生成錯(cuò)誤信息失敗,", e);
            throw new RuntimeException("生成錯(cuò)誤信息失敗");
        }

        try (FileOutputStream outputStream = new FileOutputStream(filePath)) {
            // 寫回去
            workbook.write(outputStream);
            workbook.close();
        } catch (IOException e) {
            log.error("生成錯(cuò)誤信息失敗,", e);
            throw new RuntimeException("生成錯(cuò)誤信息失敗");
        }
        return true;
    }


    public static boolean isExcel2007(String filePath) {
        return filePath.matches("^.+\\.(?i)(xlsx)$");
    }

    /**
     * 驗(yàn)證EXCEL文件
     *
     * @param file
     * @return
     */
    public static void validateExcel(MultipartFile file) {
        if (file == null) {
            throw new RuntimeException("文件為空!");
        }
        String fileName = file.getOriginalFilename();
        if (fileName != null && !isExcel2007(fileName)) {
            throw new RuntimeException("導(dǎo)入文件必須是xlsx格式!");
        }
        if (StringUtils.isEmpty(fileName) || file.getSize() == 0) {
            throw new RuntimeException("文件內(nèi)容不能為空");
        }
    }
}

捕獲類型轉(zhuǎn)換異常

導(dǎo)入的第一步就是處理字段類型錯(cuò)誤,因?yàn)槿绻霈F(xiàn)類型轉(zhuǎn)換錯(cuò)誤,會(huì)直接導(dǎo)致程序異常,這里通過try,catch捕獲ExcelAnalysisException異常來獲取出現(xiàn)錯(cuò)誤的列和行。

這里通過exception對(duì)象獲取到了field,再獲取字段上的ExcelProperty注解。

在AnalysisEventListener中實(shí)現(xiàn)校驗(yàn)邏輯

在listener中的invoke方法中為每一行數(shù)據(jù)做校驗(yàn),這里主要使用了反射

獲取到Error后,根據(jù)錯(cuò)誤信息生成Excel

這里是拿導(dǎo)入的原本Excel文件,在最后追加一列錯(cuò)誤信息列,并將錯(cuò)誤信息與行對(duì)應(yīng),代碼如下

總結(jié) 

到此這篇關(guān)于Java如何基于EasyExcel實(shí)現(xiàn)導(dǎo)入數(shù)據(jù)校驗(yàn)并生成錯(cuò)誤信息Excel的文章就介紹到這了,更多相關(guān)EasyExcel導(dǎo)入數(shù)據(jù)校驗(yàn)生成錯(cuò)誤信息內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 帶你快速入門掌握Spring的那些注解使用

    帶你快速入門掌握Spring的那些注解使用

    注解是個(gè)好東西,注解是Java語法,被Java編譯器檢查,可以減少配置錯(cuò)誤,這篇文章主要給大家介紹了關(guān)于Spring的那些注解使用的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-02-02
  • java獲取鍵盤輸入的數(shù)字,并進(jìn)行排序的方法

    java獲取鍵盤輸入的數(shù)字,并進(jìn)行排序的方法

    今天小編就為大家分享一篇java獲取鍵盤輸入的數(shù)字,并進(jìn)行排序的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-07-07
  • mybatis簡單resultMap使用詳解

    mybatis簡單resultMap使用詳解

    resultMap是Mybatis最強(qiáng)大的元素,它可以將查詢到的復(fù)雜數(shù)據(jù)(比如查詢到幾個(gè)表中數(shù)據(jù))映射到一個(gè)結(jié)果集當(dāng)中。這篇文章主要介紹了mybatis簡單resultMap使用詳解的相關(guān)資料,需要的朋友可以參考下
    2021-04-04
  • Linux環(huán)境卸載Centos7自帶的OpenJDK和安裝JDK1.8圖文教程

    Linux環(huán)境卸載Centos7自帶的OpenJDK和安裝JDK1.8圖文教程

    CentOS系統(tǒng)是開發(fā)者常用的Linux操作系統(tǒng),安裝它時(shí)會(huì)默認(rèn)安裝自帶的舊版本的OpenJDK,但在開發(fā)者平時(shí)開發(fā)Java項(xiàng)目時(shí)還是需要完整的JDK,這篇文章主要給大家介紹了關(guān)于Linux環(huán)境卸載Centos7自帶的OpenJDK和安裝JDK1.8的相關(guān)資料,需要的朋友可以參考下
    2024-07-07
  • JAVA實(shí)現(xiàn)掃描線算法(超詳細(xì))

    JAVA實(shí)現(xiàn)掃描線算法(超詳細(xì))

    掃描線算法就是從Ymin開始掃描,然后構(gòu)建出NET,之后根據(jù)NET建立AET。接下來本文通過代碼給大家介紹JAVA實(shí)現(xiàn)掃描線算法,感興趣的朋友一起看看吧
    2019-10-10
  • Activiti流程文件部署過程解析

    Activiti流程文件部署過程解析

    這篇文章主要介紹了Activiti流程文件部署過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • 使用springboot自動(dòng)配置源碼解讀

    使用springboot自動(dòng)配置源碼解讀

    自動(dòng)裝配是Spring Boot的一個(gè)核心特性,允許程序員在開發(fā)中更加專注于業(yè)務(wù)邏輯,而不是花費(fèi)大量的時(shí)間去配置和管理第三方組件,當(dāng)開發(fā)者在pom.xml文件中添加了某個(gè)依賴后,Spring Boot通過自動(dòng)配置的方式,將這些第三方組件的實(shí)例自動(dòng)注入到IOC容器中
    2024-11-11
  • java打印表格 將ResultSet中的數(shù)據(jù)打印成表格問題

    java打印表格 將ResultSet中的數(shù)據(jù)打印成表格問題

    這篇文章主要介紹了java打印表格 將ResultSet中的數(shù)據(jù)打印成表格問題。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • Java 高并發(fā)一:前言

    Java 高并發(fā)一:前言

    本系列基于煉數(shù)成金課程,為了更好的學(xué)習(xí),做了系列的記錄。 本文主要介紹 1.高并發(fā)的概念,為以后系列知識(shí)做鋪墊。 2.兩個(gè)重要的定理
    2016-09-09
  • 淺談maven的jar包和war包區(qū)別 以及打包方法

    淺談maven的jar包和war包區(qū)別 以及打包方法

    下面小編就為大家分享一篇淺談maven的jar包和war包區(qū)別 以及打包方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助
    2017-11-11

最新評(píng)論