# Excel读取工具使用说明

## 概述

这是一个功能强大的Excel读取工具，通过配置 **Excel列对象** 来定义Excel列与Java对象字段的映射关系，支持自动构建对象并集成数据校验功能。

## 核心组件

### 1. ExcelColumnMapping - Excel列映射配置
定义Excel列与对象字段的映射关系：
- **columnName** - Excel列名（表头名称）
- **columnIndex** - Excel列索引（从0开始，优先级高于列名）
- **fieldName** - 对象字段名
- **fieldType** - 字段类型
- **required** - 是否必填
- **defaultValue** - 默认值
- **dateFormat** - 日期格式
- **validationParams** - 校验参数列表

### 2. ExcelReaderUtils - Excel读取工具类
提供静态方法读取Excel：
- `readExcel(File, Class, List<ExcelColumnMapping>)` - 从文件读取
- `readExcel(File, Class, List<ExcelColumnMapping>, Map<options>)` - 从文件读取（带配置）
- `readExcel(InputStream, fileName, Class, List<ExcelColumnMapping>)` - 从输入流读取
- `readExcel(InputStream, fileName, Class, List<ExcelColumnMapping>, Map<options>)` - 从输入流读取（带配置）

### 3. ExcelReadResult - 读取结果
包含读取结果的详细信息：
- 成功数据列表
- 错误行信息（行号 -> 错误消息）
- 统计信息（总行数、成功数、失败数）

## 基本使用

### 1. 定义实体类

```java
public class UserInfo {
    private String name;
    private Integer age;
    private String phone;
    private String email;
    private Date joinDate;
    private Boolean active;
    
    // Getters and Setters...
}
```

### 2. 定义列映射关系

```java
List<ExcelColumnMapping> columnMappings = Arrays.asList(
    ExcelColumnMapping.builder()
        .columnName("姓名")           // Excel列名
        .fieldName("name")            // 对象字段名
        .fieldType(String.class)      // 字段类型
        .required()                   // 必填
        .build(),
    
    ExcelColumnMapping.builder()
        .columnName("年龄")
        .fieldName("age")
        .fieldType(Integer.class)
        .required()
        .build(),
    
    ExcelColumnMapping.builder()
        .columnName("手机号")
        .fieldName("phone")
        .fieldType(String.class)
        .required()
        .build(),
    
    ExcelColumnMapping.builder()
        .columnName("邮箱")
        .fieldName("email")
        .fieldType(String.class)
        .build(),
    
    ExcelColumnMapping.builder()
        .columnName("入职日期")
        .fieldName("joinDate")
        .fieldType(Date.class)
        .dateFormat("yyyy-MM-dd")
        .build(),
    
    ExcelColumnMapping.builder()
        .columnName("是否在职")
        .fieldName("active")
        .fieldType(Boolean.class)
        .defaultValue("true")
        .build()
);
```

### 3. 读取Excel

```java
// 读取文件
File file = new File("users.xlsx");
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
    file, 
    UserInfo.class, 
    columnMappings
);

// 处理结果
if (result.isSuccess()) {
    for (Object obj : result.getDataList()) {
        UserInfo user = (UserInfo) obj;
        System.out.println(user);
    }
    System.out.println("成功读取: " + result.getSuccessRows() + " 行");
}
```

### 4. 带数据校验的读取

