EasyExcel动态表头详解

什么是动态表头?

动态表头是指在运行时根据业务需求动态生成Excel表格的列标题,而不是在代码中预先定义固定的表头结构。这在以下场景中非常有用:

  • 根据用户选择的字段生成不同的报表

  • 处理数据库中动态字段的导出

  • 生成多级复杂表头

  • 实现可配置的数据导出功能

基础用法

1. 简单动态表头

复制代码
public class DynamicHeaderExample {
    
    public void writeWithDynamicHeader() {
        String fileName = "dynamic_header.xlsx";
        
        // 动态构建表头
        List<List<String>> head = new ArrayList<>();
        head.add(Arrays.asList("姓名"));
        head.add(Arrays.asList("年龄"));
        head.add(Arrays.asList("邮箱"));
        head.add(Arrays.asList("部门"));
        
        // 准备数据
        List<List<Object>> dataList = new ArrayList<>();
        dataList.add(Arrays.asList("张三", 25, "zhangsan@example.com", "技术部"));
        dataList.add(Arrays.asList("李四", 30, "lisi@example.com", "产品部"));
        dataList.add(Arrays.asList("王五", 28, "wangwu@example.com", "设计部"));
        
        // 写入Excel
        EasyExcel.write(fileName)
                .head(head)
                .sheet("员工信息")
                .doWrite(dataList);
    }
}

2. 多级表头

复制代码
public void writeWithMultiLevelHeader() {
    String fileName = "multi_level_header.xlsx";
    
    // 构建多级表头
    List<List<String>> head = new ArrayList<>();
    // 第一列:基本信息->个人信息->姓名
    head.add(Arrays.asList("基本信息", "个人信息", "姓名"));
    // 第二列:基本信息->个人信息->年龄
    head.add(Arrays.asList("基本信息", "个人信息", "年龄"));
    // 第三列:基本信息->联系方式->邮箱
    head.add(Arrays.asList("基本信息", "联系方式", "邮箱"));
    // 第四列:基本信息->联系方式->电话
    head.add(Arrays.asList("基本信息", "联系方式", "电话"));
    // 第五列:工作信息->部门
    head.add(Arrays.asList("工作信息", "部门"));
    // 第六列:工作信息->职位
    head.add(Arrays.asList("工作信息", "职位"));
    
    // 准备数据
    List<List<Object>> dataList = new ArrayList<>();
    dataList.add(Arrays.asList("张三", 25, "zhangsan@example.com", "13800138000", "技术部", "Java工程师"));
    dataList.add(Arrays.asList("李四", 30, "lisi@example.com", "13900139000", "产品部", "产品经理"));
    
    EasyExcel.write(fileName)
            .head(head)
            .sheet("员工详细信息")
            .doWrite(dataList);
}

高级应用

1. 根据数据库字段动态生成表头

复制代码
@Service
public class DynamicExportService {
    
    @Autowired
    private FieldConfigService fieldConfigService;
    
    public void exportUserData(List<String> selectedFields) {
        String fileName = "user_export_" + System.currentTimeMillis() + ".xlsx";
        
        // 根据选中字段构建表头
        List<List<String>> head = buildDynamicHeader(selectedFields);
        
        // 获取数据
        List<List<Object>> dataList = buildDataList(selectedFields);
        
        EasyExcel.write(fileName)
                .head(head)
                .sheet("用户数据")
                .doWrite(dataList);
    }
    
    private List<List<String>> buildDynamicHeader(List<String> selectedFields) {
        List<List<String>> head = new ArrayList<>();
        
        for (String fieldCode : selectedFields) {
            FieldConfig config = fieldConfigService.getByCode(fieldCode);
            if (config != null) {
                // 支持分组表头
                if (config.getGroupName() != null) {
                    head.add(Arrays.asList(config.getGroupName(), config.getFieldName()));
                } else {
                    head.add(Arrays.asList(config.getFieldName()));
                }
            }
        }
        
        return head;
    }
    
    private List<List<Object>> buildDataList(List<String> selectedFields) {
        List<User> users = userService.getAllUsers();
        List<List<Object>> dataList = new ArrayList<>();
        
        for (User user : users) {
            List<Object> row = new ArrayList<>();
            for (String fieldCode : selectedFields) {
                Object value = getFieldValue(user, fieldCode);
                row.add(value);
            }
            dataList.add(row);
        }
        
        return dataList;
    }
    
    private Object getFieldValue(User user, String fieldCode) {
        // 使用反射或Map方式获取字段值
        switch (fieldCode) {
            case "name": return user.getName();
            case "age": return user.getAge();
            case "email": return user.getEmail();
            case "department": return user.getDepartment();
            case "createTime": return user.getCreateTime();
            default: return "";
        }
    }
}
​
// 字段配置实体
@Data
public class FieldConfig {
    private String fieldCode;
    private String fieldName;
    private String groupName;
    private Integer sortOrder;
}

