动态导出 Excel 表格 Demo

总结

本文介绍使用 Java 和 EasyExcel 实现动态导出 Excel 表格的需求。包括核心思路、拆解步骤、Demo 测试。

详情

核心思路

核心思想是在写入 Excel 之前,动态地构建表头数据结构,然后将数据转换成与表头顺序一致的列表集合。EasyExcel 提供了灵活的 API 来支持这种操作,不需要依赖固定的 Java 类注解 (@ExcelProperty)。

拆解步骤

  1. 定义实体:用来封装所有可能导出的字段。例如,一个 StudentScore 类,包含姓名、考号、分数、合格状态、性别、评价等所有可能的字段。
  2. 动态生成表头 (Header):
    • 表头的本质是 List<List<String>> 结构。可以根据传参决定需要哪些表头,并动态地创建这个列表。
      • 外层的 List 表示“多行表头”;
      • 内层的 List<String> 表示“一行中的多个列标题”。
    • 例如,一个函数可以接收一个字段列表 ["姓名", "分数"],然后将其转换为 EasyExcel 需要的 List<List<String>> 格式,即 [["姓名"], ["分数"]]
  3. 准备动态数据 (Data):
    • 数据需要是一个 List<List<Object>> 结构。
      • 外层 List 的每个元素代表 Excel 中的一行数据。
      • 内层 List 的每个元素则对应这一行中各个单元格的数据。
    • 关键:内层 List 中数据的顺序必须与动态生成的表头顺序严格一致。
      • 我们需要编写一个转换逻辑,遍历原始的数据对象列表(如 List<StudentScore>),根据动态表头列表的字段顺序,从每个 StudentScore 对象中提取对应的值,并按顺序组装成 List<Object>
  4. 使用 EasyExcel 写入:
    • 调用 EasyExcel.write() 方法。
    • 使用 .head() 方法传入我们动态生成的表头 List<List<String>>
    • 使用 .sheet() 方法指定工作表名称。
    • 使用 .doWrite() 方法传入我们准备好的数据 List<List<Object>>

通过以上步骤,可以实现动态导出 Excel 表格的需求。

实践

Demo 测试

  • 添加必要的 Maven 依赖

  • 定义实体类

    package com.example.model;
    
    import lombok.Builder;
    import lombok.Data;
    
    @Data
    @Builder
    public class StudentScore {
        private String name;       // 姓名
        private String code;       // 考号
        private String score;      // 分数
    }
  • 导出服务 DynamicExcelExportService.java

    package com.example.service;
    
    import com.alibaba.excel.EasyExcel;
    import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
    import com.example.model.StudentScore;
    
    import java.io.OutputStream;
    import java.util.ArrayList;
    import java.util.LinkedHashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    public class DynamicExcelExportService {
    
        /**
         * 动态导出Excel
         *
         * @param outputStream      输出流,可以是 FileOutputStream 或 HttpServletResponse.getOutputStream()
         * @param requiredHeaders   需要导出的表头字段名列表,顺序即为导出顺序
         * @param allData           所有学生的数据
         */
        public void export(OutputStream outputStream, List<String> requiredHeaders, List<StudentScore> allData) {
            if (requiredHeaders == null || requiredHeaders.isEmpty()) {
                throw new IllegalArgumentException("表头不能为空");
            }
            // 1. 动态生成表头
            List<List<String>> excelHead = requiredHeaders.stream()
                    .map(headerName -> {
                        List<String> headColumn = new ArrayList<>();
                        headColumn.add(headerName);
                        return headColumn;
                    })
                    .collect(Collectors.toList());
    
            // 2. 准备动态数据
            List<List<Object>> excelData = new ArrayList<>();
            if (allData != null && !allData.isEmpty()) {
                // 使用 LinkedHashMap 来保证字段顺序与传入的 header 一致
                Map<String, String> headerFieldMap = getHeaderFieldMapping();
                //外层循环:每个元素代表 Excel 中的一行数据。
                for (StudentScore student : allData) {
                    List<Object> dataRow = new ArrayList<>();
                    //内层循环:每个元素对应一行中各个单元格的数据。
                    for (String headerName : requiredHeaders) {
                        // 根据表头名找到对应的字段名,然后从对象中取值
                        String fieldName = headerFieldMap.get(headerName);
                        dataRow.add(getFieldValue(student, fieldName));
                    }
                    excelData.add(dataRow);
                }
            }
    
            // 3. 使用 EasyExcel 写入
            EasyExcel.write(outputStream)
                    .head(excelHead) // 设置动态表头
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 可选:自适应列宽
                    .sheet("成绩") // 设置工作表名
                    .doWrite(excelData); // 写入数据
        }
    
        /**
         * 定义表头中文名和实体类字段名的映射关系
         * 使用 LinkedHashMap 保证后续遍历时的顺序
         *
         * @return 映射Map
         */
        private Map<String, String> getHeaderFieldMapping() {
            Map<String, String> map = new LinkedHashMap<>();
            map.put("姓名", "name");
            map.put("考号", "code");
            map.put("分数", "score");
            return map;
        }
    
        /**
         * 使用简化的反射(或if-else)根据字段名获取对象的值
         * 注意:在生产环境中,为了性能,可以考虑使用更高效的反射库如 cglib 或 MapStruct
         *
         * @param student   对象
         * @param fieldName 字段名
         * @return 字段值
         */
        private Object getFieldValue(StudentScore student, String fieldName) {
            if (fieldName == null) {
                return "";
            }
            switch (fieldName) {
                case "name":
                    return student.getName();
                case "code":
                    return student.getCode();
                case "score":
                    return student.getScore();
                default:
                    return ""; // or throw an exception
            }
        }
    }
  • 测试入口:模拟了两次不同场景下需要不同表头的导出请求。

      public static void main(String[] args) {
       DynamicExcelExportService exportService = new DynamicExcelExportService();
       List<StudentScore> data = generateMockData();
          // 场景一:需要 '姓名', '分数'  
    System.out.println("场景1导出Excel...");  
    List<String> headers1 = Arrays.asList("姓名", "分数");  
    try (OutputStream os1 = new FileOutputStream("test1.xlsx")) {  
           exportService.export(os1, headers1, data);  
           System.out.println("考生成绩已成功导出到 test1.xlsx");  
       } catch (Exception e) {  
    	e.printStackTrace();  
    }
    
    System.out.println("\n-----------------------------------\n");
    
          // 场景二:需要 '姓名', '考号', '分数'
    System.out.println("场景2开始导出Excel...");  
    List<String> headers2 = Arrays.asList("姓名", "考号", "分数");  
    try (OutputStream os2 = new FileOutputStream("test2.xlsx")) {  
        exportService.export(os2, headers2, data);  
        System.out.println("考生成绩已成功导出到 test2.xlsx");  
    } catch (Exception e) {  
        e.printStackTrace();  
    }
      }
    
      /**
       * 生成模拟数据
       */
      private static List<StudentScore> generateMockData() {
          List<StudentScore> list = new ArrayList<>();
          list.add(StuDataDemo.builder().name("张三").code("0001").score("95.5").build());  
          list.add(StuDataDemo.builder().name("李四").code("0002").score("88.0").build());  
          list.add(StuDataDemo.builder().name("王五").code("0003").score("59.0").build());  
          list.add(StuDataDemo.builder().name("赵六").code("0004").score("76.0").build());  
    return list;
      }

关联文章

  • 错误:lncorrect string value ‘xF0 xA4 x8B xAE’ 排查

文章作者: huan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明来源 huan !
  目录