多级数据结构导出Excel工具类,支持多级数据导入导出,支持自定义字体颜色和背景颜色,支持自定义转化器

Excel 工具类使用文档

📖 概述

本Excel工具类基于Apache POI封装,提供了简单易用的Excel导入导出功能。通过统一的@Excel注解配置,支持多级数据结构、样式配置、自定义转换器等高级功能。

✨ 核心特性

  • 🎯 统一注解 :合并了原有的3个注解为一个@Excel注解
  • 📊 多级结构:支持对象嵌套、集合等复杂数据结构
  • 🎨 样式配置:支持字体颜色、背景颜色等样式设置
  • 🔄 自定义转换:支持数据转换器,实现双向转换
  • 🚀 高性能:样式缓存机制,优化内存使用
  • 🔒 安全可靠:解决Zip bomb安全问题

📋 @Excel 注解属性详解

🏷️ 基础属性

属性 类型 默认值 必填 说明 示例
name String "" Excel列标题名称 @Excel(name = "姓名")
index int 0 Excel列索引(从0开始) @Excel(index = 3)
defaultValue String "" 单元格为空时的默认值 @Excel(defaultValue = "未填写")

🔧 字段类型配置

属性 类型 默认值 说明 使用场景
fieldType FieldType COLUMN 字段处理类型 控制字段的处理方式
FieldType 枚举值说明
枚举值 说明 适用场景 示例
COLUMN 普通列字段 基本数据类型字段 String name, Integer age
OBJECT 对象字段 嵌套对象 ClassInfo classInfo
COLLECTION 集合字段 对象集合 List<Student> students

🏷️ 分组和排序

属性 类型 默认值 说明 示例
isKey boolean false 是否为分组关键字段 用于多级数据的分组依据

🎨 样式配置

属性 类型 默认值 说明 可选值示例
fontColor IndexedColors AUTOMATIC 字体颜色 BLACK, WHITE, RED, BLUE
backgroundColor IndexedColors AUTOMATIC 背景颜色 YELLOW, LIGHT_GREEN, PINK
常用颜色枚举值
颜色类别 枚举值
基础色 BLACK, WHITE, RED, GREEN, BLUE, YELLOW, ORANGE, PINK
浅色系 LIGHT_BLUE, LIGHT_GREEN, LIGHT_YELLOW, LIGHT_ORANGE
深色系 DARK_BLUE, DARK_GREEN, DARK_RED, DARK_YELLOW
灰色系 GREY_25_PERCENT, GREY_40_PERCENT, GREY_50_PERCENT

🔄 转换器配置

属性 类型 默认值 说明 示例
converter Class<? extends ExcelConverter> DefaultConverter.class 自定义转换器 GenderConverter.class
params String[] {} 转换器参数 params = {"param1", "param2"}

📦 集合类型配置

属性 类型 默认值 说明 使用条件
type Class<?> Object.class 集合元素类型 fieldType 不为 COLUMN 时使用

🚀 使用示例

1️⃣ 基础实体类配置

java 复制代码
@Data
public class Student {
    @Excel(name = "学号", index = 0)
    private String studentId;
    
    @Excel(name = "姓名", index = 1, fontColor = IndexedColors.BLUE)
    private String name;
    
    @Excel(name = "年龄", index = 2)
    private Integer age;
    
    @Excel(name = "性别", index = 3, converter = GenderConverter.class)
    private String gender;
    
    @Excel(name = "成绩", index = 4, converter = ScoreConverter.class)
    private Double score;
    
    @Excel(name = "备注", index = 5, defaultValue = "无")
    private String remark;
}

2️⃣ 多级数据结构配置

java 复制代码
@Data
public class School {
    @Excel(name = "学校名称", index = 0, isKey = true)
    private String schoolName;
    
    @Excel(name = "学校编码", index = 1)
    private String schoolCode;
    
    @Excel(name = "建校日期", index = 2)
    private Date establishDate;
    
    // 班级信息(对象类型)
    @Excel(name = "班级信息", fieldType = Excel.FieldType.OBJECT, type = ClassInfo.class)
    private ClassInfo classInfo;
    
    // 学生列表(集合类型)
    @Excel(name = "学生列表", fieldType = Excel.FieldType.COLLECTION, type = Student.class)
    private List<Student> students;
}

3️⃣ 样式配置示例

java 复制代码
@Data
public class ClassInfo {
    @Excel(name = "班级编码", index = 5, isKey = true)
    private String classCode;
    
    // 黄色背景 + 黑色字体
    @Excel(name = "班级名称", index = 6, 
           fontColor = IndexedColors.BLACK, 
           backgroundColor = IndexedColors.YELLOW)
    private String className;
    
    // 浅绿色背景
    @Excel(name = "年级", index = 7, 
           backgroundColor = IndexedColors.LIGHT_GREEN)
    private String grade;
}

🔄 自定义转换器

转换器接口定义

java 复制代码
public interface ExcelConverter<T, E> {
    /**
     * 导出时:将Java对象转换为Excel显示值
     */
    E convertToExcel(T value, String[] params);
    
    /**
     * 导入时:将Excel单元格值转换为Java对象
     */
    T convertFromExcel(E value, String[] params);
    
    /**
     * 获取单元格样式(可选实现)
     */
    default CellStyleInfo getCellStyle(T value, String[] params) {
        return null;
    }
}

转换器实现示例

1️⃣ 性别转换器(支持动态样式)
java 复制代码
public class GenderConverter implements ExcelConverter<String, String> {
    
    @Override
    public String convertToExcel(String value, String[] params) {
        return "1".equals(value) ? "男" : "0".equals(value) ? "女" : value;
    }
    
    @Override
    public String convertFromExcel(String value, String[] params) {
        return "男".equals(value) ? "1" : "女".equals(value) ? "0" : value;
    }
    
    @Override
    public CellStyleInfo getCellStyle(String value, String[] params) {
        if ("1".equals(value)) {
            // 男生:蓝色背景 + 白色字体
            return CellStyleInfo.withColors(IndexedColors.WHITE, IndexedColors.BLUE);
        } else if ("0".equals(value)) {
            // 女生:粉色背景 + 黑色字体
            return CellStyleInfo.withColors(IndexedColors.BLACK, IndexedColors.PINK);
        }
        return null;
    }
}
2️⃣ 分数转换器(支持动态样式)
java 复制代码
public class ScoreConverter implements ExcelConverter<Double, String> {
    
    @Override
    public String convertToExcel(Double value, String[] params) {
        return value == null ? "未评分" : String.format("%.1f", value);
    }
    
    @Override
    public Double convertFromExcel(String value, String[] params) {
        if (value == null || "未评分".equals(value)) {
            return null;
        }
        try {
            return Double.parseDouble(value);
        } catch (NumberFormatException e) {
            return null;
        }
    }
    
    @Override
    public CellStyleInfo getCellStyle(Double value, String[] params) {
        if (value == null) return null;
        
        if (value >= 90) {
            // 优秀:绿色背景 + 白色字体
            return CellStyleInfo.withColors(IndexedColors.WHITE, IndexedColors.GREEN);
        } else if (value >= 80) {
            // 良好:浅绿色背景 + 黑色字体
            return CellStyleInfo.withColors(IndexedColors.BLACK, IndexedColors.LIGHT_GREEN);
        } else if (value >= 70) {
            // 中等:黄色背景 + 黑色字体
            return CellStyleInfo.withColors(IndexedColors.BLACK, IndexedColors.YELLOW);
        } else if (value >= 60) {
            // 及格:橙色背景 + 黑色字体
            return CellStyleInfo.withColors(IndexedColors.BLACK, IndexedColors.ORANGE);
        } else {
            // 不及格:红色背景 + 白色字体
            return CellStyleInfo.withColors(IndexedColors.WHITE, IndexedColors.RED);
        }
    }
}

🎨 样式配置详解

静态样式(注解配置)

