# Design: Replace Apache POI with Alibaba EasyExcel ## Architecture Overview ### 当前架构(POI) ``` Controller ↓ ExcelUtil (1944 行) ├── init() → createWorkbook() → SXSSFWorkbook ├── fillExcelData() → 迭代 list → addCell() └── importExcel() → WorkbookFactory → 解析整个文件 ``` ### 目标架构(EasyExcel) ``` Controller ↓ ExcelUtil (简化版) ├── exportExcel() → EasyExcel.write() → 流式写入 └── importExcel() → EasyExcel.read() + ReadListener → 流式读取 ``` ## Component Design ### 1. 依赖管理 **ruoyi-common/pom.xml**: ```xml org.apache.poi poi-ooxml com.alibaba easyexcel 3.3.4 ``` **pom.xml**: ```xml 3.3.4 ``` ### 2. ExcelUtil 核心重构 #### 导出流程 ```java // 当前实现 (POI) public void exportExcel(HttpServletResponse response, List list, String sheetName) { init(list, sheetName, title, Type.EXPORT); writeSheet(); // 一次性写入所有数据 wb.write(response.getOutputStream()); } // 新实现 (EasyExcel) public void exportExcel(HttpServletResponse response, List list, String sheetName) { EasyExcel.write(response.getOutputStream(), clazz) .sheet(sheetName) .head(headGenerator) // 动态表头生成 .registerWriteHandler(styleStrategy) // 样式策略 .registerWriteHandler(mergeStrategy) // 合并策略 .doWrite(list); // 流式写入 } ``` #### 导入流程 ```java // 当前实现 (POI) public List importExcel(InputStream is, int titleNum) { wb = WorkbookFactory.create(is); // 加载整个文件 // 遍历所有行,解析到内存 List for (int i = titleNum + 1; i <= rows; i++) { ... } return list; } // 新实现 (EasyExcel) public List importExcel(InputStream is, int titleNum) { List dataList = new ArrayList<>(); EasyExcel.read(is, clazz, new AnalysisEventListener() { @Override public void invoke(T data, AnalysisContext context) { // 逐行读取,不占用大量内存 dataList.add(data); } }).sheet().headRowNumber(titleNum).doRead(); return dataList; } ``` ### 3. 注解适配 #### Excel 注解调整 ```java // 当前 - 依赖 POI 类型 import org.apache.poi.ss.usermodel.HorizontalAlignment; import org.apache.poi.ss.usermodel.IndexedColors; public @interface Excel { HorizontalAlignment align() default HorizontalAlignment.CENTER; IndexedColors headerBackgroundColor() default IndexedColors.GREY_50_PERCENT; // ... } // 新实现 - EasyExcel 兼容 import com.alibaba.excel.enums.HorizontalAlignmentEnum; public @interface Excel { HorizontalAlignmentEnum align() default HorizontalAlignmentEnum.CENTER; String headerBackgroundColor() default " grey_50_percent"; // 简化为字符串 // ... } ``` ### 4. 自定义处理器实现 #### 样式处理器 ```java public class CustomStyleStrategy extends AbstractCellStyleStrategy { @Override protected void setContentCellStyle(Cell cell, Head head, Integer relativeRowIndex) { // 基于 @Excel 注解设置样式 Workbook workbook = cell.getSheet().getWorkbook(); Field field = getField(head.getHeadNameList()); Excel excel = field.getAnnotation(Excel.class); CellStyle style = createStyle(workbook, excel); cell.setCellStyle(style); } } ``` #### 合并策略 ```java public class MergeStrategy implements CellWriteHandler { @Override public void afterCellDispose(CellWriteHandlerContext context) { // 处理 needMerge 字段的合并 if (needMerge(context)) { context.getWriteSheetHolder().getSheet() .addMergedRegion(region); } } } ``` #### 自定义数据处理器适配 ```java // 原接口 public interface ExcelHandlerAdapter { Object format(Object value, String[] args, Cell cell, Workbook wb); } // 适配 EasyExcel WriteHandler public class CustomWriteHandler implements CellWriteHandler { private ExcelHandlerAdapter handler; private String[] args; @Override public void afterCellDispose(CellWriteHandlerContext context) { Object value = handler.format(context.getCellData(), args, context.getCell(), context.getWriteWorkbookHolder().getWorkbook()); // 应用处理后的值 } } ``` ## Migration Strategy ### 阶段 1:双模式支持(可选) ```java public class ExcelUtil { private boolean useEasyExcel = true; // 配置开关 public void exportExcel(...) { if (useEasyExcel) { exportWithEasyExcel(...); } else { exportWithPoi(...); // 保留旧实现 } } } ``` ### 阶段 2:直接替换(推荐) 1. 更新依赖 2. 重写 ExcelUtil 3. 更新注解 4. 逐模块测试 ## Testing Strategy ### 单元测试 ```java @Test void testLargeExport() { List data = generateData(100_000); ExcelUtil util = new ExcelUtil<>(DemoData.class); // 验证内存占用 util.exportExcel(response, data, "test"); } @Test void testLargeImport() { File file = createLargeExcel(10_000_000); // 1000万行 ExcelUtil util = new ExcelUtil<>(DemoData.class); List data = util.importExcel(new FileInputStream(file)); // 验证解析正确性和内存占用 } ``` ### 集成测试 - 测试所有现有 Controller 的导入导出接口 - 验证样式、合并、图片等功能 ## Performance Considerations | 操作 | POI (当前) | EasyExcel (目标) | |------|-----------|-----------------| | 10万行导出 | ~1GB 内存 | ~100MB 内存 | | 100万行导出 | OOM 风险 | ~200MB 内存 | | 100MB 文件导入 | ~2GB 内存 | ~150MB 内存 | | 单元格字符限制 | 32,767 | 无限制 | ## Rollback Plan 如果出现问题,可以通过以下步骤回退: 1. 恢复 `ruoyi-common/pom.xml` 中的 POI 依赖 2. 恢复 `ExcelUtil.java` 和相关文件 3. 重新编译部署 建议在分支上进行完整测试后再合并到主分支。