excel动态表头

博客介绍了EasyExcel支持动态表头的功能,可在代码里自定义表头,也能根据前端传入字段作为表头,只需将其封装到List<List> headTitles = Lists.newArrayList()中,还提到了EasyExcel文档及封装成Util。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

可以在代码里面自定义表头 也可以根据前端传入字段当表头 只需要封装到List<List> headTitles = Lists.newArrayList();即可

easyExcel支持动态表头 官网文档如下

easyExcel文档

    /**
     * 根据参数只导出指定列
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>
     * 2. 根据自己或者排除自己需要的列
     * <p>
     * 3. 直接写即可
     *
     * @since 2.1.1
     */
    @Test
    public void excludeOrIncludeWrite() {
        String fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里需要注意 在使用ExcelProperty注解的使用,如果想不空列则需要加入order字段,而不是index,order会忽略空列,然后继续往后,而index,不会忽略空列,在第几列就是第几列。

        // 根据用户传入字段 假设我们要忽略 date
        Set<String> excludeColumnFiledNames = new HashSet<String>();
        excludeColumnFiledNames.add("date");
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        EasyExcel.write(fileName, DemoData.class).excludeColumnFiledNames(excludeColumnFiledNames).sheet("模板")
            .doWrite(data());

        fileName = TestFileUtil.getPath() + "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";
        // 根据用户传入字段 假设我们只要导出 date
        Set<String> includeColumnFiledNames = new HashSet<String>();
        includeColumnFiledNames.add("date");
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        EasyExcel.write(fileName, DemoData.class).includeColumnFiledNames(includeColumnFiledNames).sheet("模板")
            .doWrite(data());
    }

封装成Util

package com.sangfor.utils;


import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.support.ExcelTypeEnum;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.*;

/**
 * 对excel操作的工具类
 * 可以根据自己用的类去生成
 *
 * @Author chenzhixiang
 * @create 2021-09-12 00:23
 */
@Slf4j
public class EasyExcelUtil {