java 复制代码
// 直接在注解中配置固定样式
@Excel(name = "重要字段", index = 1, 
       fontColor = IndexedColors.WHITE, 
       backgroundColor = IndexedColors.RED)
private String importantField;

动态样式(转换器配置)

java 复制代码
// 通过转换器根据值动态设置样式
@Excel(name = "状态", index = 2, converter = StatusConverter.class)
private String status;

public class StatusConverter implements ExcelConverter<String, String> {
    @Override
    public CellStyleInfo getCellStyle(String value, String[] params) {
        switch (value) {
            case "success": 
                return CellStyleInfo.withColors(IndexedColors.WHITE, IndexedColors.GREEN);
            case "warning": 
                return CellStyleInfo.withColors(IndexedColors.BLACK, IndexedColors.YELLOW);
            case "error": 
                return CellStyleInfo.withColors(IndexedColors.WHITE, IndexedColors.RED);
            default: 
                return null;
        }
    }
}

样式优先级

  1. 动态样式(转换器返回)- 最高优先级
  2. 静态样式(注解配置)- 次优先级
  3. 默认样式(系统默认)- 最低优先级

🔧 工具类调用方法

导出Excel

java 复制代码
// Controller示例
@GetMapping("/export")
public void exportData(HttpServletResponse response) {
    List<School> data = schoolService.getAllData();
    ExcelUtils.exportExcel(data, School.class, "学校数据.xlsx", response);
}

导入Excel

java 复制代码
// Controller示例
@PostMapping("/import")
public String importData(@RequestParam("file") MultipartFile file) {
    try (InputStream inputStream = file.getInputStream()) {
        List<School> dataList = ExcelUtils.importExcel(inputStream, School.class);
        schoolService.batchSave(dataList);
        return "导入成功,共导入 " + dataList.size() + " 条记录";
    } catch (Exception e) {
        return "导入失败:" + e.getMessage();
    }
}

⚡ 性能优化特性

样式缓存机制

  • 自动缓存:相同颜色组合的样式自动复用
  • 内存优化:避免创建重复样式,减少内存占用
  • 缓存清理:每次导出开始时自动清理缓存

Zip Bomb 安全处理

  • 自动调整:导入时临时调整POI安全限制
  • 自动恢复:导入完成后自动恢复原始设置
  • 安全保障:不影响其他应用的安全性

📝 最佳实践

1️⃣ 注解配置建议

场景 建议配置 示例
基础字段 只配置name和index @Excel(name = "姓名", index = 1)
关键字段 添加颜色突出显示 @Excel(name = "ID", index = 0, fontColor = IndexedColors.BLUE)
可空字段 设置默认值 @Excel(name = "备注", index = 5, defaultValue = "无")
分组字段 标记为key @Excel(name = "部门", index = 0, isKey = true)

2️⃣ 转换器使用建议

场景 建议 原因
简单映射 使用静态样式 性能更好,配置简单
复杂逻辑 使用转换器 灵活性更强,支持动态样式
性能敏感 缓存转换结果 避免重复计算

3️⃣ 样式配置建议

原则 说明 示例
对比鲜明 确保文字清晰可读 深色背景配浅色文字
语义明确 用颜色表达含义 红色表示错误,绿色表示成功
数量适中 避免过多颜色混乱 建议不超过5种颜色组合

4️⃣ 错误处理建议

java 复制代码
// 推荐的错误处理方式
@Override
public Double convertFromExcel(String value, String[] params) {
    try {
        return value == null ? null : Double.parseDouble(value);
    } catch (NumberFormatException e) {
        log.warn("数值转换失败: {}", value);
        return null; // 返回null而不是抛异常
    }
}

🚨 注意事项

⚠️ 配置约束

  1. 索引唯一性 :同一层级的字段index不能重复
  2. 类型匹配fieldType与字段类型要匹配
  3. 转换器泛型:转换器泛型要与字段类型匹配

⚠️ 性能考虑

  1. 大数据量:建议分批处理,单次不超过10000条记录
  2. 复杂样式:过多动态样式可能影响性能
  3. 内存使用:注意大文件导入的内存占用

⚠️ 兼容性说明

  • POI版本:基于Apache POI 5.x开发
  • Java版本:需要Java 8+
  • 文件格式 :支持.xlsx格式,建议不使用.xls

源码

java 复制代码
package org.whh.excel.annotation;

import org.apache.poi.ss.usermodel.IndexedColors;
import org.whh.excel.converter.ExcelConverter;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Excel统一注解
 * 合并了原来的 ExcelColumn、ExcelConvert、ExcelObject 三个注解的功能
 * 用于标识实体类字段的Excel处理方式
 *
 * @author whh
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Excel {

    // ========== ExcelColumn 相关属性 ==========

    /**
     * 列名称
     */
    String name() default "";

    /**
     * 列索引(从0开始),如果不指定则按字段声明顺序
     */
    int index() default -1;

    /**
     * 列宽度
     */
    int width() default 20;

    /**
     * 是否必填
     */
    boolean required() default false;

    /**
     * 日期格式(当字段为日期类型时使用)
     */
    String dateFormat() default "yyyy-MM-dd HH:mm:ss";

    /**
     * 数字格式(当字段为数字类型时使用)
     */
    String numberFormat() default "";

    /**
     * 默认值
     */
    String defaultValue() default "";

    /**
     * 是否为关键字段,用于多级数据导入时的分组标识
     * 当多行数据的所有关键字段值都相同时,这些行会被归为同一个对象
     * <p>
     * 使用示例:
     * - 如果只需要按学校编码分组:在schoolCode字段上设置 isKey = true
     * - 如果需要按姓名+年龄+性别组合分组:在name、age、gender三个字段上都设置 isKey = true
     */
    boolean isKey() default false;

    // ========== 样式相关属性 ==========

    /**
     * 字体颜色配置
     * 默认为 AUTOMATIC 表示使用系统默认颜色
     */
    IndexedColors fontColor() default IndexedColors.AUTOMATIC;

    /**
     * 背景颜色配置
     * 默认为 AUTOMATIC 表示无背景色
     */
    IndexedColors backgroundColor() default IndexedColors.AUTOMATIC;

    // ========== ExcelConvert 相关属性 ==========

    /**
     * 转换器类
     * 默认为 DefaultConverter.class 表示不使用转换器
     */
    Class<? extends ExcelConverter<?, ?>> converter() default DefaultConverter.class;

    /**
     * 转换参数(可选)
     */
    String[] params() default {};

    // ========== ExcelObject 相关属性 ==========

    /**
     * 对象类型或集合元素类型
     * 当 fieldType = OBJECT 时,表示对象类型
     * 当 fieldType = COLLECTION 时,表示集合元素类型
     * 当 fieldType = COLUMN 时,此属性无意义
     */
    Class<?> type() default Object.class;

    /**
     * 层级顺序(数字越小层级越高)
     */
    int order() default 0;

    // ========== 通用属性 ==========

    /**
     * 说明
     */
    String description() default "";

    /**
     * 字段类型枚举
     */
    FieldType fieldType() default FieldType.COLUMN;

    /**
     * 字段类型枚举定义
     */
    enum FieldType {
        /**
         * 普通列字段
         */
        COLUMN,
        /**
         * 对象字段
         */
        OBJECT,
        /**
         * 集合字段
         */
        COLLECTION
    }

    /**
     * 默认转换器(表示不使用转换器)
     */
    class DefaultConverter implements ExcelConverter<Object, Object> {
        @Override
        public Object convertToExcel(Object value, String[] params) {
            return value;
        }

        @Override
        public Object convertFromExcel(Object value, String[] params) {
            return value;
        }

        @Override
        public org.whh.excel.converter.CellStyleInfo getCellStyle(Object value, String[] params) {
            return null; // 默认转换器不提供样式
        }
    }
}
java 复制代码
package org.whh.excel.converter;

import lombok.Data;
import org.apache.poi.ss.usermodel.IndexedColors;

