导入的excel有固定表头+动态表头如何解决
自定义注解:
import java.lang.annotation.*;
/**
* 自定义注解,用于动态生成excel表头
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FieldLabel {
// 字段中文
String label();
// 字段顺序
int order() default 0;
// 分组标识
String group() default "default";
}
导出的类:
import cn.com.fsg.ihro.openacct.support.annotation.FieldLabel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
/**
* 服务费收入项明细excel导出
*
* @author makejava
* @since 2025-07-01 16:41:21
*/
@Data
public class ExcelVO {
@FieldLabel(label = "错误信息", order = 1, group = "error")
private String errMsg;
@FieldLabel(label = "业务日期", order = 11, group = "export")
private String businessDay;
@FieldLabel(label = "雇员姓名", order = 15, group = "export")
private String empName;
@FieldLabel(label = "证件类型", order = 16, group = "export")
private String idType;
@FieldLabel(label = "证件号码", order = 17, group = "export")
private String idNumber;
@Schema(description = "动态字段:服务费收入项-产品方案名称")
private Map<String, BigDecimal> dynamicMap = new HashMap<>();
}
工具里:利用反射+自定义注解+泛型 解析ExcelVO 并导出文件
import cn.com.fsg.ihro.openacct.support.annotation.FieldLabel;
import com.alibaba.excel.EasyExcel;
import org.springframework.util.CollectionUtils;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;
/**
* Excel导出工具类
*/
public class ExcelExportUtil {
/**
* 导出Excel(自动识别固定列 + 动态列)
*
* @param response 响应对象
* @param dataList 数据列表
* @param fileName 文件名
* @param groups 分组
*/
public static <T> void export(HttpServletResponse response, List<T> dataList, String fileName, String... groups) throws IOException {
if (CollectionUtils.isEmpty(dataList)) {
return;
}
// 提取动态表头 map
Set<String> dynamicHeaders = extractDynamicHeaders(dataList.get(0));
// 构建表头:固定表头+动态表头
List<List<String>> head = buildHead(dataList.get(0).getClass(), dynamicHeaders, groups);
// 构建数据
List<Map<Integer, Object>> content = convertToMapList(dataList, groups);
// 设置响应头
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
// 执行导出
EasyExcel.write(response.getOutputStream())
.head(head)
.sheet()
.doWrite(content);
}
/**
* 提取动态列头(假设 VO 中有一个 Map 字段用于存储动态列)
*
* @param vo 数据对象
*/
private static Set<String> extractDynamicHeaders(Object vo) {
// getDeclaredFields获取当前类中声明的所有字段
for (Field field : vo.getClass().getDeclaredFields()) {
// 判断当前字段的类型是否是 Map 类型或其子类
if (Map.class.isAssignableFrom(field.getType())) {
try {
field.setAccessible(true);
@SuppressWarnings("unchecked")
Map<String, Object> dynamicMap = (Map<String, Object>) field.get(vo);
return dynamicMap != null ? dynamicMap.keySet() : Collections.emptySet();
} catch (IllegalAccessException ignored) {
}
}
}
return Collections.emptySet();
}
/**
* 构建表头:固定表头+动态表头(从 VO 的 @FieldLabel 注解提取)
*
* @param excelClass Excel类
* @param dynamicHeaders 动态列
* @param groups 分组
*/
public static List<List<String>> buildHead(Class<?> excelClass, Set<String> dynamicHeaders, String... groups) {
List<List<String>> head = new ArrayList<>();
// 1、获取当前类中声明的所有字段 2、字段有FieldLabel注解 3、通过group过滤 4、通过order排序
List<Field> sortedFields = Arrays.stream(excelClass.getDeclaredFields())
.filter(f -> f.isAnnotationPresent(FieldLabel.class))
.filter(f -> groups.length == 0 || Arrays.asList(groups).contains(f.getAnnotation(FieldLabel.class).group()))
.sorted(Comparator.comparingInt(f -> f.getAnnotation(FieldLabel.class).order()))
.collect(Collectors.toList());
// 构建固定列头
for (Field field : sortedFields) {
FieldLabel annotation = field.getAnnotation(FieldLabel.class);
head.add(Collections.singletonList(annotation.label()));
}
// 添加动态列头
for (String header : dynamicHeaders) {
head.add(Collections.singletonList(header));
}
return head;
}
/**
* 构建数据:将VO 转换为 Map<Integer, Object>
*
* @param dataList 数据
* @param groups 分组
*/
public static <T> List<Map<Integer, Object>> convertToMapList(List<T> dataList, String... groups) {
return dataList.stream().map(vo -> {
Map<Integer, Object> row = new LinkedHashMap<>();
int index = 0;
// 1、获取当前类中声明的所有字段 2、字段有FieldLabel注解 3、通过group过滤 4、通过order排序
List<Field> sortedFields = Arrays.stream(vo.getClass().getDeclaredFields())
.filter(f -> f.isAnnotationPresent(FieldLabel.class))
.filter(f -> groups.length == 0 || Arrays.asList(groups).contains(f.getAnnotation(FieldLabel.class).group()))
.sorted(Comparator.comparingInt(f -> f.getAnnotation(FieldLabel.class).order()))
.collect(Collectors.toList());
// 固定字段
for (Field field : sortedFields) {
field.setAccessible(true);
try {
row.put(index++, field.get(vo));
} catch (IllegalAccessException e) {
throw new RuntimeException("字段读取失败:" + field.getName(), e);
}
}
// 动态字段
for (Object value : extractDynamicMap(vo)) {
row.put(index++, value);
}
return row;
}).collect(Collectors.toList());
}
/**
* 提取 VO 中的动态字段(支持泛型)
*
* @param vo 数据对象
*/
private static List<Object> extractDynamicMap(Object vo) {
// getDeclaredFields获取当前类中声明的所有字段
for (Field field : vo.getClass().getDeclaredFields()) {
// 判断当前字段的类型是否是 Map 类型或其子类
if (Map.class.isAssignableFrom(field.getType())) {
field.setAccessible(true);
try {
@SuppressWarnings("unchecked")
Map<String, Object> map = (Map<String, Object>) field.get(vo);
if (map != null) {
return new ArrayList<>(map.values());
}
} catch (IllegalAccessException ignored) {
}
}
}
return Collections.emptyList();
}
}
用法示例:
@GetMapping("/import-download")
@Operation(summary = "下载excel")
public void importDownload(@Valid SerIncomeDetailExcelQueryReqVO reqVO, HttpServletResponse response) throws IOException {
// Step 1: 调用 service 查询数据,并转换为 ExcelVO 数据
List<ExcelVO> dataList = service.importDownload(reqVO);
// Step 2: 使用 EasyExcel 导出
ExcelExportUtil.export(response, dataList, "服务费收入项明细-金额导入.xlsx", "export", "error");
}