    /**
     * 读取excel
     *
     * @param file  导入的文件流
     * @param model 生成的类
     * @param <T>
     * @return 对象数组
     */
    public static <T> List<T> readExcel(InputStream file, Class<T> model) {
        List<T> list = new ArrayList<>();
        EasyExcel
                //读取的文件
                .read(file)
                //反射获取类型
                .head(model)
                //excel类型
                .excelType(ExcelTypeEnum.XLSX)
                //读取的excel左下角的名字
                .sheet(0)
                //从第二行开始读
                .headRowNumber(2)
                //注册监听器
                .registerReadListener(new AnalysisEventListener<T>() {
                    @Override
                    public void invoke(T t, AnalysisContext analysisContext) {
                        list.add(t);
                    }

                    @Override
                    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
                        System.out.println("读取完毕" + model);
                    }
                }).doRead();
        return list;
    }

    /**
     * 响应给浏览器的excel文件
     *
     * @param response servlet响应对象
     * @param fileName 设置文件明
     * @param list     数据列表
     * @param clazz    响应类
     * @param <T>
     * @throws IOException
     */
    public static <T> void writerExcel(HttpServletResponse response, String fileName, List<T> list, Class<T> clazz) {
        try {
//            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("UTF-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyExcel没有关系
            String encode = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encode + ".xlsx");
            EasyExcel.write(response.getOutputStream(), clazz)
                    //设置不自动关闭流
                    .autoCloseStream(Boolean.FALSE)
//                    .registerWriteHandler(new ExcelSheetWriteHandler())
                    .sheet(fileName)
                    .doWrite(list);
        } catch (Exception e) {
            //重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = new HashMap<String, String>();
            map.put("status", "failure");
            map.put("message", "下载文件失败:" + e.getMessage());
            System.out.println(e);
            log.error("文件下载失败");
        }
    }

 /**
     * 自定义导出
     *
     * @param response servlet响应对象
     * @param fileName 设置文件明
     * @param list     数据列表
     * @param clazz    响应类
     * @param <T>
     * @throws IOException
     */
    public static <T> void customizeWriterExcel(HttpServletResponse response, String fileName, List<T> list, Class<T> clazz, Set<String> includeColumnFiledNames) {
        try {
//            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("UTF-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyExcel没有关系
            String encode = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encode + ".xlsx");
            EasyExcel.write(response.getOutputStream(), clazz)
                    //设置不自动关闭流
                    .autoCloseStream(Boolean.FALSE)
                    .includeColumnFiledNames(includeColumnFiledNames)
//                    .registerWriteHandler(new ExcelSheetWriteHandler())
                    .sheet(fileName)
                    .doWrite(list);
        } catch (Exception e) {
            //重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            Map<String, String> map = new HashMap<String, String>();
            map.put("status", "failure");
            map.put("message", "下载文件失败:" + e.getMessage());
            System.out.println(e);
            log.error("文件下载失败");
        }
    }


    /**
     * 响应给浏览器的excel文件
     *
     * @param response servlet响应对象
     * @param fileName 设置文件明
     * @param list     数据列表
     * @param <T>
     * @throws IOException
     */
    public static <T> void writerExcelNew(HttpServletResponse response, String fileName, List<T> list) {
        try {
            response.setCharacterEncoding("UTF-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyExcel没有关系
            String encode = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + encode + ".xlsx");
            EasyExcel.write(response.getOutputStream(), list.get(0).getClass())
                    //设置不自动关闭流
                    .autoCloseStream(Boolean.FALSE)
                    .sheet(fileName)
                    .doWrite(list);

        } catch (Exception e) {
            //重置response
            response.reset();
            response.setContentType("application/json");
            response.setCharacterEncoding("utf-8");
            HashMap<String, String> map = new HashMap<String, String>();
            map.put("status", "failure");
            map.put("message", "下载文件失败:" + e.getMessage());
            log.error("文件下载失败");
        }
    }


    public static void downloadExcel(HttpServletResponse response, InputStream input, String fileName) {
        try {
            OutputStream out = response.getOutputStream();
//             设置要下载的文件的名称
            response.setHeader("Content-disposition", "attachment;fileName=" + fileName);
//             通知文件的MIME类型
            response.setContentType("application/vnd.ms-excel;charset=UTF-8");
            byte[] b = new byte[2048];
            int len;
            while ((len = input.read(b)) != -1) {
                out.write(b, 0, len);
            }
            input.close();

        } catch (Exception e) {
            log.error("文件下载失败");
        }
    }
}


    @ApiOperation(value = "demo")
    @PostMapping("/demo")
    public void excel(HttpServletResponse response) throws IOException {
        // 文件输出位置
        //从response拿out 则是默认下载
        OutputStream out = response.getOutputStream();
        //固定位置下载路径
//        OutputStream out = new FileOutputStream("d://test.xlsx");
        //设置返回格式
        response.addHeader("Content-Disposition", "attachment;filename=fileName" + ".xlsx");
        ExcelWriter writer = EasyExcelFactory.write(out).build();

        // 动态添加表头,适用一些表头动态变化的场景
        WriteSheet sheet1 = new WriteSheet();
        sheet1.setSheetName("商品明细");
        sheet1.setSheetNo(0);
        // 创建一个表格,用于 Sheet 中使用
        WriteTable   table = new WriteTable( );
        table.setTableNo(1);

        //表头数据
        List<List<String>> headTitles = Lists.newArrayList();
        //第一列,1/2/3行
        headTitles.add(Lists.newArrayList( "交通"));
        headTitles.add(Lists.newArrayList( "数量"));
        headTitles.add(Lists.newArrayList( "商品"));
        headTitles.add(Lists.newArrayList( "数字"));
        headTitles.add(Lists.newArrayList( "等级"));
        headTitles.add(Lists.newArrayList( "程度"));

        //造数据
        List<List<Object>> contentList = Lists.newArrayList();
        contentList.add(Lists.newArrayList("骑车", "10", "苹果🍎", "25", "2级", "40%"));

        table.setHead(headTitles);
        // 写数据
        writer.write(contentList, sheet1, table);
        writer.finish();
        out.close();
    }