2. 配置化动态表头

复制代码
@Component
public class ConfigurableDynamicHeader {
    
    public void exportWithConfig(ExportConfig config) {
        String fileName = config.getFileName() + ".xlsx";
        
        // 根据配置构建表头
        List<List<String>> head = buildHeaderFromConfig(config);
        
        // 根据配置获取数据
        List<List<Object>> dataList = buildDataFromConfig(config);
        
        // 应用样式
        HorizontalCellStyleStrategy styleStrategy = buildStyleFromConfig(config);
        
        EasyExcel.write(fileName)
                .head(head)
                .registerWriteHandler(styleStrategy)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                .sheet(config.getSheetName())
                .doWrite(dataList);
    }
    
    private List<List<String>> buildHeaderFromConfig(ExportConfig config) {
        List<List<String>> head = new ArrayList<>();
        
        for (HeaderConfig headerConfig : config.getHeaders()) {
            List<String> headerPath = new ArrayList<>();
            
            // 支持多级表头
            if (headerConfig.getLevel1() != null) {
                headerPath.add(headerConfig.getLevel1());
            }
            if (headerConfig.getLevel2() != null) {
                headerPath.add(headerConfig.getLevel2());
            }
            if (headerConfig.getLevel3() != null) {
                headerPath.add(headerConfig.getLevel3());
            }
            
            head.add(headerPath);
        }
        
        return head;
    }
}
​
// 导出配置类
@Data
public class ExportConfig {
    private String fileName;
    private String sheetName;
    private List<HeaderConfig> headers;
    private StyleConfig styleConfig;
}
​
@Data
public class HeaderConfig {
    private String fieldCode;
    private String level1;
    private String level2;
    private String level3;
    private Integer width;
}

3. 动态表头与数据验证结合

复制代码
public class DynamicHeaderWithValidation {
    
    public void writeWithValidation() {
        String fileName = "validated_dynamic.xlsx";
        
        // 构建表头
        List<List<String>> head = Arrays.asList(
            Arrays.asList("姓名"),
            Arrays.asList("年龄"),
            Arrays.asList("邮箱"),
            Arrays.asList("手机号")
        );
        
        // 准备数据
        List<List<Object>> dataList = new ArrayList<>();
        dataList.add(Arrays.asList("张三", 25, "zhangsan@example.com", "13800138000"));
        dataList.add(Arrays.asList("李四", 30, "lisi@example.com", "13900139000"));
        
        // 创建下拉选择处理器
        DropDownWriteHandler dropDownHandler = new DropDownWriteHandler();
        
        EasyExcel.write(fileName)
                .head(head)
                .registerWriteHandler(dropDownHandler)
                .sheet("带验证的数据")
                .doWrite(dataList);
    }
}
​
// 自定义下拉选择处理器
public class DropDownWriteHandler implements SheetWriteHandler {
    
    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, 
                                WriteSheetHolder writeSheetHolder) {
        
        Sheet sheet = writeSheetHolder.getSheet();
        Workbook workbook = writeWorkbookHolder.getWorkbook();
        
        // 创建数据验证规则
        DataValidationHelper validationHelper = sheet.getDataValidationHelper();
        
        // 为年龄列添加数字验证(假设年龄在B列)
        CellRangeAddressList ageRange = new CellRangeAddressList(1, 1000, 1, 1);
        DataValidationConstraint ageConstraint = validationHelper
            .createIntegerConstraint(DataValidationConstraint.OperatorType.BETWEEN, "0", "150");
        DataValidation ageValidation = validationHelper.createValidation(ageConstraint, ageRange);
        ageValidation.setErrorStyle(DataValidation.ErrorStyle.STOP);
        ageValidation.createErrorBox("年龄错误", "年龄必须在0-150之间");
        sheet.addValidationData(ageValidation);
    }
}

实际应用场景

1. 报表系统中的可配置导出

复制代码
@RestController
@RequestMapping("/api/export")
public class ExportController {
    
    @Autowired
    private DynamicExportService exportService;
    
    @PostMapping("/users")
    public ResponseEntity<String> exportUsers(@RequestBody ExportRequest request) {
        try {
            String fileName = exportService.exportUserData(
                request.getSelectedFields(),
                request.getStartDate(),
                request.getEndDate()
            );
            
            return ResponseEntity.ok(fileName);
        } catch (Exception e) {
            return ResponseEntity.badRequest().body("导出失败:" + e.getMessage());
        }
    }
}
​
@Data
public class ExportRequest {
    private List<String> selectedFields;
    private LocalDate startDate;
    private LocalDate endDate;
    private String groupBy;
}

2. 动态报表生成

