easyexcel上传校验的方法封装

news2024/11/16 22:41:58

easyexcel版本3.1.5

使用自定义注解的方式来定义校验的类型,避免冗余代码。

//校验value不能为空,且长度最大为30
@RowCheck(value = {RowCheckType.EMPTY,RowCheckType.LENGTH},max = 30)
private String value;

 具体代码

首先定义校验类型枚举RowCheckType:

package com.zhou.util.easyexcel;

/**
 * 校验类型枚举
 * @author lang.zhou
 */
public enum RowCheckType {
    EMPTY, //非空
    EMAIL, //邮件
    PATTERN, //正则
    LENGTH, //长度
    DATE, //日期
    ENUM //枚举
}

 定义校验不通过默认的提示语模板:

package com.zhou.zhou.util.easyexcel;

/**
 * @author lang.zhou
 */
public class CheckMessage {
    public static final String DEFAULT = "default";
    public static final String EMPTY = "第{n}行{c}不能为空";
    public static final String EMAIL = "第{n}行{c}邮箱格式错误";
    public static final String PATTERN = "第{n}行{c}格式错误";
    public static final String LENGTH = "第{n}行{c}长度超过{len}";
    public static final String DATE = "第{n}行{c}日期格式错误";
    public static final String ENUM = "第{n}行{c}不在枚举中";
}

定义校验的接口类ValueChecker :

package com.zhou.util.easyexcel.checker;

import com.zhou.util.easyexcel.RowCheckType;

/**
 * @author lang.zhou
 */
public interface ValueChecker {
    
    /**
     * 实现校验逻辑
     */
    boolean check(Object value);
    /**
     * 获得校验类型
     */
    RowCheckType getType();

    /**
     * 获得默认校验提示
     */
    String getDefaultMessage();
}

校验工厂类CheckFactory:

package com.zhou.util.easyexcel.checker;

import com.zhou.util.easyexcel.RowCheck;
import com.zhou.util.easyexcel.RowCheckType;

/**
 * @author lang.zhou
 */
public class CheckerFactory {

    public static ValueChecker createChecker(RowCheckType type, RowCheck check){
        ValueChecker checker;
        if(type == RowCheckType.EMPTY){
            checker = new EmptyChecker();
        }else if(type == RowCheckType.EMAIL){
            checker = new EmailChecker();
        }else if(type == RowCheckType.PATTERN){
            checker = new PatternChecker(check.format());
        }else if(type == RowCheckType.LENGTH){
            checker = new LengthChecker(check.max());
        }else if(type == RowCheckType.DATE){
            checker = new DateChecker(check.format());
        }else if(type == RowCheckType.ENUM){
            checker = new EnumChecker(check.format());
        }else{
            checker = new EmptyChecker();
        }
        return checker;
    }
}

 封装的注解@RowCheck:

package com.zhou.util.easyexcel;

import java.lang.annotation.*;

/**
 * @author lang.zhou
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RowCheck {

    /**
     * 校验类型
     */
    RowCheckType[] value() default {};

    String[] msg() default {CheckMessage.DEFAULT};

    /**
     * 正则校验/日期格式/枚举json
     */
    String format() default "";

    /**
     * 最大长度校验
     */
    int max() default 0;


}

再定义一个easyexcel导入的映射实体类和excel模板如下:

package com.zhou.util.easyexcel.model;

import com.alibaba.excel.annotation.ExcelProperty;
import com.zhou.util.easyexcel.RowCheck;
import com.zhou.util.easyexcel.RowCheckType;
import lombok.Data;

import java.io.Serializable;

/**
 * @author lang.zhou
 */
@Data
public class ImportModel implements Serializable {

    private static final long serialVersionUID = 1L;


    @ExcelProperty(index = 0, value = "用户名")
    @RowCheck(value = {RowCheckType.EMPTY,RowCheckType.LENGTH},max = 30)
    private String username;


    @ExcelProperty(index = 1, value = "年龄")
    @RowCheck(value = RowCheckType.PATTERN,msg = "第{n}行年龄必须是整数")
    private String age;

