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());
    }
}
相关推荐
sg_knight1 小时前
RabbitMQ 中的预取值(prefetch)详解:如何真正提升消费端性能?
java·spring boot·spring·spring cloud·消息队列·rabbitmq·预取值
canonical_entropy2 小时前
API无缝升级方案:从推模式到拉模式的架构演进
后端·restful·graphql
摆烂工程师2 小时前
今天 Cloudflare 全球事故,连 GPT 和你的网站都一起“掉线”了
前端·后端·程序员
追逐时光者2 小时前
快速构建一个基础、现代化的 WinForm 管理系统
后端·.net
u***1372 小时前
【SpringBoot】【log】 自定义logback日志配置
java·spring boot·logback
T***u3333 小时前
SpringBoot集成SkyWalking,分布式链路追踪
spring boot·分布式·skywalking
在人间负债^4 小时前
Rust 实战项目:TODO 管理器
开发语言·后端·rust
Moonbit4 小时前
入围名单公布|2025 MGPIC 决赛即将拉开帷幕!
后端·算法
爱吃烤鸡翅的酸菜鱼4 小时前
用【rust】实现命令行音乐播放器
开发语言·后端·rust