```java
// 定义列映射关系（带校验）
List<ExcelColumnMapping> columnMappings = Arrays.asList(
    ExcelColumnMapping.builder()
        .columnName("姓名")
        .fieldName("name")
        .fieldType(String.class)
        .required()
        .addValidation(
            ValidationParam.builder()
                .fieldName("name")
                .fieldLabel("姓名")
                .required()
                .minLength(2)
                .maxLength(20)
                .validateMethod(ValidateMethodType.CHINESE)
                .build()
        )
        .build(),
    
    ExcelColumnMapping.builder()
        .columnName("年龄")
        .fieldName("age")
        .fieldType(Integer.class)
        .required()
        .addValidation(
            ValidationParam.builder()
                .fieldName("age")
                .fieldLabel("年龄")
                .required()
                .customValidator(value -> {
                    Integer age = (Integer) value;
                    return age >= 18 && age <= 65;
                })
                .customErrorMessage("年龄必须在18-65岁之间")
                .build()
        )
        .build(),
    
    ExcelColumnMapping.builder()
        .columnName("手机号")
        .fieldName("phone")
        .fieldType(String.class)
        .required()
        .addValidation(
            ValidationParam.builder()
                .fieldName("phone")
                .fieldLabel("手机号")
                .required()
                .validateMethod(ValidateMethodType.PHONE)
                .build()
        )
        .build(),
    
    ExcelColumnMapping.builder()
        .columnName("邮箱")
        .fieldName("email")
        .fieldType(String.class)
        .addValidation(
            ValidationParam.builder()
                .fieldName("email")
                .fieldLabel("邮箱")
                .required(false)
                .validateMethod(ValidateMethodType.EMAIL)
                .build()
        )
        .build()
);

// 配置选项
Map<String, Object> options = new HashMap<>();
options.put("continueOnError", true);  // 遇到错误继续读取

// 读取Excel
File file = new File("users.xlsx");
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
    file, 
    UserInfo.class, 
    columnMappings,
    options
);

// 处理结果
System.out.println("总行数: " + result.getTotalRows());
System.out.println("成功: " + result.getSuccessRows());
System.out.println("失败: " + result.getErrorRowsCount());

// 显示错误
if (result.hasErrors()) {
    result.getErrorRows().forEach((rowNum, error) -> {
        System.out.println("第" + rowNum + "行: " + error);
    });
}
```

### 5. 使用列索引而非列名

```java
// 使用列索引（从0开始）
List<ExcelColumnMapping> columnMappings = Arrays.asList(
    ExcelColumnMapping.builder()
        .columnIndex(0)  // 第1列
        .fieldName("name")
        .fieldType(String.class)
        .required()
        .build(),
    
    ExcelColumnMapping.builder()
        .columnIndex(1)  // 第2列
        .fieldName("age")
        .fieldType(Integer.class)
        .required()
        .build(),
    
    ExcelColumnMapping.builder()
        .columnIndex(2)  // 第3列
        .fieldName("phone")
        .fieldType(String.class)
        .required()
        .build()
);

File file = new File("users.xlsx");
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
    file, 
    UserInfo.class, 
    columnMappings
);
```

### 6. 自定义配置选项

以下是旧版本的校验代码，现在已经整合到ExcelColumnMapping中：

```java
// 旧版本（已弃用）
List<ValidationParam> validationParams = Arrays.asList(
    ValidationParam.builder()
        .fieldName("name")
        .fieldLabel("姓名")
        .required()
        .minLength(2)
        .maxLength(20)
        .validateMethod(ValidateMethodType.CHINESE)
        .build(),
    
    ValidationParam.builder()
        .fieldName("phone")
        .fieldLabel("手机号")
        .required()
        .validateMethod(ValidateMethodType.PHONE)
        .build(),
    
    ValidationParam.builder()
        .fieldName("email")
        .fieldLabel("邮箱")
        .required(false)
        .validateMethod(ValidateMethodType.EMAIL)
        .build(),
    
    ValidationParam.builder()
        .fieldName("age")
        .fieldLabel("年龄")
        .required()
        .customValidator(value -> {
            Integer age = (Integer) value;
            return age >= 18 && age <= 65;
        })
        .customErrorMessage("年龄必须在18-65岁之间")
        .build()
);

// 创建配置
ExcelReaderConfig<UserInfo> config = new ExcelReaderConfig<>(UserInfo.class)
        .setValidationParams(validationParams)
        .setContinueOnError(true);  // 遇到错误继续读取

// 读取
ExcelReader<UserInfo> reader = ExcelReader.create(config);
ExcelReadResult<UserInfo> result = reader.read(file);

// 处理结果
System.out.println("总行数: " + result.getTotalRows());
System.out.println("成功: " + result.getSuccessRows());
System.out.println("失败: " + result.getErrorRowsCount());

// 显示错误
if (result.hasErrors()) {
    result.getErrorRows().forEach((rowNum, error) -> {
        System.out.println("第" + rowNum + "行: " + error);
    });
}
```