    @ExcelProperty(index = 2, value = "邮箱")
    @RowCheck(value = {RowCheckType.EMPTY,RowCheckType.EMAIL,RowCheckType.LENGTH},msg = {CheckMessage.DEFAULT,CheckMessage.DEFAULT,CheckMessage.DEFAULT},max = 3000)
    private String email;


}

实现校验的核心逻辑:

package com.zhou.util.easyexcel;


import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.zhou.util.easyexcel.checker.CheckerFactory;
import com.zhou.util.easyexcel.checker.EnumChecker;
import com.zhou.util.easyexcel.checker.ValueChecker;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.SneakyThrows;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;


/**
 * @author lang.zhou
 */
@EqualsAndHashCode(callSuper = true)
@Data
public abstract class AbstractExcelReadListener<T> extends AnalysisEventListener<T> {


    /**
     * 每次读取数量
     */
    protected int batchSize = 1000;

    /**
     * 数据总理
     */
    protected int dataCount = 0;

    /**
     * 回调次数
     */
    protected int times = 0;

    protected int sheetIndex = 0;

    protected Class<T> headerClass;

    /**
     * 数据读取开始行
     */
    protected int headerLine = 1;

    protected Map<String, ValueChecker[]> checkTypeMap = new LinkedHashMap<>(0);
    protected Map<String,RowCheck> checkMap = new LinkedHashMap<>(0);
    protected Map<String,String> columnDesc = new LinkedHashMap<>(0);



    /**
     * 读取的数据缓存
     */
    protected List<T> dataList = new ArrayList<>();

    public AbstractExcelReadListener(Class<T> headerClass) {
        this.headerClass = headerClass;
    }

    /**
     * 读取一行后回调
     * @param data  (列索引0开始 -> 值)
     */
    @Override
    public void invoke(T data, AnalysisContext context) {
        dataList.add(data);
        if(dataList.size() >= batchSize){
            callback();
        }
    }

    /**
     * 读取结束回调
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        callback();
    }

    /**
     * 读取头信息时初始化校验器
     */
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        if(headerClass != null){
            Field[] fields = headerClass.getDeclaredFields();
            for (Field field : fields) {
                RowCheck check = field.getAnnotation(RowCheck.class);
                ExcelProperty ep = field.getAnnotation(ExcelProperty.class);
                if(check != null){
                    RowCheckType[] checkType = check.value();
                    if(checkType.length > 0){
                        ValueChecker[] valueChecks = new ValueChecker[checkType.length];
                        for (int i = 0; i < checkType.length; i++) {
                            RowCheckType rowCheckType = checkType[i];
                            //拿到校验器
                            ValueChecker checker = CheckerFactory.createChecker(rowCheckType, check);
                            valueChecks[i] = checker;
                        }

                        checkTypeMap.put(field.getName(),valueChecks);
                        checkMap.put(field.getName(),check);
                    }
                }
                if(ep != null){
                    columnDesc.put(field.getName(),ep.value()[0]);
                }
            }
        }
    }

    private void callback(){
        if(dataList.size() > 0){
            dataCount += dataList.size();
            this.dataCallback(dataList, ++ times);
            dataList.clear();
        }
    }

    /**
     * 获取dataList中当前索引在全部批次数据中的行数
     * @param i     数据在当前批次dataList中的索引
     */
    protected int getRowNum(int i){
        return (times - 1) * batchSize + i + 1;
    }

    /**
     * 读取batchSize行后回调
     */
    public abstract void dataCallback(List<T> dataList,int times);

    public void read(InputStream stream){
        ExcelReader excelReader = EasyExcelFactory.read(stream, this).headRowNumber(headerLine).build();
        ReadSheet readSheet = EasyExcel.readSheet(sheetIndex).head(headerClass).build();
        excelReader.read(readSheet);
        excelReader.finish();
    }

    private String getMsg(ValueChecker checker, String[] msg, int i){
        String err = i < msg.length ? msg[i] : checker.getDefaultMessage();
        if(Objects.equals(err,CheckMessage.DEFAULT)){
            err = checker.getDefaultMessage();
        }
        return err;
    }

    /**
     * 校验一行数据(性能有待验证)
     * @param t         数据行
     * @param errMsg    存放校验错误信息
     * @return          true-校验通过
     */
    @SneakyThrows
    protected boolean checkRow(T t, Set<String> errMsg){
        if(checkTypeMap.size() > 0){
            int dataIndex = dataList.indexOf(t);
            int rowNum = getRowNum(dataIndex);
            Field[] fields = headerClass.getDeclaredFields();
            for (Field field : fields) {
                RowCheck check = checkMap.get(field.getName());
                ValueChecker[] checkers = checkTypeMap.get(field.getName());
                if(checkers != null){
                    String[] msg = check.msg();
                    field.setAccessible(true);
                    Object o = field.get(t);
                    for (int i = 0; i < checkers.length; i++) {
                        ValueChecker checker = checkers[i];
                        if (!checker.check(o)) {
                            String err = getMsg(checker,msg,i);
                            if(StringUtils.isNotBlank(err)){
                                err = err.replace("{n}",String.valueOf(rowNum))
                                        .replace("{v}",String.valueOf(o))
                                        .replace("{len}",String.valueOf(check.max()))
                                        .replace("{c}",columnDesc.get(field.getName()));
                                errMsg.add(err);
                                return false;
                            }
                        }
                        if(checker instanceof EnumChecker){
                            Object enumKey = ((EnumChecker) checker).getEnumKey(o);
                            field.set(t,enumKey);
                        }
                    }
                }

            }
        }
        return true;
    }



}