/**
 * Excel单元格样式信息类
 * 用于在转换器中返回单元格的样式配置
 *
 * @author whh
 */
@Data
public class CellStyleInfo {

    /**
     * 字体颜色
     * 使用 IndexedColors 枚举,如 IndexedColors.BLACK, IndexedColors.WHITE 等
     * 默认为 null 表示使用注解或系统默认颜色
     */
    private IndexedColors fontColor;

    /**
     * 背景颜色
     * 使用 IndexedColors 枚举,如 IndexedColors.YELLOW, IndexedColors.LIGHT_BLUE 等
     * 默认为 null 表示使用注解或系统默认颜色
     */
    private IndexedColors backgroundColor;

    /**
     * 默认构造函数
     */
    public CellStyleInfo() {
    }

    /**
     * 构造函数
     *
     * @param fontColor       字体颜色
     * @param backgroundColor 背景颜色
     */
    public CellStyleInfo(IndexedColors fontColor, IndexedColors backgroundColor) {
        this.fontColor = fontColor;
        this.backgroundColor = backgroundColor;
    }

    /**
     * 创建只有字体颜色的样式
     *
     * @param fontColor 字体颜色
     * @return 样式信息
     */
    public static CellStyleInfo withFontColor(IndexedColors fontColor) {
        return new CellStyleInfo(fontColor, null);
    }

    /**
     * 创建只有背景颜色的样式
     *
     * @param backgroundColor 背景颜色
     * @return 样式信息
     */
    public static CellStyleInfo withBackgroundColor(IndexedColors backgroundColor) {
        return new CellStyleInfo(null, backgroundColor);
    }

    /**
     * 创建同时设置字体和背景颜色的样式
     *
     * @param fontColor       字体颜色
     * @param backgroundColor 背景颜色
     * @return 样式信息
     */
    public static CellStyleInfo withColors(IndexedColors fontColor, IndexedColors backgroundColor) {
        return new CellStyleInfo(fontColor, backgroundColor);
    }


    /**
     * 检查是否有字体颜色设置
     */
    public boolean hasFontColor() {
        return fontColor != null && fontColor != IndexedColors.AUTOMATIC;
    }

    /**
     * 检查是否有背景颜色设置
     */
    public boolean hasBackgroundColor() {
        return backgroundColor != null && backgroundColor != IndexedColors.AUTOMATIC;
    }

    /**
     * 检查是否有任何样式设置
     */
    public boolean hasAnyStyle() {
        return hasFontColor() || hasBackgroundColor();
    }
}
java 复制代码
package org.whh.excel.converter;

/**
 * Excel字段转换器接口
 * 用于实现字段值的双向转换
 *
 * @param <T> 实体类字段类型
 * @param <E> Excel中显示的类型
 * @author whh
 */
public interface ExcelConverter<T, E> {

    /**
     * 导出时的转换:将实体类字段值转换为Excel中显示的值
     *
     * @param value  实体类字段值
     * @param params 转换参数
     * @return Excel中显示的值
     */
    E convertToExcel(T value, String[] params);

    /**
     * 导入时的转换:将Excel中的值转换为实体类字段值
     *
     * @param value  Excel中的值
     * @param params 转换参数
     * @return 实体类字段值
     */
    T convertFromExcel(E value, String[] params);

    /**
     * 根据字段值返回单元格样式信息(可选实现)
     * 实现此方法可以根据不同的字段值设置不同的颜色
     * <p>
     * 使用示例:
     * - 性别字段:男生返回蓝色背景+白色字体,女生返回粉色背景+黑色字体
     * - 状态字段:成功返回绿色背景,失败返回红色背景
     * - 分数字段:高分返回绿色字体,低分返回红色字体
     *
     * @param value  实体类字段值
     * @param params 转换参数
     * @return 样式信息,返回 null 表示使用默认样式
     */
    default CellStyleInfo getCellStyle(T value, String[] params) {
        return null; // 默认实现返回 null,表示不设置特殊样式
    }
}
java 复制代码
package org.whh.excel.converter;

import org.apache.poi.ss.usermodel.IndexedColors;

/**
 * 性别转换器
 * 将"男/女"转换为"1/0",反之亦然
 * 支持根据性别设置不同的单元格样式
 *
 * @author whh
 */
public class GenderConverter implements ExcelConverter<String, String> {

    @Override
    public String convertToExcel(String value, String[] params) {
        if ("1".equals(value)) {
            return "男";
        } else if ("0".equals(value)) {
            return "女";
        }
        return value;
    }

    @Override
    public String convertFromExcel(String value, String[] params) {
        if ("男".equals(value)) {
            return "1";
        } else if ("女".equals(value)) {
            return "0";
        }
        return value;
    }

    @Override
    public CellStyleInfo getCellStyle(String value, String[] params) {
        if ("1".equals(value)) {
            // 男生:蓝色背景 + 白色字体
            return CellStyleInfo.withColors(IndexedColors.WHITE, IndexedColors.BLUE);
        } else if ("0".equals(value)) {
            // 女生:粉色背景 + 黑色字体
            return CellStyleInfo.withColors(IndexedColors.BLACK, IndexedColors.PINK);
        }
        // 其他值使用默认样式
        return null;
    }
}
java 复制代码
package org.whh.excel.util;

import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.openxml4j.util.ZipSecureFile;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.whh.excel.annotation.Excel;
import org.whh.excel.converter.CellStyleInfo;
import org.whh.excel.converter.ExcelConverter;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;


/**
 * 通用Excel导入导出工具类
 * 基于注解自动处理多级数据结构的导入导出
 * 支持@ExcelColumn、@ExcelObject、@ExcelConvert注解
 *
 * @author whh
 */
@Slf4j
public class ExcelUtils {

    // ========== 表头样式全局配置 ==========
    // 修改这些配置即可改变所有Excel导出的表头样式

    /**
     * 表头背景颜色配置
     * 常用颜色选项:
     * - IndexedColors.DARK_BLUE (深蓝色,商务风格)
     * - IndexedColors.BLUE (蓝色)
     * - IndexedColors.LIGHT_BLUE (浅蓝色)
     * - IndexedColors.GREEN (绿色)
     * - IndexedColors.DARK_GREEN (深绿色)
     * - IndexedColors.GREY_50_PERCENT (灰色)
     * - IndexedColors.ORANGE (橙色)
     * - IndexedColors.RED (红色)
     */
    private static final IndexedColors HEADER_BACKGROUND_COLOR = IndexedColors.AUTOMATIC;

    /**
     * 表头字体颜色配置
     * 常用颜色选项:
     * - IndexedColors.WHITE (白色,适合深色背景)
     * - IndexedColors.BLACK (黑色,适合浅色背景)
     * - IndexedColors.BLUE (蓝色)
     * - IndexedColors.RED (红色)
     * - IndexedColors.GREEN (绿色)
     */
    private static final IndexedColors HEADER_FONT_COLOR = IndexedColors.BLACK;

    /**
     * 表头字体大小配置 (推荐范围: 10-16)
     */
    private static final short HEADER_FONT_SIZE = 12;

    /**
     * 表头是否加粗配置
     * true: 加粗显示 (推荐)
     * false: 正常字体
     */
    private static final boolean HEADER_FONT_BOLD = true;

    /**
     * 表头边框样式配置
     * - BorderStyle.THIN (细边框,推荐)
     * - BorderStyle.MEDIUM (中等边框)
     * - BorderStyle.THICK (粗边框)
     * - BorderStyle.NONE (无边框)
     */
    private static final BorderStyle HEADER_BORDER_STYLE = BorderStyle.THIN;

    /**
     * 表头边框颜色配置
     * 常用:IndexedColors.BLACK (黑色边框)
     */
    private static final IndexedColors HEADER_BORDER_COLOR = IndexedColors.BLACK;

