基于EasyExcel封装的Excel工具类,支持高效导出和读取操作

发布于:2025-02-28 ⋅ 阅读:(16) ⋅ 点赞:(0)

以下是一个基于EasyExcel封装的Excel工具类,支持高效导出和读取操作:

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import org.apache.poi.ss.usermodel.HorizontalAlignment;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;

/**
 * Excel工具类(基于EasyExcel)
 */
public class ExcelUtil {

    /**
     * 导出Excel到输出流(单个Sheet)
     * @param outputStream 输出流
     * @param dataList 数据列表
     * @param templateClass 数据模型类
     * @param sheetName 工作表名称
     * @param <T> 数据类型
     */
    public static <T> void exportToStream(OutputStream outputStream, 
                                        List<T> dataList,
                                        Class<T> templateClass,
                                        String sheetName) {
        ExcelWriter excelWriter = null;
        try {
            // 设置表头样式
            WriteCellStyle headStyle = new WriteCellStyle();
            headStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
            
            // 设置正文样式
            WriteCellStyle contentStyle = new WriteCellStyle();
            contentStyle.setHorizontalAlignment(HorizontalAlignment.LEFT);

            // 构建样式策略
            HorizontalCellStyleStrategy styleStrategy = 
                new HorizontalCellStyleStrategy(headStyle, contentStyle);

            excelWriter = EasyExcel.write(outputStream, templateClass)
                    .registerWriteHandler(styleStrategy)
                    .build();

            WriteSheet writeSheet = EasyExcel.writerSheet(sheetName).build();
            excelWriter.write(dataList, writeSheet);
        } finally {
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }

    /**
     * 导出大数据量Excel(分Sheet写入)
     * @param outputStream 输出流
     * @param dataSupplier 数据提供函数(分页查询)
     * @param templateClass 数据模型类
     * @param sheetSize 每个Sheet最大行数
     * @param <T> 数据类型
     */
    public static <T> void exportBigData(OutputStream outputStream,
                                        PageDataSupplier<T> dataSupplier,
                                        Class<T> templateClass,
                                        int sheetSize) {
        ExcelWriter excelWriter = null;
        try {
            excelWriter = EasyExcel.write(outputStream, templateClass).build();
            
            int sheetIndex = 1;
            int page = 1;
            List<T> dataChunk;
            
            while (!(dataChunk = dataSupplier.getPageData(page, sheetSize)).isEmpty()) {
                WriteSheet writeSheet = EasyExcel.writerSheet("Sheet" + sheetIndex).build();
                excelWriter.write(dataChunk, writeSheet);
                
                if (page % (sheetSize / 5000) == 0) { // 每5000行一个Sheet
                    sheetIndex++;
                }
                page++;
            }
        } finally {
            if (excelWriter != null) {
                excelWriter.finish();
            }
        }
    }

    /**
     * 从输入流读取Excel数据
     * @param inputStream 输入流
     * @param templateClass 数据模型类
     * @param dataConsumer 数据消费接口
     * @param <T> 数据类型
     */
    public static <T> void readFromStream(InputStream inputStream,
                                         Class<T> templateClass,
                                         DataConsumer<T> dataConsumer) {
        EasyExcel.read(inputStream, templateClass, new AnalysisEventListener<T>() {
            private static final int BATCH_SIZE = 2000;
            private List<T> cachedList = new ArrayList<>(BATCH_SIZE);

            @Override
            public void invoke(T data, AnalysisContext context) {
                cachedList.add(data);
                if (cachedList.size() >= BATCH_SIZE) {
                    dataConsumer.consume(cachedList);
                    cachedList = new ArrayList<>(BATCH_SIZE);
                }
            }

            @Override
            public void doAfterAllAnalysed(AnalysisContext context) {
                if (!cachedList.isEmpty()) {
                    dataConsumer.consume(cachedList);
                }
            }
        }).sheet().doRead();
    }

    /**
     * 导出Web响应(自动设置Header)
     * @param response HttpServletResponse
     * @param fileName 文件名(不带后缀)
     * @param dataList 数据列表
     * @param templateClass 数据模型类
     * @param <T> 数据类型
     */
    public static <T> void exportWebResponse(HttpServletResponse response,
                                            String fileName,
                                            List<T> dataList,
                                            Class<T> templateClass) throws IOException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String encodedFileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encodedFileName + ".xlsx");
        exportToStream(response.getOutputStream(), dataList, templateClass, "Sheet1");
    }

    // 自定义转换器注册
    public static void registerConverter(Converter<?> converter) {
        EasyExcel.registerConverter(converter);
    }

    /**
     * 分页数据提供接口
     */
    public interface PageDataSupplier<T> {
        List<T> getPageData(int pageNumber, int pageSize);
    }

    /**
     * 数据消费接口
     */
    public interface DataConsumer<T> {
        void consume(List<T> dataBatch);
    }

    // 示例数据模型
    public static class UserExportModel {
        @ExcelProperty("用户ID")
        private Long userId;
        
        @ExcelProperty(value = "用户状态", converter = StatusConverter.class)
        private Integer status;
        
        // 其他字段...
    }

    // 示例转换器
    public static class StatusConverter implements Converter<Integer> {
        private static final Map<Integer, String> STATUS_MAP = Map.of(
            1, "正常",
            2, "冻结",
            3, "注销"
        );

        @Override
        public String convertToExcelData(Integer value, ExcelContentProperty property, 
                                       GlobalConfiguration globalConfig) {
            return STATUS_MAP.getOrDefault(value, "未知状态");
        }
    }
}

