# Design: Replace Apache POI with Alibaba EasyExcel

## Architecture Overview

### 当前架构（POI）

```
Controller
    ↓
ExcelUtil<T> (1944 行)
    ├── init() → createWorkbook() → SXSSFWorkbook
    ├── fillExcelData() → 迭代 list → addCell()
    └── importExcel() → WorkbookFactory → 解析整个文件
```

### 目标架构（EasyExcel）

```
Controller
    ↓
ExcelUtil<T> (简化版)
    ├── exportExcel() → EasyExcel.write() → 流式写入
    └── importExcel() → EasyExcel.read() + ReadListener → 流式读取
```

## Component Design

### 1. 依赖管理

**ruoyi-common/pom.xml**:
```xml
<!-- 移除 -->
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
</dependency>

<!-- 新增 -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>easyexcel</artifactId>
    <version>3.3.4</version>
</dependency>
```

**pom.xml**:
```xml
<properties>
    <easyexcel.version>3.3.4</easyexcel.version>
</properties>
```

### 2. ExcelUtil 核心重构

#### 导出流程

```java
// 当前实现 (POI)
public void exportExcel(HttpServletResponse response, List<T> list, String sheetName) {
    init(list, sheetName, title, Type.EXPORT);
    writeSheet();  // 一次性写入所有数据
    wb.write(response.getOutputStream());
}

// 新实现 (EasyExcel)
public void exportExcel(HttpServletResponse response, List<T> list, String sheetName) {
    EasyExcel.write(response.getOutputStream(), clazz)
        .sheet(sheetName)
        .head(headGenerator)        // 动态表头生成
        .registerWriteHandler(styleStrategy)  // 样式策略
        .registerWriteHandler(mergeStrategy)  // 合并策略
        .doWrite(list);              // 流式写入
}
```

#### 导入流程

```java
// 当前实现 (POI)
public List<T> importExcel(InputStream is, int titleNum) {
    wb = WorkbookFactory.create(is);  // 加载整个文件
    // 遍历所有行，解析到内存 List
    for (int i = titleNum + 1; i <= rows; i++) { ... }
    return list;
}

// 新实现 (EasyExcel)
public List<T> importExcel(InputStream is, int titleNum) {
    List<T> dataList = new ArrayList<>();
    EasyExcel.read(is, clazz, new AnalysisEventListener<T>() {
        @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<T> {
    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<DemoData> data = generateData(100_000);
    ExcelUtil<DemoData> util = new ExcelUtil<>(DemoData.class);
    // 验证内存占用
    util.exportExcel(response, data, "test");
}

@Test
void testLargeImport() {
    File file = createLargeExcel(10_000_000); // 1000万行
    ExcelUtil<DemoData> util = new ExcelUtil<>(DemoData.class);
    List<DemoData> 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. 重新编译部署

建议在分支上进行完整测试后再合并到主分支。