<think>我们之前讨论过在Java注解中实现动态表头是不直接支持的,但可以通过其他方式实现动态效果。结合Spring Boot导出Excel动态表头的需求,我们可以采用以下方案: 方案选择:使用自定义注解结合运行时解析,但更常见的做法是使用配置类或数据库配置来定义表头,然后在导出时动态生成。 这里我们采用一种更灵活的方式:通过自定义注解标记需要导出的字段,同时允许从外部(如数据库、配置中心)动态获取表头名称。 步骤: 1. 定义动态表头注解,允许使用占位符。 2. 在实体类字段上使用该注解。 3. 在导出时,通过反射读取注解,并解析占位符为实际值。 但是,考虑到表头的动态性(比如不同用户、不同场景下表头不同),我们更推荐使用配置中心或数据库存储表头信息,而不是固定在注解中。 因此,这里提供两种实现方式: 方式一:使用配置类定义表头(无需注解) 即前面的方案,通过传递HeaderConfig列表来定义表头。 方式二:使用注解+动态解析(适用于表头基本固定但需要国际化等场景) 下面重点介绍方式二(注解+动态解析): 1. 定义注解 ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface ExcelExport { /** * 表头名称(支持占位符,如${excel.header.name}) */ String header() default ""; /** * 顺序,用于控制导出的列顺序 */ int order() default 0; /** * 列宽(字符数) */ int width() default 15; } ``` 2. 在实体类中使用注解 ```java @Data public class User { @ExcelExport(header = "${excel.header.name}", order = 1, width = 20) private String name; @ExcelExport(header = "${excel.header.age}", order = 2) private Integer age; @ExcelExport(header = "${excel.header.email}", order = 3, width = 30) private String email; } ``` 3. 创建导出工具类(支持动态解析表头) ```java import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.context.MessageSource; import org.springframework.context.i18n.LocaleContextHolder; 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; public class DynamicAnnotationExcelExporter { private final MessageSource messageSource; public DynamicAnnotationExcelExporter(MessageSource messageSource) { this.messageSource = messageSource; } public <T> void export(HttpServletResponse response, String fileName, List<T> data) throws IOException, IllegalAccessException { if (data == null || data.isEmpty()) { throw new RuntimeException("导出数据为空"); } // 获取第一个对象的类类型 Class<?> clazz = data.get(0).getClass(); // 获取所有带注解的字段,并按order排序 List<Field> fields = Arrays.stream(clazz.getDeclaredFields()) .filter(f -> f.isAnnotationPresent(ExcelExport.class)) .sorted(Comparator.comparingInt(f -> f.getAnnotation(ExcelExport.class).order())) .collect(Collectors.toList()); // 创建工作簿 Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet("Sheet1"); // 创建表头行 Row headerRow = sheet.createRow(0); CellStyle headerStyle = createHeaderStyle(workbook); // 设置表头 for (int i = 0; i < fields.size(); i++) { Field field = fields.get(i); ExcelExport annotation = field.getAnnotation(ExcelExport.class); String header = annotation.header(); // 解析占位符 String headerDisplay = resolvePlaceholder(header); Cell cell = headerRow.createCell(i); cell.setCellValue(headerDisplay); cell.setCellStyle(headerStyle); // 设置列宽 sheet.setColumnWidth(i, annotation.width() * 256); } // 填充数据 for (int rowIdx = 0; rowIdx < data.size(); rowIdx++) { T item = data.get(rowIdx); Row row = sheet.createRow(rowIdx + 1); for (int colIdx = 0; colIdx < fields.size(); colIdx++) { Field field = fields.get(colIdx); field.setAccessible(true); Object value = field.get(item); Cell cell = row.createCell(colIdx); if (value != null) { cell.setCellValue(value.toString()); } else { cell.setCellValue(""); } } } // 设置响应 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx"); workbook.write(response.getOutputStream()); workbook.close(); } private String resolvePlaceholder(String placeholder) { if (placeholder.startsWith("${") && placeholder.endsWith("}")) { String key = placeholder.substring(2, placeholder.length() - 1); // 使用MessageSource获取国际化消息(也可以从其他地方获取) return messageSource.getMessage(key, null, LocaleContextHolder.getLocale()); } return placeholder; } private CellStyle createHeaderStyle(Workbook workbook) { CellStyle style = workbook.createCellStyle(); Font font = workbook.createFont(); font.setBold(true); style.setFont(font); style.setAlignment(HorizontalAlignment.CENTER); style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); style.setFillPattern(FillPatternType.SOLID_FOREGROUND); return style; } } ``` 4. 在Spring Boot中配置MessageSource(用于国际化) 在application.properties中: ```properties spring.messages.basename=messages/excel_messages ``` 创建messages/excel_messages.properties: ```properties excel.header.name=姓名 excel.header.age=年龄 excel.header.email=电子邮箱 ``` 5. 在控制器中使用 ```java @RestController @RequestMapping("/export") public class ExcelController { @Autowired private MessageSource messageSource; @GetMapping("/users") public void exportUsers(HttpServletResponse response) throws IOException, IllegalAccessException { List<User> users = userService.findAll(); DynamicAnnotationExcelExporter exporter = new DynamicAnnotationExcelExporter(messageSource); exporter.export(response, "用户列表", users); } } ``` 说明: - 通过注解定义表头,支持占位符,占位符的值通过MessageSource解析(可实现国际化)。 - 表头顺序和列宽通过注解配置。 - 导出时通过反射读取注解和字段值。 优点: 1. 实体类上直接配置表头信息,清晰直观。 2. 支持国际化。 缺点: 1. 表头变化需要修改实体类注解并重新部署(如果表头变化非常频繁,建议使用数据库配置的方式)。 2. 反射有一定性能开销。 对于需要完全动态表头的场景(如用户自定义列),还是推荐使用配置类(HeaderConfig)的方式,因为表头信息可以存储在数据库中,动态读取。 因此,根据业务需求选择合适的方式: - 固定表头+国际化:注解方式 - 完全动态表头(用户自定义):配置类方式 下面补充一个使用配置类方式(完全动态)的简化版: 配置类方式(完全动态): 1. 表头信息存储在数据库,如: id | report_type | field_name | display_name | width | order 2. 根据report_type查询表头配置 3. 导出时传递report_type,获取表头配置,然后导出数据 代码示例(控制器中): ```java @GetMapping("/dynamic") public void exportDynamic(HttpServletResponse response, String reportType) { // 从数据库获取表头配置 List<HeaderConfig> headers = headerConfigService.getByReportType(reportType); // 获取数据 List<Map<String, Object>> data = dataService.getDataByReportType(reportType); // 使用之前的DynamicExcelExporter(基于配置类的导出工具) excelExporter.export(response, reportType, headers, data); } ``` 注意:数据可以是List<Map>,这样字段名和HeaderConfig中的field_name对应即可。 总结: 两种方式各有适用场景,根据需求选择。 --- §§相关问题§§: 1. 在Spring Boot中如何实现Excel导出的异步处理? 2. 如何导出百万级数据到Excel并避免内存溢出? 3. 使用EasyExcel如何实现动态表头? 4. 如何实现Excel导出时的数据权限控制(不同用户看到不同列)?</think># Spring Boot 使用自定义 `@Excel` 注解实现动态表头 在 Spring Boot 中实现基于 `@Excel` 注解的动态表头导出,需要结合自定义注解、反射和动态配置处理。以下是完整的解决方案: ## 解决方案设计 ### 1. 定义自定义注解 `@Excel` ```java import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Excel { /** * 表头名称(支持占位符) */ String header() default ""; /** * 表头名称的配置键(用于动态获取) */ String headerKey() default ""; /** * 列顺序(数值越小越靠前) */ int order() default 0; /** * 列宽(字符数) */ int width() default 15; /** * 日期格式(用于日期类型字段) */ String dateFormat() default "yyyy-MM-dd"; /** * 是否导出该字段 */ boolean export() default true; } ``` ### 2. 创建动态表头解析器 ```java import org.springframework.context.ApplicationContext; import org.springframework.context.expression.BeanFactoryResolver; import org.springframework.expression.Expression; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; @Component public class HeaderResolver { private final ExpressionParser parser = new SpelExpressionParser(); private final ApplicationContext applicationContext; public HeaderResolver(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } public String resolveHeader(Excel annotation, Object contextObj) { // 优先使用headerKey配置 if (!annotation.headerKey().isEmpty()) { return resolveFromKey(annotation.headerKey(), contextObj); } // 使用header配置(支持SpEL表达式) return resolveExpression(annotation.header(), contextObj); } private String resolveFromKey(String key, Object contextObj) { // 实际应用中可替换为数据库查询、配置中心获取等 Map<String, String> headerMappings = new HashMap<>(); headerMappings.put("user.name", "姓名"); headerMappings.put("user.age", "年龄"); headerMappings.put("user.email", "电子邮箱"); headerMappings.put("user.department", "部门"); // 支持动态值(如从用户会话获取) if (contextObj != null) { headerMappings.put("dynamic.header", "动态表头-" + System.currentTimeMillis()); } return headerMappings.getOrDefault(key, key); } private String resolveExpression(String expression, Object contextObj) { if (expression == null || expression.isEmpty()) { return ""; } StandardEvaluationContext context = new StandardEvaluationContext(); context.setBeanResolver(new BeanFactoryResolver(applicationContext)); if (contextObj != null) { context.setRootObject(contextObj); } Expression exp = parser.parseExpression(expression); return exp.getValue(context, String.class); } } ``` ### 3. 实现基于注解的 Excel 导出工具 ```java import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Field; import java.net.URLEncoder; import java.text.SimpleDateFormat; import java.util.*; import java.util.stream.Collectors; @Component public class ExcelExporter { @Autowired private HeaderResolver headerResolver; public <T> void export(HttpServletResponse response, String fileName, List<T> data) throws IOException, IllegalAccessException { if (data == null || data.isEmpty()) { throw new IllegalArgumentException("导出数据不能为空"); } Class<?> clazz = data.get(0).getClass(); List<Field> fields = getAnnotatedFields(clazz); // 创建工作簿 Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet("Sheet1"); // 创建表头 createHeaderRow(sheet, fields, data.get(0)); // 填充数据 fillDataRows(sheet, fields, data); // 设置响应 response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx"); workbook.write(response.getOutputStream()); workbook.close(); } private List<Field> getAnnotatedFields(Class<?> clazz) { return Arrays.stream(clazz.getDeclaredFields()) .filter(f -> f.isAnnotationPresent(Excel.class)) .filter(f -> f.getAnnotation(Excel.class).export()) .sorted(Comparator.comparingInt(f -> f.getAnnotation(Excel.class).order())) .collect(Collectors.toList()); } private void createHeaderRow(Sheet sheet, List<Field> fields, Object contextObj) { Row headerRow = sheet.createRow(0); CellStyle headerStyle = createHeaderStyle(sheet.getWorkbook()); for (int i = 0; i < fields.size(); i++) { Field field = fields.get(i); Excel annotation = field.getAnnotation(Excel.class); // 动态解析表头名称 String headerName = headerResolver.resolveHeader(annotation, contextObj); Cell cell = headerRow.createCell(i); cell.setCellValue(headerName); cell.setCellStyle(headerStyle); // 设置列宽 sheet.setColumnWidth(i, annotation.width() * 256); } } private void fillDataRows(Sheet sheet, List<Field> fields, List<?> data) throws IllegalAccessException { for (int rowIndex = 0; rowIndex < data.size(); rowIndex++) { Row row = sheet.createRow(rowIndex + 1); Object item = data.get(rowIndex); for (int colIndex = 0; colIndex < fields.size(); colIndex++) { Field field = fields.get(colIndex); field.setAccessible(true); Object value = field.get(item); Cell cell = row.createCell(colIndex); setCellValue(cell, value, field.getAnnotation(Excel.class)); } } } private void setCellValue(Cell cell, Object value, Excel annotation) { if (value == null) { cell.setCellValue(""); return; } if (value instanceof Date) { SimpleDateFormat sdf = new SimpleDateFormat(annotation.dateFormat()); cell.setCellValue(sdf.format((Date) value)); } else if (value instanceof Number) { cell.setCellValue(((Number) value).doubleValue()); } else if (value instanceof Boolean) { cell.setCellValue((Boolean) value ? "是" : "否"); } else { cell.setCellValue(value.toString()); } } private CellStyle createHeaderStyle(Workbook workbook) { CellStyle style = workbook.createCellStyle(); Font font = workbook.createFont(); font.setBold(true); style.setFont(font); style.setAlignment(HorizontalAlignment.CENTER); style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); style.setFillPattern(FillPatternType.SOLID_FOREGROUND); style.setBorderBottom(BorderStyle.THIN); style.setBorderTop(BorderStyle.THIN); style.setBorderLeft(BorderStyle.THIN); style.setBorderRight(BorderStyle.THIN); return style; } } ``` ### 4. 在实体类中使用 `@Excel` 注解 ```java public class User { @Excel(headerKey = "user.name", order = 1, width = 20) private String name; @Excel(headerKey = "user.age", order = 2) private Integer age; @Excel(header = "'电子邮箱'", order = 3, width = 30) // 使用固定值 private String email; @Excel(header = "#{T(java.time.LocalDate).now().getYear() + '年部门'}", order = 4, width = 25) // 使用SpEL表达式 private String department; @Excel(headerKey = "dynamic.header", order = 5, width = 20) // 完全动态表头 private String customField; @Excel(header = "注册日期", order = 6, dateFormat = "yyyy年MM月dd日") private Date registerDate; // 构造器、getter、setter省略 } ``` ### 5. 创建控制器 ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletResponse; import java.util.ArrayList; import java.util.Date; import java.util.List; @RestController @RequestMapping("/api/users") public class UserController { @Autowired private ExcelExporter excelExporter; @GetMapping("/export") public void exportUsers(HttpServletResponse response) { List<User> users = new ArrayList<>(); users.add(new User("张三", 30, "zhangsan@example.com", "技术部", "自定义值1", new Date())); users.add(new User("李四", 28, "lisi@example.com", "市场部", "自定义值2", new Date())); users.add(new User("王五", 35, "wangwu@example.com", "财务部", "自定义值3", new Date())); try { excelExporter.export(response, "用户列表", users); } catch (Exception e) { throw new RuntimeException("导出失败", e); } } } ``` ## 动态表头实现原理 1. **注解解析**: - 通过反射获取所有带有 `@Excel` 注解的字段 - 根据 `order` 属性排序字段 2. **动态表头解析**: - **headerKey 方式**:从配置源(数据库/缓存/配置文件)获取实际表头名称 - **header 方式**: - 固定字符串:直接使用 - SpEL 表达式:使用 Spring 表达式语言动态计算表头值 - **上下文对象**:使用当前数据对象作为表达式上下文 3. **动态数据渲染**: - 根据字段类型和注解配置(如日期格式)格式化数据 - 支持各种常见数据类型的转换 4. **扩展性**: - 通过实现 `HeaderResolver` 接口可自定义表头解析逻辑 - 支持从数据库、配置中心、用户会话等获取表头配置 ## 高级功能扩展 ### 1. 多语言支持 ```java @Component public class I18nHeaderResolver implements HeaderResolver { @Autowired private MessageSource messageSource; @Override public String resolveHeader(Excel annotation, Object contextObj) { Locale locale = LocaleContextHolder.getLocale(); if (!annotation.headerKey().isEmpty()) { return messageSource.getMessage( annotation.headerKey(), null, annotation.headerKey(), // 默认值 locale ); } // 其他解析逻辑... } } ``` ### 2. 基于用户角色的动态表头 ```java public class RoleBasedHeaderResolver implements HeaderResolver { @Autowired private HeaderConfigService configService; @Override public String resolveHeader(Excel annotation, Object contextObj) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String role = authentication.getAuthorities().iterator().next().getAuthority(); return configService.getHeaderForRole( annotation.headerKey(), role ); } } ``` ### 3. 数据库驱动配置 ```java @Service public class DatabaseHeaderResolver implements HeaderResolver { @Autowired private HeaderConfigRepository repository; @Override public String resolveHeader(Excel annotation, Object contextObj) { if (!annotation.headerKey().isEmpty()) { return repository.findByKeyAndTenant( annotation.headerKey(), getCurrentTenant() ).orElse(annotation.header()); } return annotation.header(); } private String getCurrentTenant() { // 获取当前租户信息 } } ``` ## 最佳实践建议 1. **性能优化**: - 缓存频繁使用的表头配置 - 大数据量导出使用 `SXSSFWorkbook` - 避免在循环内进行复杂计算 2. **安全考虑**: - 验证用户对导出数据的权限 - 对动态表达式进行沙箱限制 - 防止反射攻击 3. **错误处理**: - 添加详细的日志记录 - 提供友好的错误提示 - 支持导出任务状态查询 4. **扩展性设计**: - 使用策略模式支持多种表头解析方式 - 支持自定义数据格式化器 - 提供导出事件监听器 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值