Spring Boot + EasyExcel实现Excel文件高效处理

一、环境准备

1. 添加依赖

<!-- EasyExcel核心依赖 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.2</version>
</dependency>

<!-- Spring Web文件处理 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- 数据校验 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

二、基础功能实现

1. 数据模型定义

@Data
public class UserExportVO {
    @ExcelProperty("用户ID")
    private Long id;

    @ExcelProperty("用户名")
    private String username;

    @ExcelProperty(value = "创建时间", format = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;

    @ExcelProperty("状态")
    @ExcelConverterEnum(codeProperty = "code", descProperty = "desc")
    private UserStatus status;
}

@Data
public class UserImportDTO {
    @ExcelProperty("用户名")
    @NotBlank(message = "用户名不能为空")
    private String username;

    @ExcelProperty("邮箱")
    @Email(message = "邮箱格式不正确")
    private String email;

    @ExcelProperty("手机号")
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式错误")
    private String phone;
}

public enum UserStatus {
    ACTIVE(1, "激活"),
    DISABLED(0, "禁用");

    @EnumValue
    private final Integer code;
    private final String desc;
}

2. Excel导出实现

@RestController
@RequestMapping("/api/excel")
public class ExcelController {

    @GetMapping("/export/users")
    public void exportUserList(HttpServletResponse response) throws IOException {
        // 1. 设置响应头
        String fileName = URLEncoder.encode("用户列表.xlsx", StandardCharsets.UTF_8);
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);

        // 2. 查询数据(示例)
        List<UserExportVO> dataList = userService.getExportData();

        // 3. 写入Excel
        EasyExcel.write(response.getOutputStream(), UserExportVO.class)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 自动列宽
                .sheet("用户数据")
                .doWrite(dataList);
    }
}

3. Excel导入实现

@PostMapping("/import/users")
public ApiResult<?> importUsers(@RequestParam("file") MultipartFile file) {
    try {
        // 1. 创建监听器
        UserImportListener listener = new UserImportListener(userService);

        // 2. 读取Excel
        EasyExcel.read(file.getInputStream(), UserImportDTO.class, listener)
                .sheet()
                .headRowNumber(1) // 跳过标题行
                .doRead();

        // 3. 返回处理结果
        return ApiResult.success("导入成功", listener.getResult());
    } catch (Exception e) {
        return ApiResult.error("导入失败:" + e.getMessage());
    }
}

// 自定义读取监听器
public class UserImportListener extends AnalysisEventListener<UserImportDTO> {
    private final UserService userService;
    private final List<User> successList = new ArrayList<>();
    private final List<ImportError> errorList = new ArrayList<>();
    
    public UserImportListener(UserService userService) {
        this.userService = userService;
    }

