导入的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;}// 提取动态表头 mapSet<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");}