• 【SpringBoot】SpringBoot项目如何接入Excel?


    前言

    我们在做项目的时候,Excel 导入与导出是项目中经常用到的功能,在 Java 中常用 poi 实现 Excel 的导入与导出。由于 poi 占用内存较大,在高并发下很容易发生 OOM 或者频繁 fullgc,阿里基于 poi 开源了 EasyExcel 项目

    除了节约内存,EasyExcel 还简化了 API,通过注解映射 Excel 单元格与对象字段之间的关系,简单的几行代码就能搞定复杂的导入导出功能了。

    实现

    依赖注入

    首先需要引入依赖,坐标如下。

    <dependency>
        <groupId>com.zzuhkp</groupId>
        <artifactId>easyexcel-spring-boot-starter</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    不过很不幸的是目前还没传至中央仓库,需要的小伙伴可自行上传到私有仓库或直接把代码嵌入自己的项目。

    Excel导入

    为了接收 Excel 文件内容,我们需要定义一个对应的 Model 类。

    @Data
    public class DemoData {
    
        @ExcelProperty(index = 0)
        private Integer integer;
    
        @ExcelProperty(index = 1)
        private String string;
    
        @ExcelProperty(index = 2)
        private Date date;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    基本导入功能

    然后使用 List 参数接收即可。

    @PostMapping("/list/obj")
    public List<DemoData> listObj(@ExcelParam List<DemoData> list) {
        return list;
    }
    
    • 1
    • 2
    • 3
    • 4

    注意参数前添加了 @ExcelParam 注解,用来标识 Excel 文件参数。这样,一个导入功能实现了,是不是很简单呢?

    默认情况下接收名称为 file 的表单字段作为 Excel 文件,如果不满足还可以修改。

    @ExcelParam(value = "file", required = true)
    
    • 1

    进阶导入功能

    有时候,我们可能比较关心对象对应 Excel 的元数据,例如这个对象是第几行记录产生的,这个对象的字段对应 Excel 第几列,这个时候我们可以使用 ReadRows 参数接收 Excel

    @PostMapping("/list/rows")
    public ReadRows<DemoData> readRows(@ExcelParam ReadRows<DemoData> readRows) {
        return readRows;
    }
    
    • 1
    • 2
    • 3
    • 4

    ReadRows 使用两个字段记录行映射关系与列映射关系。

    public class ReadRows<T> {
    
        private ExcelReadHeadProperty excelReadHeadProperty;
    
        private List<ReadRow<T>> rows;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ExcelReadHeadPropertyEasyExcel 自带的类,表示列映射关系的元数据。ReadRow 是框架自定义的类,表示行映射关系的元数据。

    看下 ReadRow 定义吧。

    public class ReadRow<T> {
    
        // 行索引,从 0 开始
        private final Integer rowIndex;
    
        // 行记录对应对象
        private final T data;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    使用 ExcelReadHeadProperty 获取字段对应列索引的示例代码如下。

    // 对象字段名称 -> 从 0 开始的列索引
    Map<String, Integer> fieldColumnIndexMap = readRows.getExcelReadHeadProperty().getHeadMap().values()
            .stream().collect(Collectors.toMap(Head::getFieldName, Head::getColumnIndex));
    
    • 1
    • 2
    • 3

    导出

    这里对 Excel 的导出进行了简单的支持。将 List 定义为 controller 方法返回值即可。

    @ExcelResponse
    @GetMapping("/list/download")
    public List<DemoData> downloadList() {
        return Arrays.asList(new DemoData(1, "hello", new Date()), new DemoData(2, "excel", new Date()));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    需要注意的是使用 @ExcelResponse 注解表示响应内容为 Excel 文件。默认情况,下载的文件名称为 default.xlxs,写入到名称为 Sheet1 的工作表中。如果不满足需求可以修改。

    @ExcelResponse(fileName = "测试文件", sheetName = "工作表1")
    
    • 1

    Excel 导入参数校验

    参数校验是 Excel 导入常用的功能,这里进行了强有力的支持,使用体验如原生 spring boot 校验般顺滑。

    开启校验

    与 spring boot 原生使用方式一样,将 @Validated@Valid 注解添加到 @ExcelParam 参数上即可。

    @PostMapping("/list/obj")
    public List<DemoData> listObj(@ExcelParam @Validated List<DemoData> list) {
        return list;
    }
    
    • 1
    • 2
    • 3
    • 4

    校验规则定义

    Bean Validation 定义校验规则

    默认情况下框架使用 JSR-303 Bean Validation 规范定义的校验注解校验,需要手动引入 spring-boot-starter-validation,可通过设置环境变量 easyexcel.validator.default.enable=false 关闭。

    @Data
    public class DemoData {
    
        @NotNull(message = "参数不能为空")
        private Integer integer;
    
        private String string;
    
        private Date date;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    另外还可以自定义注解对对象校验。

    ... 省略其他元注解
    @Constraint(validatedBy = {DemoDataValid.DemoDataValidator.class})
    public @interface DemoDataValid {
      ... 省略注解属性
      
        class DemoDataValidator implements ConstraintValidator<DemoDataValid, DemoData> {
    
            @Override
            public boolean isValid(DemoData value, ConstraintValidatorContext context) {
                context.disableDefaultConstraintViolation();
                context.buildConstraintViolationWithTemplate("测试对象校验").addConstraintViolation();
                return false;
            }
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    @DemoDataValid
    public class DemoData {
        ... 省略属性
    }
    
    • 1
    • 2
    • 3
    • 4

    ExcelValidator 接口定义校验规则

    Bean Validation 注解只能校验单个字段或对象,如果需要对所有的对象进行校验,可以实现框架定义的 ExcelValidator 接口,然后将实现定义为 Spring Bean。

    这个接口定义如下。

    public interface ExcelValidator<T> {
    
        ExcelValidErrors validate(ReadRows<T> readRows);
    }
    
    • 1
    • 2
    • 3
    • 4

    ExcelValidErrors 用于接收校验的错误信息,分别使用接口 ExcelValidObjectErrorExcelValidFieldError 接口定义行错误信息和单元格错误信息。

    public class ExcelValidErrors {
     // 行错误信息或单元格错误信息列表
        private final List<ExcelValidObjectError> errors;
    }
    
    public interface ExcelValidObjectError {
    
        // 获取行号,从 1 开始
        Integer getRow();
    
        // 获取错误消息
        String getMessage();
    }
    
    public interface ExcelValidFieldError extends ExcelValidObjectError {
    
        // 获取列,从 1 开始
        Integer getColumn();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    例如,如果需要对所有的 DemoData 校验 integer 字段的值不能重复,可以使用如下的代码。

    @Component
    public class CustomExcelValidator implements ExcelValidator<DemoData> {
        @Override
        public ExcelValidErrors validate(ReadRows<DemoData> readRows) {
            ExcelValidErrors errors = new ExcelValidErrors();
    
            Map<Integer, List<ReadRow<DemoData>>> group = readRows.getRows().stream()
                    .collect(Collectors.groupingBy(item -> item.getData().getInteger()));
    
            for (Map.Entry<Integer, List<ReadRow<DemoData>>> entry : group.entrySet()) {
                if (entry.getValue().size() > 1) {
                    for (ReadRow<DemoData> readRow : entry.getValue()) {
                        errors.addError(new DefaultExcelObjectError(readRow.getRowIndex() + 1, "参数重复"));
                    }
                }
            }
            return errors;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    校验结果接收

    与 Spring MVC 设计类似,这里也提供了两种接收校验结果的方式。

    异常捕获接收校验结果

    开启校验后,如果校验结果中包含错误,会将错误信息封装到 ExcelValidException,并抛出异常,可以通过全局异常捕获的方式收集错误信息。

    @RestControllerAdvice
    public class GlobalExceptionControllerAdvice {
    
        @ExceptionHandler(ExcelValidException.class)
        public String handleException(ExcelValidException e) {
            ExcelValidErrors errors = e.getErrors();
            return JSON.toJSONString(errors);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    controller 方法参数接收校验结果

    如果不想通过异常捕获的方式接收校验的错误信息,还可以将错误信息添加到 @ExcelParam 参数的后面,示例代码如下。

    @PostMapping("/list/obj")
    public List<DemoData> listObj(@ExcelParam @Validated List<DemoData> list, ExcelValidErrors errors) {
        if (errors.hasErrors()) {
            String messages = errors.getAllErrors().stream().map(ExcelValidObjectError::getMessage).collect(Collectors.joining(" | "));
            throw new RuntimeException("发现异常:" + messages);
        }
        return list;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
  • 相关阅读:
    【Python】scrapy 命令提示找不到文件
    C/C++语言100题练习计划 85——统计方形(枚举实现)
    树,二叉树的概念与结构
    如何做到一套FPGA工程无缝兼容两款不同的板卡?
    MATLAB调用工业相机读取图像数据
    【从零开始学习 SystemVerilog】3.1.1、SystemVerilog 控制流—— while 和 do-while 循环
    2023网络工程毕业设计选题推荐 - 计算机毕业设计题目大全
    SpringBoot+mongodb实现分组统计、时间范围查询、去重排序查询、组合排序、分页查询
    mac安装virtualenv和virtualenvwrapper
    Day31力扣打卡
  • 原文地址:https://blog.csdn.net/weixin_44427181/article/details/127459180