当前位置:   article > 正文

java实现excel的导入导出(带参数校验:非空校验、数据格式校验)_easyexcel导入校验字段非空

easyexcel导入校验字段非空

一、简单说明

本次封装引入阿里开源框架EasyExcel,EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。 github地址:GitHub - alibaba/easyexcel: 快速、简洁、解决大文件内存溢出的java处理Excel工具64M内存20秒读取75M(46W行25列)的Excel(3.0.2+版本)

  1. <dependency>
  2. <groupId>com.alibaba</groupId>
  3. <artifactId>easyexcel</artifactId>
  4. <version>3.1.1</version>
  5. </dependency>

结构图如下:

1.1结构说明:

1.annotation:注解

@ExcelPropertyCheck(自己写的注解用作导入数据校验)

  1. @Target({ElementType.FIELD, ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Inherited
  4. @Documented
  5. public @interface ExcelPropertyCheck {
  6. boolean required() default true; ----是否为空,默认不为空。
  7. boolean checkFormat() default false; ----是否进行格式检验,默认不进行。
  8. int type() default -1; ----格式检验类型,int 已经支持的类型有 0->ip、1->端口、2->时间日期格式
  9. int length() default -1; ----长度校验, int 字符串的长度,-1不进行校验
  10. }

@ExcelProperty(框架自带的,用于标记excel传输类和一些通用的导入导出配置)

  1. @Target(ElementType.FIELD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Inherited
  4. public @interface ExcelProperty {
  5. String[] value() default {""}; ----导出时对应字段的表头名称
  6. int index() default -1;----排列顺序,最好不要配默认按字段顺序。
  7. int order() default Integer.MAX_VALUE; ----同上
  8. Class<? extends Converter<?>> converter() default AutoConverter.class; ----转换器
  9. String format() default ""; ----格式划输出
  10. }

特别提醒:!!!

@ExcelPropertyCheck该注解作用类上时只支持required,其余属性无效。字段上的注解配置会覆盖类上的配置。  

2.constant:常量类,格式校验的类型定义,如电话号码、日期、IP地址等。

  1. /**
  2. * excel导入字段类型常量
  3. */
  4. public class ExcelPropertyType {
  5. //时间日期格式校验
  6. public static final int DATE_TIME = 0;
  7. }

3.converter:转换器

读写均可使用,实现Converter重写方法即可。

如将excel 中的日期转为 LocalDateTime 或者将LocalDateTime 转为excel表中的日期

或者将数据库的枚举值0,1,2,3导入到excel文件变成对应的中文汉字

  1. /**
  2. * String and string converter
  3. *
  4. */
  5. public class CustomStringStringConverter implements Converter<String> {
  6. /**
  7. * 这里读的时候会调用
  8. *
  9. * @param context
  10. * @return
  11. */
  12. @Override
  13. public String convertToJavaData(ReadConverterContext<?> context) {
  14. return "自定义:" + context.getReadCellData().getStringValue();
  15. }
  16. /**
  17. * 这里是写的时候会调用 不用管
  18. *
  19. * @return
  20. */
  21. @Override
  22. public WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) {
  23. return new WriteCellData<>(context.getValue());
  24. }
  25. }

上述代码中泛型为String 表示此字段经过处理后最后返回的类型为String 

4.listener:监听器

ReadListener<T>(EasyExcel提供的)

  1. ReadListener<T>读监听器,框架提供。提供读取excel不同时期的监听方法。
  2. 分别为:
  3. onException()-------------->“读取发生异常时监听”、
  4. invokeHead()----------------->“读取表头信息监听”、
  5. invoke()--------------------->“读取每行数据监听”、
  6. doAfterAllAnalysed()----------->“所有数据读取完毕监听”。

BaseListener<T>我们自己封装的读监听器,在里面结合注解配置完成数据校验。

  1. BaseListener<T>我们自己封装的读监听器,在里面结合注解配置完成数据校验。
  2. 对外提供的字段:
  3. private final List<Map<String, Object>> mapData = new ArrayList<>();
  4. private final List<T> data = new ArrayList<>();
  5. private final Map<Integer, String> errorMessageMap = new HashMap<>();
  6. //非空校验map
  7. private final Map<String, Boolean> nullAbleFieldMap = new HashMap<>();
  8. //格式校验map
  9. private final Map<String, String> checkFormatFieldMap = new HashMap<>();
  10. //长度校验map
  11. private final Map<String, Integer> checkLengthFieldMap = new HashMap<>();
  12. //枚举值校验map
  13. private final Map<String, String[]> checkEnumFieldMap = new HashMap<>();
字段描述备注       
mapDataexcel读取完毕的Map数据Map<String,Object>
dataexcel读取完毕的对于实体ListList<T>
errorMessageMap错误信息mapMap<Integer,String>;key为错误行号,value为描述。
nullAbleFieldMap
非空校验map
Map<String, Boolean>
checkFormatFieldMap
格式校验map:电话号码,ip,日期
Map<String, String>
checkLengthFieldMap
长度校验map
Map<String, Integer>
checkEnumFieldMap
枚举值检验map
Map<String, String[]>

  

结合注解做参数校验或者格式校验的实现思路(反射加泛型)

 步骤一:在表头读取的监听方法里利用反射判断检验注解ExcelPropertyCheck加在了那些ExcelDTO哪些属性上,并且ExcelPropertyCheck注解的具体属性是什么将这些存入分门别类的map里面。

步骤二:在读取每行数据的时候,结合步骤一的map,判断数据是否需要校验,并且从map中取出校验的类型是什么,再去完成校验,如果校验失败则将错误信息放入errorMessageMap,当前读取的excel这一行数据全部校验通过则数据放入data 里面

完整的BaseListener<T>代码如下:

  1. import com.alibaba.excel.context.AnalysisContext;
  2. import com.alibaba.excel.metadata.data.ReadCellData;
  3. import com.alibaba.excel.read.listener.ReadListener;
  4. import com.alibaba.excel.read.metadata.holder.ReadSheetHolder;
  5. import com.baomidou.mybatisplus.core.toolkit.BeanUtils;
  6. import org.slf4j.Logger;
  7. import org.slf4j.LoggerFactory;
  8. import org.springframework.util.StringUtils;
  9. import java.lang.reflect.Field;
  10. import java.util.*;
  11. public class BaseListener<T> implements ReadListener<T> {
  12. private final Logger logger = LoggerFactory.getLogger(BaseListener.class);
  13. private Class<?> headClazz;
  14. //读取数据map形式
  15. private final List<Map<String, Object>> mapData = new ArrayList<>();
  16. //读取数据实体类泛型形式
  17. private final List<T> data = new ArrayList<>();
  18. //非空校验map
  19. private final Map<String, Boolean> nullAbleFieldMap = new HashMap<>();
  20. //格式校验map
  21. private final Map<String, String> checkFormatFieldMap = new HashMap<>();
  22. //长度校验map
  23. private final Map<String, Integer> checkLengthFieldMap = new HashMap<>();
  24. //枚举值校验map
  25. private final Map<String, String[]> checkEnumFieldMap = new HashMap<>();
  26. //数据校验错误信息map key:错误的行号 value:错误信息描述
  27. private final Map<Integer, String> errorMessageMap = new HashMap<>();
  28. public List<Map<String, Object>> getMapData() {
  29. return mapData;
  30. }
  31. public List<T> getData() {
  32. return data;
  33. }
  34. public Map<Integer, String> getErrorMessageMap() {
  35. return errorMessageMap;
  36. }
  37. public BaseListener() {
  38. }
  39. /**
  40. * @param headClazz excel model 类对象
  41. */
  42. public BaseListener(Class<?> headClazz) {
  43. this.headClazz = headClazz;
  44. }
  45. /**
  46. * 读取发生异常时的方法
  47. *
  48. * @param exception
  49. * @param context
  50. * @throws Exception
  51. */
  52. @Override
  53. public void onException(Exception exception, AnalysisContext context) throws Exception {
  54. logger.debug("发生了异常");
  55. }
  56. /**
  57. * 读取头信息
  58. *
  59. * @param headMap
  60. * @param context
  61. */
  62. @Override
  63. public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
  64. ExcelPropertyCheck clazzHeadAnno = this.headClazz.getAnnotation(ExcelPropertyCheck.class);
  65. Field[] declaredFields = headClazz.getDeclaredFields();
  66. if (clazzHeadAnno != null && clazzHeadAnno.required()) {
  67. for (Field declaredField : declaredFields) {
  68. nullAbleFieldMap.put(declaredField.getName(), true);
  69. }
  70. }
  71. for (Field declaredField : declaredFields) {
  72. ExcelPropertyCheck annotation = declaredField.getAnnotation(ExcelPropertyCheck.class);
  73. if (annotation != null) {
  74. if (annotation.checkFormat()) {
  75. checkFormatFieldMap.put(declaredField.getName(), annotation.type() + "");
  76. }
  77. if (annotation.required()) {
  78. nullAbleFieldMap.put(declaredField.getName(), true);
  79. } else {
  80. nullAbleFieldMap.remove(declaredField.getName());
  81. }
  82. if (annotation.required() && annotation.length() != -1) {
  83. checkLengthFieldMap.put(declaredField.getName(), annotation.length());
  84. }
  85. if (annotation.required() && annotation.value().length != 0) {
  86. checkEnumFieldMap.put(declaredField.getName(), annotation.value());
  87. }
  88. }
  89. }
  90. }
  91. /**
  92. * 读取每一行数据
  93. *
  94. * @param t
  95. * @param analysisContext
  96. */
  97. @Override
  98. public void invoke(T t, AnalysisContext analysisContext) {
  99. int rowIndex = ((ReadSheetHolder) analysisContext.currentReadHolder()).getRowIndex() + 1;
  100. StringBuilder error = new StringBuilder();
  101. Field[] declaredFields = t.getClass().getDeclaredFields();
  102. //必填校验和格式校验
  103. for (Field declaredField : declaredFields) {
  104. try {
  105. declaredField.setAccessible(true);
  106. if (nullAbleFieldMap.get(declaredField.getName()) != null) {
  107. if (nullAbleFieldMap.get(declaredField.getName())) {
  108. Object o = declaredField.get(t);
  109. if (!Objects.nonNull(o)) {
  110. error.append(declaredField.getName()).append("为空;");
  111. } else {
  112. //字段不为空进行长度校验
  113. if (checkLengthFieldMap.get(declaredField.getName()) != null) {
  114. if (String.valueOf(o).length() > checkLengthFieldMap.get(declaredField.getName())) {
  115. error.append(declaredField.getName()).append("长度错误;");
  116. }
  117. }
  118. if (checkEnumFieldMap.get(declaredField.getName()) != null) {
  119. if (Integer.parseInt(String.valueOf(o)) == -1) {
  120. error.append(declaredField.getName()).append("枚举值错误;");
  121. }
  122. }
  123. }
  124. }
  125. }
  126. //是否需要进行格式校验
  127. if (checkFormatFieldMap.get(declaredField.getName()) != null) {
  128. String res = check(String.valueOf(declaredField.get(t)), Integer.valueOf(checkFormatFieldMap.get(declaredField.getName())));
  129. if (StringUtils.hasText(res)) {
  130. error.append(res);
  131. }
  132. }
  133. } catch (IllegalAccessException e) {
  134. e.printStackTrace();
  135. }
  136. }
  137. if (StringUtils.hasText(error.toString())) {
  138. errorMessageMap.put(rowIndex, error.toString());
  139. }
  140. mapData.add(BeanUtils.beanToMap(t));
  141. data.add(t);
  142. }
  143. @Override
  144. public void doAfterAllAnalysed(AnalysisContext analysisContext) {
  145. logger.info("excel解析完毕,共解析{}条数据,错误数据{}条,错误详情{}", data.size(), errorMessageMap.size(), errorMessageMap);
  146. }
  147. private String check(String str, Integer checkType) {
  148. switch (checkType) {
  149. case 0:
  150. return DateUtil.isDateTimeFormat(str) ? "" : "日期格式错误;";
  151. default:
  152. return "";
  153. }
  154. }
  155. }

如果导入时除了简单校验还需要特色校验如数据库去重,业务判断等,请另外写一个xxxReadListener 来继承BaseListener<T> 泛型为对应excel传输对象。重写监听方法时请务必super()一下,否则基本校验就没了!

5.model:excel传输对象

注解配置

为了不污染实体类和字段冗余请在这里定义excel传输对象加注解。

  1. @Data
  2. @EqualsAndHashCode
  3. @ExcelPropertyCheck
  4. public class DemoExcelDto {
  5. @ExcelProperty("id")
  6. @ExcelPropertyCheck(required = false)
  7. private Long id; //记录标识
  8. @ExcelProperty("导入时间")
  9. @ExcelPropertyCheck(required = true, checkFormat = true, type = ExcelPropertyType.DATE_TIME)
  10. private LocalDateTime importTime;
  11. }

二、使用说明

1.读Excel

EasyExcelUtils.importExcel(file, IpDepartsExcelDto.class, listener)

  1. @RequestMapping("importExcel")
  2. @ResponseBody
  3. public Result importIps(MultipartFile file) {
  4. //XXXReadListener listener = new XXXReadListener (XXXExcelDto.class);
  5. //没有复杂的校验就用BaseListener
  6. BaseListener<DemoExcelDto> listener = new BaseListener<>();
  7. //获取校验错误信息
  8. Map errorMap = EasyExcelUtils.importExcel(file, IpDepartsExcelDto.class, listener);
  9. //获取读取完毕的数据,即使发生校验错误也会继续读取
  10. List<IpDeparts> ipDeparts = EasyExcelUtils.dto2model(listener.getData() ,new IpDeparts());
  11. if (errorMap.size() != 0) {
  12. //errorMap校验数据不为空就是excel数据校验有不通过的数据。
  13. return Result.failure(CommonResultStatus.IMPORT_FAIL, errorMap.toString());
  14. } else {
  15. //mapper保存数据库。
  16. mapper.saveAll(ipDeparts);
  17. return Result.success(CommonResultStatus.OK);
  18. }
  19. }

2.写Excel

EasyExcelUtils.downloadExcel(response, fileName, ipDepartsExcelDtoList, IpDepartsExcelDto.class)

  1. @GetMapping("download")
  2. public void download(HttpServletResponse response) throws UnsupportedEncodingException {
  3. String fileName = URLEncoder.encode("test", "utf-8");
  4. List<DemoExcelDto> demoExcelDtos = new ArrayList<>();
  5. for (int i = 0; i < 5; i++) {
  6. DemoExcelDto demoExcelDto = new DemoExcelDto(i, "2020-08-01");
  7. demoExcelDtos.add(demoExcelDto);
  8. }
  9. try {
  10. EasyExcelUtils.downloadExcel(response, fileName, demoExcelDtos, DemoExcelDto.class);
  11. } catch (IOException e) {
  12. e.printStackTrace();
  13. }
  14. }

三、EasyExcelUtils(简单的导入导出工具类)

  1. import com.alibaba.excel.EasyExcel;
  2. import com.alibaba.excel.util.MapUtils;
  3. import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
  4. import com.alibaba.fastjson.JSON;
  5. import org.springframework.beans.BeanUtils;
  6. import org.springframework.web.multipart.MultipartFile;
  7. import javax.servlet.http.HttpServletResponse;
  8. import java.io.IOException;
  9. import java.util.Collection;
  10. import java.util.List;
  11. import java.util.Map;
  12. import java.util.stream.Collectors;
  13. public class EasyExcelUtils {
  14. /**
  15. * 导出excel
  16. *
  17. * @param response
  18. * @param fileName 文件名
  19. * @param data List<数据集合>
  20. * @throws IOException
  21. */
  22. public static <T> void downloadExcel(HttpServletResponse response, String fileName, List<T> data, Class<?> clazz) throws IOException {
  23. try {
  24. response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
  25. response.setCharacterEncoding("utf-8");
  26. response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
  27. // 这里需要设置不关闭流
  28. EasyExcel.write(response.getOutputStream(), clazz).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).autoCloseStream(Boolean.FALSE).sheet("sheet1")
  29. .doWrite(data);
  30. } catch (Exception e) {
  31. // 重置response
  32. response.reset();
  33. response.setContentType("application/json");
  34. response.setCharacterEncoding("utf-8");
  35. Map<String, String> map = MapUtils.newHashMap();
  36. map.put("status", "failure");
  37. map.put("message", "下载文件失败" + e.getMessage());
  38. response.getWriter().println(JSON.toJSONString(map));
  39. }
  40. }
  41. /**
  42. * 读取excel 文件
  43. *
  44. * @param file 文件
  45. * @param cl 类对象
  46. * @param listener 参数校验监听器
  47. */
  48. public static <T> Map importExcel(MultipartFile file, Class<T> cl, BaseListener<T> listener) {
  49. try {
  50. EasyExcel.read(file.getInputStream(), cl, listener).sheet().doRead();
  51. return listener.getErrorMessageMap();
  52. } catch (IOException e) {
  53. return null;
  54. }
  55. }
  56. /**
  57. * excleDto 转为对应model实体类
  58. *
  59. * @param data
  60. * @param t
  61. * @param <T>
  62. * @return
  63. */
  64. public static <T> List<T> dto2model(Collection<?> data, T t) {
  65. return data.stream().map(e -> {
  66. T t1 = null;
  67. try {
  68. t1 = (T) t.getClass().newInstance();
  69. } catch (Exception exception) {
  70. exception.printStackTrace();
  71. }
  72. BeanUtils.copyProperties(e, t1);
  73. return t1;
  74. }).collect(Collectors.toList());
  75. }
  76. }

四、总结:

经过简单的封装完成了一个带参数校验的简单使用案例,直接引入项目后在controller直接调用EasyExcelUtils的 "importExcel"方法和"downloadExcel"就能实现excel的导入导出,是不是很方便呢。

有问题留言大家一起交流学习。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/笔触狂放9/article/detail/725996
推荐阅读
相关标签
  

闽ICP备14008679号