复制代码
public class DynamicReportGenerator {
    
    public void generateReport(ReportTemplate template, Map<String, Object> params) {
        String fileName = template.getName() + "_" + 
                         LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")) + 
                         ".xlsx";
        
        // 根据模板构建表头
        List<List<String>> head = buildHeaderFromTemplate(template);
        
        // 根据参数查询数据
        List<List<Object>> dataList = queryDataByTemplate(template, params);
        
        // 应用模板样式
        List<WriteHandler> handlers = buildWriteHandlers(template);
        
        ExcelWriterBuilder builder = EasyExcel.write(fileName).head(head);
        
        // 注册所有处理器
        for (WriteHandler handler : handlers) {
            builder.registerWriteHandler(handler);
        }
        
        builder.sheet(template.getSheetName()).doWrite(dataList);
    }
}

最佳实践

1. 性能优化

复制代码
public class OptimizedDynamicExport {
    
    public void exportLargeData(List<String> fields) {
        String fileName = "large_data_export.xlsx";
        
        // 构建表头
        List<List<String>> head = buildHeader(fields);
        
        // 使用ExcelWriter进行流式写入
        try (ExcelWriter excelWriter = EasyExcel.write(fileName).head(head).build()) {
            WriteSheet writeSheet = EasyExcel.writerSheet("数据").build();
            
            // 分批处理大量数据
            int pageSize = 10000;
            int pageNum = 1;
            List<List<Object>> batchData;
            
            do {
                batchData = queryDataByPage(fields, pageNum, pageSize);
                if (!batchData.isEmpty()) {
                    excelWriter.write(batchData, writeSheet);
                }
                pageNum++;
            } while (batchData.size() == pageSize);
        }
    }
}

2. 错误处理

复制代码
public class SafeDynamicExport {
    
    public void safeExport(List<String> fields) {
        try {
            validateFields(fields);
            
            List<List<String>> head = buildHeader(fields);
            List<List<Object>> dataList = buildData(fields);
            
            EasyExcel.write("safe_export.xlsx")
                    .head(head)
                    .sheet("数据")
                    .doWrite(dataList);
                    
        } catch (IllegalArgumentException e) {
            log.error("字段验证失败:{}", e.getMessage());
            throw new BusinessException("导出字段配置错误");
        } catch (Exception e) {
            log.error("导出过程中发生错误:", e);
            throw new BusinessException("数据导出失败");
        }
    }
    
    private void validateFields(List<String> fields) {
        if (fields == null || fields.isEmpty()) {
            throw new IllegalArgumentException("导出字段不能为空");
        }
        
        List<String> validFields = getValidFields();
        for (String field : fields) {
            if (!validFields.contains(field)) {
                throw new IllegalArgumentException("无效的字段:" + field);
            }
        }
    }
}

总结

EasyExcel的动态表头功能非常强大,主要优势包括:

  1. 灵活性强:可根据运行时条件动态生成表头

  2. 支持多级:能够创建复杂的多级表头结构

  3. 易于扩展:可与其他功能(样式、验证等)结合使用

  4. 性能优良:即使处理大量数据也能保持良好性能

在实际使用中,建议:

  • 合理设计表头结构,避免过于复杂

  • 注意数据类型匹配,确保数据与表头对应

  • 对用户输入进行验证,防止恶意或错误的字段配置

  • 在处理大量数据时使用分批处理机制

通过动态表头功能,可以轻松实现灵活的数据导出需求,大大提升系统的可配置性和用户体验。

相关推荐
敲代码的哈吉蜂1 小时前
Nginx配置文件的管理及优化参数
java·服务器·nginx
XiaoLeisj1 小时前
Android RecyclerView 实战:从基础列表到多类型 Item、分割线与状态复用问题
android·java
青山是哪个青山3 小时前
Linux 基础与环境搭建
linux·服务器·网络
勇往直前plus4 小时前
从文件到屏幕:Python/java 字符编码、解码、文本处理的底层逻辑解析
java·开发语言·python
_OP_CHEN4 小时前
【Linux系统编程】(三十九)吃透线程概念:从底层原理到实战应用
linux·运维·操作系统·线程·进程·多线程·c/c++
_OP_CHEN4 小时前
【MySQL数据库基础】(一)保姆级 MySQL 环境配置教程!CentOS 7+Ubuntu 双系统全覆盖
linux·数据库·sql·mysql·ubuntu·centos·环境配置
Codefengfeng4 小时前
linux系统安装软件教程
linux·运维·服务器
Drifter_yh10 小时前
【黑马点评】Redisson 分布式锁核心原理剖析
java·数据库·redis·分布式·spring·缓存
袁袁袁袁满10 小时前
Linux云服务器如何判断系统是否发生过异常断电?
linux·运维·服务器