使用示例

1. 简单导出到文件:

try (FileOutputStream fos = new FileOutputStream("export.xlsx")) {
    List<User> userList = userService.getAllUsers();
    ExcelUtil.exportToStream(fos, userList, User.class, "用户数据");
}

2. Web响应导出:

@GetMapping("/export")
public void exportUsers(HttpServletResponse response) throws IOException {
    List<User> userList = userService.getAllUsers();
    ExcelUtil.exportWebResponse(response, "用户列表", userList, User.class);
}

3. 大数据量分页导出:

ExcelUtil.exportBigData(response.getOutputStream(), 
    (page, size) -> userService.getUsersByPage(page, size),
    User.class,
    100_0000); // 每个Sheet存储100万数据

4. 读取Excel数据:

InputStream inputStream = new FileInputStream("import.xlsx");
ExcelUtil.readFromStream(inputStream, UserImportModel.class, batch -> {
    userService.batchProcess(batch);
});

主要特性说明

  1. 高性能导出:

    • 使用分Sheet写入策略,每个Sheet最多存储指定行数
    • 自动注册样式策略(表头居中,内容左对齐)
    • 支持流式导出,避免内存溢出
  2. 灵活读取:

    • 批量处理机制(默认每2000条处理一次)
    • 自动内存管理,适合大文件读取
  3. 扩展功能:

    • 支持自定义转换器(通过registerConverter方法)
    • 支持Web响应自动配置(自动设置Content-Type和文件名)
    • 提供分页数据获取接口,适合数据库分页查询
  4. 内存安全:

    • 导出时自动分批次写入
    • 读取时使用事件驱动模型,不保留完整数据在内存
    • 默认开启SXSSF模式(流式写入)
  5. 样式配置:

    • 预定义表头和内容样式
    • 可通过覆盖registerWriteHandler方法自定义样式

最佳实践建议

  1. 大文件导出:

    • 使用exportBigData方法配合分页查询
    • 设置合适的Sheet大小(推荐50-100万行/Sheet)
    • 添加内存监控逻辑
  2. 数据转换:

    • 为枚举字段创建专用转换器
    • 对日期字段使用@ExcelProperty#format
    • 复杂转换建议使用数据库联查
  3. 异常处理:

    • 在读取时添加数据校验逻辑
    • 使用全局异常处理器捕获ExcelAnalysisException
  4. 性能优化:

    • 导出时关闭自动列宽计算(.autoTrim(false)
    • 读取时设置合适的缓存大小(ReadCache
    • 避免在循环中创建大量临时对象