    @Override
    public void invoke(UserImportDTO data, AnalysisContext context) {
        // 数据校验
        Set<ConstraintViolation<UserImportDTO>> violations = Validation.buildDefaultValidatorFactory()
                .getValidator()
                .validate(data);
        
        if (!violations.isEmpty()) {
            errorList.add(new ImportError(
                    context.readRowHolder().getRowIndex(),
                    violations.stream()
                            .map(ConstraintViolation::getMessage)
                            .collect(Collectors.joining(";"))
            );
            return;
        }

        // 转换为实体并保存
        try {
            User user = convertToEntity(data);
            successList.add(user);
            
            // 每100条批量插入
            if (successList.size() >= 100) {
                userService.batchInsert(successList);
                successList.clear();
            }
        } catch (Exception e) {
            errorList.add(new ImportError(
                    context.readRowHolder().getRowIndex(),
                    e.getMessage()
            ));
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 处理剩余数据
        if (!successList.isEmpty()) {
            userService.batchInsert(successList);
        }
    }
}

三、高级功能扩展

1. 动态表头导出

public void dynamicExport(HttpServletResponse response) {
    // 1. 构建动态表头
    List<List<String>> head = new ArrayList<>();
    head.add(Collections.singletonList("基础信息"));
    head.add(Arrays.asList("姓名", "年龄"));
    head.add(Arrays.asList("联系方式", "手机号", "邮箱"));

    // 2. 构建数据
    List<List<Object>> data = List.of(
            List.of("张三", 25, "13800138000", "zhangsan@example.com"),
            List.of("李四", 30, "13900139000", "lisi@example.com")
    );

    // 3. 写入Excel
    EasyExcel.write(response.getOutputStream())
            .head(head)
            .sheet("动态表头")
            .doWrite(data);
}

2. 百万数据导出优化

public void largeDataExport(HttpServletResponse response) {
    // 1. 分页查询参数
    int pageSize = 5000;
    int totalPage = calculateTotalPage();

    // 2. 分批次写入
    try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
            .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
            .build()) {

        WriteSheet writeSheet = EasyExcel.writerSheet("大数据量").head(UserExportVO.class).build();
        
        for (int page = 0; page < totalPage; page++) {
            List<UserExportVO> data = userService.getBatchData(page, pageSize);
            excelWriter.write(data, writeSheet);
        }
    }
}

3. 模板下载与数据校验

@GetMapping("/template")
public void downloadTemplate(HttpServletResponse response) throws IOException {
    String fileName = URLEncoder.encode("用户导入模板.xlsx", StandardCharsets.UTF_8);
    response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
    response.setHeader("Content-Disposition", "attachment;filename=" + fileName);

    // 创建带下拉列表的模板
    ExcelWriter writer = EasyExcel.write(response.getOutputStream())
            .head(UserImportDTO.class)
            .registerWriteHandler(new SelectSheetWriteHandler(
                    Collections.singletonMap("状态", Arrays.asList("激活", "禁用"))
            ))
            .build();
    
    writer.write(Collections.emptyList(), EasyExcel.writerSheet("模板").build());
    writer.finish();
}

四、配置优化建议

1. 文件上传限制配置

spring:
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB

2. 全局异常处理

@RestControllerAdvice
public class GlobalExceptionHandler {
    
    @ExceptionHandler(Exception.class)
    public ApiResult<?> handleException(Exception e) {
        if (e instanceof ExcelAnalysisException) {
            return ApiResult.error("Excel解析失败:" + e.getMessage());
        }
        return ApiResult.error("系统异常:" + e.getMessage());
    }
}

3. 导出样式定制

public class CustomCellStyleStrategy extends AbstractCellStyleStrategy {
    @Override
    protected void setHeadCellStyle(Cell cell, Head head, Integer relativeRowIndex) {
        CellStyle style = cell.getSheet().getWorkbook().createCellStyle();
        Font font = cell.getSheet().getWorkbook().createFont();
        font.setBold(true);
        font.setColor(IndexedColors.WHITE.getIndex());
        style.setFont(font);
        style.setFillForegroundColor(IndexedColors.BLUE.getIndex());
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        cell.setCellStyle(style);
    }
}

五、常见问题解决方案

  1. 中文乱码问题

    response.setCharacterEncoding("UTF-8");
    response.setHeader("Content-Disposition", 
        "attachment;filename*=UTF-8''" + fileName);
  2. 内存溢出处理

    • 使用SXSSFWorkbook模式

    • 增加JVM参数:-Xms512m -Xmx2048m

  3. 复杂表头合并

    .registerWriteHandler(new CellRangeStrategy(0, 0, 0, 3)) // 合并第一行前四列

六、最佳实践建议

  1. 数据校验

    • 前端校验:文件类型、大小

    • 后端校验:字段格式、业务规则

    • 数据库校验:唯一性约束

  2. 性能优化

    • 使用分页查询避免内存溢出

    • 开启批处理模式(JDBC Batch)

    • 异步导出(生成文件后提供下载链接)

  3. 安全防护

    • 文件类型白名单校验

    • 病毒扫描

    • 权限控制(导出范围限制)

  4. 用户体验优化

    • 进度提示(WebSocket推送进度)

    • 错误报告生成(导出错误行信息)

    • 断点续传(大文件分片上传)

推荐结合阿里云OSS实现云端文件存储,提升大文件处理能力。对于复杂业务场景,可考虑集成Apache POI进行更底层的Excel操作。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值