導入的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");}