其他的校验实现类:

@Data
public class EmptyChecker implements ValueChecker {
    
    private RowCheckType type = RowCheckType.EMPTY;
    private String defaultMessage = CheckMessage.EMPTY;

    @Override
    public boolean check(Object value) {
        return !isEmpty(value);
    }

    public static boolean isEmpty(Object o){
        if(o==null){
            return true;
        }
        if(o instanceof String){
            return StringTool.isBlank(o.toString());
        }
        if(o instanceof Collection){
            return ((Collection<?>) o).isEmpty();
        }
        if(o instanceof Map){
            return ((Map<?,?>) o).isEmpty();
        }
        if(o.getClass().isArray()){
            return Array.getLength(o) == 0;
        }
        return false;
    }

}

@Data
public class DateChecker implements ValueChecker {

    private RowCheckType type = RowCheckType.DATE;
    private String defaultMessage = CheckMessage.DATE;

    private String format;

    public DateChecker(String format) {
        this.format = format;
    }

    @Override
    public boolean check(Object value) {
        if(value != null){
            Date date = TimeUtil.formatDate(value.toString(), format);
            return date != null;
        }
        return true;
    }
}

@Data
public class EmailChecker implements ValueChecker {
    public static final Pattern EMAIL_PATTERN = Pattern.compile("^\\w+([-\\\\.]\\w+)*@\\w+([-\\\\.]\\w+)*\\.\\w+([-\\\\.]\\w+)*$");
    private RowCheckType type = RowCheckType.EMAIL;
    private String defaultMessage = CheckMessage.EMAIL;

    @Override
    public boolean check(Object value) {
        if(value != null){
            String str = value.toString();
            return StringUtils.isNotBlank(str) && EMAIL_PATTERN.matcher(str).matches();;
        }
        return true;
    }
}


/**
 * @author lang.zhou
 */
@Data
public class EnumChecker implements ValueChecker {

    private RowCheckType type = RowCheckType.ENUM;
    private String defaultMessage = CheckMessage.ENUM;

    private Map<String,?> json;

    public EnumChecker(String format) {
        try {
            this.json = reserveMap(JSON.parseObject(format));
        }catch (Exception e){
            //ignore
        }
    }

    private Map<String,Object> reserveMap(Map<String, String> enums){
        Map<String,Object> map = new HashMap<>(enums.size());
        for (Map.Entry<String, String> entry : enums.entrySet()) {
            map.put(entry.getValue(),entry.getKey());
        }
        return map;
    }

    @Override
    public boolean check(Object value) {
        if(value != null){
            return getEnumKey(value) != null;

        }
        return true;
    }

    public Object getEnumKey(Object value) {
        if(value != null && json != null){
            return json.get(value.toString());
        }
        return null;
    }
}



@Data
public class LengthChecker implements ValueChecker {

    private RowCheckType type = RowCheckType.LENGTH;
    private String defaultMessage = CheckMessage.LENGTH;

    private int max = 0;

    public LengthChecker(int max) {
        this.max = max;
    }

