大家好,我是小胖。本次给大家带来的SpringBoot中经过自界说注解+反射完成excel导入数据拼装及字段校验的完成办法。这种完成办法其实是很一般、惯例的办法,但许多同学在开发过程中,可能却不太简略想到他。当然我也是许多同学中的一员。

题外话

在之前的文章中,我讲到了Springboot中运用自界说注解+反射机制,经过完成BeanPostProcessorpostProcessBeforeInitialization接口的办法,完成了装备类主动注入的问题。有兴趣的同学能够去看一看:

《SpringBoot经过自界说注解完成装备类的主动注入 》

背景

在前段时间的开发作业中,接手了一个很简略,很一般的开发任务。要求完成一个单表的根底数据的批量导入功用。评估下来,用户每次批量导入的数据量也就几千条,也不大。是不是很简略,没有骗你们吧。但是呢,我实践去看的时分发现,好家伙,表里居然一百多个字段,全部是需求导入的(PS:表字段过多为什么没有分表的问题属于历史遗留问题,这儿不做评判)。并且我遍寻整个项目,却没有找到处理批量导入的公共办法,类似功用全部都是if...else...!!!???

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

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

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

所以我决断仿照。。。不可,不能决断!所以我就给项目简略写了批量导入的公共办法。

思路

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

  1. 必填校验
  2. 判空
  3. 格式,包括email,电话,身份证等特别格式,长度等
  4. 与excel列的对应联系
  5. 字典:需求将导入数据中的内容转成字典值入库
  6. index:和cell对应联系
  7. 实体类数据拼装
  8. 校验失利提示

其实,咱们写的每一个if判别,都是在做同一个作业。那吗,针对这个场景,咱们就能够选用注解+反射的办法来解决。

开搞

自界说注解

首先,咱们需求添加一个自界说注解。该注解主要符号相应字段与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  "";
}

界说一个公共的静态办法

改公共办法需求包括三个参数:

  1. class:用于拼装数据
  2. Map<Integer,String[]>:我这儿是将excel的内容全部读取出来保存在了Map中。
  3. 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导入的数据组装及字段校验

总结

经过自界说注解+反射的办法,完成对批量导入数据的校验及拼装。这是一个非常惯例和简略的完成办法。不得不说,SpringBoot自界说注解真的是个好东西。假如有类似这种重复作业的场景,不妨多考虑考虑,是否能够经过该机制完成。

最终

许多时分,技能便是层窗户纸,戳破了也就很简略了。不怕咱们技能不会,最怕的是咱们想不到~

感谢JRM进行浏览,期望能给一些同学带来帮助,哪怕一个也好。全剧终~