Java使用EasyExcel动态添加自增序号列

作者:庄周de蝴蝶 时间:2023-11-17 01:54:37 

目录
  • 前言

  • 实现

  • 思路

  • 其它

  • 总结

前言

本文将介绍如何通过使用EasyExcel自定义 * 实现在最终的Excel文件中新增一列自增的序号列,最终的效果如下:

Java使用EasyExcel动态添加自增序号列

此外,本文所使用的完整代码示例已上传到GitHub。

实现

本文主要是通过自定义一个继承AbstractRowWriteHandler的 * 来实现在最终导出的结果中新增序号列,通过修改源码中保存头部标题的Map内容来给自己添加的序号列留出位置,先展示最终的代码:


/**
* 自定义 excel 行处理器, 增加序号列
*
* @author butterfly
* @date 2020-09-05
*/
@Component
public class AddNoHandler extends AbstractRowWriteHandler {

private boolean init = true;

@Override
public void beforeRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,
                               Integer rowIndex, Integer relativeRowIndex, Boolean isHead) {
 if (init) {
  // 修改存储头部及对应字段信息的 map, 将其中的内容均右移一位, 给新增的序列号预留为第一列
  ExcelWriteHeadProperty excelWriteHeadProperty = writeSheetHolder.excelWriteHeadProperty();
  Map<Integer, Head> headMap = excelWriteHeadProperty.getHeadMap();
  Map<Integer, ExcelContentProperty> contentMap = excelWriteHeadProperty.getContentPropertyMap();
  int size = headMap.size();
  for (int current = size; current > 0; current--) {
   int previous = current - 1;
   headMap.put(current, headMap.get(previous));
   contentMap.put(current, contentMap.get(previous));
  }
  // 空出第一列
  headMap.remove(0);
  contentMap.remove(0);
  // 只需要修改一次 map 即可, 故使用 init 变量进行控制
  init = false;
 }
}

@Override
public void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
         Integer relativeRowIndex, Boolean isHead) {
 // 在行创建完成后添加序号列
 Cell cell = row.createCell(0);
 int rowNum = row.getRowNum();
 if (rowNum == 0) {
  cell.setCellValue(ExcelConstant.TITLE);
 } else {
  cell.setCellValue(rowNum);
 }
}

@Override
public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
       Integer relativeRowIndex, Boolean isHead) {
 if (row.getLastCellNum() > 1) {
  // 将自定义新增的序号列的样式设置与默认的样式一致
  row.getCell(0).setCellStyle(row.getCell(1).getCellStyle());
 }
}

}

afterRowCreate和afterRowDispose方法中的内容很好理解,一个用于控制控制新增序号列的内容,一个用于控制新增列的样式。而beforeRowCreate中的内容则稍微复杂,主要用于给新增的序号列空出位置。同时,由于beforeRowCreate会在每次创建行时均会被调用,但是原始的存储头部信息的Map只需要修改一次,因此这里通过使用init变量控制只会修改一次。

思路

通过查看com.alibaba.excel.write.executor.ExcelWriteAddExecutor类中的addOneRowOfDataToExcel方法源码,可以看到在新增行的前后会分别调用beforeRowCreate和afterRowCreate方法,并且在一行数据添加完成后会调用afterRowDispose,这也是上述 * 生效的原理,源码如下:


private void addOneRowOfDataToExcel(Object oneRowData, int n, int relativeRowIndex,
                                   Map<Integer, Field> sortedAllFiledMap) {
   // 行数据为空, 直接返回
   if (oneRowData == null) {
       return;
   }
   // 创建数据行对象, 同时分别在创建行前后调用 *
   WriteHandlerUtils.beforeRowCreate(writeContext, n, relativeRowIndex, Boolean.FALSE);
   Row row = WorkBookUtil.createRow(writeContext.writeSheetHolder().getSheet(), n);
   WriteHandlerUtils.afterRowCreate(writeContext, row, relativeRowIndex, Boolean.FALSE);
   // 将实体数据内容填充到行中
   if (oneRowData instanceof List) {
       addBasicTypeToExcel((List) oneRowData, row, relativeRowIndex);
   } else {
       // 下面会继续查看这个方法
       addJavaObjectToExcel(oneRowData, row, relativeRowIndex, sortedAllFiledMap);
   }
   // 行创建完成后, 调用相应 *
   WriteHandlerUtils.afterRowDispose(writeContext, row, relativeRowIndex, Boolean.FALSE);
}

而之所以我们修改headMap和contentMap的内容就可以实现最终效果,只需要继续查看该类中addJavaObjectToExcel方法的代码即可知道原因:


