serv-excel工具

This commit is contained in:
2025-10-16 11:25:09 +08:00
parent 716d9d34a2
commit 7f65404f28
7 changed files with 1778 additions and 0 deletions

View File

@@ -18,4 +18,19 @@
<maven.compiler.target>21</maven.compiler.target> <maven.compiler.target>21</maven.compiler.target>
</properties> </properties>
<dependencies>
<!-- Apache POI for Excel -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>${poi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>
</dependencies>
</project> </project>

View File

@@ -0,0 +1,208 @@
package org.xyzh.common.utils.excel;
import org.xyzh.common.utils.validation.ValidationParam;
import java.util.ArrayList;
import java.util.List;
/**
* @description Excel列映射配置
* @filename ExcelColumnMapping.java
* @author yslg
* @copyright xyzh
* @since 2025-10-16
*/
public class ExcelColumnMapping {
/**
* @description Excel列名表头名称
*/
private String columnName;
/**
* @description Excel列索引从0开始优先级高于列名
*/
private Integer columnIndex;
/**
* @description 对象字段名
*/
private String fieldName;
/**
* @description 字段类型
*/
private Class<?> fieldType;
/**
* @description 是否必填
*/
private boolean required = false;
/**
* @description 默认值
*/
private String defaultValue;
/**
* @description 日期格式当字段类型为Date时使用
*/
private String dateFormat = "yyyy-MM-dd";
/**
* @description 校验参数列表
*/
private List<ValidationParam> validationParams;
private ExcelColumnMapping() {
this.validationParams = new ArrayList<>();
}
public String getColumnName() {
return columnName;
}
public Integer getColumnIndex() {
return columnIndex;
}
public String getFieldName() {
return fieldName;
}
public Class<?> getFieldType() {
return fieldType;
}
public boolean isRequired() {
return required;
}
public String getDefaultValue() {
return defaultValue;
}
public String getDateFormat() {
return dateFormat;
}
public List<ValidationParam> getValidationParams() {
return validationParams;
}
/**
* @description Builder类
*/
public static class Builder {
private ExcelColumnMapping mapping = new ExcelColumnMapping();
/**
* 设置Excel列名
*/
public Builder columnName(String columnName) {
mapping.columnName = columnName;
return this;
}
/**
* 设置Excel列索引从0开始
*/
public Builder columnIndex(int columnIndex) {
mapping.columnIndex = columnIndex;
return this;
}
/**
* 设置对象字段名
*/
public Builder fieldName(String fieldName) {
mapping.fieldName = fieldName;
return this;
}
/**
* 设置字段类型
*/
public Builder fieldType(Class<?> fieldType) {
mapping.fieldType = fieldType;
return this;
}
/**
* 设置是否必填
*/
public Builder required(boolean required) {
mapping.required = required;
return this;
}
/**
* 设置为必填
*/
public Builder required() {
mapping.required = true;
return this;
}
/**
* 设置默认值
*/
public Builder defaultValue(String defaultValue) {
mapping.defaultValue = defaultValue;
return this;
}
/**
* 设置日期格式
*/
public Builder dateFormat(String dateFormat) {
mapping.dateFormat = dateFormat;
return this;
}
/**
* 添加校验参数
*/
public Builder addValidation(ValidationParam param) {
mapping.validationParams.add(param);
return this;
}
/**
* 设置校验参数列表
*/
public Builder validations(List<ValidationParam> params) {
mapping.validationParams = params;
return this;
}
public ExcelColumnMapping build() {
if (mapping.fieldName == null || mapping.fieldName.isEmpty()) {
throw new IllegalArgumentException("字段名不能为空");
}
if (mapping.columnName == null && mapping.columnIndex == null) {
throw new IllegalArgumentException("必须指定列名或列索引");
}
if (mapping.fieldType == null) {
mapping.fieldType = String.class;
}
return mapping;
}
}
public static Builder builder() {
return new Builder();
}
@Override
public String toString() {
return "ExcelColumnMapping{" +
"columnName='" + columnName + '\'' +
", columnIndex=" + columnIndex +
", fieldName='" + fieldName + '\'' +
", fieldType=" + (fieldType != null ? fieldType.getSimpleName() : "null") +
", required=" + required +
'}';
}
}

View File

@@ -0,0 +1,142 @@
package org.xyzh.common.utils.excel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @description Excel读取结果
* @filename ExcelReadResult.java
* @author yslg
* @copyright xyzh
* @since 2025-10-16
*/
public class ExcelReadResult<T> {
/**
* @description 是否成功
*/
private boolean success;
/**
* @description 成功读取的数据列表
*/
private List<T> dataList;
/**
* @description 失败的行数据(行号 -> 错误信息)
*/
private Map<Integer, String> errorRowsMap;
/**
* @description 总行数(不包括表头)
*/
private int totalRows;
/**
* @description 成功行数
*/
private int successRows;
/**
* @description 失败行数
*/
private int errorRowsCount;
/**
* @description 错误信息
*/
private String errorMessage;
public ExcelReadResult() {
this.success = true;
this.dataList = new ArrayList<>();
this.errorRowsMap = new HashMap<>();
this.totalRows = 0;
this.successRows = 0;
this.errorRowsCount = 0;
}
public boolean isSuccess() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public List<T> getDataList() {
return dataList;
}
public void setDataList(List<T> dataList) {
this.dataList = dataList;
}
public Map<Integer, String> getErrorRows() {
return errorRowsMap;
}
public void setErrorRows(Map<Integer, String> errorRowsMap) {
this.errorRowsMap = errorRowsMap;
}
public int getTotalRows() {
return totalRows;
}
public void setTotalRows(int totalRows) {
this.totalRows = totalRows;
}
public int getSuccessRows() {
return successRows;
}
public void setSuccessRows(int successRows) {
this.successRows = successRows;
}
public int getErrorRowsCount() {
return errorRowsCount;
}
public void setErrorRowsCount(int errorRowsCount) {
this.errorRowsCount = errorRowsCount;
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public void addData(T data) {
this.dataList.add(data);
this.successRows++;
}
public void addError(int rowNum, String error) {
this.errorRowsMap.put(rowNum, error);
this.errorRowsCount++;
}
public boolean hasErrors() {
return this.errorRowsCount > 0;
}
@Override
public String toString() {
return "ExcelReadResult{" +
"success=" + success +
", totalRows=" + totalRows +
", successRows=" + successRows +
", errorRows=" + errorRowsCount +
", errorMessage='" + errorMessage + '\'' +
'}';
}
}

View File

@@ -0,0 +1,426 @@
package org.xyzh.common.utils.excel;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.xyzh.common.utils.validation.ValidationParam;
import org.xyzh.common.utils.validation.ValidationResult;
import org.xyzh.common.utils.validation.ValidationUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* @description Excel读取工具类非泛型版本
* @filename ExcelReaderUtils.java
* @author yslg
* @copyright xyzh
* @since 2025-10-16
*/
public class ExcelReaderUtils {
/**
* @description 从文件读取Excel
* @param file Excel文件
* @param targetClass 目标对象Class
* @param columnMappings Excel列映射配置列表
* @return ExcelReadResult
*/
public static ExcelReadResult<Object> readExcel(File file, Class<?> targetClass, List<ExcelColumnMapping> columnMappings) {
try (FileInputStream fis = new FileInputStream(file)) {
return readExcel(fis, file.getName(), targetClass, columnMappings, new HashMap<>());
} catch (Exception e) {
ExcelReadResult<Object> result = new ExcelReadResult<>();
result.setSuccess(false);
result.setErrorMessage("读取Excel文件失败: " + e.getMessage());
return result;
}
}
/**
* @description 从文件读取Excel带配置
* @param file Excel文件
* @param targetClass 目标对象Class
* @param columnMappings Excel列映射配置列表
* @param options 配置选项
* @return ExcelReadResult
*/
public static ExcelReadResult<Object> readExcel(File file, Class<?> targetClass,
List<ExcelColumnMapping> columnMappings,
Map<String, Object> options) {
try (FileInputStream fis = new FileInputStream(file)) {
return readExcel(fis, file.getName(), targetClass, columnMappings, options);
} catch (Exception e) {
ExcelReadResult<Object> result = new ExcelReadResult<>();
result.setSuccess(false);
result.setErrorMessage("读取Excel文件失败: " + e.getMessage());
return result;
}
}
/**
* @description 从输入流读取Excel
* @param inputStream 输入流
* @param fileName 文件名
* @param targetClass 目标对象Class
* @param columnMappings Excel列映射配置列表
* @return ExcelReadResult
*/
public static ExcelReadResult<Object> readExcel(InputStream inputStream, String fileName,
Class<?> targetClass,
List<ExcelColumnMapping> columnMappings) {
return readExcel(inputStream, fileName, targetClass, columnMappings, new HashMap<>());
}
/**
* @description 从输入流读取Excel带配置
* @param inputStream 输入流
* @param fileName 文件名
* @param targetClass 目标对象Class
* @param columnMappings Excel列映射配置列表
* @param options 配置选项headerRowIndex, dataStartRowIndex, sheetIndex, sheetName, skipEmptyRow, maxRows, continueOnError
* @return ExcelReadResult
*/
public static ExcelReadResult<Object> readExcel(InputStream inputStream, String fileName,
Class<?> targetClass,
List<ExcelColumnMapping> columnMappings,
Map<String, Object> options) {
ExcelReadResult<Object> result = new ExcelReadResult<>();
try {
// 获取配置
int headerRowIndex = (int) options.getOrDefault("headerRowIndex", 0);
int dataStartRowIndex = (int) options.getOrDefault("dataStartRowIndex", 1);
int sheetIndex = (int) options.getOrDefault("sheetIndex", 0);
String sheetName = (String) options.get("sheetName");
boolean skipEmptyRow = (boolean) options.getOrDefault("skipEmptyRow", true);
int maxRows = (int) options.getOrDefault("maxRows", 0);
boolean continueOnError = (boolean) options.getOrDefault("continueOnError", true);
// 创建Workbook
Workbook workbook = createWorkbook(inputStream, fileName);
// 获取Sheet
Sheet sheet = getSheet(workbook, sheetIndex, sheetName);
if (sheet == null) {
result.setSuccess(false);
result.setErrorMessage("未找到指定的Sheet");
return result;
}
// 解析表头
Map<String, Integer> headerMap = parseHeader(sheet, headerRowIndex);
// 读取数据
readData(sheet, headerMap, targetClass, columnMappings, dataStartRowIndex,
skipEmptyRow, maxRows, continueOnError, result);
workbook.close();
} catch (Exception e) {
result.setSuccess(false);
result.setErrorMessage("读取Excel失败: " + e.getMessage());
}
return result;
}
/**
* @description 创建Workbook对象
*/
private static Workbook createWorkbook(InputStream inputStream, String fileName) throws Exception {
if (fileName.endsWith(".xlsx")) {
return new XSSFWorkbook(inputStream);
} else if (fileName.endsWith(".xls")) {
return new HSSFWorkbook(inputStream);
} else {
throw new IllegalArgumentException("不支持的文件格式,仅支持.xls和.xlsx");
}
}
/**
* @description 获取Sheet
*/
private static Sheet getSheet(Workbook workbook, int sheetIndex, String sheetName) {
if (sheetName != null && !sheetName.isEmpty()) {
return workbook.getSheet(sheetName);
} else {
return workbook.getSheetAt(sheetIndex);
}
}
/**
* @description 解析表头
*/
private static Map<String, Integer> parseHeader(Sheet sheet, int headerRowIndex) {
Map<String, Integer> headerMap = new HashMap<>();
Row headerRow = sheet.getRow(headerRowIndex);
if (headerRow != null) {
for (Cell cell : headerRow) {
String headerName = getCellValue(cell).toString().trim();
if (!headerName.isEmpty()) {
headerMap.put(headerName, cell.getColumnIndex());
}
}
}
return headerMap;
}
/**
* @description 读取数据
*/
private static void readData(Sheet sheet, Map<String, Integer> headerMap,
Class<?> targetClass, List<ExcelColumnMapping> columnMappings,
int startRow, boolean skipEmptyRow, int maxRows,
boolean continueOnError, ExcelReadResult<Object> result) {
int lastRowNum = sheet.getLastRowNum();
int endRow = maxRows > 0 ? Math.min(startRow + maxRows, lastRowNum + 1) : lastRowNum + 1;
for (int rowNum = startRow; rowNum < endRow; rowNum++) {
Row row = sheet.getRow(rowNum);
// 跳过空行
if (skipEmptyRow && isEmptyRow(row)) {
continue;
}
result.setTotalRows(result.getTotalRows() + 1);
try {
// 将行数据转换为对象
Object data = convertRowToObject(row, headerMap, targetClass, columnMappings, rowNum + 1);
// 数据校验
List<ValidationParam> allValidations = new ArrayList<>();
for (ExcelColumnMapping mapping : columnMappings) {
if (!mapping.getValidationParams().isEmpty()) {
allValidations.addAll(mapping.getValidationParams());
}
}
if (!allValidations.isEmpty()) {
ValidationResult validationResult = ValidationUtils.validate(data, allValidations);
if (!validationResult.isValid()) {
result.addError(rowNum + 1, validationResult.getFirstError());
if (!continueOnError) {
break;
}
continue;
}
}
result.addData(data);
} catch (Exception e) {
result.addError(rowNum + 1, e.getMessage());
if (!continueOnError) {
break;
}
}
}
}
/**
* @description 判断是否为空行
*/
private static boolean isEmptyRow(Row row) {
if (row == null) {
return true;
}
for (Cell cell : row) {
if (cell != null && cell.getCellType() != CellType.BLANK) {
String value = getCellValue(cell).toString().trim();
if (!value.isEmpty()) {
return false;
}
}
}
return true;
}
/**
* @description 将行数据转换为对象
*/
private static Object convertRowToObject(Row row, Map<String, Integer> headerMap,
Class<?> targetClass, List<ExcelColumnMapping> columnMappings,
int rowNum) throws Exception {
Object obj = targetClass.getDeclaredConstructor().newInstance();
for (ExcelColumnMapping mapping : columnMappings) {
// 获取单元格
Cell cell = getCell(row, mapping, headerMap);
// 获取单元格值
Object cellValue = getCellValue(cell);
// 处理空值
if (cellValue == null || cellValue.toString().trim().isEmpty()) {
if (mapping.isRequired()) {
throw new IllegalArgumentException("" + rowNum + "行,字段[" +
(mapping.getColumnName() != null ? mapping.getColumnName() : "索引" + mapping.getColumnIndex())
+ "]不能为空");
}
// 使用默认值
if (mapping.getDefaultValue() != null && !mapping.getDefaultValue().isEmpty()) {
cellValue = mapping.getDefaultValue();
} else {
continue;
}
}
// 类型转换
Object fieldValue = convertValue(cellValue, mapping.getFieldType(), mapping.getDateFormat());
// 设置字段值
Field field = getField(targetClass, mapping.getFieldName());
field.setAccessible(true);
field.set(obj, fieldValue);
}
return obj;
}
/**
* @description 获取字段(支持父类)
*/
private static Field getField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
try {
return clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
// 尝试从父类获取
Class<?> superClass = clazz.getSuperclass();
if (superClass != null) {
return getField(superClass, fieldName);
}
throw e;
}
}
/**
* @description 获取单元格
*/
private static Cell getCell(Row row, ExcelColumnMapping mapping, Map<String, Integer> headerMap) {
if (row == null) {
return null;
}
// 优先使用索引
if (mapping.getColumnIndex() != null) {
return row.getCell(mapping.getColumnIndex());
}
// 使用列名
Integer columnIndex = headerMap.get(mapping.getColumnName());
if (columnIndex != null) {
return row.getCell(columnIndex);
}
return null;
}
/**
* @description 获取单元格值
*/
private static Object getCellValue(Cell cell) {
if (cell == null) {
return null;
}
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue();
} else {
return cell.getNumericCellValue();
}
case BOOLEAN:
return cell.getBooleanCellValue();
case FORMULA:
return cell.getCellFormula();
case BLANK:
return "";
default:
return cell.toString();
}
}
/**
* @description 类型转换
*/
private static Object convertValue(Object value, Class<?> targetType, String dateFormat) throws Exception {
if (value == null) {
return null;
}
String strValue = value.toString().trim();
// String类型
if (targetType == String.class) {
return strValue;
}
// Integer类型
if (targetType == Integer.class || targetType == int.class) {
if (value instanceof Number) {
return ((Number) value).intValue();
}
return Integer.parseInt(strValue);
}
// Long类型
if (targetType == Long.class || targetType == long.class) {
if (value instanceof Number) {
return ((Number) value).longValue();
}
return Long.parseLong(strValue);
}
// Double类型
if (targetType == Double.class || targetType == double.class) {
if (value instanceof Number) {
return ((Number) value).doubleValue();
}
return Double.parseDouble(strValue);
}
// Float类型
if (targetType == Float.class || targetType == float.class) {
if (value instanceof Number) {
return ((Number) value).floatValue();
}
return Float.parseFloat(strValue);
}
// Boolean类型
if (targetType == Boolean.class || targetType == boolean.class) {
if (value instanceof Boolean) {
return value;
}
return Boolean.parseBoolean(strValue) || "1".equals(strValue) ||
"".equals(strValue) || "true".equalsIgnoreCase(strValue);
}
// Date类型
if (targetType == Date.class) {
if (value instanceof Date) {
return value;
}
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
return sdf.parse(strValue);
}
// 其他类型
return value;
}
}

