首页 > 其他分享 >SpringBoot自定义注解+反射实现 excel 导入的数据组装及字段校验

SpringBoot自定义注解+反射实现 excel 导入的数据组装及字段校验

时间:2024-01-30 23:00:34浏览次数:30  
标签:SpringBoot 自定义 excel 校验 vo 导入 class append String

本次给大家带来的SpringBoot中通过自定义注解+反射实现excel导入数据组装及字段校验的实现方式。这种实现方式其实是很普通、常规的方法,但很多同学在开发过程中,可能却不太容易想到他。当然我也是众多同学中的一员。

1背景

在前段时间的开发工作中,接手了一个很简单,很普通的开发任务。要求实现一个单表的基础数据的批量导入功能。

评估下来,用户每次批量导入的数据量也就几千条,也不大。是不是很简单,没有骗你们吧。但是呢,我实际去看的时候发现,好家伙,表里竟然一百多个字段,全部是需要导入的。

PS:表字段过多为什么没有分表的问题属于历史遗留问题,这里不做评判。

并且我遍寻整个项目,却没有找到处理批量导入的公共方法,相似功能全部都是if...else...!!!???

SpringBoot自定义注解+反射实现 excel 导入的数据组装及字段校验_Code

当时我的心理活动是这样的:

:???

:我*,不是吧,这咋搞。

:我总不能去写一百多个判断吧?这样搞估计能被锤死,在我写那么多判断好累的呀!!!

于是我果断仿照。。。不行,不能果断!于是我就给项目简单写了批量导入的公共方法。

2思路

对于导入数据的校验来说,核心其实只有几个方面:

  • 必填校验
  • 判空
  • 格式,包含email,电话,身份证等特殊格式,长度等
  • 与excel列的对应关系
  • 字典:需要将导入数据中的内容转成字典值入库
  • index:和cell对应关系
  • 实体类数据组装
  • 校验失败提示

其实,我们写的每一个if判断,都是在做同一个事情。那吗,针对这个场景,我们就可以采用注解+反射的方式来解决。

3开搞

自定义注解

首先,我们需要添加一个自定义注解。该注解主要标记相应字段与cell的对应关系以及需要进行的处理。(PS:上面提到的特殊格式的校验,这里没有做实现,需要的增加一个字段保存正则表达式即可)

@Retention(RetentionPolicy.RUNTIME)  
@Target({ElementType.FIELD})  
public @interface ImportValidation {  
    //下标,与excel中列对应,从0开始  
    int index();  
    //是否必填,默认是必填  
    boolean nullAble() default true;  
    // 字典的Code,用于字典转换  
    String domainCode() default "";  
    //字典的名称,用于错误提醒  
    String name() default  "";  
  
}

定义一个公共的静态方法

改公共方法需要包含三个参数:

  • class:用于组装数据
  • Map<Integer,String[]>:我这里是将excel的内容全部读取出来保存在了Map中。
  • domainCodes:所有涉及的字段转换,调用方应将字段按照code组装成Map的形式以供使用