private void addJavaObjectToExcel(Object oneRowData, Row row, int relativeRowIndex,
                                 Map<Integer, Field> sortedAllFiledMap) {
       WriteHolder currentWriteHolder = writeContext.currentWriteHolder();
    // 将自己的实体数据映射到 beanMap
       BeanMap beanMap = BeanMap.create(oneRowData);
       Set<String> beanMapHandledSet = new HashSet<String>();
       int cellIndex = 0;
       // If it's a class it needs to be cast by type
       if (HeadKindEnum.CLASS.equals(writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadKind())) {
           // 我们修改的就是这里的 headMap 和 contentPropertyMap 内容
           Map<Integer, Head> headMap = writeContext.currentWriteHolder().excelWriteHeadProperty().getHeadMap();
           Map<Integer, ExcelContentProperty> contentPropertyMap =
               writeContext.currentWriteHolder().excelWriteHeadProperty().getContentPropertyMap();
           // 遍历所有的列头, 插入数据
           for (Map.Entry<Integer, ExcelContentProperty> entry : contentPropertyMap.entrySet()) {
               // 获取 cell 的下标, 后续将内容插入指定的列
               cellIndex = entry.getKey();
               ExcelContentProperty excelContentProperty = entry.getValue();
               String name = excelContentProperty.getField().getName();
               if (!beanMap.containsKey(name)) {
                   continue;
               }
               // 控制单元格的内容
               Head head = headMap.get(cellIndex);
               WriteHandlerUtils.beforeCellCreate(writeContext, row, head, cellIndex,
                                                  relativeRowIndex, Boolean.FALSE);
               Cell cell = WorkBookUtil.createCell(row, cellIndex);
               WriteHandlerUtils.afterCellCreate(writeContext, cell, head, relativeRowIndex, Boolean.FALSE);
               Object value = beanMap.get(name);
               CellData cellData = converterAndSet(currentWriteHolder, excelContentProperty.getField().getType(),
                                                   cell, value, excelContentProperty, head, relativeRowIndex);
               WriteHandlerUtils.afterCellDispose(writeContext, cellData, cell, head,
                                                  relativeRowIndex, Boolean.FALSE);
               beanMapHandledSet.add(name);
           }
       }
       // 省略了后面无关的内容
   }

其它

通过以上自定义的 * ,就可以写一个简单的demo进行测试:


/**
* Excel 下载控制器
*
* @author butterfly
* @date 2021-09-05
*/
@RestController
public class ExcelController {

/**
    * 添加序号列测试
    *
 * @param response response
    */
   @GetMapping("/col")
   public void col(HttpServletResponse response) {
       try {
           List<Student> students = getStudentList();
           EasyExcel.write(response.getOutputStream(), Student.class)
                   .registerWriteHandler(new AddNoHandler())
                   .sheet()
                   .doWrite(students);
       } catch (Exception e) {
           System.out.println(ExcelConstant.DOWNLOAD_FAILED);
       }
   }
   /**
    * 生成学生列表
    *
    * @return 学生列表
    */
   private List<Student> getStudentList() {
       return Arrays.asList(
               new Student("2021090101", "张三", 19),
               new Student("2021090102", "李四", 18),
               new Student("2021090103", "王二", 20)
       );
   }
}

然后是前端的测试代码:


<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <title>文件下载测试</title>
   <script src="https://cdn.staticfile.org/axios/0.21.2/axios.min.js"></script>
</head>
<body>
   <button onclick="col()">添加序号列测试</button>
   <script>
       function col() {
           download('http://localhost:8080/col', 'col.xlsx')
       }

function download(url, name) {
           axios({
               url: url,
               responseType: 'blob'
           }).then((response) => {
               const URL = window.URL.createObjectURL(response.data)
               const tempLink = document.createElement('a')
               tempLink.style.display = 'none'
               tempLink.href = URL
               tempLink.setAttribute('download', name)
               if (typeof tempLink.download === 'undefined') {
                   tempLink.setAttribute('target', '_blank')
               }
               document.body.appendChild(tempLink)
               tempLink.click()
               document.body.removeChild(tempLink)
               window.URL.revokeObjectURL(URL)
           })
       }
   </script>
</body>
</html>

Java使用EasyExcel动态添加自增序号列

总结

以上便是实现动态添加自增序号列的一种思路,除此之外还可以通过在实体中添加序号这一个字段,然后修改列表的数据,或者只需要在afterRowCreate中设置序号数据即可,而不需要再修改头部Map的数据。还有一种通过自定义模板,然后通过填充模板来实现同样的效果,可以参考https://www.yuque.com/easyexcel/doc/fill,但是这两种方法都需要对原有数据有所修改,或者需要增加定义模板的操作,这里不再介绍。

来源:https://juejin.cn/post/7004361975148838943

标签:Java,EasyExcel,自增序号列
0
投稿

猜你喜欢

  • 浅谈利用Spring的AbstractRoutingDataSource解决多数据源的问题

    2021-09-07 07:20:59
  • springboot整合spring-retry的实现示例

    2022-01-13 01:25:53
  • spring profile 多环境配置管理详解

    2023-01-23 17:53:58
  • Springboot实现给前端返回一个tree结构方法

    2022-04-02 23:37:07
  • 23种设计模式(12)java模版方法模式

    2021-10-24 11:00:31
  • Android实现仿通讯录侧边栏滑动SiderBar效果代码

    2021-08-03 21:07:45
  • java interface的两个经典用法

    2021-08-17 06:20:56
  • 区块链java代码实现

    2023-02-04 04:00:24
  • 基于Ok+Rxjava+retrofit实现断点续传下载

    2021-08-27 02:21:49
  • 关于@ApiImplicitParams、ApiImplicitParam的使用说明

    2023-11-09 10:49:34
  • 零基础写Java知乎爬虫之先拿百度首页练练手

    2022-11-14 09:07:05
  • springboot如何实现自动装配源码解读

    2023-11-10 15:44:20
  • 使用 Lambda 取代 Android 中的匿名类

    2023-11-16 14:01:04
  • 解决SpringBoot加载application.properties配置文件的坑

    2023-02-05 19:55:27
  • C# SQLite库使用技巧

    2023-10-27 19:05:21
  • 深入浅出MyBatis中映射文件和实体类的关联性

    2022-08-29 07:34:08
  • 基于springboot+vue实现垃圾分类管理系统

    2023-04-17 08:39:11
  • Springboot+AOP实现返回数据提示语国际化的示例代码

    2021-08-18 19:49:12
  • C# 获取文件夹里所有文件名的详细代码

    2023-01-07 02:05:20
  • C#中字符串与字节数组的转换方式

    2023-06-28 10:43:53
  • asp之家 软件编程 m.aspxhome.com