什么是动态表头?
动态表头是指在运行时根据业务需求动态生成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的动态表头功能非常强大,主要优势包括:
-
灵活性强:可根据运行时条件动态生成表头
-
支持多级:能够创建复杂的多级表头结构
-
易于扩展:可与其他功能(样式、验证等)结合使用
-
性能优良:即使处理大量数据也能保持良好性能
在实际使用中,建议:
-
合理设计表头结构,避免过于复杂
-
注意数据类型匹配,确保数据与表头对应
-
对用户输入进行验证,防止恶意或错误的字段配置
-
在处理大量数据时使用分批处理机制
通过动态表头功能,可以轻松实现灵活的数据导出需求,大大提升系统的可配置性和用户体验。