使用excelEasy实现web导出xlsx文件


前言

记录工作过程中用到excel导出的学习过程。


一、引入依赖

	<dependency>
	     <groupId>com.alibaba</groupId>
	     <artifactId>easyexcel</artifactId>
	     <version>3.0.5</version>
	</dependency>

二、使用步骤

首先easyExcel中有两种方式将数据写入excel文件。分别是写excel填充excel

  • 写excel主要是实现数据列表的循环写入,不支持太过复杂的excel表格格式。
  • 填充excel则是弥补了这方面的短板,通过读取你自己指定的模板来填充数据。

用大白话来讲就是,如果你只是需要每列有一个简单的表头,然后将列表数据填入excel,最后导出。可以使用写excel方式。如果你需要有复杂的表头信息,则选择填充excel方式。

1.写excel方式

  1. 创建对应的实体类(要导出的列表中的元素类型)。下面举个简单的例子
@Data
@EqualsAndHashCode
public class StorageIndicationVO {
    @ExcelProperty(value = "钢种", index = 1)
    String steelType;
    @ExcelProperty(value = "规格", index = 2)
    String cargoSpecification;
    @ExcelProperty(value = "数量", index = 3)
    Integer amount;
    @ExcelProperty(value = "重量", index = 4)
    BigDecimal weight;
    @ExcelIgnore
    String consignor;
    @ExcelIgnore
    String variety;
    @ExcelProperty(value = "序号", index = 0)
    Integer index;
}

其中的注解如下:

  • @ExcelProperty表示该属性是excel表格中的一列,value值代表该列的表头名,index值表示该列是excel表格中的第几列(列从0开始)
  • @ExcelIgnore表示该属性不属于excel表格中的一列。即导出数据后,该属性的值不会导出到excel文件中。