public static Result assembleExcelData(Class entryClass, Map<Integer, String[]> excelData,  
       Map<String,Object> domainCodes){  
       ....

数据组装

这里直接看代码

public static Result assembleExcelData(Class entryClass, Map<Integer, String[]> excelData,  
                                       Map<String,Object> domainCodes){  
    //保存返回的结果  
    Result result = new Result();  
    //组装后的数据LIST  
    List<Object> returnList = new ArrayList<>();  
    //保存校验失败信息  
    StringBuilder errorMsg = new StringBuilder();  
    //循环excel数据  
    excelData.forEach((i,cells)->{  
        Object vo = null;  
        try {  
            //按照传入的Class,生成对应实例  
            vo= entryClass.newInstance();  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        //获取并循环Bean中的所有字段,进行校验和组装  
        for (Field field : entryClass.getDeclaredFields()) {  
            //如果包含有ImportValidation注解的话,才进行处理。  
            if (field.isAnnotationPresent(ImportValidation.class)) {  
                ImportValidation annotation = field.getAnnotation(ImportValidation.class);  
                //cell下表  
                int index = annotation.index();  
                //字典Code  
                String domainCode = annotation.domainCode();  
                //是否必填  
                boolean nullAble = annotation.nullAble();  
                //字段名称  
                String name = annotation.name();  
                //获取单元格内容,并前后去空格处理  
                String cellData = cells[index].trim();  
                /*如果字段为空,且字段设置不能为空,则进行错误提醒*/  
                try {  
                    //若必填,则进行判断校验并提醒  
                    if (StringUtils.isEmpty(cellData) && !nullAble) {  
                        errorMsg.append("第").append(i).append("行: ").append(name).append("字段不能为空!\r\n");  
                    }  
                    /*如果字典编码为空,则可以直接赋值*/  
                    else if (StringUtils.isEmpty(domainCode) || StringUtils.isEmpty(cellData)) {  
                        //给对应字段赋值  
                        setFiled(field, vo, cellData);  
                    } else {  
                       //进行字典转换  
                        List<Map> domains = (List<Map>) domainCodes.get(domainCode);  
                        boolean match = false;  
                        for (Map map : domains) {  
                            if (map.get("TEXT").equals(cellData)) {                                 
                                //给对应字段赋值  
                                setFiled(field, vo, String.valueOf(map.get("VALUE")));  
                                match = true;  
                                break;  
                            }  
                        }  
                        /*如果没有匹配,则转换失败*/  
                        if (!match) {  
                            errorMsg.append("第").append(i).append("行: ").append(name).append("字段字典值不存在!!\r\n");  
                        }  
                    }  
                } catch (Exception e) {  
                    errorMsg.append("第").append(i).append("行: ").append(name).append("字段填写格式不正确!!\r\n");  
                }  
  
            }  
        }  
        //组装LIST  
        returnList.add(vo);  
    });  
    //如果有错误信息的话,返回错误信息,返回错误标记  
    if (errorMsg.length()>0){  
        result = Result.buildError();  
        result.setMsg(errorMsg.toString());  
    }  
    //放入组装后的LIST。校验失败的字段值为空  
    result.setData(returnList);  
  
    return result;  
}  
  
//反射给Filed赋值  
    public static void setFiled(Field filed,Object vo,String data) throws IllegalAccessException {  
        try {  
            //当单元格值不为空的时候才需要进行赋值操作  
            if (StringUtils.isNotEmpty(data)){  
                //获取Bean 属性字段的类型  
                Type fileType = filed.getGenericType();  
                filed.setAccessible(true);  
                //如果是String  
                if (fileType.equals(String.class)){  
                    filed.set(vo,data);  
                }  
                //如果是int  
                else if(fileType.equals(int.class)||fileType.equals(Integer.class)){  
                    filed.set(vo,Integer.valueOf(data));  
                }  
                //如果是Double  
                else if(fileType.equals(Double.class)||fileType.equals(double.class)){  
                    filed.set(vo,Double.valueOf(data));  
                }  
                //如果是Long  
                else if(fileType.equals(Long.class)||fileType.equals(long.class)){  
                    filed.set(vo,Long.valueOf(data));  
                }  
                //如果是BigDecimal  
                else if(fileType.equals(BigDecimal.class)){  
                    filed.set(vo,new BigDecimal(data));  
                }  
                //如果是日期  
                else if(fileType.equals(Date.class)){  
                    filed.set(vo, DateUtils.parseIso8601DateTime(data));  
                }  
            }  
  
        } catch (Exception e) {  
            throw e;  
        }  
  
    }

使用

我这里如果校验失败的话是给前端返回一个错误提醒内容的txt文件。可自行根据项目情况处理。校验成功则做插入的操作。

String domainCodesStr = "MM_DIC_PART_ATTR,MM_DIC_PART_TYPE,MM_DIC_PART_BELONG,MM_DIC_BASE_UNIT," +  
        "MM_DIC_PART_SOURCE,MM_DIC_W_UNIT,MM_MIN_SHELF_LIFE_UNIT,MM_CURRENCY";  
/*查询相关字典,进行校验和转换*/  
Map<String, Object> domainsCodes = wsDataDomainService.getDataByDomainCodes(domainCodesStr.split(","));  
/*校验并组装数据*/  
Result result = ExcelUtils.assembleExcelData(MmPartNumber.class, excelData, domainsCodes);  
if (result.getCode() != 0) {  
    String realPath = SpringContextHolder.getServletContext().getRealPath("/");  
    String destination = realPath + "导入错误信息.txt";  
    /*返回错误信息文件*/  
    File file = new File(destination);  
    if (!file.exists()) {  
        file.createNewFile();  
    }  
  
    FileWriter fileWriter = new FileWriter(file);  
    fileWriter.write(result.getMsg());  
    fileWriter.close();  
    HttpServletResponse response = context.getHttpServletResponse();  
    FileDownload.fileDownload(response, realPath + "导入错误信息.txt", "导入错误信息.txt");  
  
} else {  
//TODO BatchInsert  
}

效果

SpringBoot自定义注解+反射实现 excel 导入的数据组装及字段校验_字段_02

4总结

通过自定义注解+反射的方式,实现对批量导入数据的校验及组装。这是一个非常常规和简单的实现方式。不得不说,SpringBoot自定义注解真的是个好东西。如果有类似这种重复工作的场景,不妨多考虑考虑,是否可以通过该机制实现。

5最后

很多时候,技术就是层窗户纸,戳破了也就很简单了。不怕我们技术不会,最怕的是我们想不到~

最后说一句(求关注!别白嫖!)

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!

标签:SpringBoot,自定义,excel,校验,vo,导入,class,append,String
From: https://blog.51cto.com/u_16502039/9496266

相关文章

  • informer cache自定义索引
    informercache默认通过namespace/name作为key把对象保存到map中。条件查询时一般通过labels.Selector来过滤,但这需要遍历所有元素,informercache可以类似于MySQL那样建立索引,来提高查询速度。//map根据指定的key来给对象分类//IndexFuncknowshowtocomputethesetofind......
  • Mybatis-plus分页查询(SpringBoot)
    2024-01-30OS:Windows10 22H2IDE:IDEA2022.2.5JDKversion:19Mavenversion:3.6.3数据库:MySQL8.1.0mybatis-plus:3.5.3.1 一、在springBoot启动类中将分页插件加入到ioc容器里面启动类 @SpringBootApplication@MapperScan("com.ssm.mapper")publicclassMain{......
  • springboot集成mqtt
    SpringBoot集成MQTT(简单版)一、docker安装emqx环境(Linux系统)emqx:mqtt服务器(broker)version:'3'services:emqx:image:emqx/emqxcontainer_name:emqxrestart:alwaysports:-8001:18083-8002:1883-8003:8083-8004......
  • [office] excel2003文件转成pdf文件的方法
    Excel中经常需要转换成PDF文件格式,Excel具体该如何转换成PDF呢?接下来是小编为大家带来的excel2003文件转成pdf文件的方法,供大家参考。excel2003文件转成pdf文件的方法:Excel转换PDF格式步骤1:下载安装pdf虚拟打印机Excel转换PDF格式步骤2:文件】【打印】,打印机选择pd......
  • python自定义装饰器
    被装饰函数带参数或不带参数#coding=utf8#自定义装饰器函数,需使用嵌套函数importtimedefdecorator_foo(func):definner_func(*args,**kwargs):start_time=time.time()func(*args,**kwargs)print('runfunccost{}s'.format(time.......
  • python自定义装饰器
    被装饰函数带参数或不带参数#coding=utf8#自定义装饰器函数,需使用嵌套函数importtimedefdecorator_foo(func):definner_func(*args,**kwargs):start_time=time.time()func(*args,**kwargs)print('runfunccost{}s'.format(time.......
  • 今年接到一个根据excel来更新数据库的需求,用php写个小脚本
    需求大概内容是,excel中有些条目需要删除、有些需要新增,就需要基于这份excel生成删、增的SQL。要求是这样的:蓝色要删除的,黄色是要新增的,白色和灰色的不用管。我第一时间就在想:还得识别单元格颜色?excel长这样: 这种小需求用php来处理就很方便,用的框架是yii。发现读取到的只......
  • 达梦---自定义函数 find_in_set()
    createorreplaceFUNCTIONFIND_IN_SET(piv_str1varchar2,piv_str2varchar2,p_sepvarchar2:=',')RETURNNUMBERISl_idxnumber:=0;--用于计算piv_str2中分隔符的位置strvarchar2(500);--根据分隔符截取的子字符串piv_......
  • [office] excel2013中怎样插入联机图-
    excel2013插入联机图教程:1、启动excel2013,单击插入“插图”联机图片。2、弹出一个插入图片界面,点击office剪切画。3、输入要搜索的关键字,单击放大镜按钮,搜索完毕看到结果。4、选择一张,双击即可插入到excel2013中去了。以上就是excel2013插......
  • springboot项目启动时候初始化一些数据
    最近在看缓存预热的问题的时候,其中有一种解决方法,就是在项目启动的时候就自动加载到缓存中那缓存我就不说了,就关于项目启动的时候,可以初始化一些数据,以下为两种初始化的方式,可以参考1、编写类去实现ApplicationRunner接口,实现run()方法。2、编写类去实现CommandLineRunner接口,......