    @Override
    public boolean check(Object value) {
        if(value != null){
            return value.toString().length() <= max;
        }
        return true;
    }
}


@Data
public class PatternChecker implements ValueChecker {

    private RowCheckType type = RowCheckType.PATTERN;
    private String defaultMessage = CheckMessage.PATTERN;

    private Pattern pattern;

    public PatternChecker(String format) {
        pattern = Pattern.compile(format);
    }

    @Override
    public boolean check(Object value) {
        if(value != null){
            return pattern.matcher(value.toString()).matches();
        }
        return true;
    }

}

调用方式:

        //错误信息
        Set<String> err = new LinkedHashSet<>();

        AbstractExcelReadListener<ImportModel> listener = new AbstractExcelReadListener<ImportModel>(ImportModel.class) {
            @Override
            public void dataCallback(List<ConsignorinfoImportModel> dataList, int times) {
                log.info("第{}次读取后保存",times);
                for (int i = 0; i < dataList.size(); i++) {
                    ImportModel dto = dataList.get(i);
                    Set<String> oneErr = new LinkedHashSet<>();
                    try{
                        //内置校验
                        if(!this.checkRow(dto,oneErr)){
                            continue;
                        }
                        //todo
                        //这里可以进行自定义校验
                    }finally {
                        err.addAll(oneErr);
                    }
                }
            }
        };
        listener.read(stream);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1379276.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

4.8 SUMMARY 4.9 EXERCISES

总之&#xff0c;在现代处理器中&#xff0c;程序的执行速度可能会受到内存速度的严重限制。为了很好地利用CUDA设备的执行吞吐量&#xff0c;应该在内核代码中获得高计算与全局内存访问率。如果获得的比率很低&#xff0c;则内核受内存约束&#xff1b;即其执行速度受从内存访…

Docker安装Elesticsearch7详细步骤

​ 1、创建安装目录 mkdir -p /usr/local/docker/es-docker 2、配置虚拟内存 如果不配置&#xff0c;后面启动es会报错。 max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144] 配置如下 vi /etc/sysctl.conf vm.max_map_coun…

MySQL夯实之路-存储引擎深入浅出

innoDB Mysql4.1以后的版本将表的数据和索引放在单独的文件中 采用mvcc来支持高并发&#xff0c;实现了四个标准的隔离级别&#xff0c;默认为可重复读&#xff0c;并且通过间隙锁&#xff08;next-key locking&#xff09;策略防止幻读&#xff08;查询的行中的间隙也会锁定…

使用Navicat导入csv数据至mysql

问题 使用Navicat导入csv数据至mysql 详细问题 笔者有已进行数据处理的csv&#xff0c;需要将数据信息导入mysql中 解决方案 步骤1、建立数据表&#xff0c;字段信息&#xff08;最好&#xff09;与csv字段信息保持一致&#xff0c;方便后续导入。 具体的&#xff0c;双击…

WPS - 表格虚线变成实线解决方案(Office 同上)

1、选中表格区域&#xff0c;在表格中选中需要调整为实线的表格区域 2、点击设置单元格格式&#xff0c;鼠标进行右击并点击设置单元格格式选项 3、选择实线&#xff0c;在单元格格式下的边框&#xff0c;调整到实线 4、设置为实线&#xff0c;即可将表格的虚线设置为实线

Django(七)

Django(六) 4.编辑用户 点击编辑&#xff0c;跳转到编辑页面&#xff08;将编辑行的ID携带过去&#xff09;。编辑页面&#xff08;默认数据&#xff0c;根据ID获取并设置到页面中&#xff09;提交&#xff1a; 错误提示数据校验在数据库更新 user_edit.html {% exte…

JavaScript Web Worker用法指南

&#x1f9d1;‍&#x1f393; 个人主页&#xff1a;《爱蹦跶的大A阿》 &#x1f525;当前正在更新专栏&#xff1a;《VUE》 、《JavaScript保姆级教程》、《krpano》 ​ ​ ✨ 前言 Web Worker可以将耗时任务放到后台执行,避免阻塞UI。本文将详细介绍Web Worker的用法,让你…

Spark原理——运行过程

