serv-excel工具
This commit is contained in:
@@ -18,4 +18,19 @@
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
</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>
|
||||
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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` 查看完整使用示例。
|
||||
|
||||
Reference in New Issue
Block a user