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());
    }
}
相关推荐
IT_陈寒2 小时前
闭包陷阱让我加了两天班,JavaScript你真行
前端·人工智能·后端
易协同低代码3 小时前
通达OA核心类库TD类深度解析
后端
Gopher_HBo3 小时前
Go语言学习笔记(十八)Gin处理Session
后端
谭光志4 小时前
工具塞满上下文窗口怎么办?深度拆解 AI Agent Tool Search 按需加载实现原理
前端·后端·ai编程
她说..4 小时前
Java 默认值设置方式
java·开发语言·后端·springboot
foggyprojects5 小时前
从0开始,一句话启动AI DataAgent
后端·数据分析·ai编程
郡杰5 小时前
一些基础和问题解决
后端
陈随易5 小时前
前端项目部署只要30秒
前端·后端·程序员
YIAN5 小时前
从零手写文件读取 MCP 服务:一文吃透 Model Context Protocol 全链路通信原理
前端·后端·mcp
Imchendiana5 小时前
《狂人日记NO.9》— 前后端一把梭,我的全栈实录
前端·后端