    /**
     * 导出数据到Excel(通用方法)
     */
    public static <T> void exportExcel(List<T> data, Class<T> clazz, String fileName, HttpServletResponse response) {
        // 清理样式缓存,避免不同工作簿间的样式冲突
        clearStyleCache();

        try (Workbook workbook = new XSSFWorkbook()) {
            Sheet sheet = workbook.createSheet();

            // 解析实体类结构
            List<FieldInfo> allFields = parseEntityStructure(clazz);

            // 创建表头
            createHeader(sheet, allFields);

            // 填充数据
            int currentRow = 1;
            for (T item : data) {
                currentRow = fillMultiLevelData(sheet, item, allFields, currentRow);
            }

            // 设置响应头
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName + ".xlsx", "UTF-8"));

            // 设置缓存控制,避免代理服务器问题
            response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
            response.setHeader("Pragma", "no-cache");
            response.setDateHeader("Expires", 0);

            workbook.write(response.getOutputStream());
            log.info("Excel导出成功,文件名: {}, 数据量: {} 条", fileName, data.size());

        } catch (org.springframework.web.context.request.async.AsyncRequestNotUsableException e) {
            // 客户端取消下载或连接中断,这是正常情况,不需要报错
            log.info("客户端连接中断 (可能是多线程下载工具如迅雷等导致): {}", e.getMessage());
        } catch (java.io.IOException e) {
            if (e.getMessage() != null && (e.getMessage().contains("你的主机中的软件中止了一个已建立的连接") || e.getMessage().contains("Connection reset") || e.getMessage().contains("Broken pipe"))) {
                // 网络连接中断,通常是客户端问题(多线程下载工具常见)
                log.info("客户端网络连接中断 (通常是多线程下载工具如迅雷等的正常行为,文件仍会正确下载): {}", e.getMessage());
            } else {
                log.error("Excel导出时发生IO异常", e);
                throw new RuntimeException("Excel导出失败", e);
            }
        } catch (org.apache.poi.openxml4j.exceptions.OpenXML4JRuntimeException e) {
            // 检查是否是因为客户端中断导致的POI异常
            Throwable cause = e.getCause();
            if (cause instanceof org.apache.poi.openxml4j.exceptions.OpenXML4JException) {
                // 进一步检查根本原因
                String message = e.getMessage();
                if (message != null && (message.contains("failed to be saved in the stream") || message.contains("Fail to save"))) {
                    log.info("Excel文件保存过程中客户端连接中断 (通常是多线程下载工具导致,文件会正确下载): {}", message);
                    return; // 不抛出异常,因为这是正常的多线程下载行为
                }
            }
            log.error("Excel导出失败", e);
            throw new RuntimeException("Excel导出失败", e);
        } catch (Exception e) {
            log.error("Excel导出失败", e);
            throw new RuntimeException("Excel导出失败", e);
        }
    }

    /**
     * 导入Excel数据(通用方法)
     */
    public static <T> List<T> importExcel(InputStream inputStream, Class<T> clazz) {
        List<T> result;

        // 保存原始的压缩率限制
        double originalMinInflateRatio = ZipSecureFile.getMinInflateRatio();

        try {
            // 临时调整压缩率限制,允许处理包含大量样式的Excel文件
            // 从默认的 0.01 调整到 0.005,以支持样式丰富的Excel文件
            ZipSecureFile.setMinInflateRatio(0.005);

            try (Workbook workbook = WorkbookFactory.create(inputStream)) {
                Sheet sheet = workbook.getSheetAt(0);

                // 解析实体类结构
                List<FieldInfo> allFields = parseEntityStructure(clazz);

                // 读取数据并构建对象
                result = parseMultiLevelData(sheet, clazz, allFields);

                log.info("Excel导入成功,共导入 {} 条记录", result.size());
            }

        } catch (Exception e) {
            log.error("Excel导入失败", e);
            throw new RuntimeException("Excel导入失败", e);
        } finally {
            // 恢复原始的压缩率限制
            ZipSecureFile.setMinInflateRatio(originalMinInflateRatio);
            log.debug("已恢复POI安全设置,压缩率限制: {}", originalMinInflateRatio);
        }
        return result;
    }

    /**
     * 解析实体类结构,获取所有层级的字段信息
     */
    private static <T> List<FieldInfo> parseEntityStructure(Class<T> clazz) {
        List<FieldInfo> allFields = new ArrayList<>();
        collectAllFields(clazz, allFields, 0);

        // 按层级和索引排序
        allFields.sort((a, b) -> {
            if (a.level != b.level) {
                return Integer.compare(a.level, b.level);
            }
            return Integer.compare(a.columnIndex, b.columnIndex);
        });

        // 重新分配列索引
        int currentIndex = 0;
        for (FieldInfo fieldInfo : allFields) {
            if (fieldInfo.fieldType == Excel.FieldType.COLUMN) {
                fieldInfo.columnIndex = currentIndex++;
            }
        }

        return allFields;
    }

    /**
     * 递归收集所有字段信息
     */
    private static void collectAllFields(Class<?> clazz, List<FieldInfo> allFields, int level) {
        Field[] fields = clazz.getDeclaredFields();

        for (Field field : fields) {
            field.setAccessible(true);

            Excel excel = field.getAnnotation(Excel.class);

            if (excel != null) {
                FieldInfo fieldInfo = new FieldInfo();
                fieldInfo.field = field;
                fieldInfo.excel = excel;
                fieldInfo.level = level;
                fieldInfo.fieldType = excel.fieldType();

                if (fieldInfo.fieldType == Excel.FieldType.COLUMN) {
                    fieldInfo.columnIndex = excel.index() != -1 ? excel.index() : Integer.MAX_VALUE;
                }

                // 初始化转换器
                if (excel.converter() != Excel.DefaultConverter.class) {
                    try {
                        @SuppressWarnings("unchecked") ExcelConverter<Object, Object> converter = (ExcelConverter<Object, Object>) excel.converter().getDeclaredConstructor().newInstance();
                        fieldInfo.converter = converter;
                    } catch (Exception e) {
                        log.warn("初始化转换器失败: {}", excel.converter().getName());
                    }
                }

                allFields.add(fieldInfo);

                // 如果是对象或集合类型,递归收集子字段
                if (fieldInfo.fieldType == Excel.FieldType.OBJECT || fieldInfo.fieldType == Excel.FieldType.COLLECTION) {
                    Class<?> actualType = getActualType(field);
                    if (actualType != Object.class) {
                        collectAllFields(actualType, allFields, level + 1);
                    }
                }
            }
        }
    }

    /**
     * 获取字段的实际类型
     */
    private static Class<?> getActualType(Field field) {
        Excel excel = field.getAnnotation(Excel.class);
        if (excel != null && excel.type() != Object.class) {
            return excel.type();
        }

        Type genericType = field.getGenericType();
        if (genericType instanceof ParameterizedType) {
            ParameterizedType paramType = (ParameterizedType) genericType;
            Type[] actualTypeArguments = paramType.getActualTypeArguments();
            if (actualTypeArguments.length > 0) {
                return (Class<?>) actualTypeArguments[0];
            }
        }

        return field.getType();
    }

    /**
     * 创建表头
     */
    private static void createHeader(Sheet sheet, List<FieldInfo> allFields) {
        Row headerRow = sheet.createRow(0);

        // 创建表头样式
        CellStyle headerStyle = createHeaderStyle(sheet.getWorkbook());

        for (FieldInfo fieldInfo : allFields) {
            if (fieldInfo.fieldType == Excel.FieldType.COLUMN) {
                Cell cell = headerRow.createCell(fieldInfo.columnIndex);

                String headerName = "";
                if (fieldInfo.excel != null && StringUtils.isNotBlank(fieldInfo.excel.name())) {
                    headerName = fieldInfo.excel.name();
                } else {
                    headerName = fieldInfo.field.getName();
                }

                cell.setCellValue(headerName);

                // 应用表头样式
                cell.setCellStyle(headerStyle);

                // 设置列宽
                if (fieldInfo.excel != null && fieldInfo.excel.width() > 0) {
                    sheet.setColumnWidth(fieldInfo.columnIndex, fieldInfo.excel.width() * 256);
                } else {
                    sheet.setColumnWidth(fieldInfo.columnIndex, 20 * 256);
                }
            }
        }
    }

    /**
     * 创建表头样式
     */
    private static CellStyle createHeaderStyle(Workbook workbook) {
        CellStyle headerStyle = workbook.createCellStyle();

        // 创建字体(使用全局配置)
        Font headerFont = workbook.createFont();
        headerFont.setBold(HEADER_FONT_BOLD);  // 使用配置的加粗设置
        headerFont.setFontHeightInPoints(HEADER_FONT_SIZE);  // 使用配置的字体大小
        headerFont.setColor(HEADER_FONT_COLOR.getIndex());  // 使用配置的字体颜色

        // 将字体应用到样式
        headerStyle.setFont(headerFont);

        // 设置背景色(使用全局配置)
        headerStyle.setFillForegroundColor(HEADER_BACKGROUND_COLOR.getIndex());
        headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);

        // 设置对齐方式
        headerStyle.setAlignment(HorizontalAlignment.CENTER);  // 水平居中
        headerStyle.setVerticalAlignment(VerticalAlignment.CENTER);  // 垂直居中

        // 设置边框(使用全局配置)
        headerStyle.setBorderTop(HEADER_BORDER_STYLE);
        headerStyle.setBorderBottom(HEADER_BORDER_STYLE);
        headerStyle.setBorderLeft(HEADER_BORDER_STYLE);
        headerStyle.setBorderRight(HEADER_BORDER_STYLE);

        // 设置边框颜色(使用全局配置)
        headerStyle.setTopBorderColor(HEADER_BORDER_COLOR.getIndex());
        headerStyle.setBottomBorderColor(HEADER_BORDER_COLOR.getIndex());
        headerStyle.setLeftBorderColor(HEADER_BORDER_COLOR.getIndex());
        headerStyle.setRightBorderColor(HEADER_BORDER_COLOR.getIndex());

        return headerStyle;
    }


    // 样式缓存,避免创建重复样式(线程安全)
    private static final Map<String, CellStyle> styleCache = new ConcurrentHashMap<>();

    /**
     * 清理样式缓存
     * 在每次导出开始时调用,避免不同工作簿间的样式冲突
     */
    private static void clearStyleCache() {
        styleCache.clear();
    }

    /**
     * 创建或获取单元格样式(支持动态颜色和样式缓存)
     */
    private static CellStyle createCellStyle(Workbook workbook, FieldInfo fieldInfo, Object value) {
        // 默认颜色(从注解获取)
        IndexedColors fontColor = fieldInfo.excel.fontColor();
        IndexedColors backgroundColor = fieldInfo.excel.backgroundColor();
        // 如果有转换器,尝试获取动态样式
        if (fieldInfo.converter != null) {
            try {
                CellStyleInfo styleInfo = fieldInfo.converter.getCellStyle(value, fieldInfo.excel.params());
                if (styleInfo != null) {
                    // 动态样式优先级高于注解样式
                    if (styleInfo.hasFontColor()) {
                        fontColor = styleInfo.getFontColor();
                    }
                    if (styleInfo.hasBackgroundColor()) {
                        backgroundColor = styleInfo.getBackgroundColor();
                    }
                }
            } catch (Exception e) {
                log.warn("获取动态样式失败: {}", e.getMessage());
            }
        }

        // 检查是否需要创建样式
        boolean needStyle = fontColor != IndexedColors.AUTOMATIC || backgroundColor != IndexedColors.AUTOMATIC;

        // 只有在需要时才创建样式
        if (!needStyle) {
            return null;
        }

        // 创建样式缓存键
        String cacheKey = fontColor.name() + "_" + backgroundColor.name();

        // 先查缓存
        CellStyle cachedStyle = styleCache.get(cacheKey);
        if (cachedStyle != null) {
            return cachedStyle;
        }
        CellStyle cellStyle = workbook.createCellStyle();
        // 应用字体样式
        if (fontColor != IndexedColors.AUTOMATIC) {
            Font font = workbook.createFont();
            font.setColor(fontColor.getIndex());
            cellStyle.setFont(font);
        }

        // 应用背景样式
        if (backgroundColor != IndexedColors.AUTOMATIC) {
            cellStyle.setFillForegroundColor(backgroundColor.getIndex());
            cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        }
        // 缓存样式
        styleCache.put(cacheKey, cellStyle);
        return cellStyle;
    }

    /**
     * 填充多级数据
     */
    private static int fillMultiLevelData(Sheet sheet, Object rootItem, List<FieldInfo> allFields, int startRow) {
        try {
            // 展开为平面数据行
            List<Map<String, Object>> dataRows = expandToFlatRows(rootItem, allFields);
            // 填充每行数据
            for (int i = 0; i < dataRows.size(); i++) {
                Row row = sheet.createRow(startRow + i);
                Map<String, Object> rowData = dataRows.get(i);

                for (FieldInfo fieldInfo : allFields) {
                    if (fieldInfo.fieldType == Excel.FieldType.COLUMN) {
                        Cell cell = row.createCell(fieldInfo.columnIndex);
                        // 使用层级特定的唯一键获取值
                        String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();
                        Object value = rowData.get(uniqueKey);
                        String cellValue = convertToExcelValue(value, fieldInfo);
                        cell.setCellValue(cellValue);

                        // 应用单元格样式(支持注解样式和动态样式)
                        CellStyle cellStyle = createCellStyle(sheet.getWorkbook(), fieldInfo, value);
                        if (cellStyle != null) {
                            cell.setCellStyle(cellStyle);
                        }
                    }
                }
            }
            // 创建合并单元格
            if (dataRows.size() > 1) {
                createMergeRegions(sheet, rootItem, allFields, startRow, dataRows.size());
            }
            return startRow + dataRows.size();
        } catch (Exception e) {
            log.error("填充多级数据失败", e);
            return startRow + 1;
        }
    }

    /**
     * 展开对象为平面数据行
     */
    private static List<Map<String, Object>> expandToFlatRows(Object item, List<FieldInfo> allFields) {
        List<Map<String, Object>> result = new ArrayList<>();
        try {
            expandObjectToRows(item, new HashMap<>(), allFields, result);
        } catch (Exception e) {
            log.warn("展开对象失败", e);
        }
        return result.isEmpty() ? List.of(new HashMap<>()) : result;
    }

    /**
     * 递归展开对象到行数据
     */
    private static void expandObjectToRows(Object item, Map<String, Object> parentData, List<FieldInfo> allFields, List<Map<String, Object>> result) throws Exception {
        Map<String, Object> currentData = new HashMap<>(parentData);
        List<Object> childItems = new ArrayList<>();
        // 收集当前层级的字段值
        for (FieldInfo fieldInfo : allFields) {
            if (fieldInfo.field.getDeclaringClass().isAssignableFrom(item.getClass())) {
                Object value = fieldInfo.field.get(item);

                if (fieldInfo.fieldType == Excel.FieldType.COLLECTION && value instanceof Collection) {
                    childItems.addAll((Collection<?>) value);
                } else if (fieldInfo.fieldType == Excel.FieldType.COLUMN) {
                    // 使用层级+字段名作为唯一键,避免不同层级的同名字段相互覆盖
                    String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();
                    currentData.put(uniqueKey, value);  // 使用唯一键存储,避免覆盖
                }
            }
        }
        // 如果有子对象,递归处理
        if (!childItems.isEmpty()) {
            for (Object childItem : childItems) {
                expandObjectToRows(childItem, currentData, allFields, result);
            }
        } else {
            result.add(currentData);
        }
    }

    /**
     * 创建合并单元格(支持任意多级数据结构)
     */
    private static void createMergeRegions(Sheet sheet, Object rootItem, List<FieldInfo> allFields, int startRow, int totalRows) {
        try {
            // 构建层级结构树
            HierarchyNode rootNode = buildHierarchyTree(rootItem, allFields, startRow);
            // 递归创建所有层级的合并区域
            createMergeRegionsRecursively(sheet, rootNode, allFields);
        } catch (Exception e) {
            log.warn("创建合并单元格失败", e);
        }
    }

    /**
     * 构建层级结构树(支持任意层级)
     */
    private static HierarchyNode buildHierarchyTree(Object rootItem, List<FieldInfo> allFields, int startRow) {
        HierarchyNode rootNode = new HierarchyNode();
        rootNode.item = rootItem;
        rootNode.level = 0;
        rootNode.startRow = startRow;
        // 递归构建层级树
        buildHierarchyTreeRecursively(rootNode, allFields);
        return rootNode;
    }

    /**
     * 递归构建层级结构树
     */
    private static void buildHierarchyTreeRecursively(HierarchyNode node, List<FieldInfo> allFields) {
        try {
            List<Object> childItems = new ArrayList<>();

            // 找到当前对象的子对象集合
            for (FieldInfo fieldInfo : allFields) {
                if (fieldInfo.field.getDeclaringClass().isAssignableFrom(node.item.getClass()) && fieldInfo.fieldType == Excel.FieldType.COLLECTION) {

                    Object value = fieldInfo.field.get(node.item);
                    if (value instanceof Collection) {
                        childItems.addAll((Collection<?>) value);
                    }
                    break; // 每个对象只处理一个子集合
                }
            }
            // 为每个子对象创建节点
            int currentRow = node.startRow;
            for (Object childItem : childItems) {
                HierarchyNode childNode = new HierarchyNode();
                childNode.item = childItem;
                childNode.level = node.level + 1;
                childNode.startRow = currentRow;
                childNode.parent = node;
                node.children.add(childNode);
                // 递归构建子节点
                buildHierarchyTreeRecursively(childNode, allFields);
                // 计算当前子节点占用的行数
                childNode.rowCount = calculateNodeRowCount(childNode);
                currentRow += childNode.rowCount;
            }
            // 如果没有子节点,则为叶子节点,占用1行
            if (childItems.isEmpty()) {
                node.rowCount = 1;
            } else {
                // 有子节点时,行数为所有子节点行数之和
                node.rowCount = node.children.stream().mapToInt(child -> child.rowCount).sum();
            }
        } catch (Exception e) {
            log.warn("构建层级树失败", e);
            node.rowCount = 1;
        }
    }

    /**
     * 计算节点占用的行数
     */
    private static int calculateNodeRowCount(HierarchyNode node) {
        if (node.children.isEmpty()) {
            return 1;
        }
        return node.children.stream().mapToInt(child -> child.rowCount).sum();
    }

    /**
     * 递归创建所有层级的合并区域
     */
    private static void createMergeRegionsRecursively(Sheet sheet, HierarchyNode node, List<FieldInfo> allFields) {
        try {
            // 为当前层级的字段创建合并区域
            if (node.rowCount > 1) {
                List<FieldInfo> currentLevelFields = allFields.stream().filter(f -> f.fieldType == Excel.FieldType.COLUMN && f.level == node.level).toList();
                for (FieldInfo fieldInfo : currentLevelFields) {
                    CellRangeAddress mergeRegion = new CellRangeAddress(node.startRow, node.startRow + node.rowCount - 1, fieldInfo.columnIndex, fieldInfo.columnIndex);
                    sheet.addMergedRegion(mergeRegion);

                    // 设置居中样式
                    setCellStyle(sheet, node.startRow, fieldInfo.columnIndex);
                }
            }
            // 递归处理子节点
            for (HierarchyNode child : node.children) {
                createMergeRegionsRecursively(sheet, child, allFields);
            }
        } catch (Exception e) {
            log.warn("递归创建合并区域失败", e);
        }
    }

    /**
     * 层级节点类(支持任意层级的数据结构)
     */
    private static class HierarchyNode {
        Object item;              // 当前对象
        int level;               // 层级深度 (0=根层级)
        int startRow;            // 起始行号
        int rowCount;            // 占用行数
        HierarchyNode parent;    // 父节点
        List<HierarchyNode> children = new ArrayList<>();  // 子节点列表
    }

    /**
     * 设置单元格样式(保留已有样式,只添加居中对齐)
     */
    private static void setCellStyle(Sheet sheet, int row, int col) {
        try {
            Row sheetRow = sheet.getRow(row);
            if (sheetRow != null) {
                Cell cell = sheetRow.getCell(col);
                if (cell != null) {
                    CellStyle existingStyle = cell.getCellStyle();
                    CellStyle newStyle = sheet.getWorkbook().createCellStyle();

                    // 如果已有样式,复制其属性
                    if (existingStyle != null && existingStyle.getIndex() != 0) {
                        // 复制颜色和字体属性
                        newStyle.cloneStyleFrom(existingStyle);
                    }

                    // 设置居中对齐
                    newStyle.setAlignment(HorizontalAlignment.CENTER);
                    newStyle.setVerticalAlignment(VerticalAlignment.CENTER);

                    cell.setCellStyle(newStyle);
                }
            }
        } catch (Exception e) {
            log.warn("设置单元格样式失败", e);
        }
    }

    /**
     * 转换值为Excel显示格式
     */
    private static String convertToExcelValue(Object value, FieldInfo fieldInfo) {
        if (value == null) {
            return fieldInfo.excel != null ? fieldInfo.excel.defaultValue() : "";
        }

        // 使用转换器
        if (fieldInfo.converter != null) {
            try {
                Object converted = fieldInfo.converter.convertToExcel(value, fieldInfo.excel.params());
                return String.valueOf(converted);
            } catch (Exception e) {
                log.warn("字段转换失败: {}", fieldInfo.field.getName());
            }
        }
        // 日期格式化
        if (value instanceof Date && fieldInfo.excel != null && StringUtils.isNotBlank(fieldInfo.excel.dateFormat())) {
            SimpleDateFormat sdf = new SimpleDateFormat(fieldInfo.excel.dateFormat());
            return sdf.format((Date) value);
        }
        return String.valueOf(value);
    }

    /**
     * 解析Excel数据为对象列表
     */
    private static <T> List<T> parseMultiLevelData(Sheet sheet, Class<T> clazz, List<FieldInfo> allFields) {
        List<T> result = new ArrayList<>();
        try {
            if (sheet.getPhysicalNumberOfRows() <= 1) {
                log.warn("Excel文件没有数据行");
                return result;
            }
            // 读取表头
            Row headerRow = sheet.getRow(0);
            Map<String, Integer> headerMap = parseHeader(headerRow, allFields);
            // 读取所有数据行
            List<Map<String, Object>> rowDataList = readAllRows(sheet, headerMap, allFields);
            if (rowDataList.isEmpty()) {
                log.warn("没有读取到任何数据");
                return result;
            }
            // 构建多级对象结构
            result = buildMultiLevelObjects(rowDataList, clazz, allFields);
        } catch (Exception e) {
            log.error("解析Excel数据失败", e);
            throw new RuntimeException("解析Excel数据失败", e);
        }
        return result;
    }

    /**
     * 解析Excel表头
     */
    private static Map<String, Integer> parseHeader(Row headerRow, List<FieldInfo> allFields) {
        Map<String, Integer> headerMap = new HashMap<>();

        if (headerRow != null) {
            // 按层级排序字段,确保低层级的字段优先映射
            List<FieldInfo> sortedFields = allFields.stream().filter(f -> f.fieldType == Excel.FieldType.COLUMN && f.excel != null).sorted((a, b) -> Integer.compare(a.level, b.level)).toList();
            // 记录每个列名已经被哪些层级使用过
            Map<String, Set<Integer>> usedLevels = new HashMap<>();
            for (int i = 0; i < headerRow.getPhysicalNumberOfCells(); i++) {
                Cell cell = headerRow.getCell(i);
                if (cell != null) {
                    String headerName = cell.getStringCellValue();
                    // 按层级顺序找到第一个未映射的匹配字段
                    for (FieldInfo fieldInfo : sortedFields) {
                        if (headerName.equals(fieldInfo.excel.name())) {
                            String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();
                            // 检查这个字段是否已经被映射过
                            if (!headerMap.containsKey(uniqueKey)) {
                                // 记录此列名在此层级的使用
                                usedLevels.computeIfAbsent(headerName, k -> new HashSet<>()).add(fieldInfo.level);
                                headerMap.put(uniqueKey, i);
                                break;
                            }
                        }
                    }
                }
            }
        }
        log.debug("解析表头完成,映射关系: {}", headerMap);
        return headerMap;
    }

    /**
     * 读取所有数据行
     */
    private static List<Map<String, Object>> readAllRows(Sheet sheet, Map<String, Integer> headerMap, List<FieldInfo> allFields) {
        List<Map<String, Object>> rowDataList = new ArrayList<>();
        for (int i = 1; i <= sheet.getLastRowNum(); i++) {
            Row row = sheet.getRow(i);
            if (row == null) continue;
            Map<String, Object> rowData = new HashMap<>();
            boolean hasData = false;
            // 读取每个字段的值
            for (FieldInfo fieldInfo : allFields) {
                if (fieldInfo.fieldType == Excel.FieldType.COLUMN && fieldInfo.excel != null) {
                    String fieldName = fieldInfo.field.getName();
                    // 使用层级特定的唯一键查找列索引
                    String uniqueKey = "L" + fieldInfo.level + "_" + fieldName;
                    Integer colIndex = headerMap.get(uniqueKey);
                    if (colIndex != null) {
                        Cell cell = row.getCell(colIndex);
                        Object value = getCellValue(cell, fieldInfo);
                        // 使用层级特定的唯一键存储,避免同名字段覆盖
                        rowData.put(uniqueKey, value);
                        if (value != null && !value.toString().trim().isEmpty()) {
                            hasData = true;
                        }
                    }
                }
            }
            if (hasData) {
                rowData.put("_rowIndex", i);
                rowDataList.add(rowData);
            }
        }
        // 填补合并单元格的空值问题
        fillMergedCellValues(rowDataList, allFields);
        return rowDataList;
    }

    /**
     * 填补合并单元格的空值问题
     * 严格按层级和子对象组隔离,只在同层级同对象组内填补空值
     */
    private static void fillMergedCellValues(List<Map<String, Object>> rowDataList, List<FieldInfo> allFields) {
        if (rowDataList.isEmpty()) return;
        // 按层级分组字段
        Map<Integer, List<String>> levelFields = new HashMap<>();
        for (FieldInfo fieldInfo : allFields) {
            if (fieldInfo.fieldType == Excel.FieldType.COLUMN && fieldInfo.excel != null) {
                String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();
                levelFields.computeIfAbsent(fieldInfo.level, k -> new ArrayList<>()).add(uniqueKey);
            }
        }
        // 找到最大层级(叶子节点层级)
        int maxLevel = levelFields.keySet().stream().mapToInt(Integer::intValue).max().orElse(0);
        // 按层级逐级处理,严格隔离
        for (int currentLevel = 0; currentLevel < maxLevel; currentLevel++) {
            List<String> currentLevelFieldKeys = levelFields.get(currentLevel);
            if (currentLevelFieldKeys == null || currentLevelFieldKeys.isEmpty()) continue;
            // 获取当前层级的关键字段
            List<String> currentLevelKeyFields = getKeyFieldNames(allFields, currentLevel);
            // 按当前层级的对象分组
            Map<String, List<Map<String, Object>>> currentLevelGroups = new LinkedHashMap<>();
            for (Map<String, Object> rowData : rowDataList) {
                String levelKey = buildObjectKey(rowData, currentLevelKeyFields, currentLevel);
                currentLevelGroups.computeIfAbsent(levelKey, k -> new ArrayList<>()).add(rowData);
            }
            // 为当前层级的每个对象组分别填补空值
            for (Map.Entry<String, List<Map<String, Object>>> levelGroup : currentLevelGroups.entrySet()) {
                String levelKey = levelGroup.getKey();
                List<Map<String, Object>> levelRowData = levelGroup.getValue();
                for (String fieldKey : currentLevelFieldKeys) {
                    // 检查当前层级对象组内是否有空值需要填补
                    boolean hasEmptyValue = false;
                    Object masterValue = null;
                    // 先找到第一个非空值作为主值
                    for (Map<String, Object> rowData : levelRowData) {
                        Object currentValue = rowData.get(fieldKey);
                        if (currentValue != null && !currentValue.toString().trim().isEmpty()) {
                            if (masterValue == null) {
                                masterValue = currentValue;
                            }
                        } else {
                            hasEmptyValue = true;
                        }
                    }
                    // 只有同时满足"有主值"和"有空值"的条件才进行填充
                    if (masterValue != null && hasEmptyValue) {
                        int filledCount = 0;
                        for (Map<String, Object> rowData : levelRowData) {
                            Object currentValue = rowData.get(fieldKey);
                            if (currentValue == null || currentValue.toString().trim().isEmpty()) {
                                rowData.put(fieldKey, masterValue);
                                filledCount++;
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * 获取单元格值,支持合并单元格
     */
    private static Object getCellValue(Cell cell, FieldInfo fieldInfo) {
        if (cell == null) return null;
        try {
            // 检查是否为合并单元格
            Sheet sheet = cell.getSheet();
            int rowIndex = cell.getRowIndex();
            int colIndex = cell.getColumnIndex();
            // 查找合并区域
            for (CellRangeAddress range : sheet.getMergedRegions()) {
                if (range.isInRange(rowIndex, colIndex)) {
                    // 如果是合并单元格,获取合并区域的第一个单元格的值
                    Row firstRow = sheet.getRow(range.getFirstRow());
                    if (firstRow != null) {
                        Cell firstCell = firstRow.getCell(range.getFirstColumn());
                        if (firstCell != null && firstCell != cell) {
                            return getCellValue(firstCell, fieldInfo);
                        }
                    }
                }
            }
            // 普通单元格处理
            switch (cell.getCellType()) {
                case STRING:
                    String stringValue = cell.getStringCellValue();
                    return convertCellValue(stringValue, fieldInfo);
                case NUMERIC:
                    double numericValue = cell.getNumericCellValue();
                    // 如果字段类型是Date,尝试转换为日期
                    if (fieldInfo.field.getType() == Date.class) {
                        return cell.getDateCellValue();
                    } else if (fieldInfo.field.getType() == Integer.class || fieldInfo.field.getType() == int.class) {
                        return (int) numericValue;
                    }
                    return numericValue;
                case BOOLEAN:
                    return cell.getBooleanCellValue();
                default:
                    return null;
            }
        } catch (Exception e) {
            log.warn("读取单元格值失败: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 转换单元格值
     */
    private static Object convertCellValue(String value, FieldInfo fieldInfo) {
        if (value == null || value.trim().isEmpty()) return null;
        Class<?> fieldType = fieldInfo.field.getType();
        try {
            // 如果有自定义转换器,先尝试转换
            if (fieldInfo.converter != null) {
                return fieldInfo.converter.convertFromExcel(value, new String[0]);
            }
            // 根据字段类型转换
            if (fieldType == String.class) {
                return value;
            } else if (fieldType == Integer.class || fieldType == int.class) {
                return Integer.parseInt(value);
            } else if (fieldType == Long.class || fieldType == long.class) {
                return Long.parseLong(value);
            } else if (fieldType == Double.class || fieldType == double.class) {
                return Double.parseDouble(value);
            } else if (fieldType == Boolean.class || fieldType == boolean.class) {
                return Boolean.parseBoolean(value);
            } else if (fieldType == Date.class) {
                // 尝试解析日期
                SimpleDateFormat[] formats = {new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), new SimpleDateFormat("yyyy-MM-dd"), new SimpleDateFormat("yyyy/MM/dd")};
                for (SimpleDateFormat format : formats) {
                    try {
                        return format.parse(value);
                    } catch (Exception ignored) {
                    }
                }
            }
            return value;
        } catch (Exception e) {
            log.warn("转换单元格值失败: {} -> {}", value, fieldType.getSimpleName());
            return value;
        }
    }

    /**
     * 构建多级对象结构
     */
    private static <T> List<T> buildMultiLevelObjects(List<Map<String, Object>> rowDataList, Class<T> clazz, List<FieldInfo> allFields) {
        List<T> result = new ArrayList<>();
        try {
            // 找到最大层级深度
            int maxLevel = allFields.stream().filter(f -> f.fieldType == Excel.FieldType.COLUMN).mapToInt(f -> f.level).max().orElse(0);
            log.debug("最大层级深度: {}", maxLevel);
            // 按层级分组数据
            Map<String, List<Map<String, Object>>> groupedData = groupDataByHierarchy(rowDataList, allFields);
            // 构建根对象
            for (Map.Entry<String, List<Map<String, Object>>> entry : groupedData.entrySet()) {
                T rootObject = buildSingleObject(entry.getValue(), clazz, allFields, 0);
                if (rootObject != null) {
                    result.add(rootObject);
                }
            }
        } catch (Exception e) {
            log.error("构建多级对象失败", e);
        }
        return result;
    }

    /**
     * 按层级分组数据(支持任意级别)
     */
    private static Map<String, List<Map<String, Object>>> groupDataByHierarchy(List<Map<String, Object>> rowDataList, List<FieldInfo> allFields) {
        Map<String, List<Map<String, Object>>> groupedData = new LinkedHashMap<>();
        // 获取根层级的关键字段组合
        List<String> rootKeyFieldNames = getKeyFieldNames(allFields, 0);
        for (Map<String, Object> rowData : rowDataList) {
            // 构建根对象的唯一标识
            String key = buildObjectKey(rowData, rootKeyFieldNames, 0);
            groupedData.computeIfAbsent(key, k -> new ArrayList<>()).add(rowData);
        }
        return groupedData;
    }

    /**
     * 获取指定层级的关键字段名称列表
     */
    private static List<String> getKeyFieldNames(List<FieldInfo> allFields, int level) {
        List<String> keyFieldNames = new ArrayList<>();
        // 获取当前层级的所有字段
        List<FieldInfo> currentLevelFields = allFields.stream().filter(f -> f.fieldType == Excel.FieldType.COLUMN && f.level == level).toList();
        // 收集所有标记为isKey=true的字段
        for (FieldInfo field : currentLevelFields) {
            Excel excel = field.excel;
            if (excel != null && excel.isKey()) {
                keyFieldNames.add(field.field.getName());
            }
        }
        // 如果没有找到任何关键字段,使用第一个字段作为默认关键字段
        if (keyFieldNames.isEmpty() && !currentLevelFields.isEmpty()) {
            keyFieldNames.add(currentLevelFields.get(0).field.getName());
        }
        return keyFieldNames;
    }

    /**
     * 构建对象的唯一标识键
     */
    private static String buildObjectKey(Map<String, Object> rowData, List<String> keyFieldNames, int level) {
        StringBuilder key = new StringBuilder();
        for (String fieldName : keyFieldNames) {
            // 使用层级特定的唯一键获取值
            String uniqueKey = "L" + level + "_" + fieldName;
            Object value = rowData.get(uniqueKey);
            key.append(value != null ? value.toString() : "null").append("|");
        }
        return key.toString();
    }

    /**
     * 构建单个对象
     */
    private static <T> T buildSingleObject(List<Map<String, Object>> rowDataList, Class<T> clazz, List<FieldInfo> allFields, int level) {
        try {
            T object = clazz.getDeclaredConstructor().newInstance();

            // 设置当前层级的字段值
            Map<String, Object> representativeRow = findRepresentativeRow(rowDataList, allFields, level);
            List<FieldInfo> currentLevelFields = allFields.stream().filter(f -> f.fieldType == Excel.FieldType.COLUMN && f.level == level).toList();
            for (FieldInfo fieldInfo : currentLevelFields) {
                // 使用层级特定的唯一键,避免同名字段覆盖问题
                String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();
                Object value = representativeRow.get(uniqueKey);
                if (value != null) {
                    fieldInfo.field.set(object, value);
                }
            }
            List<FieldInfo> childCollectionFields = allFields.stream().filter(f -> f.fieldType == Excel.FieldType.COLLECTION && f.level == level).toList();

            for (FieldInfo collectionField : childCollectionFields) {
                Class<?> elementType = getActualType(collectionField.field);
                // 按子对象分组
                Map<String, List<Map<String, Object>>> childGroups = groupChildData(rowDataList, allFields, level + 1);
                List<Object> childObjects = new ArrayList<>();
                for (List<Map<String, Object>> childRowData : childGroups.values()) {
                    Object childObject = buildSingleObject(childRowData, elementType, allFields, level + 1);
                    if (childObject != null) {
                        childObjects.add(childObject);
                    }
                }
                collectionField.field.set(object, childObjects);
            }
            return object;
        } catch (Exception e) {
            log.error("构建对象失败: {}", e.getMessage());
            return null;
        }
    }

    /**
     * 找到代表性的行数据(用于获取当前层级的字段值)
     * 对于每个字段,优先选择有值的行数据
     */
    private static Map<String, Object> findRepresentativeRow(List<Map<String, Object>> rowDataList, List<FieldInfo> allFields, int level) {
        if (rowDataList.isEmpty()) {
            return new HashMap<>();
        }
        if (rowDataList.size() == 1) {
            return rowDataList.get(0);
        }
        // 获取当前层级的所有字段
        List<FieldInfo> currentLevelFields = allFields.stream().filter(f -> f.fieldType == Excel.FieldType.COLUMN && f.level == level).toList();
        // 创建一个组合的代表性行数据
        Map<String, Object> representativeRow = new HashMap<>(rowDataList.get(0));
        // 对于每个字段,找到第一个非空值
        for (FieldInfo fieldInfo : currentLevelFields) {
            String uniqueKey = "L" + fieldInfo.level + "_" + fieldInfo.field.getName();
            Object currentValue = representativeRow.get(uniqueKey);
            // 如果当前值是空的,尝试从其他行中找到非空值
            if (currentValue == null || currentValue.toString().trim().isEmpty()) {
                for (Map<String, Object> rowData : rowDataList) {
                    Object value = rowData.get(uniqueKey);
                    if (value != null && !value.toString().trim().isEmpty()) {
                        representativeRow.put(uniqueKey, value);
                        break;
                    }
                }
            }
        }
        return representativeRow;
    }

    /**
     * 分组子数据(支持任意级别)
     */
    private static Map<String, List<Map<String, Object>>> groupChildData(List<Map<String, Object>> rowDataList, List<FieldInfo> allFields, int childLevel) {
        Map<String, List<Map<String, Object>>> childGroups = new LinkedHashMap<>();
        // 获取子层级的关键字段组合
        List<String> childKeyFieldNames = getKeyFieldNames(allFields, childLevel);
        for (Map<String, Object> rowData : rowDataList) {
            // 构建子对象的唯一标识
            String key = buildObjectKey(rowData, childKeyFieldNames, childLevel);
            childGroups.computeIfAbsent(key, k -> new ArrayList<>()).add(rowData);
        }
        return childGroups;
    }

    /**
     * 字段信息类
     */
    private static class FieldInfo {
        Field field;
        Excel excel;
        ExcelConverter<Object, Object> converter;
        int level;
        int columnIndex;
        Excel.FieldType fieldType;
    }
}

🔗 相关链接


最后更新:2025-08-25