运行过程 逻辑图 是什么 怎么生成 具体怎么生成 val textRDD sc.parallelize(Seq("Hadoop Spark", "Hadoop Flume", "Spark Sqoop")) val splitRDD textRDD.flatMap(_.split(" ")) val tupleRDD splitRDD.map((_, 1)) val reduceRD…

代币合约 ERC20 Token接口

代币合约 在以太坊上发布代币就要遵守以太坊的规则&#xff0c;那么以太坊有什么规则呢?以太坊的精髓就是利用代码规定如何运作&#xff0c;由于在以太坊上发布智能合约是不能修改和删除的&#xff0c;所以智能合约一旦发布&#xff0c;就意味着永久有效&#xff0c;不可篡改…

ESU毅速丨复杂结构模具可尝试3D打印随形水路

冷却水路对模具的生产效率影响巨大&#xff0c;一些结构复杂、骨位深的模具常规水路加工困难且冷却效果不理想&#xff0c;这时可尝试3D打印来制造水路。3D打印技术可以制造出具有复杂内部结构和任意几何形状的部件&#xff0c;特别适合结构复杂、骨位深、薄壁等特征的模具水路…

解决uni-app小程序获取路由及路由参数

代码: this.id = this.$route.query.id;错误信息: 解决方案: // 获取query对象// #ifdef H5this.id = this.$route

电子学会C/C++编程等级考试2023年09月(三级)真题解析

C/C++编程(1~8级)全部真题・点这里 第1题:谁是你的潜在朋友 “臭味相投”——这是我们描述朋友时喜欢用的词汇。两个人是朋友通常意味着他们存在着许多共同的兴趣。然而作为一个宅男,你发现自己与他人相互了解的机会并不太多。幸运的是,你意外得到了一份北大图书馆的图书借…

最新AI绘画Midjourney绘画提示词Prompt大全

一、Midjourney绘画工具 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭…

提供一些防扫描被封禁、防溯源工具

1► 介绍 SecScanC2可以创建P2P网络进行安全扫描和C2。该工具可以帮助安全研究人员更有效地进行渗透测试&#xff0c;防止扫描被封禁&#xff0c;保护自己免受溯源。 2► 工具特性 P2P&#xff1a;将大量互联网节点构建成P2P网络 防止扫描被封禁&#xff1a;随机或指定节点…

Photoshop 2024 (PS2024) v25 直装版 支持win/mac版

Photoshop 2024 提供了多种创意工具&#xff0c;如画笔、铅笔、涂鸦和渐变等&#xff0c;用户可以通过这些工具来创建独特和令人印象深刻的设计效果。增强的云同步&#xff1a;通过 Adobe Creative Cloud&#xff0c;用户可以方便地将他们的工作从一个设备无缝同步到另一个设备…

Springboot注解@EnableConfigurationProperties和@ConfigurationProperties关系和作用

目录 EnableConfigurationProperties和ConfigurationProperties关系是什么&#xff1f; 简介 ConfigurationProperties EnableConfigurationProperties 二者之间的联系 总结 EnableConfigurationProperties和ConfigurationProperties关系是什么&#xff1f; 其实我能明白…

计算机毕业设计 基于Java的美食信息推荐系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

openssl3.2 - 官方demo学习 - cipher - aesgcm.c

文章目录 openssl3.2 - 官方demo学习 - cipher - aesgcm.c概述笔记END openssl3.2 - 官方demo学习 - cipher - aesgcm.c 概述 AES-256-GCM 在这个实验中验证了EVP_CIPHER_fetch()中算法名称字符串的来源定位. 在工程中配置环境变量PATH, 且合并环境. 这样就不用将openSSL的D…

kubeadm安装kubernetes

基本环境配置 节点分为&#xff1a;master&#xff0c;node&#xff0c;masterlb(keepalived虚拟Ip&#xff0c;不占用机器) k8s-master01 16 k8s-node01 113 15 k8s-node02 115 进入之后直接选done done 上海 123456 设置静态ip 然后去虚拟机里面设置ens即可 查看命…

【算法与数据结构】62、LeetCode不同路径

文章目录 一、题目二、解法2.1 动态规划解法2.2 数论解法 三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 2.1 动态规划解法 思路分析&#xff1a;机器人只能向下或者向右移动&#xff0c;那么到达&a…