View File

@@ -0,0 +1,429 @@
package org.xyzh.common.utils.excel;
import org.xyzh.common.utils.validation.ValidationParam;
import org.xyzh.common.utils.validation.method.ValidateMethodType;
import java.io.File;
import java.util.*;
/**
* @description Excel工具使用示例
* @filename ExcelUtilsExample.java
* @author yslg
* @copyright xyzh
* @since 2025-10-16
*/
public class ExcelUtilsExample {
/**
* 示例实体类:用户信息
*/
public static class UserInfo {
private String name;
private Integer age;
private String phone;
private String email;
private String idCard;
private Date joinDate;
private Boolean active;
// Getters and Setters
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
public String getPhone() { return phone; }
public void setPhone(String phone) { this.phone = phone; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public String getIdCard() { return idCard; }
public void setIdCard(String idCard) { this.idCard = idCard; }
public Date getJoinDate() { return joinDate; }
public void setJoinDate(Date joinDate) { this.joinDate = joinDate; }
public Boolean getActive() { return active; }
public void setActive(Boolean active) { this.active = active; }
@Override
public String toString() {
return "UserInfo{" +
"name='" + name + '\'' +
", age=" + age +
", phone='" + phone + '\'' +
", email='" + email + '\'' +
", idCard='" + idCard + '\'' +
", joinDate=" + joinDate +
", active=" + active +
'}';
}
}
/**
* 示例1基本使用
*/
public static void example1_BasicUsage() {
System.out.println("========== 示例1: 基本使用 ==========");
// 1. 定义列映射关系
List<ExcelColumnMapping> columnMappings = Arrays.asList(
ExcelColumnMapping.builder()
.columnName("姓名")
.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)
.validations(Arrays.asList(
ValidationParam.builder()
.fieldName("email")
.fieldLabel("邮箱")
.required()
.validateMethod(ValidateMethodType.EMAIL)
.build()
))
.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()
);
// 2. 读取Excel
File file = new File("users.xlsx");
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
file,
UserInfo.class,
columnMappings
);
// 3. 处理结果
if (result.isSuccess()) {
System.out.println("读取成功!");
System.out.println("总行数: " + result.getTotalRows());
System.out.println("成功行数: " + result.getSuccessRows());
for (Object obj : result.getDataList()) {
UserInfo user = (UserInfo) obj;
System.out.println(user);
}
} else {
System.out.println("读取失败: " + result.getErrorMessage());
}
}
/**
* 示例2带数据校验
*/
public static void example2_WithValidation() {
System.out.println("\n========== 示例2: 带数据校验 ==========");
// 定义列映射关系(带校验)
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(),
ExcelColumnMapping.builder()
.columnName("身份证号")
.fieldName("idCard")
.fieldType(String.class)
.addValidation(
ValidationParam.builder()
.fieldName("idCard")
.fieldLabel("身份证号")
.required(false)
.validateMethod(ValidateMethodType.ID_CARD)
.build()
)
.build()
);
// 读取配置
Map<String, Object> options = new HashMap<>();
options.put("continueOnError", true); // 遇到错误继续读取
File file = new File("users.xlsx");
ExcelReadResult<Object> result = ExcelReaderUtils.readExcel(
file,
UserInfo.class,
columnMappings,
options
);
// 处理结果
System.out.println("读取完成!");
System.out.println("总行数: " + result.getTotalRows());
System.out.println("成功行数: " + result.getSuccessRows());
System.out.println("失败行数: " + result.getErrorRowsCount());
// 显示错误信息
if (result.hasErrors()) {
System.out.println("\n错误信息:");
result.getErrorRows().forEach((rowNum, error) -> {
System.out.println("" + rowNum + "行: " + error);
});
}
}
/**
* 示例3使用列索引
*/
public static void example3_UseColumnIndex() {
System.out.println("\n========== 示例3: 使用列索引 ==========");
// 使用列索引而非列名
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
);
System.out.println("成功读取: " + result.getSuccessRows() + "");
}
/**
* 示例4自定义配置
*/
public static void example4_CustomConfig() {
System.out.println("\n========== 示例4: 自定义配置 ==========");
List<ExcelColumnMapping> columnMappings = Arrays.asList(
ExcelColumnMapping.builder()
.columnName("姓名")
.fieldName("name")
.fieldType(String.class)
.required()
.build(),
ExcelColumnMapping.builder()
.columnName("年龄")
.fieldName("age")
.fieldType(Integer.class)
.required()
.build()
);
// 自定义配置
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
);
System.out.println("读取结果: " + result);
}
/**
* 示例5在Controller中使用
*/
public static void example5_InController() {
System.out.println("\n========== 示例5: 在Controller中使用代码示例==========");
System.out.println("""
@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()
);
// 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());
}
}
""");
}
public static void main(String[] args) {
// 运行示例
// example1_BasicUsage();
// example2_WithValidation();
// example3_UseColumnIndex();
// example4_CustomConfig();
example5_InController();
}
}

View File

@@ -0,0 +1,542 @@
# 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、Integer、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` 查看完整使用示例。

View File

@@ -53,6 +53,9 @@
<!-- Redis 依赖版本 --> <!-- Redis 依赖版本 -->
<spring-data-redis.version>3.5.4</spring-data-redis.version> <spring-data-redis.version>3.5.4</spring-data-redis.version>
<!-- excel -->
<poi.version>5.2.3</poi.version>
<lombok.version>1.18.40</lombok.version> <lombok.version>1.18.40</lombok.version>
</properties> </properties>
@@ -252,6 +255,19 @@
<version>${spring-data-redis.version}</version> <version>${spring-data-redis.version}</version>
</dependency> </dependency>
<!-- excel -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>${poi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>
</dependencies> </dependencies>
</dependencyManagement> </dependencyManagement>
<dependencies> <dependencies>