Commit dedc1814 by zhangxingmin

配置

parent 69b63b70
...@@ -79,6 +79,7 @@ public class ApiExcelServiceImpl implements ApiExcelService { ...@@ -79,6 +79,7 @@ public class ApiExcelServiceImpl implements ApiExcelService {
response.setMap(result); response.setMap(result);
return Result.success(response); return Result.success(response);
} catch (Exception e) { } catch (Exception e) {
log.info("Excel解析异常:{}",e.getMessage());
throw new BusinessException("Excel解析异常!"); throw new BusinessException("Excel解析异常!");
} }
} }
......
...@@ -5,11 +5,66 @@ import java.lang.annotation.Retention; ...@@ -5,11 +5,66 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
// Excel集合注解 /**
@Retention(RetentionPolicy.RUNTIME) * 增强的Excel集合注解,支持复杂数据结构
*/
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelCollection { public @interface ExcelCollection {
Class<?> type(); // 集合元素类型
int startRow(); // 集合起始行 /**
int endRow() default -1; // 集合结束行(-1表示自动检测) * 集合元素类型
} */
Class<?> type();
/**
* 起始行(0-based)
*/
int startRow();
/**
* 结束行(0-based,-1表示自动检测)
*/
int endRow() default -1;
/**
* 每个对象占用的行数(默认1行)
*/
int rowSpan() default 1;
/**
* 数据分组模式
*/
GroupMode groupMode() default GroupMode.SINGLE_ROW;
/**
* 分组键字段(用于GROUP_BY_KEY模式)
*/
String groupKey() default "";
// 在 ExcelCollection 注解中添加
String endFlagField() default ""; // 用于判断集合结束的字段名
/**
* 下一个对象的第一个字段标题,用于判断集合结束
*/
String nextFieldTitle() default "";
/**
* 数据分组模式枚举
*/
enum GroupMode {
/**
* 单行模式:每行一个对象
*/
SINGLE_ROW,
/**
* 固定行跨度:每个对象占用固定行数
*/
FIXED_ROW_SPAN,
/**
* 按键分组:根据指定字段的值进行分组
*/
GROUP_BY_KEY
}
}
\ No newline at end of file
...@@ -5,14 +5,110 @@ import java.lang.annotation.Retention; ...@@ -5,14 +5,110 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
// Excel字段注解 /**
@Retention(RetentionPolicy.RUNTIME) * 通用Excel字段注解
*/
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelField { public @interface ExcelField {
String name(); // 标题名称
int titleRow(); // 标题所在行 /**
int titleCol(); // 标题所在列 * 字段名称(用于日志和错误提示)
int valueRow() default -1; // 值所在行(默认与标题同行) */
int valueCol() default -1; // 值所在列(默认标题列+1) String name() default "";
String dateFormat() default "yyyy/MM/dd"; // 日期格式
/**
* 标题行(0-based)
*/
int titleRow() default -1;
/**
* 标题列(0-based)
*/
int titleCol() default -1;
/**
* 值行(0-based,-1表示使用titleRow)
*/
int valueRow() default -1;
/**
* 值列(0-based,-1表示使用titleCol + 1)
*/
int valueCol() default -1;
/**
* 日期格式
*/
String dateFormat() default "yyyy/MM/dd";
/**
* 是否必须
*/
boolean required() default false;
/**
* 默认值
*/
String defaultValue() default "";
/**
* 字段类型
*/
FieldType fieldType() default FieldType.SINGLE;
/**
* 字段类型枚举
*/
enum FieldType {
/**
* 单值字段
*/
SINGLE,
/**
* 列表字段(水平方向)
*/
HORIZONTAL_LIST,
/**
* 列表字段(垂直方向)
*/
VERTICAL_LIST
}
/**
* 列表字段配置(当fieldType为LIST时使用)
*/
ListConfig listConfig() default @ListConfig;
@interface ListConfig {
/**
* 起始行(0-based)
*/
int startRow() default -1;
/**
* 起始列(0-based)
*/
int startCol() default -1;
/**
* 结束行(0-based,-1表示自动检测)
*/
int endRow() default -1;
/**
* 结束列(0-based,-1表示自动检测)
*/
int endCol() default -1;
/**
* 方向:true为水平,false为垂直
*/
boolean horizontal() default true;
/**
* 元素类型
*/
Class<?> elementType() default String.class;
}
} }
...@@ -5,10 +5,35 @@ import java.lang.annotation.Retention; ...@@ -5,10 +5,35 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
// Excel Sheet注解 /**
@Retention(RetentionPolicy.RUNTIME) * 通用Excel Sheet注解
*/
@Target(ElementType.TYPE) @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelSheet { public @interface ExcelSheet {
int sheetIndex() default 0; // Sheet索引
String sheetName() default ""; // Sheet名称 /**
* Sheet索引(0-based)
*/
int sheetIndex() default 0;
/**
* Sheet名称
*/
String sheetName() default "";
/**
* 数据起始行(0-based)
*/
int dataStartRow() default 0;
/**
* 是否跳过空行
*/
boolean skipEmptyRows() default true;
/**
* 最大行数限制
*/
int maxRows() default 1000;
} }
package com.yd.oss.service.service.impl; package com.yd.oss.service.service.impl;
import com.alibaba.fastjson.JSON;
import com.yd.oss.feign.annotation.ExcelCollection; import com.yd.oss.feign.annotation.ExcelCollection;
import com.yd.oss.feign.annotation.ExcelField; import com.yd.oss.feign.annotation.ExcelField;
import com.yd.oss.feign.annotation.ExcelSheet; import com.yd.oss.feign.annotation.ExcelSheet;
...@@ -21,20 +22,17 @@ import java.util.*; ...@@ -21,20 +22,17 @@ import java.util.*;
*/ */
@Service @Service
public class ExcelParserServiceImpl implements ExcelParserService { public class ExcelParserServiceImpl implements ExcelParserService {
private static final Logger log = LoggerFactory.getLogger(ExcelParserServiceImpl.class); private static final Logger log = LoggerFactory.getLogger(ExcelParserServiceImpl.class);
/** /**
* 解析Excel文件(多Sheet页版本) * 解析Excel文件(多Sheet页版本)
* @param file 上传的Excel文件
* @param sheetClasses 每个Sheet页对应的类类型
* @return 包含所有Sheet页解析结果的Map,key为Sheet索引
*/ */
@Override @Override
public Map<Integer, Object> parseExcelWithMultipleSheets(MultipartFile file, Class<?>... sheetClasses) throws Exception { public Map<Integer, Object> parseExcelWithMultipleSheets(MultipartFile file, Class<?>... sheetClasses) throws Exception {
Map<Integer, Object> resultMap = new HashMap<>(); Map<Integer, Object> resultMap = new HashMap<>();
Workbook workbook = WorkbookFactory.create(file.getInputStream()); Workbook workbook = WorkbookFactory.create(file.getInputStream());
try { try {
for (Class<?> clazz : sheetClasses) { for (Class<?> clazz : sheetClasses) {
ExcelSheet sheetAnnotation = clazz.getAnnotation(ExcelSheet.class); ExcelSheet sheetAnnotation = clazz.getAnnotation(ExcelSheet.class);
...@@ -42,32 +40,27 @@ public class ExcelParserServiceImpl implements ExcelParserService { ...@@ -42,32 +40,27 @@ public class ExcelParserServiceImpl implements ExcelParserService {
log.warn("类 {} 缺少@ExcelSheet注解,跳过处理", clazz.getSimpleName()); log.warn("类 {} 缺少@ExcelSheet注解,跳过处理", clazz.getSimpleName());
continue; continue;
} }
Sheet sheet; Sheet sheet = getSheet(workbook, sheetAnnotation);
if (!sheetAnnotation.sheetName().isEmpty()) {
sheet = workbook.getSheet(sheetAnnotation.sheetName());
} else {
sheet = workbook.getSheetAt(sheetAnnotation.sheetIndex());
}
if (sheet == null) { if (sheet == null) {
log.warn("Sheet {} 不存在", log.warn("Sheet {} 不存在",
!sheetAnnotation.sheetName().isEmpty() ? !sheetAnnotation.sheetName().isEmpty() ?
sheetAnnotation.sheetName() : sheetAnnotation.sheetIndex()); sheetAnnotation.sheetName() : sheetAnnotation.sheetIndex());
continue; continue;
} }
Object instance = clazz.getDeclaredConstructor().newInstance(); Object instance = clazz.getDeclaredConstructor().newInstance();
// 解析普通字段 // 解析普通字段
parseFieldsWithMergedCells(sheet, instance); parseFieldsWithMergedCells(sheet, instance);
// 解析集合字段 // 解析集合字段
parseCollectionFields(sheet, instance); parseCollectionFields(sheet, instance);
log.info("解析结果: {}", JSON.toJSONString(instance));
resultMap.put(sheetAnnotation.sheetIndex(), instance); resultMap.put(sheetAnnotation.sheetIndex(), instance);
} }
return resultMap; return resultMap;
} finally { } finally {
workbook.close(); workbook.close();
...@@ -76,41 +69,21 @@ public class ExcelParserServiceImpl implements ExcelParserService { ...@@ -76,41 +69,21 @@ public class ExcelParserServiceImpl implements ExcelParserService {
/** /**
* 解析单个Sheet页 * 解析单个Sheet页
* @param file
* @param clazz
* @param <T>
* @return
* @throws Exception
*/ */
public <T> T parseExcel(MultipartFile file, Class<T> clazz) throws Exception { public <T> T parseExcel(MultipartFile file, Class<T> clazz) throws Exception {
Workbook workbook = WorkbookFactory.create(file.getInputStream()); Workbook workbook = WorkbookFactory.create(file.getInputStream());
try { try {
// 获取Sheet信息
ExcelSheet sheetAnnotation = clazz.getAnnotation(ExcelSheet.class); ExcelSheet sheetAnnotation = clazz.getAnnotation(ExcelSheet.class);
Sheet sheet; Sheet sheet = getSheet(workbook, sheetAnnotation);
if (sheetAnnotation != null) {
if (!sheetAnnotation.sheetName().isEmpty()) {
sheet = workbook.getSheet(sheetAnnotation.sheetName());
} else {
sheet = workbook.getSheetAt(sheetAnnotation.sheetIndex());
}
} else {
sheet = workbook.getSheetAt(0); // 默认第一个Sheet
}
if (sheet == null) { if (sheet == null) {
throw new RuntimeException("Sheet not found"); throw new RuntimeException("Sheet not found");
} }
T instance = clazz.getDeclaredConstructor().newInstance(); T instance = clazz.getDeclaredConstructor().newInstance();
// 解析普通字段
parseFieldsWithMergedCells(sheet, instance); parseFieldsWithMergedCells(sheet, instance);
// 解析集合字段
parseCollectionFields(sheet, instance); parseCollectionFields(sheet, instance);
return instance; return instance;
} finally { } finally {
workbook.close(); workbook.close();
...@@ -118,12 +91,25 @@ public class ExcelParserServiceImpl implements ExcelParserService { ...@@ -118,12 +91,25 @@ public class ExcelParserServiceImpl implements ExcelParserService {
} }
/** /**
* 获取Sheet
*/
private Sheet getSheet(Workbook workbook, ExcelSheet sheetAnnotation) {
if (sheetAnnotation == null) {
return workbook.getSheetAt(0);
}
if (!sheetAnnotation.sheetName().isEmpty()) {
Sheet sheet = workbook.getSheet(sheetAnnotation.sheetName());
if (sheet != null) return sheet;
}
return workbook.getSheetAt(sheetAnnotation.sheetIndex());
}
/**
* 处理合并单元格的字段解析 * 处理合并单元格的字段解析
* @param sheet
* @param instance
*/ */
private void parseFieldsWithMergedCells(Sheet sheet, Object instance) { private void parseFieldsWithMergedCells(Sheet sheet, Object instance) {
// 获取所有合并区域
List<CellRangeAddress> mergedRegions = sheet.getMergedRegions(); List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
for (Field field : instance.getClass().getDeclaredFields()) { for (Field field : instance.getClass().getDeclaredFields()) {
...@@ -170,10 +156,6 @@ public class ExcelParserServiceImpl implements ExcelParserService { ...@@ -170,10 +156,6 @@ public class ExcelParserServiceImpl implements ExcelParserService {
/** /**
* 查找包含指定行列的合并区域 * 查找包含指定行列的合并区域
* @param mergedRegions
* @param row
* @param column
* @return
*/ */
private CellRangeAddress findMergedRegion(List<CellRangeAddress> mergedRegions, int row, int column) { private CellRangeAddress findMergedRegion(List<CellRangeAddress> mergedRegions, int row, int column) {
for (CellRangeAddress region : mergedRegions) { for (CellRangeAddress region : mergedRegions) {
...@@ -185,268 +167,801 @@ public class ExcelParserServiceImpl implements ExcelParserService { ...@@ -185,268 +167,801 @@ public class ExcelParserServiceImpl implements ExcelParserService {
} }
/** /**
* 将单元格值转换为字符串 * 解析集合字段(支持固定行跨度模式)
* @param cell 单元格对象
* @return 单元格值的字符串表示,若单元格为null则返回null
*/ */
private String getCellValueAsString(Cell cell) { private void parseCollectionFields(Sheet sheet, Object instance) throws Exception {
if (cell == null) { for (Field field : instance.getClass().getDeclaredFields()) {
return null; ExcelCollection collectionAnnotation = field.getAnnotation(ExcelCollection.class);
if (collectionAnnotation != null) {
List<Object> collectionData = new ArrayList<>();
Class<?> collectionType = collectionAnnotation.type();
log.info("开始解析集合字段: {}, 类型: {}, 起始行: {}, 分组模式: {}",
field.getName(), collectionType.getSimpleName(),
collectionAnnotation.startRow(), collectionAnnotation.groupMode());
// 获取目标类的所有字段及其Excel注解
Map<Field, ExcelField> fieldMappings = new HashMap<>();
for (Field elementField : collectionType.getDeclaredFields()) {
ExcelField excelField = elementField.getAnnotation(ExcelField.class);
if (excelField != null) {
fieldMappings.put(elementField, excelField);
}
}
if (collectionAnnotation.groupMode() == ExcelCollection.GroupMode.FIXED_ROW_SPAN) {
// 固定行跨度模式 - 每个元素占用固定行数
parseFixedRowSpanCollection(sheet, collectionData, collectionType,
fieldMappings, collectionAnnotation);
} else {
// 单行模式 - 每个元素占用一行
parseSingleRowCollection(sheet, collectionData, collectionType,
fieldMappings, collectionAnnotation);
}
// 设置集合字段值
field.setAccessible(true);
field.set(instance, collectionData);
log.info("集合字段 {} 解析完成,共 {} 条数据", field.getName(), collectionData.size());
}
} }
switch (cell.getCellType()) { }
case STRING:
return cell.getStringCellValue().trim(); /**
case NUMERIC: * 解析固定行跨度的集合(支持结束标志字段和下一个字段标题)
if (DateUtil.isCellDateFormatted(cell)) { */
// 日期类型转换为字符串(可根据需要调整格式) private void parseFixedRowSpanCollection(Sheet sheet, List<Object> collectionData,
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(cell.getDateCellValue()); Class<?> collectionType,
Map<Field, ExcelField> fieldMappings,
ExcelCollection collectionAnnotation) throws Exception {
int rowSpan = collectionAnnotation.rowSpan();
int startRow = collectionAnnotation.startRow();
String endFlagField = collectionAnnotation.endFlagField();
String nextFieldTitle = collectionAnnotation.nextFieldTitle();
log.info("开始解析固定行跨度集合,起始行: {}, 行跨度: {}, 结束标志字段: {}, 下一字段标题: {}",
startRow, rowSpan, endFlagField, nextFieldTitle);
// 获取结束标志字段的映射信息
Field endFlagFieldObj = null;
ExcelField endFlagExcelField = null;
if (!endFlagField.isEmpty()) {
try {
endFlagFieldObj = collectionType.getDeclaredField(endFlagField);
endFlagExcelField = endFlagFieldObj.getAnnotation(ExcelField.class);
log.info("找到结束标志字段: {}, 位置: 行{}, 列{}",
endFlagField, endFlagExcelField.valueRow(), endFlagExcelField.valueCol());
} catch (NoSuchFieldException e) {
log.warn("结束标志字段 {} 不存在,将使用默认结束判断", endFlagField);
}
}
// 遍历每个元素组
for (int groupIndex = 0; groupIndex < 1000; groupIndex++) {
int groupStartRow = startRow + (groupIndex * rowSpan);
int nextGroupStartRow = groupStartRow + rowSpan;
// 检查是否超出表格范围
if (groupStartRow > sheet.getLastRowNum()) {
log.info("超出表格范围,集合解析结束");
break;
}
log.debug("=== 解析组 {},起始行: {} ===", groupIndex + 1, groupStartRow);
// 关键修改:先解析当前组
try {
Object element = collectionType.getDeclaredConstructor().newInstance();
boolean hasData = parseGroupData(sheet, groupStartRow, element, fieldMappings);
if (hasData) {
collectionData.add(element);
log.info("成功解析组 {},组起始行: {}", groupIndex + 1, groupStartRow);
} else { } else {
// 数字类型避免科学计数法,转换为字符串 log.debug("组起始行 {} 没有有效数据,跳过", groupStartRow);
return String.valueOf(cell.getNumericCellValue());
// 如果连续3个组没有数据,认为集合结束
if (groupIndex > 0 && !hasDataInNextGroup(sheet, nextGroupStartRow, fieldMappings)) {
log.info("检测到连续空组,集合解析结束");
break;
}
} }
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue()); // 关键修改:在解析当前组后,检查下一个组是否应该结束
case FORMULA: boolean shouldEnd = false;
try {
return cell.getStringCellValue().trim(); // 1. 检查结束标志字段(检查下一个组)
} catch (Exception e) { if (!endFlagField.isEmpty() && endFlagExcelField != null) {
// 公式计算结果不是字符串时,尝试获取数值 if (shouldEndByEndFlag(sheet, nextGroupStartRow, rowSpan, endFlagExcelField)) {
return String.valueOf(cell.getNumericCellValue()); log.info("检测到下一个组结束标志为空,集合解析结束");
shouldEnd = true;
}
} }
default:
return ""; // 2. 检查下一个对象的第一个字段标题
if (!shouldEnd && nextFieldTitle != null && !nextFieldTitle.isEmpty()) {
if (shouldEndByNextFieldTitle(sheet, nextGroupStartRow, fieldMappings, nextFieldTitle)) {
log.info("检测到下一个对象标题 '{}',集合解析结束", nextFieldTitle);
shouldEnd = true;
}
}
if (shouldEnd) {
break;
}
} catch (Exception e) {
log.error("解析组起始行 {} 时发生异常: {}", groupStartRow, e.getMessage(), e);
}
} }
log.info("固定行跨度集合解析完成,共解析 {} 个元素", collectionData.size());
} }
/** /**
* 解析普通字段 * 根据下一个对象的第一个字段标题判断是否应该结束集合解析
* @param sheet
* @param instance
*/ */
private void parseFields(Sheet sheet, Object instance) { private boolean shouldEndByNextFieldTitle(Sheet sheet, int nextGroupStartRow,
for (Field field : instance.getClass().getDeclaredFields()) { Map<Field, ExcelField> fieldMappings,
ExcelField excelField = field.getAnnotation(ExcelField.class); String expectedNextFieldTitle) {
if (excelField != null) { try {
try { if (expectedNextFieldTitle == null || expectedNextFieldTitle.isEmpty()) {
// 获取值单元格 return false;
int valueRow = excelField.valueRow() >= 0 ? excelField.valueRow() : excelField.titleRow(); }
int valueCol = excelField.valueCol() >= 0 ? excelField.valueCol() : excelField.titleCol() + 1;
// 获取第一个字段的标题位置(用于检查下一个对象的标题)
Row row = sheet.getRow(valueRow); Field firstField = getFirstFieldByColumnOrder(fieldMappings);
if (row == null) continue; if (firstField == null) {
return false;
Cell cell = row.getCell(valueCol); }
if (cell == null) continue;
ExcelField firstExcelField = fieldMappings.get(firstField);
// 转换单元格值 int titleRow = firstExcelField.titleRow();
Object value = convertCellValue(cell, field.getType(), excelField.dateFormat()); int titleCol = firstExcelField.titleCol();
// 设置字段值 log.debug("检查下一个字段标题: 下一组起始行={}, 标题行={}, 标题列={}, 预期标题='{}'",
field.setAccessible(true); nextGroupStartRow, titleRow, titleCol, expectedNextFieldTitle);
field.set(instance, value);
} catch (Exception e) { // 检查下一组的标题行
log.warn("设置字段 {} 值失败: {}", field.getName(), e.getMessage()); int nextTitleRow = nextGroupStartRow + titleRow;
} if (nextTitleRow > sheet.getLastRowNum()) {
log.debug("下一组标题行 {} 超出表格范围", nextTitleRow);
return true;
} }
Row row = sheet.getRow(nextTitleRow);
if (row == null) {
log.debug("下一组标题行 {} 为null", nextTitleRow);
return true;
}
Cell titleCell = row.getCell(titleCol);
if (titleCell == null) {
log.debug("下一组标题行{}列{}为null", nextTitleRow, titleCol);
return true;
}
String actualTitle = getCellValueAsString(titleCell);
log.debug("实际下一组标题: '{}'", actualTitle);
// 如果下一组的标题与预期下一个对象的标题匹配,说明集合应该结束
if (expectedNextFieldTitle.equals(actualTitle)) {
log.info("检测到下一个对象标题 '{}',集合解析结束", expectedNextFieldTitle);
return true;
}
return false;
} catch (Exception e) {
log.warn("检查下一个字段标题失败: {}", e.getMessage());
return false;
} }
} }
/** /**
* 解析集合字段(增强版,支持动态结束行检测) * 按列顺序获取第一个字段(列号最小的字段)
* @param sheet
* @param instance
* @throws Exception
*/ */
private void parseCollectionFields(Sheet sheet, Object instance) throws Exception { private Field getFirstFieldByColumnOrder(Map<Field, ExcelField> fieldMappings) {
for (Field field : instance.getClass().getDeclaredFields()) { if (fieldMappings.isEmpty()) {
ExcelCollection collectionAnnotation = field.getAnnotation(ExcelCollection.class); return null;
if (collectionAnnotation != null) { }
List<Object> collectionData = new ArrayList<>();
return fieldMappings.entrySet().stream()
// 确定结束行(支持自动检测) .min(Comparator.comparingInt(entry -> entry.getValue().valueCol()))
int endRow = collectionAnnotation.endRow() >= 0 ? .map(Map.Entry::getKey)
collectionAnnotation.endRow() : findCollectionEndRow(sheet, collectionAnnotation); .orElse(null);
}
// 遍历行,解析集合元素
for (int rowNum = collectionAnnotation.startRow(); rowNum <= endRow; rowNum++) { /**
Row row = sheet.getRow(rowNum); * 根据结束标志字段判断是否应该结束集合解析
if (row == null || isRowEmpty(row)) { * 关键修改:检查当前组的结束标志,而不是下一个组
continue; // 跳过空行 */
} private boolean shouldEndByEndFlag(Sheet sheet, int currentGroupStartRow,
int rowSpan, ExcelField endFlagExcelField) {
try { try {
Object element = collectionAnnotation.type().getDeclaredConstructor().newInstance(); // 计算结束标志字段在当前组中的实际位置
boolean hasData = false; int actualRow = currentGroupStartRow + endFlagExcelField.valueRow();
int actualCol = endFlagExcelField.valueCol();
// 解析元素字段
for (Field elementField : collectionAnnotation.type().getDeclaredFields()) { log.debug("检查结束标志: 当前组起始行={}, 实际行={}, 实际列={}",
ExcelField excelField = elementField.getAnnotation(ExcelField.class); currentGroupStartRow, actualRow, actualCol);
if (excelField != null) {
// 计算值所在列 // 检查行号是否有效
int valueCol = excelField.valueCol() >= 0 ? if (actualRow > sheet.getLastRowNum()) {
excelField.valueCol() : excelField.titleCol(); log.debug("结束标志检查: 行号 {} 超出表格范围", actualRow);
return true;
Cell cell = row.getCell(valueCol); }
if (cell != null) {
Object value = convertCellValue(cell, elementField.getType(), excelField.dateFormat()); Row row = sheet.getRow(actualRow);
elementField.setAccessible(true); if (row == null) {
elementField.set(element, value); log.debug("结束标志检查: 行 {} 为null", actualRow);
hasData = true; return true;
} }
}
} Cell cell = row.getCell(actualCol);
if (cell == null) {
if (hasData) { log.debug("结束标志检查: 行{}列{}为null", actualRow, actualCol);
collectionData.add(element); return true;
} }
} catch (Exception e) {
log.warn("解析集合元素失败: {}", e.getMessage()); String cellValue = getCellValueAsString(cell);
log.debug("结束标志字段值: '{}'", cellValue);
// 如果结束标志字段的值为空,说明集合结束
if (cellValue == null || cellValue.trim().isEmpty()) {
log.info("结束标志字段为空,集合解析结束");
return true;
}
return false;
} catch (Exception e) {
log.warn("检查结束标志失败: {}", e.getMessage());
return false;
}
}
/**
* 获取第一个字段(用于结束判断)
*/
private Field getFirstField(Map<Field, ExcelField> fieldMappings) {
if (fieldMappings.isEmpty()) {
return null;
}
return fieldMappings.keySet().iterator().next();
}
/**
* 检查是否应该结束集合解析
*/
private boolean shouldEndCollection(Sheet sheet, int groupStartRow,
Field firstField, ExcelField excelField,
List<Object> collectionData) {
try {
if (firstField == null || excelField == null) {
return false;
}
// 获取上一个对象的第一个字段值
Object lastElement = collectionData.get(collectionData.size() - 1);
firstField.setAccessible(true);
Object lastValue = firstField.get(lastElement);
// 获取当前组的第一个字段值
int actualRow = groupStartRow + excelField.valueRow();
int actualCol = excelField.valueCol();
if (actualRow > sheet.getLastRowNum()) {
return true;
}
Row row = sheet.getRow(actualRow);
if (row == null) {
return true;
}
Cell cell = row.getCell(actualCol);
if (cell == null) {
return true;
}
String currentCellValue = getCellValueAsString(cell);
Object currentValue = convertStringValue(currentCellValue, firstField.getType(), excelField.dateFormat());
// 如果当前值与上一个值不同,说明集合结束
if (!Objects.equals(lastValue, currentValue)) {
log.debug("第一个字段值发生变化: {} -> {}", lastValue, currentValue);
return true;
}
return false;
} catch (Exception e) {
log.warn("检查集合结束标志失败: {}", e.getMessage());
return false;
}
}
/**
* 解析组数据(通用方法)
*/
private boolean parseGroupData(Sheet sheet, int groupStartRow, Object element,
Map<Field, ExcelField> fieldMappings) throws Exception {
boolean hasData = false;
for (Map.Entry<Field, ExcelField> entry : fieldMappings.entrySet()) {
Field elementField = entry.getKey();
ExcelField excelField = entry.getValue();
int actualRow = groupStartRow + excelField.valueRow();
int actualCol = excelField.valueCol();
log.debug("字段 {}: 组起始行={}, 相对行={}, 实际行号={}, 列号={}",
elementField.getName(), groupStartRow, excelField.valueRow(), actualRow, actualCol);
// 检查行号是否有效
if (actualRow > sheet.getLastRowNum()) {
log.debug("行号 {} 超出表格范围", actualRow);
continue;
}
Row row = sheet.getRow(actualRow);
if (row == null) {
log.debug("行 {} 为null,跳过", actualRow);
continue;
}
Cell cell = row.getCell(actualCol);
if (cell == null) {
log.debug("行{}列{}为null,跳过", actualRow, actualCol);
continue;
}
String cellValue = getCellValueAsString(cell);
if (cellValue == null || cellValue.trim().isEmpty()) {
log.debug("行{}列{}的值为空,跳过", actualRow, actualCol);
continue;
}
log.debug("字段 {}: 行{}列{} = '{}'",
elementField.getName(), actualRow, actualCol, cellValue);
try {
Object value = convertStringValue(cellValue, elementField.getType(), excelField.dateFormat());
elementField.setAccessible(true);
elementField.set(element, value);
hasData = true;
log.debug("成功设置字段 {}: {} -> {}", elementField.getName(), cellValue, value);
} catch (Exception e) {
log.error("设置字段 {} 值失败,行{}列{},值'{}': {}",
elementField.getName(), actualRow, actualCol, cellValue, e.getMessage());
}
}
return hasData;
}
/**
* 检查下一个组是否有数据
*/
private boolean hasDataInNextGroup(Sheet sheet, int nextGroupStartRow,
Map<Field, ExcelField> fieldMappings) {
for (Map.Entry<Field, ExcelField> entry : fieldMappings.entrySet()) {
ExcelField excelField = entry.getValue();
int valueCol = excelField.valueCol();
// 直接使用相对行号
int actualRow = nextGroupStartRow + excelField.valueRow();
if (actualRow > sheet.getLastRowNum()) {
continue;
}
Row row = sheet.getRow(actualRow);
if (row != null) {
Cell cell = row.getCell(valueCol);
if (cell != null) {
String cellValue = getCellValueAsString(cell);
if (cellValue != null && !cellValue.trim().isEmpty()) {
return true;
} }
} }
// 设置集合字段值
field.setAccessible(true);
field.set(instance, collectionData);
} }
} }
return false;
} }
/** /**
* 自动检测集合的结束行 * 解析单行模式的集合(支持结束标志字段)
* @param sheet
* @param collectionAnnotation
* @return
*/ */
private int findCollectionEndRow(Sheet sheet, ExcelCollection collectionAnnotation) { private void parseSingleRowCollection(Sheet sheet, List<Object> collectionData,
int endRow = collectionAnnotation.startRow(); Class<?> collectionType,
int maxEmptyRows = 5; // 连续空行的最大数量 Map<Field, ExcelField> fieldMappings,
ExcelCollection collectionAnnotation) throws Exception {
// 获取所有需要检查的列 int startRow = collectionAnnotation.startRow();
Set<Integer> columnsToCheck = new HashSet<>(); String endFlagField = collectionAnnotation.endFlagField();
for (Field field : collectionAnnotation.type().getDeclaredFields()) { String nextFieldTitle = collectionAnnotation.nextFieldTitle();
ExcelField excelField = field.getAnnotation(ExcelField.class);
if (excelField != null) { log.info("=== 开始解析单行集合 ===");
int col = excelField.valueCol() >= 0 ? excelField.valueCol() : excelField.titleCol(); log.info("集合类型: {}, 起始行: {}, 结束标志字段: {}, 下一字段标题: {}",
columnsToCheck.add(col); collectionType.getSimpleName(), startRow, endFlagField, nextFieldTitle);
// 获取结束标志字段的映射信息
Field endFlagFieldObj = null;
ExcelField endFlagExcelField = null;
if (!endFlagField.isEmpty()) {
try {
endFlagFieldObj = collectionType.getDeclaredField(endFlagField);
endFlagExcelField = endFlagFieldObj.getAnnotation(ExcelField.class);
log.info("结束标志字段映射: 字段={}, 相对行={}, 列={}",
endFlagField, endFlagExcelField.valueRow(), endFlagExcelField.valueCol());
} catch (NoSuchFieldException e) {
log.warn("结束标志字段 {} 不存在,将使用默认结束判断", endFlagField);
} }
} }
if (columnsToCheck.isEmpty()) { // 输出字段映射信息
return sheet.getLastRowNum(); // 如果没有字段注解,返回最后一行 log.info("字段映射信息:");
for (Map.Entry<Field, ExcelField> entry : fieldMappings.entrySet()) {
Field field = entry.getKey();
ExcelField excelField = entry.getValue();
log.info(" {}: 相对行={}, 列={}", field.getName(), excelField.valueRow(), excelField.valueCol());
} }
int emptyRowCount = 0; // 遍历行,解析集合元素
for (int rowNum = collectionAnnotation.startRow(); rowNum <= sheet.getLastRowNum(); rowNum++) { int parsedCount = 0;
for (int rowNum = startRow; rowNum <= sheet.getLastRowNum(); rowNum++) {
Row row = sheet.getRow(rowNum); Row row = sheet.getRow(rowNum);
log.info("--- 检查行 {} ---", rowNum);
if (row == null) { if (row == null) {
emptyRowCount++; log.info("行 {} 为null,跳过", rowNum);
if (emptyRowCount >= maxEmptyRows) { continue;
return rowNum - maxEmptyRows; // 返回连续空行前的最后一行 }
// 检查结束标志
boolean shouldEnd = false;
if (!endFlagField.isEmpty() && endFlagExcelField != null) {
shouldEnd = shouldEndSingleRowByEndFlag(sheet, rowNum, endFlagExcelField);
log.info("结束标志检查结果: {}", shouldEnd);
}
// 检查下一个字段标题(新增逻辑)
if (!shouldEnd && nextFieldTitle != null && !nextFieldTitle.isEmpty()) {
shouldEnd = shouldEndSingleRowByNextFieldTitle(sheet, rowNum, fieldMappings, nextFieldTitle);
log.info("下一字段标题检查结果: {}", shouldEnd);
}
if (shouldEnd) {
log.info("检测到单行集合结束条件,集合解析结束");
break;
}
try {
Object element = collectionType.getDeclaredConstructor().newInstance();
boolean hasData = parseSingleRowData(sheet, rowNum, element, fieldMappings);
if (hasData) {
collectionData.add(element);
parsedCount++;
log.info("✅ 成功解析单行集合元素,行{}: {}", rowNum, JSON.toJSONString(element));
} else {
log.info("❌ 行 {} 没有有效数据,跳过", rowNum);
// 如果连续3行没有数据,认为集合结束
if (!hasDataInNextRows(sheet, rowNum + 1, 3, fieldMappings)) {
log.info("检测到连续空行,单行集合解析结束");
break;
}
} }
} catch (Exception e) {
log.warn("解析单行集合元素失败,行{}: {}", rowNum, e.getMessage());
}
}
log.info("=== 单行集合解析完成,共解析 {} 个元素 ===", parsedCount);
}
/**
* 检查单行模式是否应该根据下一个字段标题结束
*/
private boolean shouldEndSingleRowByNextFieldTitle(Sheet sheet, int currentRow,
Map<Field, ExcelField> fieldMappings,
String expectedNextFieldTitle) {
try {
if (expectedNextFieldTitle == null || expectedNextFieldTitle.isEmpty()) {
return false;
}
// 获取第一个字段的标题位置
Field firstField = getFirstFieldByColumnOrder(fieldMappings);
if (firstField == null) {
return false;
}
ExcelField firstExcelField = fieldMappings.get(firstField);
int titleRow = firstExcelField.titleRow();
int titleCol = firstExcelField.titleCol();
// 检查下一行的标题
int nextTitleRow = currentRow + 1;
if (nextTitleRow > sheet.getLastRowNum()) {
return true;
}
Row row = sheet.getRow(nextTitleRow);
if (row == null) {
return true;
}
Cell titleCell = row.getCell(titleCol);
if (titleCell == null) {
return true;
}
String actualTitle = getCellValueAsString(titleCell);
// 如果下一行的标题与预期下一个对象的标题匹配,说明集合应该结束
if (expectedNextFieldTitle.equals(actualTitle)) {
log.info("检测到单行模式下一个对象标题 '{}',集合解析结束", expectedNextFieldTitle);
return true;
}
return false;
} catch (Exception e) {
log.warn("检查单行模式下一个字段标题失败: {}", e.getMessage());
return false;
}
}
/**
* 检查单行模式是否应该根据结束标志结束
*/
private boolean shouldEndSingleRowByEndFlag(Sheet sheet, int currentRow,
ExcelField endFlagExcelField) {
try {
int actualCol = endFlagExcelField.valueCol();
log.debug("检查单行结束标志: 行={}, 列={}", currentRow, actualCol);
Row row = sheet.getRow(currentRow);
if (row == null) {
log.debug("结束标志检查: 行 {} 为null", currentRow);
return true;
}
Cell cell = row.getCell(actualCol);
if (cell == null) {
log.debug("结束标志检查: 行{}列{}为null", currentRow, actualCol);
return true;
}
String cellValue = getCellValueAsString(cell);
log.debug("单行结束标志字段值: '{}'", cellValue);
// 如果结束标志字段的值为空,说明集合结束
if (cellValue == null || cellValue.trim().isEmpty()) {
log.info("单行结束标志字段为空,集合解析结束");
return true;
}
return false;
} catch (Exception e) {
log.warn("检查单行结束标志失败: {}", e.getMessage());
return false;
}
}
/**
* 解析单行数据
*/
private boolean parseSingleRowData(Sheet sheet, int rowNum, Object element,
Map<Field, ExcelField> fieldMappings) throws Exception {
boolean hasData = false;
Row row = sheet.getRow(rowNum);
if (row == null) {
return false;
}
for (Map.Entry<Field, ExcelField> entry : fieldMappings.entrySet()) {
Field elementField = entry.getKey();
ExcelField excelField = entry.getValue();
int actualCol = excelField.valueCol();
log.debug("单行字段 {}: 行={}, 列={}",
elementField.getName(), rowNum, actualCol);
Cell cell = row.getCell(actualCol);
if (cell == null) {
log.debug("行{}列{}为null,跳过", rowNum, actualCol);
continue; continue;
} }
boolean hasData = false; String cellValue = getCellValueAsString(cell);
for (int col : columnsToCheck) { if (cellValue == null || cellValue.trim().isEmpty()) {
Cell cell = row.getCell(col); log.debug("行{}列{}的值为空,跳过", rowNum, actualCol);
if (cell != null && cell.getCellType() != CellType.BLANK) { continue;
hasData = true; }
break;
log.debug("单行字段 {}: 行{}列{} = '{}'",
elementField.getName(), rowNum, actualCol, cellValue);
try {
Object value = convertStringValue(cellValue, elementField.getType(), excelField.dateFormat());
elementField.setAccessible(true);
elementField.set(element, value);
hasData = true;
log.debug("成功设置单行字段 {}: {} -> {}", elementField.getName(), cellValue, value);
} catch (Exception e) {
log.error("设置单行字段 {} 值失败,行{}列{},值'{}': {}",
elementField.getName(), rowNum, actualCol, cellValue, e.getMessage());
}
}
return hasData;
}
/**
* 检查后续行是否有数据
*/
private boolean hasDataInNextRows(Sheet sheet, int startRow, int checkRowCount,
Map<Field, ExcelField> fieldMappings) {
for (int i = 0; i < checkRowCount; i++) {
int rowNum = startRow + i;
if (rowNum > sheet.getLastRowNum()) {
return false;
}
Row row = sheet.getRow(rowNum);
if (row == null) {
continue;
}
for (Map.Entry<Field, ExcelField> entry : fieldMappings.entrySet()) {
ExcelField excelField = entry.getValue();
int valueCol = excelField.valueCol();
Cell cell = row.getCell(valueCol);
if (cell != null) {
String cellValue = getCellValueAsString(cell);
if (cellValue != null && !cellValue.trim().isEmpty()) {
return true;
}
} }
} }
}
if (hasData) { return false;
emptyRowCount = 0; }
endRow = rowNum;
} else { /**
* 自动检测集合的结束行
*/
private int findCollectionEndRow(Sheet sheet, int startRow) {
int maxEmptyRows = 3; // 连续空行的最大数量
int emptyRowCount = 0;
int lastDataRow = startRow;
for (int rowNum = startRow; rowNum <= sheet.getLastRowNum(); rowNum++) {
Row row = sheet.getRow(rowNum);
if (isRowEmpty(row)) {
emptyRowCount++; emptyRowCount++;
if (emptyRowCount >= maxEmptyRows) { if (emptyRowCount >= maxEmptyRows) {
return endRow; // 返回最后有数据的行 return lastDataRow;
} }
} else {
emptyRowCount = 0;
lastDataRow = rowNum;
} }
} }
return sheet.getLastRowNum(); return sheet.getLastRowNum();
} }
/** /**
* 单元格值转换 * 将单元格值转换为字符串
* @param cell
* @param targetType
* @param dateFormat
* @return
*/ */
private Object convertCellValue(Cell cell, Class<?> targetType, String dateFormat) { private String getCellValueAsString(Cell cell) {
if (cell == null) {
return null;
}
switch (cell.getCellType()) { switch (cell.getCellType()) {
case STRING: case STRING:
return convertStringValue(cell.getStringCellValue().trim(), targetType, dateFormat); return cell.getStringCellValue().trim();
case NUMERIC: case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) { if (DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue(); return new SimpleDateFormat("yyyy/MM/dd").format(cell.getDateCellValue());
} else { } else {
return convertNumericValue(cell.getNumericCellValue(), targetType); // 数字类型避免科学计数法,转换为字符串
double numericValue = cell.getNumericCellValue();
if (numericValue == (long) numericValue) {
return String.valueOf((long) numericValue);
} else {
return String.valueOf(numericValue);
}
} }
case BOOLEAN: case BOOLEAN:
return cell.getBooleanCellValue(); return String.valueOf(cell.getBooleanCellValue());
case FORMULA: case FORMULA:
try { try {
return convertStringValue(cell.getStringCellValue().trim(), targetType, dateFormat); return cell.getStringCellValue().trim();
} catch (Exception e) { } catch (Exception e) {
return cell.getCellFormula(); // 公式计算结果不是字符串时,尝试获取数值
return String.valueOf(cell.getNumericCellValue());
} }
default: default:
return null; return "";
} }
} }
/**
* 字符串值类型转换
*/
private Object convertStringValue(String value, Class<?> targetType, String dateFormat) { private Object convertStringValue(String value, Class<?> targetType, String dateFormat) {
if (value == null || value.isEmpty()) return null; if (value == null || value.isEmpty()) return null;
if (targetType == String.class) { try {
return value; if (targetType == String.class) {
} else if (targetType == Integer.class || targetType == int.class) { return value;
return Integer.parseInt(value); } else if (targetType == Integer.class || targetType == int.class) {
} else if (targetType == Long.class || targetType == long.class) { // 处理可能的小数点
return Long.parseLong(value); if (value.contains(".")) {
} else if (targetType == Double.class || targetType == double.class) { return (int) Double.parseDouble(value);
return Double.parseDouble(value); }
} else if (targetType == BigDecimal.class) { return Integer.parseInt(value);
return new BigDecimal(value); } else if (targetType == Long.class || targetType == long.class) {
} else if (targetType == Date.class) { if (value.contains(".")) {
try { return (long) Double.parseDouble(value);
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat); }
return sdf.parse(value); return Long.parseLong(value);
} catch (ParseException e) { } else if (targetType == Double.class || targetType == double.class) {
log.warn("日期格式转换失败: {}", value); return Double.parseDouble(value);
return null; } else if (targetType == BigDecimal.class) {
} // 特殊处理数值,确保是数值
} else if (targetType == Boolean.class || targetType == boolean.class) { try {
return "是".equals(value) || "YES".equalsIgnoreCase(value) || "TRUE".equalsIgnoreCase(value); return new BigDecimal(value);
} } catch (NumberFormatException e) {
return value; log.warn("BigDecimal转换失败,尝试清理字符串: {}", value);
} // 清理非数字字符(保留小数点和负号)
String cleanValue = value.replaceAll("[^\\d.-]", "");
private Object convertNumericValue(double value, Class<?> targetType) { if (!cleanValue.isEmpty()) {
if (targetType == Integer.class || targetType == int.class) { return new BigDecimal(cleanValue);
return (int) value; }
} else if (targetType == Long.class || targetType == long.class) { return null;
return (long) value; }
} else if (targetType == Double.class || targetType == double.class) { } else if (targetType == Date.class) {
return value; try {
} else if (targetType == BigDecimal.class) { SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
return BigDecimal.valueOf(value); return sdf.parse(value);
} else if (targetType == Date.class) { } catch (ParseException e) {
return DateUtil.getJavaDate(value); log.warn("日期格式转换失败: {}", value);
return null;
}
} else if (targetType == Boolean.class || targetType == boolean.class) {
return "是".equals(value) || "YES".equalsIgnoreCase(value) || "TRUE".equalsIgnoreCase(value) || "1".equals(value);
}
} catch (Exception e) {
log.warn("类型转换失败: 值='{}', 目标类型={}, 错误: {}", value, targetType.getSimpleName(), e.getMessage());
} }
return value;
return null;
} }
/**
* 检查行是否为空
*/
private boolean isRowEmpty(Row row) { private boolean isRowEmpty(Row row) {
if (row == null) return true;
for (Cell cell : row) { for (Cell cell : row) {
if (cell != null && cell.getCellType() != CellType.BLANK) { if (cell != null && cell.getCellType() != CellType.BLANK) {
return false; String value = getCellValueAsString(cell);
if (value != null && !value.trim().isEmpty()) {
return false;
}
} }
} }
return true; return true;
} }
} }
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment