导出excel替换

This commit is contained in:
wkc
2026-01-27 17:55:53 +08:00
parent 79cd4fe755
commit 5b0c338b5e
20 changed files with 4261 additions and 1503 deletions

View File

@@ -0,0 +1,251 @@
# 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. 重新编译部署
建议在分支上进行完整测试后再合并到主分支。

View File

@@ -0,0 +1,121 @@
# Proposal: Replace Apache POI with Alibaba EasyExcel
## Summary
将若依框架中的 Apache POI 替换为 Alibaba EasyExcel以解决大文本量 Excel 导入导出时的内存占用和性能问题。当前使用 POI 的 SXSSFWorkbook 虽然支持流式写入,但在处理大量数据时仍然存在性能瓶颈,且不支持真正的流式读取。
## Motivation
### 问题分析
1. **当前 POI 实现的限制**
- `ExcelUtil.java:112` - `sheetSize` 硬编码为 65536这是 Excel 2003 的限制
- `ExcelUtil.java:1682` - 使用 SXSSFWorkbook 进行流式写入,但内存优化有限
- `ExcelUtil.java:325-525` - `importExcel` 方法一次性加载整个 Sheet 到内存,无法处理大文件
- POI 的 DOM 解析模式导致大文件内存占用过高
2. **业务需求**
- 需要支持导入导出超过 10 万行数据
- 需要支持单元格内容超过 32767 字符POI STRING 类型限制)
- 需要降低内存占用,避免 OOM
3. **EasyExcel 优势**
- 基于 SAX 解析,真正的流式读写
- 内存占用仅与行数据大小相关,与文件大小无关
- API 设计简洁,注解驱动
- 官方维护活跃,社区成熟
## Proposed Solution
### 技术方案
1. **保持 API 兼容性**
- 保留 `@Excel``@Excels` 注解接口
- 保留 `ExcelUtil<T>` 的公共方法签名
- 保留 `ExcelHandlerAdapter` 接口(适配 EasyExcel 的写处理器)
2. **核心实现变更**
- 使用 `EasyExcel.write()` 替代 POI 的 Workbook/Sheet 操作
- 使用 `EasyExcel.read()` 替代 POI 的 Workbook 解析
- 实现自定义的 `HeadGenerator``ContentStyleStrategy` 以支持样式
3. **依赖变更**
- 移除 `poi-ooxml` 依赖
- 添加 `easyexcel` 依赖(版本 3.3.4,支持 Spring Boot 3
### 影响范围
**修改的文件**
- `ruoyi-common/pom.xml` - 更新依赖
- `ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil.java` - 重写实现
- `ruoyi-common/src/main/java/com/ruoyi/common/annotation/Excel.java` - 调整注解属性
- `ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelHandlerAdapter.java` - 适配新接口
**无需修改的文件**(向后兼容):
- 所有 Controller 层代码13 个文件使用 ExcelUtil
- 实体类上的 `@Excel` 注解
## Alternatives Considered
### 方案 1升级 POI 版本
- **优点**:代码改动最小
- **缺点**无法从根本上解决内存问题POI 的架构限制依然存在
### 方案 2使用 Hutool 的 Excel 工具
- **优点**API 简洁
- **缺点**:底层仍使用 POI内存问题未解决
### 方案 3使用 EasyExcel选定方案
- **优点**:真正的流式处理,内存占用低,社区成熟
- **缺点**:需要适配现有注解和 API
## Compatibility Notes
### 破坏性变更
1. **样式支持**EasyExcel 的样式支持有限,以下功能可能需要简化:
- 合并单元格(`needMerge`
- 图片插入(`ColumnType.IMAGE`
- 数据验证(`combo`, `comboReadDict`
- 自定义颜色(`headerBackgroundColor`, `color` 等)
2. **Sheet 分割**EasyExcel 自动处理大文件,无需手动分割 Sheet
3. **统计行**`isStatistics` 需要通过监听器实现
### 保留的功能
- ✅ 注解驱动配置
- ✅ 字典类型转换(`dictType`
- ✅ 表达式转换(`readConverterExp`
- ✅ 日期格式化(`dateFormat`
- ✅ 导入/导出模板生成
- ✅ 字段筛选(`includeFields`, `excludeFields`
## Success Criteria
1. **性能指标**
- 导出 10 万行数据,内存占用 < 500MB
- 导入 100MB Excel 文件,内存占用 < 200MB
2. **功能完整性**
- 所有现有 Controller 的导入导出功能正常工作
- 支持超过 65536 行数据
3. **兼容性**
- 现有实体类注解无需修改
- 现有业务代码无需修改
## Timeline
阶段划分(具体任务见 tasks.md
1. 依赖更新与环境准备
2. 核心 ExcelUtil 重写
3. 注解与接口适配
4. 集成测试与验证
5. 文档更新
## Related Changes
- 无前置依赖
- 可能影响:所有使用 Excel 导入导出的模块

View File

@@ -0,0 +1,332 @@
# Spec: Excel Import/Export with EasyExcel
## ADDED Requirements
### Requirement: 流式导出大量数据
The system **MUST** support streaming export using EasyExcel to handle datasets exceeding 100,000 rows while maintaining low memory footprint.
系统**必须**支持使用 EasyExcel 进行流式导出,以处理超过 10 万行的数据集,同时保持低内存占用。
#### Scenario: 导出 10 万行用户数据
**Given** 系统中有 10 万条用户记录
**When** 管理员调用用户导出接口
**Then** 系统应:
- 成功生成包含 10 万行数据的 Excel 文件
- 内存占用不超过 500MB
- 导出时间在合理范围内(< 30 秒)
- 生成的文件包含所有必要的表头和样式
#### Scenario: 导出超过 65536 行数据
**Given** 数据集包含 10 万行记录(超过传统 Excel 限制)
**When** 调用导出功能
**Then** 系统应:
- 自动使用 .xlsx 格式
- 不进行 Sheet 分割EasyExcel 自动处理)
- 所有数据完整导出到单个文件
---
### Requirement: 流式导入大文件
The system **MUST** support streaming import using EasyExcel to process Excel files larger than 100MB.
系统**必须**支持使用 EasyExcel 进行流式读取,以处理超过 100MB 的 Excel 文件。
#### Scenario: 导入包含 10 万行数据的 Excel 文件
**Given** 一个包含 10 万行数据的 Excel 文件(约 50MB
**When** 管理员上传该文件进行导入
**Then** 系统应:
- 逐行解析文件,不一次性加载到内存
- 内存占用不超过 200MB
- 正确解析所有数据行
- 返回完整的解析结果列表
#### Scenario: 导入包含超长文本的单元格
**Given** Excel 文件中某些单元格包含超过 32,767 个字符(超过 POI 限制)
**When** 执行导入操作
**Then** 系统应:
- 完整读取单元格内容
- 不截断或丢失数据
- 正确映射到实体类字段
---
### Requirement: 保持注解驱动配置兼容性
The system **MUST** maintain compatibility with existing `@Excel` and `@Excels` annotations, allowing existing entity classes to work without modification.
系统**必须**保持与现有 `@Excel``@Excels` 注解的兼容性,使现有实体类无需修改即可使用新实现。
#### Scenario: 使用现有注解导出数据
**Given** 实体类上已定义 `@Excel` 注解
```java
@Excel(name = "用户名", sort = 1)
private String userName;
@Excel(name = "性别", dictType = "sys_user_sex", sort = 2)
private String sex;
@Excel(name = "创建时间", dateFormat = "yyyy-MM-dd HH:mm:ss", sort = 3)
private Date createTime;
```
**When** 调用导出功能
**Then** 系统应:
- 正确解析 `@Excel` 注解配置
- 按指定的 `sort` 顺序排列列
- 应用字典转换(`dictType`
- 应用日期格式化(`dateFormat`
- 生成包含正确表头的 Excel 文件
#### Scenario: 使用复合注解导出嵌套属性
**Given** 实体类使用 `@Excels` 注解配置多个导出规则
```java
@Excels({
@Excel(name = "部门名称", targetAttr = "deptName", sort = 10),
@Excel(name = "部门编码", targetAttr = "deptCode", sort = 11)
})
private SysDept dept;
```
**When** 调用导出功能
**Then** 系统应:
- 正确解析嵌套对象属性
- 生成多个对应的列
- 正确填充数据
---
### Requirement: 样式与格式支持
The system **SHALL** support basic cell styling configuration through annotations, including alignment, background color, etc.
系统**应当**支持通过注解配置基本的单元格样式,包括对齐方式、背景色等。
#### Scenario: 应用自定义样式
**Given** 实体类定义了样式配置
```java
@Excel(
name = "金额",
align = HorizontalAlignmentEnum.RIGHT,
backgroundColor = "yellow",
color = "red",
sort = 5
)
private BigDecimal amount;
```
**When** 导出数据
**Then** 系统应:
- 单元格内容右对齐
- 背景色为黄色
- 字体颜色为红色
#### Scenario: 数字格式化
**Given** 字段配置了精度和舍入模式
```java
@Excel(name = "单价", scale = 2, roundingMode = BigDecimal.ROUND_HALF_UP)
private BigDecimal price;
```
**When** 导出包含该字段的数据
**Then** 系统应:
- 数值保留 2 位小数
- 使用四舍五入规则
---
### Requirement: 导入模板生成
The system **MUST** support generating empty import templates for users to fill and upload.
系统**必须**支持生成空的导入模板,供用户填写后上传。
#### Scenario: 生成导入模板
**Given** 实体类配置了 `@Excel` 注解,其中部分字段标记为仅导入
```java
@Excel(name = "用户名", type = Type.ALL)
private String userName;
@Excel(name = "密码", type = Type.IMPORT) // 仅导入
private String password;
```
**When** 调用 `importTemplateExcel()` 方法
**Then** 系统应:
- 生成包含所有 `type=IMPORT``type=ALL` 字段的表头
- 仅导入字段(`type=IMPORT`)不出现在模板中
- 应用数据验证配置(如果有)
- 设置合适的列宽
---
### Requirement: 字段筛选功能
The system **MUST** support runtime control of exported fields.
系统**必须**支持运行时动态控制导出包含的字段。
#### Scenario: 显示指定字段
**Given** 实体类有 20 个字段,但只想导出其中 5 个
**When** 调用 `util.showColumn("id", "name", "email", "phone", "status")`
**Then** 导出的 Excel 应:
- 仅包含指定的 5 个字段
- 其他字段不出现在导出结果中
#### Scenario: 排除指定字段
**Given** 实体类有 20 个字段,想导出其中 18 个
**When** 调用 `util.hideColumn("password", "salt")`
**Then** 导出的 Excel 应:
- 包含除 `password``salt` 外的所有字段
- 敏感字段不出现在导出结果中
---
### Requirement: 自定义数据处理器
The system **MUST** support custom data handlers for special cell data processing.
系统**必须**支持通过自定义处理器对单元格数据进行特殊处理。
#### Scenario: 使用自定义处理器格式化数据
**Given** 定义了自定义处理器
```java
public class CustomAmountHandler implements ExcelHandlerAdapter {
@Override
public Object format(Object value, String[] args, Cell cell, Workbook wb) {
// 将金额转换为中文大写
return convertToChinese((BigDecimal) value);
}
}
```
**And** 实体字段配置使用该处理器
```java
@Excel(name = "金额(大写)", handler = CustomAmountHandler.class)
private BigDecimal amount;
```
**When** 导出数据
**Then** 金额列应显示为中文大写形式(如:壹万贰仟叁佰肆拾伍元陆角柒分)
---
### Requirement: 数据验证支持
The system **SHALL** support configuring dropdown options and prompt information for import templates.
系统**应当**支持为导入模板配置下拉选项和提示信息。
#### Scenario: 配置下拉选项
**Given** 字段配置了固定的下拉选项
```java
@Excel(name = "状态", combo = {"启用", "禁用"})
private String status;
```
**When** 生成导入模板
**Then** 该单元格应:
- 显示下拉箭头
- 仅能选择"启用"或"禁用"
- 无法输入其他值
#### Scenario: 从字典读取下拉选项
**Given** 字段配置从字典读取选项
```java
@Excel(name = "性别", dictType = "sys_user_sex", comboReadDict = true)
private String sex;
```
**When** 生成导入模板
**Then** 该单元格的下拉列表应:
- 包含字典中定义的所有选项
- 动态从系统缓存读取
---
## MODIFIED Requirements
### Requirement: ExcelUtil API 兼容性
The modified `ExcelUtil` **MUST** maintain API compatibility with existing code.
修改后的 `ExcelUtil` **必须**保持与现有代码的 API 兼容性。
#### Scenario: 使用现有导出方法
**Given** 现有 Controller 代码
```java
List<SysUser> list = userService.selectUserList(user);
ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
util.exportExcel(response, list, "用户数据");
```
**When** 替换为 EasyExcel 实现
**Then** 代码应无需修改即可正常工作
#### Scenario: 使用现有导入方法
**Given** 现有导入代码
```java
ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
List<SysUser> userList = util.importExcel(file.getInputStream());
```
**When** 替换为 EasyExcel 实现
**Then** 代码应无需修改即可正常工作
---
## REMOVED Requirements
### 无
此变更不删除任何现有功能需求,仅替换底层实现。

View File

@@ -0,0 +1,122 @@
# Tasks: Replace Apache POI with Alibaba EasyExcel
## 依赖更新与环境准备
- [x] 1.1 更新 `pom.xml`,添加 `easyexcel.version` 属性(版本 3.3.4
- [x] 1.2 更新 `ruoyi-common/pom.xml`,保留 `poi-ooxml` 依赖,添加 `easyexcel` 依赖
- [ ] 1.3 验证依赖解析成功:`mvn dependency:tree`
- [x] 1.4 备份现有的 `ExcelUtil.java` 文件到 `excel/` 目录(保留参考)
## 注解与接口适配
- [x] 2.1 更新 `Excel.java` 注解:
- [x] 2.1.1 移除 POI 类型的 import`HorizontalAlignment`, `IndexedColors`
- [x] 2.1.2 使用字符串替代(`align`, `headerBackgroundColor` 等)
- [x] 2.1.3 调整颜色属性为字符串表示
- [x] 2.1.4 更新 `Javadoc` 注释
- [x] 2.2 更新 `ExcelHandlerAdapter.java` 接口:
- [x] 2.2.1 保留原有方法签名
- [x] 2.2.2 添加 EasyExcel `Cell``Workbook` 类型适配说明
- [x] 2.3 验证注解编译无误
## 核心 ExcelUtil 重写
- [x] 3.1 创建新的 `ExcelUtil.java` 基础结构:
- [x] 3.1.1 保留泛型类定义 `ExcelUtil<T>`
- [x] 3.1.2 保留现有公共方法签名
- [x] 3.1.3 移除 POI 相关的私有字段和方法
- [x] 3.2 实现导出功能:
- [x] 3.2.1 `exportExcel(HttpServletResponse, List<T>, String, String)` - 主入口
- [x] 3.2.2 `exportExcel(HttpServletResponse, List<T>, String)` - 简化版
- [x] 3.2.3 `exportExcel()` - 返回文件名版本
- [x] 3.2.4 `importTemplateExcel()` - 生成导入模板
- [x] 3.3 实现导入功能:
- [x] 3.3.1 `importExcel(InputStream)` - 默认标题行
- [x] 3.3.2 `importExcel(InputStream, int titleNum)` - 自定义标题行
- [x] 3.3.3 `importExcel(String sheetName, InputStream, int titleNum)` - 指定 Sheet
- [x] 3.4 实现 EasyExcel 集成组件:
- [x] 3.4.1 `ExcelStyleHandler` - 样式处理器(基于 `@Excel` 注解)
- [x] 3.4.2 `ReadListener` - 读取监听器(内置在 `importExcel` 中)
- [x] 3.5 实现辅助功能:
- [x] 3.5.1 字典类型转换(`dictType`
- [x] 3.5.2 表达式转换(`readConverterExp`
- [x] 3.5.3 字段筛选(`showColumn`, `hideColumn`
- [ ] 3.5.4 日期格式化(`dateFormat`- EasyExcel 自动处理
- [ ] 3.5.5 自定义处理器(`handler`- 需要进一步适配
## 集成测试与验证
- [ ] 4.1 单元测试:
- [ ] 4.1.1 测试基本导出功能(少量数据)
- [ ] 4.1.2 测试大数据量导出10 万行)
- [ ] 4.1.3 测试基本导入功能
- [ ] 4.1.4 测试大文件导入100MB
- [ ] 4.1.5 测试超长文本单元格
- [ ] 4.1.6 测试样式应用
- [ ] 4.1.7 测试字典转换
- [ ] 4.1.8 测试字段筛选
- [ ] 4.2 现有功能集成测试:
- [ ] 4.2.1 测试 `SysUserController` 导入导出
- [ ] 4.2.2 测试 `SysRoleController` 导入导出
- [ ] 4.2.3 测试 `SysDictDataController` 导入导出
- [ ] 4.2.4 测试 `SysPostController` 导入导出
- [ ] 4.2.5 测试 `SysConfigController` 导入导出
- [ ] 4.2.6 测试 `SysOperlogController` 导入导出
- [ ] 4.2.7 测试 `SysLogininforController` 导入导出
- [ ] 4.2.8 测试 `SysJobController` 导入导出
- [ ] 4.2.9 测试 `SysJobLogController` 导入导出
- [ ] 4.3 性能验证:
- [ ] 4.3.1 使用 VisualVM 监控导出 10 万行数据的内存占用
- [ ] 4.3.2 使用 VisualVM 监控导入 100MB 文件的内存占用
- [ ] 4.3.3 记录并对比迁移前后的性能指标
## 文档更新
- [ ] 5.1 更新 `openspec/project.md`
- [ ] 5.1.1 添加 EasyExcel 到技术栈
- [ ] 5.1.2 更新项目约定中的 Excel 处理说明
- [ ] 5.2 创建 API 文档:
- [ ] 5.2.1 记录 `ExcelUtil` 公共方法的使用方式
- [ ] 5.2.2 记录 `@Excel` 注解的所有属性说明
- [ ] 5.2.3 提供迁移指南(如果 API 有变化)
- [ ] 5.3 更新 `CLAUDE.md`(如需要):
- [ ] 5.3.1 添加 Excel 相关的开发约定
## 依赖关系
```
1. 依赖更新
2. 注解适配
3. ExcelUtil 重写
4. 集成测试
5. 文档更新
```
## 并行任务
以下任务可以并行执行:
- 2.1-2.3(注解适配)可与 3.1-3.2ExcelUtil 基础结构)并行
- 4.1(单元测试)可与 4.2(集成测试)部分并行
## 验收标准
所有任务完成后,必须满足:
1. 编译通过,无依赖错误
2. 所有现有 Controller 的导入导出功能正常
3. 导出 10 万行数据内存占用 < 500MB
4. 导入 100MB 文件内存占用 < 200MB
5. 现有实体类注解无需修改