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());
    }
}
相关推荐
东风破_10 小时前
从跨域到 WebSocket:前端跨域方案、SSE 与双向实时通信详解
前端·后端
行百里er12 小时前
Gateway 上别硬塞 MVC:DeferredImportSelector 看菜下锅
spring boot·后端·开源
名字还没想好☜12 小时前
Java 遍历时删元素抛 ConcurrentModificationException:fail-fast 原理与三种正确删法
后端
IT_陈寒13 小时前
React Hooks闭包陷阱差点让我加班到凌晨
前端·人工智能·后端
黑马程序员毕设13 小时前
基于Java的医院药品管理系统的优化设计与实现
java·开发语言·spring boot·小程序·架构·课程设计·毕设
码事漫谈13 小时前
本体论,彻底懂
后端
创新技术阁13 小时前
FastapiAdmin 系统日志体系与核心配置参数详解
前端·后端·fastapi
掘金者阿豪13 小时前
SQL Server 迁到金仓,真正麻烦的不是数据,而是这些 T-SQL
后端
xcl092514 小时前
酒馆预约系统开发实战:从需求分析到上线全流程指南
java·spring boot·需求分析
Kyrie_kk14 小时前
Java--IO--Path文件访问
java·后端