```java
List<ExcelColumnMapping> columnMappings = Arrays.asList(
    // 列映射配置...
);

// 自定义配置选项
Map<String, Object> options = new HashMap<>();
options.put("sheetName", "员工信息");      // 指定Sheet名称
options.put("headerRowIndex", 0);         // 表头在第1行
options.put("dataStartRowIndex", 1);      // 数据从第2行开始
options.put("skipEmptyRow", true);        // 跳过空行
options.put("maxRows", 1000);             // 最多读取1000行
options.put("continueOnError", true);     // 遇到错误继续

File file = new File("users.xlsx");
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
    file, 
    UserInfo.class, 
    columnMappings,
    options
);
```

## 在Controller中使用

```java
@PostMapping("/import")
public ResultDomain<String> importUsers(@RequestParam("file") MultipartFile file) {
    try {
        // 1. 定义列映射关系（带校验）
        List<ExcelColumnMapping> columnMappings = Arrays.asList(
            ExcelColumnMapping.builder()
                .columnName("姓名")
                .fieldName("name")
                .fieldType(String.class)
                .required()
                .addValidation(
                    ValidationParam.builder()
                        .fieldName("name")
                        .fieldLabel("姓名")
                        .required()
                        .validateMethod(ValidateMethodType.CHINESE)
                        .build()
                )
                .build(),
            
            ExcelColumnMapping.builder()
                .columnName("手机号")
                .fieldName("phone")
                .fieldType(String.class)
                .required()
                .addValidation(
                    ValidationParam.builder()
                        .fieldName("phone")
                        .fieldLabel("手机号")
                        .required()
                        .validateMethod(ValidateMethodType.PHONE)
                        .build()
                )
                .build(),
            
            ExcelColumnMapping.builder()
                .columnName("邮箱")
                .fieldName("email")
                .fieldType(String.class)
                .addValidation(
                    ValidationParam.builder()
                        .fieldName("email")
                        .fieldLabel("邮箱")
                        .validateMethod(ValidateMethodType.EMAIL)
                        .build()
                )
                .build()
        );
        
        // 2. 读取Excel
        ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
            file.getInputStream(),
            file.getOriginalFilename(),
            UserInfo.class,
            columnMappings
        );
        
        // 3. 处理结果
        if (result.hasErrors()) {
            StringBuilder errorMsg = new StringBuilder();
            errorMsg.append("导入失败，共").append(result.getErrorRowsCount()).append("行数据有误：\n");
            result.getErrorRows().forEach((rowNum, error) -> {
                errorMsg.append("第").append(rowNum).append("行: ").append(error).append("\n");
            });
            return ResultDomain.fail(errorMsg.toString());
        }
        
        // 4. 保存数据
        List<UserInfo> users = new ArrayList<>();
        for (Object obj : result.getDataList()) {
            users.add((UserInfo) obj);
        }
        userService.batchSave(users);
        
        return ResultDomain.success("导入成功，共导入" + result.getSuccessRows() + "条数据");
        
    } catch (Exception e) {
        return ResultDomain.fail("导入失败: " + e.getMessage());
    }
}
```

## 支持的数据类型

- **String** - 字符串
- **Integer/int** - 整数
- **Long/long** - 长整数
- **Double/double** - 双精度浮点数
- **Float/float** - 单精度浮点数
- **Boolean/Boolean** - 布尔值（支持：true/false、1/0、是/否）
- **Date** - 日期（需指定dateFormat）

