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());
    }
}
相关推荐
陈随易17 小时前
Postman 被曝自动往 Claude Code 和 Codex 装插件
前端·后端·程序员
云-缩影18 小时前
拆解 AI 图像生成应用的后端架构:ECS、OSS 和大模型 API 怎么配合
java·spring boot·后端
考虑考虑20 小时前
Posrgresql恢复.backup文件
数据库·后端·postgresql
无奈何杨20 小时前
风控决策系统决策流,策略模式,权重、顺序、投票、最坏
后端
AI开源软件20 小时前
SpringBoot3+Vue3 财务管理系统如何设计:从业务单据到总账的模块划分
spring boot·vue3·领域建模·会计凭证·ruoyi office·业财协同·财务系统设计
Patrick_Wilson21 小时前
Go 为什么需要 defer?从函数怎么收尾说起
后端·go·编程语言
苏三说技术21 小时前
DeepSeek Harness入驻B站,评论区被干成了大型插件集市!
后端
橙序员小站1 天前
我一句话让 AI 把公众号工作台搭完了:部署实测记录
后端
Patrick_Wilson1 天前
从文本输出理解 Go 方法与接口
后端·go·编程语言
QCoding1 天前
企业级Agent Evaluation实战:Agent能跑就能上线?AI Agent到底怎么测?
后端·spring