对应excel格式为:
在这里插入图片描述
注:如果不需要表头的话,可以不需要设定对应实体类模板,直接导出即可。

  1. 写excel的两种方式:

    1. 一次性写入:(要输入的数据格式是相同的)

      public void exportStorageInfoGroupByConsignor(List<StorageIndicationVO> storageInfoGroupByConsignor, HttpServletResponse response){
      	// 设置响应头信息
      	response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
          response.setCharacterEncoding("utf-8");
          // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
          String fileName = URLEncoder.encode("文件名", "UTF-8").replaceAll("\\+", "%20");
          response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
      	// 新建一个excel,写入相应的数据,最后向请求方响应
      	EasyExcel.write(response.getOutputStream()) // 指定输出的位置,即前端请求的浏览器
      	.head(StorageIndicationVO.class)            // 指定表头模板
      	.sheet("表单名")                            // 指定工作表名称(一个excel有多个工作表,这里默认第一个)
      	.doWrite(storageInfoGroupByConsignor);      // 进行写入操作,storageInfoGroupByConsignor是列表数据
      }
      

      对应的结果:
      在这里插入图片描述

    2. 分多次写入:(要输入的数据格式有不相同的,比如说在最后一行需要填入总计数量)

      public void exportStorageInfoGroupByConsignor(List<StorageIndicationVO> storageInfoGroupByConsignor, HttpServletResponse response){
      	// 统计总数量
      	Integer totalAmount = storageInfoGroupByConsignor.stream().map(StorageIndicationVO::getAmount).filter(Objects::nonNull).reduce(0, Integer::sum);
      	// 设置响应头信息
      	response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
          response.setCharacterEncoding("utf-8");
          // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
          String fileName = URLEncoder.encode("文件名", "UTF-8").replaceAll("\\+", "%20");
          response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
      	// 新建一个excel,指定响应的对象(即输出位置)
      	ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
      	// 新建工作表sheet
          WriteSheet writeSheet = EasyExcel.writerSheet().head(StorageIndicationVO.class).sheetName("库存表").build();
          List<List<String>> head = new ArrayList<>();
          List<String> head1 = new ArrayList<>();
          head1.add(null);
          head1.add(null);
          head1.add("总数量:");
          head1.add(totalAmount);
          head.add(head1);
          // 第一次插入数据
          excelWriter.write(head, writeSheet);   // head是第一次要插入的数据,writeSheet是指定的工作表
          AtomicReference<Integer> i = new AtomicReference<>(1);
          storageInfoGroupByConsignor = storageInfoGroupByConsignor.stream().peek(c -> c.setIndex(i.getAndSet(i.get() + 1))).collect(Collectors.toList());
          // 第二次插入数据
          excelWriter.write(storageInfoGroupByConsignor, writeSheet); // storageInfoGroupByConsignor是第二次要插入的数据,writeSheet是同一个工作表
      	// 关闭excelWriter。这里必须关闭,不然导出时文件为空,显示台报excelWriter不能关闭警告
          excelWriter.finish();
      }
      

      对应的结果:
      在这里插入图片描述

  2. 常见的设置:

    1. 复杂头的写入

      @Getter
      @Setter
      @EqualsAndHashCode
      public class ComplexHeadData {
      	@ExcelProperty({"主标题", "字符串标题"})
      	private String string;
      	@ExcelProperty({"主标题", "日期标题"})
      	private Date date;
      	@ExcelProperty({"主标题", "数字标题"})
      	private Double doubleData;
      }
      

      相应的excel:
      在这里插入图片描述

    2. 写入多个Sheet

    try {
            // 设置响应头信息
            response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
            response.setCharacterEncoding("utf-8");
            // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
            String fileName = URLEncoder.encode("文件名", "UTF-8").replaceAll("\\+", "%20");
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
            // 新建一个excel,指定响应的对象(即输出位置)
            ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
            // 新建工作表sheet
            WriteSheet writeSheet = EasyExcel.writerSheet().head(StorageIndicationVO.class).build();
            // 填充序号
            AtomicReference<Integer> i = new AtomicReference<>(1);
            storageInfoGroupByConsignor = storageInfoGroupByConsignor.stream().peek(c -> c.setIndex(i.getAndSet(i.get() + 1))).collect(Collectors.toList());
            // 向不同的Sheet中插入数据,这里为了方便,没有修改插入的数据,事实上这里的数据列表是可以不一样的
            for (int num = 0; num < 2; num++) {
                writeSheet.setSheetName("Sheet" + num); // 为每一个工作表取不同的名字,多个工作表的名字必须不同!
                excelWriter.write(storageInfoGroupByConsignor, writeSheet); // 向不同的工作表中写入相同的数据
            }
            // 向最后一个工作表中的末尾新增一行数据,用来与前面的工作表做区分
            List<List<String>> addition = ListUtils.newArrayList();
            List<String> addition1 = ListUtils.newArrayList("Sheet2,新增一行用来与Sheet1区分");
            addition.add(addition1);
            excelWriter.write(addition, writeSheet);
            // 关闭excelWriter。这里必须关闭,不然导出时文件为空,显示台报excelWriter不能关闭警告
            excelWriter.finish();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    

    对应的excel:
    在这里插入图片描述
    在这里插入图片描述

    1. 合并单元格
    @Getter
    @Setter
    @EqualsAndHashCode
    // 将第6-7行的2-3列合并成一个单元格
    // @OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)
    public class DemoMergeData {
       // 这一列 每隔2行 合并单元格
       @ContentLoopMerge(eachRow = 2)
       @ExcelProperty("字符串标题")
       private String string;
       @ExcelProperty("日期标题")
       private Date date;
       @ExcelProperty("数字标题")
       private Double doubleData;
    }
    

    对应excel:
    在这里插入图片描述

    1. 图片导出

注意:写excel的方式有个最大的缺陷就是无法在表头的上边添加数据,即表头的上面永远是不变的数据。例子如下:
在这里插入图片描述

上述图片中红框部分是个变量,我需要根据前端传来的值来填入。但用写excel的方式是做不到的。写excel方式默认表头部分放在最上面。也就是说,厦门同金(江苏优特钢)这几个字顶多排在列表数据中的第一个,而不可能排在表头的上面。

2.填充excel方式(推荐方式

好处:使用这种方式基本不需要使用代码去调试表格的复杂样式。所有的样式均可以直接在模板中设定,然后仅将对应的值填充到相应的位置。

  1. 创建xlsx模板
    xlsx模板
    填充excel分单个元素(一个单元格)单列表对象(一行或多行数据)多列表对象(一行或多行数据)

  2. 填充方式:

    • 对于单个元素(一个单元格):新建一个Map<String, Object>对象,key为模板中对应的变量,如:title。value为你想要插入到相应位置的真正的值。

      // HelloWorld是当前类,这里是获取exportTemplate3.xlsx文件所在的路径
      String filePath = Objects.requireNonNull(HelloWorld.class.getResource("/exportTemplate3.xlsx")).getPath(); 
      // 新建excel,设置响应对象(请求的浏览器)和对应的excel模板
      ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(filePath).build();
      // 新建工作表
      WriteSheet writeSheet = EasyExcel.writerSheet().build();
      // 设置表头和表尾信息
      Map<String, Object> map = new HashMap<>();
      map.put("title", "测试案例");
      map.put("totalAmount", totalAmount);
      map.put("totalWeight", totalWeight);
      // 填充表头和结尾
      excelWriter.fill(map, writeSheet);
      
    • 对于单个列表对象(一行或多行数据):可以直接填充到xlsx文件中

      // HelloWorld是当前类,这里是获取exportTemplate3.xlsx文件所在的路径
      String filePath = Objects.requireNonNull(HelloWorld.class.getResource("/exportTemplate3.xlsx")).getPath(); 
      // 新建excel,设置响应对象(请求的浏览器)和对应的excel模板
      ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(filePath).build();
      // 新建工作表
      WriteSheet writeSheet = EasyExcel.writerSheet().build();
      // 填充列表,这里的inStorageInfoVOS是一个对象列表List<相应的类名>
      excelWriter.fill(inStorageInfoVOS, writeSheet);
      

      用这种方式填充时,因为每一行数据对应的key都是一样的,所以需要在前面加一个“.”来表示模板中这一部分是用来填充列表的。如下图:
      填充列表

    • 对于多列表对象(有时单个列表对象中缺少了某个属性时,就可以使用这种):需要为每个列表对象取别名,用来在文件模板中区分不同列表的key。毕竟不同列表中的对象属性名是有可能重复的。

      // 新建excel,设置响应对象(请求的浏览器)和对应的excel模板
      String filePath = Objects.requireNonNull(HelloWorld.class.getResource("/exportTemplate3.xlsx")).getPath();
      ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(filePath).build();
      // 新建工作表
      WriteSheet writeSheet = EasyExcel.writerSheet().build();
      // 设置列表中每写入一行,都会通过新增一行来写入。不管当前行是否有预留的空间
      FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
      // 填充列表
      // 此处为列表对象inStorageInfoVOS取了一个别名data1
      excelWriter.fill(new FillWrapper("data1", inStorageInfoVOS), fillConfig, writeSheet);
      // 通过 List<Map<String, Object>> 设置自定义列表数据,而不通过持久层对象
      List<Map<String, Object>> tempLists = ListUtils.newArrayList();
      Map<String, Object> temp;
      for (int index = 1; index <= inStorageInfoVOS.size(); index++) {
          temp = new HashMap<>();
          // 为自定义列表数据中添加一个key为index的属性
          temp.put("index", String.valueOf(index));
          tempLists.add(temp);
      }
      // 此处为列表对象tempLists取了一个别名data2
      excelWriter.fill(new FillWrapper("data2", tempLists), writeSheet);
      // 关闭excelWriter。不关闭的话,缓存中的数据不会写入文件,导致文件内容为空
      excelWriter.finish();
      

      使用这个方式来填充时,因为有多个列表,所以需要在原有的基础上添加上各自的别名。其中,橙框部分是data1列表对象填充的位置,红框部分是data2列表填充的部分。
      在这里插入图片描述

最后附上我这部分案例的全部代码,让读者能够理解的更清楚。

@PostMapping("/exportInStorageInfoGroupByConsignor")
public void exportInStorageInfoGroupByConsignor(@RequestBody List<InStorageInfoVO> inStorageInfoVOS, HttpServletResponse response) {
    Integer totalAmount = inStorageInfoVOS.stream().map(InStorageInfoVO::getAmount).filter(Objects::nonNull).reduce(0, Integer::sum);
    BigDecimal totalWeight = inStorageInfoVOS.stream().map(InStorageInfoVO::getWeight).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
    try {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
        // 新建excel,设置响应对象(请求的浏览器)和对应的excel模板
        String filePath = Objects.requireNonNull(HelloWorld.class.getResource("/exportTemplate3.xlsx")).getPath();
        ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(filePath).build();
        // 新建工作表
        WriteSheet writeSheet = EasyExcel.writerSheet().build();
        // 设置表头和表尾信息
        Map<String, Object> map = new HashMap<>();
        map.put("title", "测试案例");
        map.put("totalAmount", totalAmount);
        map.put("totalWeight", totalWeight);
        // 填充表头和结尾
        excelWriter.fill(map, writeSheet);
        // 设置列表中每写入一行,都会通过新增一行来写入。不管当前行是否有预留的空间
        FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
        // 填充列表
        excelWriter.fill(new FillWrapper("data1", inStorageInfoVOS), fillConfig, writeSheet);
        List<Map<String, Object>> tempLists = ListUtils.newArrayList();
        Map<String, Object> temp;
        for (int index = 1; index <= inStorageInfoVOS.size(); index++) {
            temp = new HashMap<>();
            temp.put("index", String.valueOf(index));
            tempLists.add(temp);
        }
        excelWriter.fill(new FillWrapper("data2", tempLists), writeSheet);
        // 关闭excelWriter。不关闭的话,缓存中的数据不会写入文件,导致文件内容为空
        excelWriter.finish();
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

结果显示:

结果显示


总结

以上就是今天要记录的内容,本文仅仅简单介绍了easyExcel的使用,如果想了解更多可以前往easyExcel官网查看.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值