## 配置选项

### ExcelColumnMapping Builder方法

| 方法 | 说明 | 必填 |
|------|------|------|
| columnName(String) | 设置Excel列名 | columnName和columnIndex至少一个 |
| columnIndex(int) | 设置Excel列索引（从0开始，优先级高于columnName） | columnName和columnIndex至少一个 |
| fieldName(String) | 设置对象字段名 | 是 |
| fieldType(Class<?>) | 设置字段类型 | 否（默认String.class） |
| required() / required(Boolean) | 设置是否必填 | 否（默认false） |
| defaultValue(String) | 设置默认值 | 否 |
| dateFormat(String) | 设置日期格式 | 否（默认"yyyy-MM-dd"） |
| addValidation(ValidationParam) | 添加校验参数 | 否 |
| validations(List<ValidationParam>) | 设置校验参数列表 | 否 |

### Options配置项

传递给 `readExcel` 方法的 `Map<String, Object> options` 支持以下选项：

| 键名 | 类型 | 说明 | 默认值 |
|------|------|------|--------|
| headerRowIndex | int | 表头所在行（从0开始） | 0 |
| dataStartRowIndex | int | 数据起始行（从0开始） | 1 |
| sheetIndex | int | Sheet索引（从0开始） | 0 |
| sheetName | String | Sheet名称（优先级高于sheetIndex） | null |
| skipEmptyRow | Boolean | 跳过空行 | true |
| maxRows | int | 最大读取行数（0=不限制） | 0 |
| continueOnError | Boolean | 遇到错误继续读取 | true |

## 核心特性

1. **配置驱动**：通过ExcelColumnMapping配置Excel列与对象字段的映射关系
2. **无需注解**：不需要在实体类上添加注解，更加灵活
3. **数据校验**：集成ValidationUtils和ValidateMethodType进行专业数据校验
4. **灵活配置**：支持多种配置选项（Sheet选择、行范围、错误处理等）
5. **错误处理**：详细的错误信息和错误行记录
6. **类型转换**：自动进行类型转换
7. **空值处理**：支持默认值和空值校验
8. **多Sheet支持**：可指定Sheet名称或索引
9. **两种映射方式**：支持列名和列索引两种方式

## 注意事项

1. **实体类要求**：必须有无参构造函数
2. **字段访问**：字段必须有setter方法或可访问（支持父类字段）
3. **列映射优先级**：columnIndex优先级高于columnName
4. **日期类型**：必须指定dateFormat
5. **布尔类型**：支持多种格式（true/false、1/0、是/否）
6. **错误处理**：根据continueOnError选项决定遇到错误时是否继续
7. **文件格式**：支持.xls和.xlsx两种格式
8. **类型转换**：自动转换支持String、Interge、Long、Double、Float、Boolean、Date

## 依赖

```xml
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>5.2.3</version>
</dependency>

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>5.2.3</version>
</dependency>
```

## API说明

### ExcelReaderUtils 静态方法

```java
// 从文件读取（基本）
public static ExcelReadResult<Object> readExcel(
    File file, 
    Class<?> targetClass, 
    List<ExcelColumnMapping> columnMappings
)

// 从文件读取（带配置）
public static ExcelReadResult<Object> readExcel(
    File file, 
    Class<?> targetClass, 
    List<ExcelColumnMapping> columnMappings,
    Map<String, Object> options
)

// 从输入流读取（基本）
public static ExcelReadResult<Object> readExcel(
    InputStream inputStream,
    String fileName,
    Class<?> targetClass,
    List<ExcelColumnMapping> columnMappings
)

// 从输入流读取（带配置）
public static ExcelReadResult<Object> readExcel(
    InputStream inputStream,
    String fileName,
    Class<?> targetClass,
    List<ExcelColumnMapping> columnMappings,
    Map<String, Object> options
)
```

## 完整示例

参考 `ExcelUtilsExample.java` 查看完整使用示例。

