本文柯苏远写于2024年5月1日 16点0分
本篇文章不介绍EasyExcel和Apache POI的具体API。
背景
前端有一个将数据看板导出excel的功能,整个数据看板分为5个模块。
一期需求:将这5个模块的数据展示一起导入到excel里。
二期需求:将这5个模块的数据按需导入到excel文档里,就是说前端有勾选这5个模块的按钮,选了哪几个模块就需要导入哪几个模块的数据到excel里,是动态的。
实现一期需求
由于项目里本来就存在EasyExcel所以就直接考虑用EasyExcel来完成5个模块数据的整体导出。
我的实现方式很直观:
- 用产品提供的excel文档,填入自己定义好的占位符。
- 将这个有占位符的excel模板文档上传到公司的文件服务器,得到一个文件的网络资源链接。
- 在程序里通过这个excel模板文件网络资源链接得到一个输入流。
- 然后再将得到的5个模块数据填充进这个输入流。(这里是用EasyExcel来实现的) 简要代码如下:
java
public static void writeOpsExcel(HttpServletResponse response, String filePath, String fileName, String sheetName, OverviewStatisticsVO overviewStatisticsVO, List<OpsScreenVO> vos) throws IOException {
// 得到网络资源的输入流,这是自己写的工具方法
InputStream is = IoUtils.getIs(filePath);
ExcelWriter excelWriter = EasyExcelFactory.write(getOutputStream(fileName, response)).withTemplate(is).build();
try {
WriteSheet writeSheet = EasyExcelFactory.writerSheet().build();
// overviewSatisticsVO和vos是填充数据
excelWriter.fill(overviewStatisticsVO, writeSheet);
FillConfig fillConfig = FillConfig.builder().direction(WriteDirectionEnum.VERTICAL).build();
excelWriter.fill(vos, fillConfig, writeSheet);
} finally {
if (excelWriter != null) {
excelWriter.finish();
}
}
}
这个excel模板极其复杂,大家来瞅一眼:(右边还有呢,还没有截完)
按照我上面的四个步骤已经可以完美实现这个文档导出需求了,然后就按期送测交付呗。
得,送测当天,产品突然说需求改动了,变成按需导出模块数据到excel了。
实现二期需求
一期和二期的需求变动点从直观上来看其实就是模块数量从固定5个变成动态可选的了。
但是后端的技术方案几乎要推倒重来了。
设想技术方案
在一期基础上实现二期需求,我设想的技术方案:
- 前端传入每个模块标识,比如1-5对应5个模块。
- 我根据excel模板文件网络资源链接得到输入流。
- 将这个输入流传给EasyExcel,然后根据每个模块标识去删除前端没有传入的模块标识对应的单元格区域。
- 由于模块数据单元格是连在一起的,所以删掉之后会出现这种情况:模块数据-已删除的空白单元格-模块数据,所以需要将有数据的移动到一起。
好,我们的技术方案方向找好了,去看可行性:
- 首先简单的看了下EasyExcel里ExcelWriter相关的api,发现没有对单元格进行删除的api。
- 然后又去问了文心一言,她的回答如下图:
好吧,貌似并不支持。 3. 去官网求证一下:关于Easyexcel | Easy Excel (alibaba.com) 确实全篇内容没有提到删除的字眼。 4. 甚至加了个官网交流群在群里问了,没人回复,哈哈。
到这里我就知道大概率直接用EasyExcel的方案是行不通了。
Apache POI
Apache POI 登场,这个是EasyExcel的底层依赖,能够操作excel文档的粒度更加小,更灵活。
缺点嘛,EasyExcel的官网说大文件的导入和导出会很消耗内存。
继续用上面说的设想方案,Apache POI 确实提供了删除指定区域单元格以及某块区域单元格左移的API实现。但是由于我的模板文件有合并单元格之类的复杂样式操作,导致移动之后格式错乱了。
替换方案
灵机一动,想到了另一种替换方案:
- 前端传入每个模块标识,比如1-5对应5个模块。
- 我根据excel模板文件网络资源链接得到输入流。
- 将这个输入流传给POI进行操作。
- 我们可以再创建一个空sheet。
- 将前端传入的模板标识对应的sheet模板单元格给复制到我们新创建好的sheet中去。
- 完成对应模板单元格复制操作之后,将原先的sheet删除掉。
总之,核心思想就是用一个空sheet来装对应的模板单元格,等装好之后删除原先有所有模板单元格的那个sheet,一种"狸猫换太子"的方式。
遇到问题
ok,说干就干这种方式总算是可以了,但是在实际操作中还是遇到了各种问题,说几个记忆犹新的:
- 还是单元格合并,但是问题比较清晰了,容易解决。
- 之前的模板有冻结行和列,这个需要注意下。
- 最最麻烦的就是模块单元格区域填充的"严丝合缝"了,就是说不能出现模块数据-空-模块数据 这种形式,逻辑不难,但是要注意模块总列数的累计,做一个偏移量。
到这里,一期和二期的功能是实现完了,也算是稍微对EasyExcel和Apache POI有了了解。 接下来对比一下这两个技术方案。
总结:EasyExcel 和 Apache POI的对比
- EasyExcel 按固定模板进行导入导出更加方便,官网也说更加节省内存。
- 但是EasyExcel我觉得应用的场景不太灵活,比如我的这个二期需求,需要对指定单元格做操作的貌似不支持。
- Apache POI嘛,灵活好用,强大的API支持,毕竟EasyExcel底层用的也是它,缺点嘛:EasyExcel官网说它耗费内存,有内存溢出风险。
可能EasyExcel也能够支持我的这个需求,但是从我的调研结果来说是实现不了的,有精通EasyExcel的小伙伴看见这篇文章的话,还望不吝赐教,谢谢。