Commit dedc1814 by zhangxingmin

配置

parent 69b63b70
......@@ -79,6 +79,7 @@ public class ApiExcelServiceImpl implements ApiExcelService {
response.setMap(result);
return Result.success(response);
} catch (Exception e) {
log.info("Excel解析异常:{}",e.getMessage());
throw new BusinessException("Excel解析异常!");
}
}
......
......@@ -5,11 +5,66 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// Excel集合注解
@Retention(RetentionPolicy.RUNTIME)
/**
* 增强的Excel集合注解,支持复杂数据结构
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
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;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// Excel字段注解
@Retention(RetentionPolicy.RUNTIME)
/**
* 通用Excel字段注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelField {
String name(); // 标题名称
int titleRow(); // 标题所在行
int titleCol(); // 标题所在列
int valueRow() default -1; // 值所在行(默认与标题同行)
int valueCol() default -1; // 值所在列(默认标题列+1)
String dateFormat() default "yyyy/MM/dd"; // 日期格式
/**
* 字段名称(用于日志和错误提示)
*/
String name() default "";
/**
* 标题行(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;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// Excel Sheet注解
@Retention(RetentionPolicy.RUNTIME)
/**
* 通用Excel Sheet注解
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
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;
import com.alibaba.fastjson.JSON;
import com.yd.oss.feign.annotation.ExcelCollection;
import com.yd.oss.feign.annotation.ExcelField;
import com.yd.oss.feign.annotation.ExcelSheet;
......@@ -26,9 +27,6 @@ public class ExcelParserServiceImpl implements ExcelParserService {
/**
* 解析Excel文件(多Sheet页版本)
* @param file 上传的Excel文件
* @param sheetClasses 每个Sheet页对应的类类型
* @return 包含所有Sheet页解析结果的Map,key为Sheet索引
*/
@Override
public Map<Integer, Object> parseExcelWithMultipleSheets(MultipartFile file, Class<?>... sheetClasses) throws Exception {
......@@ -43,13 +41,7 @@ public class ExcelParserServiceImpl implements ExcelParserService {
continue;
}
Sheet sheet;
if (!sheetAnnotation.sheetName().isEmpty()) {
sheet = workbook.getSheet(sheetAnnotation.sheetName());
} else {
sheet = workbook.getSheetAt(sheetAnnotation.sheetIndex());
}
Sheet sheet = getSheet(workbook, sheetAnnotation);
if (sheet == null) {
log.warn("Sheet {} 不存在",
!sheetAnnotation.sheetName().isEmpty() ?
......@@ -65,6 +57,7 @@ public class ExcelParserServiceImpl implements ExcelParserService {
// 解析集合字段
parseCollectionFields(sheet, instance);
log.info("解析结果: {}", JSON.toJSONString(instance));
resultMap.put(sheetAnnotation.sheetIndex(), instance);
}
......@@ -76,39 +69,19 @@ public class ExcelParserServiceImpl implements ExcelParserService {
/**
* 解析单个Sheet页
* @param file
* @param clazz
* @param <T>
* @return
* @throws Exception
*/
public <T> T parseExcel(MultipartFile file, Class<T> clazz) throws Exception {
Workbook workbook = WorkbookFactory.create(file.getInputStream());
try {
// 获取Sheet信息
ExcelSheet sheetAnnotation = clazz.getAnnotation(ExcelSheet.class);
Sheet sheet;
if (sheetAnnotation != null) {
if (!sheetAnnotation.sheetName().isEmpty()) {
sheet = workbook.getSheet(sheetAnnotation.sheetName());
} else {
sheet = workbook.getSheetAt(sheetAnnotation.sheetIndex());
}
} else {
sheet = workbook.getSheetAt(0); // 默认第一个Sheet
}
Sheet sheet = getSheet(workbook, sheetAnnotation);
if (sheet == null) {
throw new RuntimeException("Sheet not found");
}
T instance = clazz.getDeclaredConstructor().newInstance();
// 解析普通字段
parseFieldsWithMergedCells(sheet, instance);
// 解析集合字段
parseCollectionFields(sheet, instance);
return instance;
......@@ -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) {
// 获取所有合并区域
List<CellRangeAddress> mergedRegions = sheet.getMergedRegions();
for (Field field : instance.getClass().getDeclaredFields()) {
......@@ -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) {
for (CellRangeAddress region : mergedRegions) {
......@@ -185,184 +167,692 @@ public class ExcelParserServiceImpl implements ExcelParserService {
}
/**
* 将单元格值转换为字符串
* @param cell 单元格对象
* @return 单元格值的字符串表示,若单元格为null则返回null
* 解析集合字段(支持固定行跨度模式)
*/
private String getCellValueAsString(Cell cell) {
if (cell == null) {
return null;
private void parseCollectionFields(Sheet sheet, Object instance) throws Exception {
for (Field field : instance.getClass().getDeclaredFields()) {
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);
}
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue().trim();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
// 日期类型转换为字符串(可根据需要调整格式)
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(cell.getDateCellValue());
}
if (collectionAnnotation.groupMode() == ExcelCollection.GroupMode.FIXED_ROW_SPAN) {
// 固定行跨度模式 - 每个元素占用固定行数
parseFixedRowSpanCollection(sheet, collectionData, collectionType,
fieldMappings, collectionAnnotation);
} else {
// 数字类型避免科学计数法,转换为字符串
return String.valueOf(cell.getNumericCellValue());
// 单行模式 - 每个元素占用一行
parseSingleRowCollection(sheet, collectionData, collectionType,
fieldMappings, collectionAnnotation);
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
// 设置集合字段值
field.setAccessible(true);
field.set(instance, collectionData);
log.info("集合字段 {} 解析完成,共 {} 条数据", field.getName(), collectionData.size());
}
}
}
/**
* 解析固定行跨度的集合(支持结束标志字段和下一个字段标题)
*/
private void parseFixedRowSpanCollection(Sheet sheet, List<Object> collectionData,
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 {
return cell.getStringCellValue().trim();
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 {
log.debug("组起始行 {} 没有有效数据,跳过", groupStartRow);
// 如果连续3个组没有数据,认为集合结束
if (groupIndex > 0 && !hasDataInNextGroup(sheet, nextGroupStartRow, fieldMappings)) {
log.info("检测到连续空组,集合解析结束");
break;
}
}
// 关键修改:在解析当前组后,检查下一个组是否应该结束
boolean shouldEnd = false;
// 1. 检查结束标志字段(检查下一个组)
if (!endFlagField.isEmpty() && endFlagExcelField != null) {
if (shouldEndByEndFlag(sheet, nextGroupStartRow, rowSpan, endFlagExcelField)) {
log.info("检测到下一个组结束标志为空,集合解析结束");
shouldEnd = true;
}
}
// 2. 检查下一个对象的第一个字段标题
if (!shouldEnd && nextFieldTitle != null && !nextFieldTitle.isEmpty()) {
if (shouldEndByNextFieldTitle(sheet, nextGroupStartRow, fieldMappings, nextFieldTitle)) {
log.info("检测到下一个对象标题 '{}',集合解析结束", nextFieldTitle);
shouldEnd = true;
}
}
if (shouldEnd) {
break;
}
} catch (Exception e) {
// 公式计算结果不是字符串时,尝试获取数值
return String.valueOf(cell.getNumericCellValue());
log.error("解析组起始行 {} 时发生异常: {}", groupStartRow, e.getMessage(), e);
}
default:
return "";
}
log.info("固定行跨度集合解析完成,共解析 {} 个元素", collectionData.size());
}
/**
* 解析普通字段
* @param sheet
* @param instance
* 根据下一个对象的第一个字段标题判断是否应该结束集合解析
*/
private void parseFields(Sheet sheet, Object instance) {
for (Field field : instance.getClass().getDeclaredFields()) {
ExcelField excelField = field.getAnnotation(ExcelField.class);
if (excelField != null) {
private boolean shouldEndByNextFieldTitle(Sheet sheet, int nextGroupStartRow,
Map<Field, ExcelField> fieldMappings,
String expectedNextFieldTitle) {
try {
// 获取值单元格
int valueRow = excelField.valueRow() >= 0 ? excelField.valueRow() : excelField.titleRow();
int valueCol = excelField.valueCol() >= 0 ? excelField.valueCol() : excelField.titleCol() + 1;
if (expectedNextFieldTitle == null || expectedNextFieldTitle.isEmpty()) {
return false;
}
Row row = sheet.getRow(valueRow);
if (row == null) continue;
// 获取第一个字段的标题位置(用于检查下一个对象的标题)
Field firstField = getFirstFieldByColumnOrder(fieldMappings);
if (firstField == null) {
return false;
}
Cell cell = row.getCell(valueCol);
if (cell == null) continue;
ExcelField firstExcelField = fieldMappings.get(firstField);
int titleRow = firstExcelField.titleRow();
int titleCol = firstExcelField.titleCol();
// 转换单元格值
Object value = convertCellValue(cell, field.getType(), excelField.dateFormat());
log.debug("检查下一个字段标题: 下一组起始行={}, 标题行={}, 标题列={}, 预期标题='{}'",
nextGroupStartRow, titleRow, titleCol, expectedNextFieldTitle);
// 设置字段值
field.setAccessible(true);
field.set(instance, value);
// 检查下一组的标题行
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("设置字段 {} 值失败: {}", field.getName(), e.getMessage());
log.warn("检查下一个字段标题失败: {}", e.getMessage());
return false;
}
}
/**
* 按列顺序获取第一个字段(列号最小的字段)
*/
private Field getFirstFieldByColumnOrder(Map<Field, ExcelField> fieldMappings) {
if (fieldMappings.isEmpty()) {
return null;
}
return fieldMappings.entrySet().stream()
.min(Comparator.comparingInt(entry -> entry.getValue().valueCol()))
.map(Map.Entry::getKey)
.orElse(null);
}
/**
* 解析集合字段(增强版,支持动态结束行检测)
* @param sheet
* @param instance
* @throws Exception
* 根据结束标志字段判断是否应该结束集合解析
* 关键修改:检查当前组的结束标志,而不是下一个组
*/
private void parseCollectionFields(Sheet sheet, Object instance) throws Exception {
for (Field field : instance.getClass().getDeclaredFields()) {
ExcelCollection collectionAnnotation = field.getAnnotation(ExcelCollection.class);
if (collectionAnnotation != null) {
List<Object> collectionData = new ArrayList<>();
private boolean shouldEndByEndFlag(Sheet sheet, int currentGroupStartRow,
int rowSpan, ExcelField endFlagExcelField) {
try {
// 计算结束标志字段在当前组中的实际位置
int actualRow = currentGroupStartRow + endFlagExcelField.valueRow();
int actualCol = endFlagExcelField.valueCol();
// 确定结束行(支持自动检测)
int endRow = collectionAnnotation.endRow() >= 0 ?
collectionAnnotation.endRow() : findCollectionEndRow(sheet, collectionAnnotation);
log.debug("检查结束标志: 当前组起始行={}, 实际行={}, 实际列={}",
currentGroupStartRow, actualRow, actualCol);
// 遍历行,解析集合元素
for (int rowNum = collectionAnnotation.startRow(); rowNum <= endRow; rowNum++) {
Row row = sheet.getRow(rowNum);
if (row == null || isRowEmpty(row)) {
continue; // 跳过空行
// 检查行号是否有效
if (actualRow > sheet.getLastRowNum()) {
log.debug("结束标志检查: 行号 {} 超出表格范围", actualRow);
return true;
}
Row row = sheet.getRow(actualRow);
if (row == null) {
log.debug("结束标志检查: 行 {} 为null", actualRow);
return true;
}
Cell cell = row.getCell(actualCol);
if (cell == null) {
log.debug("结束标志检查: 行{}列{}为null", actualRow, 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 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 {
Object element = collectionAnnotation.type().getDeclaredConstructor().newInstance();
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 (Field elementField : collectionAnnotation.type().getDeclaredFields()) {
ExcelField excelField = elementField.getAnnotation(ExcelField.class);
if (excelField != null) {
// 计算值所在列
int valueCol = excelField.valueCol() >= 0 ?
excelField.valueCol() : excelField.titleCol();
for (Map.Entry<Field, ExcelField> entry : fieldMappings.entrySet()) {
Field elementField = entry.getKey();
ExcelField excelField = entry.getValue();
Cell cell = row.getCell(valueCol);
if (cell != null) {
Object value = convertCellValue(cell, elementField.getType(), excelField.dateFormat());
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;
}
}
}
}
return false;
}
/**
* 解析单行模式的集合(支持结束标志字段)
*/
private void parseSingleRowCollection(Sheet sheet, List<Object> collectionData,
Class<?> collectionType,
Map<Field, ExcelField> fieldMappings,
ExcelCollection collectionAnnotation) throws Exception {
int startRow = collectionAnnotation.startRow();
String endFlagField = collectionAnnotation.endFlagField();
String nextFieldTitle = collectionAnnotation.nextFieldTitle();
log.info("=== 开始解析单行集合 ===");
log.info("集合类型: {}, 起始行: {}, 结束标志字段: {}, 下一字段标题: {}",
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);
}
}
// 输出字段映射信息
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 parsedCount = 0;
for (int rowNum = startRow; rowNum <= sheet.getLastRowNum(); rowNum++) {
Row row = sheet.getRow(rowNum);
log.info("--- 检查行 {} ---", rowNum);
if (row == null) {
log.info("行 {} 为null,跳过", rowNum);
continue;
}
// 检查结束标志
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("解析集合元素失败: {}", e.getMessage());
log.warn("解析单行集合元素失败,行{}: {}", rowNum, e.getMessage());
}
}
// 设置集合字段值
field.setAccessible(true);
field.set(instance, collectionData);
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;
}
}
/**
* 自动检测集合的结束行
* @param sheet
* @param collectionAnnotation
* @return
* 检查单行模式是否应该根据结束标志结束
*/
private int findCollectionEndRow(Sheet sheet, ExcelCollection collectionAnnotation) {
int endRow = collectionAnnotation.startRow();
int maxEmptyRows = 5; // 连续空行的最大数量
private boolean shouldEndSingleRowByEndFlag(Sheet sheet, int currentRow,
ExcelField endFlagExcelField) {
try {
int actualCol = endFlagExcelField.valueCol();
// 获取所有需要检查的列
Set<Integer> columnsToCheck = new HashSet<>();
for (Field field : collectionAnnotation.type().getDeclaredFields()) {
ExcelField excelField = field.getAnnotation(ExcelField.class);
if (excelField != null) {
int col = excelField.valueCol() >= 0 ? excelField.valueCol() : excelField.titleCol();
columnsToCheck.add(col);
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;
}
if (columnsToCheck.isEmpty()) {
return sheet.getLastRowNum(); // 如果没有字段注解,返回最后一行
String cellValue = getCellValueAsString(cell);
log.debug("单行结束标志字段值: '{}'", cellValue);
// 如果结束标志字段的值为空,说明集合结束
if (cellValue == null || cellValue.trim().isEmpty()) {
log.info("单行结束标志字段为空,集合解析结束");
return true;
}
int emptyRowCount = 0;
for (int rowNum = collectionAnnotation.startRow(); rowNum <= sheet.getLastRowNum(); rowNum++) {
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) {
emptyRowCount++;
if (emptyRowCount >= maxEmptyRows) {
return rowNum - maxEmptyRows; // 返回连续空行前的最后一行
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;
}
boolean hasData = false;
for (int col : columnsToCheck) {
Cell cell = row.getCell(col);
if (cell != null && cell.getCellType() != CellType.BLANK) {
String cellValue = getCellValueAsString(cell);
if (cellValue == null || cellValue.trim().isEmpty()) {
log.debug("行{}列{}的值为空,跳过", rowNum, actualCol);
continue;
}
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;
break;
log.debug("成功设置单行字段 {}: {} -> {}", elementField.getName(), cellValue, value);
} catch (Exception e) {
log.error("设置单行字段 {} 值失败,行{}列{},值'{}': {}",
elementField.getName(), rowNum, actualCol, cellValue, e.getMessage());
}
}
if (hasData) {
emptyRowCount = 0;
endRow = rowNum;
} else {
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;
}
}
}
}
return false;
}
/**
* 自动检测集合的结束行
*/
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++;
if (emptyRowCount >= maxEmptyRows) {
return endRow; // 返回最后有数据的行
return lastDataRow;
}
} else {
emptyRowCount = 0;
lastDataRow = rowNum;
}
}
......@@ -370,48 +860,76 @@ public class ExcelParserServiceImpl implements ExcelParserService {
}
/**
* 单元格值转换
* @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()) {
case STRING:
return convertStringValue(cell.getStringCellValue().trim(), targetType, dateFormat);
return cell.getStringCellValue().trim();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue();
return new SimpleDateFormat("yyyy/MM/dd").format(cell.getDateCellValue());
} 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:
return cell.getBooleanCellValue();
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
try {
return convertStringValue(cell.getStringCellValue().trim(), targetType, dateFormat);
return cell.getStringCellValue().trim();
} catch (Exception e) {
return cell.getCellFormula();
// 公式计算结果不是字符串时,尝试获取数值
return String.valueOf(cell.getNumericCellValue());
}
default:
return null;
return "";
}
}
/**
* 字符串值类型转换
*/
private Object convertStringValue(String value, Class<?> targetType, String dateFormat) {
if (value == null || value.isEmpty()) return null;
try {
if (targetType == String.class) {
return value;
} else if (targetType == Integer.class || targetType == int.class) {
// 处理可能的小数点
if (value.contains(".")) {
return (int) Double.parseDouble(value);
}
return Integer.parseInt(value);
} else if (targetType == Long.class || targetType == long.class) {
if (value.contains(".")) {
return (long) Double.parseDouble(value);
}
return Long.parseLong(value);
} else if (targetType == Double.class || targetType == double.class) {
return Double.parseDouble(value);
} else if (targetType == BigDecimal.class) {
// 特殊处理数值,确保是数值
try {
return new BigDecimal(value);
} catch (NumberFormatException e) {
log.warn("BigDecimal转换失败,尝试清理字符串: {}", value);
// 清理非数字字符(保留小数点和负号)
String cleanValue = value.replaceAll("[^\\d.-]", "");
if (!cleanValue.isEmpty()) {
return new BigDecimal(cleanValue);
}
return null;
}
} else if (targetType == Date.class) {
try {
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
......@@ -421,32 +939,29 @@ public class ExcelParserServiceImpl implements ExcelParserService {
return null;
}
} else if (targetType == Boolean.class || targetType == boolean.class) {
return "是".equals(value) || "YES".equalsIgnoreCase(value) || "TRUE".equalsIgnoreCase(value);
return "是".equals(value) || "YES".equalsIgnoreCase(value) || "TRUE".equalsIgnoreCase(value) || "1".equals(value);
}
return value;
} catch (Exception e) {
log.warn("类型转换失败: 值='{}', 目标类型={}, 错误: {}", value, targetType.getSimpleName(), e.getMessage());
}
private Object convertNumericValue(double value, Class<?> targetType) {
if (targetType == Integer.class || targetType == int.class) {
return (int) value;
} else if (targetType == Long.class || targetType == long.class) {
return (long) value;
} else if (targetType == Double.class || targetType == double.class) {
return value;
} else if (targetType == BigDecimal.class) {
return BigDecimal.valueOf(value);
} else if (targetType == Date.class) {
return DateUtil.getJavaDate(value);
}
return value;
return null;
}
/**
* 检查行是否为空
*/
private boolean isRowEmpty(Row row) {
if (row == null) return true;
for (Cell cell : row) {
if (cell != null && cell.getCellType() != CellType.BLANK) {
String value = getCellValueAsString(cell);
if (value != null && !value.trim().isEmpty()) {
return false;
}
}
}
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