SpringBoot整合EasyExcel实现Excel表头校验

前言

在实际的业务开发中,我们经常需要处理Excel文件的导入导出功能。EasyExcel作为阿里巴巴开源的一款Excel处理工具,以其高性能、低内存占用而广受欢迎。今天我们来探讨如何使用SpringBoot整合EasyExcel,并实现表头校验功能。

一、什么是EasyExcel

EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel。

二、项目依赖

首先在pom.xml中添加必要的依赖:

XML 复制代码
<dependencies>
    <spring-boot-starter-web>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </spring-boot-starter-web>
    
    <easyexcel>
        <groupId>com.alibaba</groupId>
        <artifactId>easyexcel</artifactId>
        <version>3.1.1</version>
    </easyexcel>
    
    <lombok>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </lombok>
</dependencies>

三、实现表头校验

1. 创建Excel数据模型

java 复制代码
@Data
public class UserImportVO {
    
    @ExcelProperty("姓名")
    private String name;
    
    @ExcelProperty("年龄")
    private Integer age;
    
    @ExcelProperty("邮箱")
    private String email;
    
    @ExcelProperty("手机号")
    private String phone;
}

2. 自定义表头校验器

java 复制代码
public class HeadValidator implements ReadListener<UserImportVO> {
    
    private final List<String> expectedHead;
    private final List<String> actualHead = new ArrayList<>();
    private boolean headValid = true;
    private String errorMsg;
    
    public HeadValidator() {
        // 预期的表头列表
        this.expectedHead = Arrays.asList("姓名", "年龄", "邮箱", "手机号");
    }
    
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        // 提取实际表头
        headMap.values().forEach(cellData -> {
            String cellValue = cellData.getStringValue();
            actualHead.add(cellValue);
        });
        
        // 校验表头
        validateHead();
    }
    
    private void validateHead() {
        if (actualHead.size() != expectedHead.size()) {
            headValid = false;
            errorMsg = String.format("表头数量不匹配,期望%d个,实际%d个", 
                expectedHead.size(), actualHead.size());
            return;
        }
        
        for (int i = 0; i < expectedHead.size(); i++) {
            if (!expectedHead.get(i).equals(actualHead.get(i))) {
                headValid = false;
                errorMsg = String.format("第%d列表头不匹配,期望:%s,实际:%s", 
                    i + 1, expectedHead.get(i), actualHead.get(i));
                break;
            }
        }
    }
    
    @Override
    public void invoke(UserImportVO data, AnalysisContext context) {
        // 表头校验不通过时,直接抛出异常
        if (!headValid) {
            throw new ExcelDataConvertException(context.readRowHolder().getRowIndex(), 
                null, errorMsg, null);
        }
        // 数据处理逻辑
        processData(data);
    }
    
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 所有数据解析完成后的操作
        System.out.println("Excel解析完成");
    }
    
    private void processData(UserImportVO data) {
        // 具体的业务处理逻辑
        System.out.println("处理数据:" + data);
    }
    
    public boolean isHeadValid() {
        return headValid;
    }
    
    public String getErrorMsg() {
        return errorMsg;
    }
}

3. 更灵活的表头校验器(支持顺序不敏感)

java 复制代码
public class FlexibleHeadValidator implements ReadListener<UserImportVO> {
    
    private final Set<String> expectedHeadSet;
    private final Set<String> actualHeadSet = new HashSet<>();
    private boolean headValid = true;
    private String errorMsg;
    
    public FlexibleHeadValidator() {
        // 预期的表头集合(顺序不敏感)
        this.expectedHeadSet = new HashSet<>(Arrays.asList("姓名", "年龄", "邮箱", "手机号"));
    }
    
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        // 提取实际表头
        headMap.values().forEach(cellData -> {
            String cellValue = cellData.getStringValue();
            actualHeadSet.add(cellValue);
        });
        
        // 校验表头
        validateHead();
    }
    
    private void validateHead() {
        if (actualHeadSet.size() != expectedHeadSet.size()) {
            headValid = false;
            errorMsg = String.format("表头数量不匹配,期望%d个,实际%d个", 
                expectedHeadSet.size(), actualHeadSet.size());
            return;
        }
        
        if (!actualHeadSet.containsAll(expectedHeadSet)) {
            headValid = false;
            Set<String> missingHeaders = new HashSet<>(expectedHeadSet);
            missingHeaders.removeAll(actualHeadSet);
            errorMsg = "缺少必要的表头:" + String.join(",", missingHeaders);
        }
    }
    
    // 其他方法同上...
}

4. 控制器层实现

java 复制代码
@RestController
@RequestMapping("/excel")
public class ExcelImportController {
    
    @PostMapping("/import")
    public ResponseEntity<String> importExcel(@RequestParam("file") MultipartFile file) {
        try {
            HeadValidator headValidator = new HeadValidator();
            
            EasyExcel.read(file.getInputStream(), UserImportVO.class, headValidator)
                    .sheet()
                    .headRowNumber(1)
                    .doRead();
            
            if (!headValidator.isHeadValid()) {
                return ResponseEntity.badRequest().body("表头校验失败:" + headValidator.getErrorMsg());
            }
            
            return ResponseEntity.ok("Excel导入成功");
            
        } catch (ExcelDataConvertException e) {
            return ResponseEntity.badRequest().body("数据解析错误,第" + (e.getRowIndex() + 1) + "行:" + e.getMessage());
        } catch (Exception e) {
            return ResponseEntity.badRequest().body("文件处理失败:" + e.getMessage());
        }
    }
}

5. 全局异常处理

java

java 复制代码
@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(MultipartException.class)
    public ResponseEntity<String> handleMultipartException(MultipartException e) {
        return ResponseEntity.badRequest().body("文件上传失败:" + e.getMessage());
    }
    
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception e) {
        return ResponseEntity.badRequest().body("系统错误:" + e.getMessage());
    }
}
相关推荐
初次攀爬者4 小时前
Kafka + KRaft模式架构基础介绍
后端·kafka
洛森唛4 小时前
Elasticsearch DSL 查询语法大全:从入门到精通
后端·elasticsearch
拳打南山敬老院4 小时前
Context 不是压缩出来的,而是设计出来的
前端·后端·aigc
初次攀爬者5 小时前
Kafka + ZooKeeper架构基础介绍
后端·zookeeper·kafka
LucianaiB5 小时前
Openclaw 安装使用保姆级教程(最新版)
后端
初次攀爬者5 小时前
Kafka 基础介绍
spring boot·kafka·消息队列
华仔啊5 小时前
Stream 代码越写越难看?JDFrame 让 Java 逻辑回归优雅
java·后端
用户8307196840825 小时前
spring ai alibaba + nacos +mcp 实现mcp服务负载均衡调用实战
spring boot·spring·mcp
哈密瓜的眉毛美5 小时前
零基础学Java|第五篇:进制转换与位运算、原码反码补码
后端
开心就好20255 小时前
免 Xcode 的 iOS 开发新选择?聊聊一款更轻量的 iOS 开发 IDE kxapp 快蝎
后端·ios