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

本文主要是介绍SpringBoot自定义注解+反射实现 excel 导入的数据组装及字段校验,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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

1背景

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

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

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

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

图片

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

:???

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

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

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

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  
}  
效果

图片

4总结

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

5最后

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

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

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

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

这篇关于SpringBoot自定义注解+反射实现 excel 导入的数据组装及字段校验的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/662531

相关文章

Java实战之利用POI生成Excel图表

《Java实战之利用POI生成Excel图表》ApachePOI是Java生态中处理Office文档的核心工具,这篇文章主要为大家详细介绍了如何在Excel中创建折线图,柱状图,饼图等常见图表,需要的... 目录一、环境配置与依赖管理二、数据源准备与工作表构建三、图表生成核心步骤1. 折线图(Line Ch

Python3脚本实现Excel与TXT的智能转换

《Python3脚本实现Excel与TXT的智能转换》在数据处理的日常工作中,我们经常需要将Excel中的结构化数据转换为其他格式,本文将使用Python3实现Excel与TXT的智能转换,需要的可以... 目录场景应用:为什么需要这种转换技术解析:代码实现详解核心代码展示改进点说明实战演练:从Excel到

如何使用CSS3实现波浪式图片墙

《如何使用CSS3实现波浪式图片墙》:本文主要介绍了如何使用CSS3的transform属性和动画技巧实现波浪式图片墙,通过设置图片的垂直偏移量,并使用动画使其周期性地改变位置,可以创建出动态且具有波浪效果的图片墙,同时,还强调了响应式设计的重要性,以确保图片墙在不同设备上都能良好显示,详细内容请阅读本文,希望能对你有所帮助...

Spring Boot 3 整合 Spring Cloud Gateway实践过程

《SpringBoot3整合SpringCloudGateway实践过程》本文介绍了如何使用SpringCloudAlibaba2023.0.0.0版本构建一个微服务网关,包括统一路由、限... 目录引子为什么需要微服务网关实践1.统一路由2.限流防刷3.登录鉴权小结引子当前微服务架构已成为中大型系统的标

C# string转unicode字符的实现

《C#string转unicode字符的实现》本文主要介绍了C#string转unicode字符的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随... 目录1. 获取字符串中每个字符的 Unicode 值示例代码:输出:2. 将 Unicode 值格式化

python安装whl包并解决依赖关系的实现

《python安装whl包并解决依赖关系的实现》本文主要介绍了python安装whl包并解决依赖关系的实现,文中通过图文示例介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录一、什么是whl文件?二、我们为什么需要使用whl文件来安装python库?三、我们应该去哪儿下

Python脚本实现图片文件批量命名

《Python脚本实现图片文件批量命名》这篇文章主要为大家详细介绍了一个用python第三方库pillow写的批量处理图片命名的脚本,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录前言源码批量处理图片尺寸脚本源码GUI界面源码打包成.exe可执行文件前言本文介绍一个用python第三方库pi

Java集合中的List超详细讲解

《Java集合中的List超详细讲解》本文详细介绍了Java集合框架中的List接口,包括其在集合中的位置、继承体系、常用操作和代码示例,以及不同实现类(如ArrayList、LinkedList和V... 目录一,List的继承体系二,List的常用操作及代码示例1,创建List实例2,增加元素3,访问元

Java中将异步调用转为同步的五种实现方法

《Java中将异步调用转为同步的五种实现方法》本文介绍了将异步调用转为同步阻塞模式的五种方法:wait/notify、ReentrantLock+Condition、Future、CountDownL... 目录异步与同步的核心区别方法一:使用wait/notify + synchronized代码示例关键

Nginx实现动态封禁IP的步骤指南

《Nginx实现动态封禁IP的步骤指南》在日常的生产环境中,网站可能会遭遇恶意请求、DDoS攻击或其他有害的访问行为,为了应对这些情况,动态封禁IP是一项十分重要的安全策略,本篇博客将介绍如何通过NG... 目录1、简述2、实现方式3、使用 fail2ban 动态封禁3.1 安装 fail2ban3.2 配