dumingliang 21 saat önce
ebeveyn
işleme
0fd6dcaa98

+ 428 - 0
gw-common/src/main/java/com/goldenwater/common/utils/poi/ExcelImportHelper.java

@@ -0,0 +1,428 @@
+package com.goldenwater.common.utils.poi;
+
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+import com.goldenwater.common.annotation.Excel;
+import com.goldenwater.common.utils.DateUtils;
+import com.goldenwater.common.utils.StringUtils;
+import com.goldenwater.common.utils.reflect.ReflectUtils;
+import org.apache.poi.ss.usermodel.*;
+import org.apache.poi.ss.usermodel.DateUtil;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.WorkbookFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Excel动态导入工具类
+ * 支持从Excel的指定行读取列名作为变量名,动态映射到实体类字段
+ */
+public class ExcelImportHelper {
+
+    private static final Logger log = LoggerFactory.getLogger(ExcelImportHelper.class);
+
+    /**
+     * 从Excel文件导入数据,使用指定行作为列名行
+     *
+     * @param is       输入流
+     * @param clazz    目标实体类
+     * @param headerRow 作为列名的行号(从0开始,如2表示第3行)
+     * @param <T>      目标类型
+     * @return 实体列表
+     */
+    public static <T> List<T> importExcel(InputStream is, Class<T> clazz, int headerRow) {
+        List<T> list = new ArrayList<>();
+        try {
+            Workbook wb = WorkbookFactory.create(is);
+            Sheet sheet = wb.getSheetAt(0);
+            if (sheet == null) {
+                throw new RuntimeException("Excel文件sheet不存在");
+            }
+
+            int rows = sheet.getLastRowNum();
+            if (rows <= headerRow) {
+                throw new RuntimeException("Excel文件数据行不足");
+            }
+
+            // 读取表头行(列名行)
+            Row header = sheet.getRow(headerRow);
+            if (header == null) {
+                throw new RuntimeException("Excel文件表头行为空");
+            }
+
+            // 构建列名到字段的映射
+            // Map: 列索引 -> 实体字段
+            Map<Integer, Field> columnFieldMap = buildColumnFieldMap(header, clazz);
+
+            // 从headerRow+1开始读取数据行
+            for (int i = headerRow + 1; i <= rows; i++) {
+                Row row = sheet.getRow(i);
+                if (isRowEmpty(row)) {
+                    continue;
+                }
+
+                T entity = clazz.getDeclaredConstructor().newInstance();
+                boolean hasData = false;
+
+                for (Map.Entry<Integer, Field> entry : columnFieldMap.entrySet()) {
+                    int columnIndex = entry.getKey();
+                    Field field = entry.getValue();
+
+                    try {
+                        Object value = getCellValue(row, columnIndex);
+                        if (value != null && !isEmptyValue(value)) {
+                            // 根据字段类型转换值
+                            Object convertedValue = convertValue(value, field.getType(), field);
+                            if (convertedValue != null) {
+                                ReflectUtils.invokeSetter(entity, field.getName(), convertedValue);
+                                hasData = true;
+                            }
+                        }
+                    } catch (Exception e) {
+                        log.warn("设置字段[" + field.getName() + "]失败: " + e.getMessage());
+                    }
+                }
+
+                if (hasData) {
+                    list.add(entity);
+                }
+            }
+
+            log.info("Excel导入完成,共导入{}条数据", list.size());
+        } catch (Exception e) {
+            log.error("Excel导入失败", e);
+            throw new RuntimeException("Excel导入失败: " + e.getMessage(), e);
+        }
+        return list;
+    }
+
+    /**
+     * 构建列索引到实体字段的映射
+     * 支持通过字段名、@Excel注解的name属性进行匹配
+     */
+    private static Map<Integer, Field> buildColumnFieldMap(Row header, Class<?> clazz) {
+        Map<Integer, Field> map = new HashMap<>();
+        Field[] allFields = clazz.getDeclaredFields();
+
+        // 收集实体类所有字段(包括父类)
+        List<Field> allFieldsList = new ArrayList<>(Arrays.asList(allFields));
+        Class<?> superClass = clazz.getSuperclass();
+        while (superClass != null && superClass != Object.class) {
+            allFieldsList.addAll(Arrays.asList(superClass.getDeclaredFields()));
+            superClass = superClass.getSuperclass();
+        }
+
+        for (int colIndex = 0; colIndex < header.getLastCellNum(); colIndex++) {
+            Cell cell = header.getCell(colIndex);
+            if (cell == null) {
+                continue;
+            }
+            String headerName = getCellStringValue(cell);
+            if (StringUtils.isEmpty(headerName)) {
+                continue;
+            }
+
+            // 去除空格和特殊字符,用于匹配
+            String normalizedHeader = normalizeName(headerName);
+
+            // 尝试匹配字段
+            Field matchedField = findFieldByName(normalizedHeader, allFieldsList);
+            if (matchedField != null) {
+                map.put(colIndex, matchedField);
+                log.debug("列[{}] '{}' -> 字段 '{}'", colIndex, headerName, matchedField.getName());
+            } else {
+                log.debug("列[{}] '{}' 未匹配到字段", colIndex, headerName);
+            }
+        }
+        return map;
+    }
+
+    /**
+     * 通过名称查找字段
+     * 匹配优先级:
+     * 1. 字段名完全匹配(不区分大小写)
+     * 2. 字段名去除下划线匹配
+     * 3. @Excel注解的name属性匹配
+     */
+    private static Field findFieldByName(String headerName, List<Field> fields) {
+        // 1. 字段名精确匹配(不区分大小写)
+        for (Field field : fields) {
+            if (field.getName().equalsIgnoreCase(headerName)) {
+                return field;
+            }
+        }
+
+        // 2. 字段名去除下划线匹配
+        String headerNoUnderscore = headerName.replace("_", "");
+        for (Field field : fields) {
+            String fieldNameNoUnderscore = field.getName().replace("_", "");
+            if (fieldNameNoUnderscore.equalsIgnoreCase(headerNoUnderscore)) {
+                return field;
+            }
+        }
+
+        // 3. @Excel注解的name属性匹配
+        for (Field field : fields) {
+            Excel excelAnnotation = field.getAnnotation(Excel.class);
+            if (excelAnnotation != null && StringUtils.isNotEmpty(excelAnnotation.name())) {
+                String annotationName = normalizeName(excelAnnotation.name());
+                if (annotationName.equalsIgnoreCase(headerName) ||
+                        annotationName.replace("_", "").equalsIgnoreCase(headerNoUnderscore)) {
+                    return field;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * 规范化名称:去除空格、统一大小写
+     */
+    private static String normalizeName(String name) {
+        if (name == null) {
+            return "";
+        }
+        return name.trim().replaceAll("\\s+", "").toLowerCase();
+    }
+
+    /**
+     * 判断行是否为空
+     */
+    private static boolean isRowEmpty(Row row) {
+        if (row == null) {
+            return true;
+        }
+        for (int i = 0; i < row.getLastCellNum(); i++) {
+            Cell cell = row.getCell(i);
+            if (cell != null && cell.getCellType() != CellType.BLANK) {
+                String value = getCellStringValue(cell);
+                if (StringUtils.isNotEmpty(value)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * 获取单元格的值(兼容不同类型)
+     */
+    private static Object getCellValue(Row row, int cellIndex) {
+        Cell cell = row.getCell(cellIndex);
+        if (cell == null) {
+            return null;
+        }
+
+        switch (cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue().trim();
+            case NUMERIC:
+                if (DateUtil.isCellDateFormatted(cell)) {
+                    return cell.getDateCellValue();
+                }
+                double numericValue = cell.getNumericCellValue();
+                // 如果是整数,转为Long;否则转为Double
+                if (numericValue == Math.floor(numericValue) && !Double.isInfinite(numericValue)) {
+                    return (long) numericValue;
+                }
+                return numericValue;
+            case BOOLEAN:
+                return cell.getBooleanCellValue();
+            case FORMULA:
+                try {
+                    return cell.getStringCellValue();
+                } catch (Exception e) {
+                    return cell.getNumericCellValue();
+                }
+            default:
+                return null;
+        }
+    }
+
+    /**
+     * 获取单元格的字符串值
+     */
+    private static String getCellStringValue(Cell cell) {
+        if (cell == null) {
+            return "";
+        }
+        switch (cell.getCellType()) {
+            case STRING:
+                return cell.getStringCellValue();
+            case NUMERIC:
+                if (DateUtil.isCellDateFormatted(cell)) {
+                    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(cell.getDateCellValue());
+                }
+                double val = cell.getNumericCellValue();
+                if (val == Math.floor(val)) {
+                    return String.valueOf((long) val);
+                }
+                return String.valueOf(val);
+            case BOOLEAN:
+                return String.valueOf(cell.getBooleanCellValue());
+            case FORMULA:
+                try {
+                    return cell.getStringCellValue();
+                } catch (Exception e) {
+                    return String.valueOf(cell.getNumericCellValue());
+                }
+            default:
+                return "";
+        }
+    }
+
+    /**
+     * 转换值到目标类型
+     */
+    private static Object convertValue(Object value, Class<?> targetType, Field field) {
+        if (value == null) {
+            return null;
+        }
+
+        // 处理String类型
+        if (targetType == String.class) {
+            String strVal = value.toString().trim();
+            // 如果是数字类型且以.0结尾,去除.0
+            if (strVal.matches("^\\d+\\.0$")) {
+                strVal = strVal.substring(0, strVal.length() - 2);
+            }
+            // 日期字符串转换
+            Excel excelAnnotation = field.getAnnotation(Excel.class);
+            if (excelAnnotation != null && StringUtils.isNotEmpty(excelAnnotation.dateFormat())) {
+                try {
+                    return DateUtils.parseDate(strVal);
+                } catch (Exception e) {
+                    return strVal;
+                }
+            }
+            return strVal;
+        }
+
+        // 处理BigDecimal类型
+        if (targetType == BigDecimal.class) {
+            if (value instanceof BigDecimal) {
+                return value;
+            }
+            if (value instanceof Number) {
+                return new BigDecimal(value.toString());
+            }
+            String strVal = value.toString().trim();
+            if (StringUtils.isEmpty(strVal)) {
+                return null;
+            }
+            try {
+                return new BigDecimal(strVal);
+            } catch (NumberFormatException e) {
+                log.warn("无法转换'{}'为BigDecimal", strVal);
+                return null;
+            }
+        }
+
+        // 处理Date类型
+        if (targetType == java.util.Date.class) {
+            if (value instanceof java.util.Date) {
+                return value;
+            }
+            if (value instanceof String) {
+                String strVal = ((String) value).trim();
+                if (StringUtils.isEmpty(strVal)) {
+                    return null;
+                }
+                try {
+                    return DateUtils.parseDate(strVal);
+                } catch (Exception e) {
+                    log.warn("无法转换'{}'为Date", strVal);
+                    return null;
+                }
+            }
+            return null;
+        }
+
+        // 处理Integer/int类型
+        if (targetType == Integer.class || targetType == int.class) {
+            if (value instanceof Number) {
+                return ((Number) value).intValue();
+            }
+            String strVal = value.toString().trim();
+            if (StringUtils.isEmpty(strVal)) {
+                return targetType == int.class ? 0 : null;
+            }
+            try {
+                return (int) Double.parseDouble(strVal);
+            } catch (NumberFormatException e) {
+                return targetType == int.class ? 0 : null;
+            }
+        }
+
+        // 处理Long/long类型
+        if (targetType == Long.class || targetType == long.class) {
+            if (value instanceof Number) {
+                return ((Number) value).longValue();
+            }
+            String strVal = value.toString().trim();
+            if (StringUtils.isEmpty(strVal)) {
+                return targetType == long.class ? 0L : null;
+            }
+            try {
+                return (long) Double.parseDouble(strVal);
+            } catch (NumberFormatException e) {
+                return targetType == long.class ? 0L : null;
+            }
+        }
+
+        // 处理Double/double类型
+        if (targetType == Double.class || targetType == double.class) {
+            if (value instanceof Number) {
+                return ((Number) value).doubleValue();
+            }
+            String strVal = value.toString().trim();
+            if (StringUtils.isEmpty(strVal)) {
+                return targetType == double.class ? 0.0 : null;
+            }
+            try {
+                return Double.parseDouble(strVal);
+            } catch (NumberFormatException e) {
+                return targetType == double.class ? 0.0 : null;
+            }
+        }
+
+        // 处理Float/float类型
+        if (targetType == Float.class || targetType == float.class) {
+            if (value instanceof Number) {
+                return ((Number) value).floatValue();
+            }
+            String strVal = value.toString().trim();
+            if (StringUtils.isEmpty(strVal)) {
+                return targetType == float.class ? 0.0f : null;
+            }
+            try {
+                return Float.parseFloat(strVal);
+            } catch (NumberFormatException e) {
+                return targetType == float.class ? 0.0f : null;
+            }
+        }
+
+        // 默认返回原值
+        return value;
+    }
+
+    /**
+     * 判断值是否为空
+     */
+    private static boolean isEmptyValue(Object value) {
+        if (value == null) {
+            return true;
+        }
+        if (value instanceof String) {
+            return StringUtils.isEmpty((String) value);
+        }
+        return false;
+    }
+}