1.前言
最近遇到一个需求,就是将json转成导出到excel中导出,我查了下,主要的工具有apache poi,easypoi和easyexcel,我以前倒是使用过poi,但是当时没有统一的博客,可能在简书,也可能在知乎,最后的代码也不知道弄到那里去了,现在再次拾起来,如何导出excel文件。
参考文章:
1.poi、easypoi和easyexcel的使用 介绍了poi,easypoi和easyexcel,分别有相应的代码示例,都有自定义数据结构的例子。
2.java- excel工具类(EasyPoi)
3.EasyExcel与EasyPoi性能对比
4.JAVA-JSON文件转excel文件
5.JAVA使用hutool poi工具读取Excel模板并写值输出文件 这里使用了 hutool 封装的 poi 进行 excel 的读取和写入。
2.文件生成
下面的示例代码,主要展示了创建两个表格,并通过浏览器进行下载的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| @RequestMapping(value = "exportToExcel") public void exportToExcel(String json,HttpServletResponse response){ try { List<List<String>> headList = new ArrayList<>(); headList.add(Lists.newArrayList("姓名")); headList.add(Lists.newArrayList("年龄")); headList.add(Lists.newArrayList("操作时间"));
List<List<Object>> dataList = new ArrayList<>(); for (int i = 0; i < 10; i++) { List<Object> data = new ArrayList<>(); data.add("张三" + i); data.add(20 + i); data.add(new Date(System.currentTimeMillis() + i)); dataList.add(data); }
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20"); response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build(); WriteSheet writeSheet = EasyExcel.writerSheet(1, "模版1").head(headList).build(); excelWriter.write(dataList,writeSheet); WriteSheet writeSheet2 = EasyExcel.writerSheet(2, "模版2").head(headList).build(); excelWriter.write(dataList,writeSheet2); excelWriter.finish();
}catch (Exception e){ logger.error("exportToExcel",e); } }
|
参考文章:
1.EasyExcel将数据库中的多张表数据写入文件的多个sheet 写入多张表格
3.前端下载
前端主要使用了axios进行了文件下载。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| axios({ method: 'post', url, data, responseType: 'arraybuffer' }) .then(res => { let filename="测试.xlsx"; const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'}); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = [filename]; a.click(); window.URL.revokeObjectURL(url); })
|
参考文章:
1.Blob 格式 Excel 文件下载 使用axios设置responseType:’arraybuffer’,借助于Blob实现了excel文件下载
4.样式和行高
通过实现自定义的列宽策略,可以设置不同的行和列的样式。主要就是实现 CellWriteHandler 和 RowWriteHandler 接口,在接口中,实现 after 开头的方法,通过 Workbook 创建一个样式,然后编辑这个样式,最后应用到cell上面。
1 2 3 4 5 6 7
| Workbook workbook = context.getWriteSheetHolder().getSheet().getWorkbook(); CellStyle cellStyle = workbook.createCellStyle(); cellStyle.setAlignment(HorizontalAlignment.CENTER);
cell.SetCellStyle(cellStyle);
|
然后将其注册EasyExcel写入的时候
1 2 3 4 5 6 7 8 9
| EasyExcel.write(outputStream) .head(headNameList) .sheet(fileName.split("\\.")[0]) .registerWriteHandler(new SimpleColumnWidthStyleStrategy(25)) .registerWriteHandler(new SimpleRowHeightStyleStrategy((short)25,(short)25)) .doWrite(datalist);
|
参考文章:
1.easyexcel 列宽、行高、样式 这里主要使用的是注解的方式实现的
2.alibaba easyexcel 自适应(行宽, 行高)
3.EasyExcel 的行高列宽的单位,直接设置行高列宽的大小_坐等夕阳落time的博客-程序员资料_easyexcel 设置单元格高度 EasyExcel 注解方式和非注解方式设置行宽列高
4.EasyExcel 批量设置单元格样式(字体样式、背景颜色、边框样式、对齐方式、自动换
5.POI实现EXCEL单元格合并及边框样式
5.多级表头
根据网上的一个资料,我成功实现了多级表头
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
JSONArray header=monthData.getJSONArray("header"); List<List<String>> headList = new ArrayList<List<String>>(); String basicInfo="";
headList.add( Lists.newArrayList(basicInfo,"站点名称","站点名称" ));
header.stream().forEach(item->{ JSONObject headerItem=(JSONObject) item; String label=headerItem.getString("label"); JSONArray children=headerItem.getJSONArray("children"); children.stream().forEach(child->{ JSONObject childItem=(JSONObject) child; String title=childItem.getString("label"); headList.add( Lists.newArrayList(basicInfo,label ,title) ); }); });
headList.add( Lists.newArrayList(basicInfo,"月度统计","月度统计"));
ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build(); WriteSheet writeSheet = EasyExcel.writerSheet(1, "月度报表").head(headList).build(); excelWriter.write(dataList,writeSheet);
|
参考文章:
1.EASYEXCEL的不确定表头(根据数据生成表头)的EXCEL导出和二级表头或多级表头以及设置表头的宽度自适应
2.easyExcel的复杂表头多级表头导入 这里虽然写了多级表头,但是代码部分内容很少,都是说明性的,不知道最后的结果到底是什么生成的
3.easyExcel复杂表头的设置与样式的设置(非注解) 这里有单表头和多表头的代码。
4.EasyExcel自定义多级表头导出
6.合并单元格
合并单元格主要通过自定义合并策略实现的。这里有多个方向可以实现,根据官方的代码进行分析如下:
1.官方的 LoopMergeStrategy 实现的是 RowWriteHandler 接口,这个接口里面实现了 afterRowDispose 方法,也就是渲染每一行数据的时候,都会调用的方法,在这个方法里面,可以创建一个 CellRangeAddress 需要合并的区域,包括行号和列号。
2.官方的 OnceAbsoluteMergeStrategy 合并策略,实现的是 SheetWriteHandler 接口,这个接口里面的 afterSheetCreate 方法,在创建表格的时候会执行一次,并创建一个 CellRangeAddress 合并区域,实现单元格的合并。
3.官方的 AbstractMergeStrategy 合并策略,实现的是 CellWriteHandler 接口,这个接口里面的 afterCellDispose 方法,在渲染每一个单元格的时候,会进行调用,也是创建一个 合并区域进行合并。
4.CellRangeAddress 方法包括四个参数,CellRangeAddress(int firstRow, int lastRow, int firstCol, int lastCol) (开始合并行,结束合并行,开始合并列,结束合并列)
总结起来,就是在渲染行或者列或者是数据表的时候,根据一定的条件,创建一个或者是多个CellRangeAddress待合并对象,这样就可以实现单元格的合并了。如果要合并行,可以实现具体实现RowWriteHandler 接口,如果要合并列,可以实现 CellWriteHandler ,这个接口里面可以获取 context.getHeadData() 表头内容,自定义策略步骤:
1.实现自定义策略,根据需要继承的方法编写自定义合并策略,根据debug的现象来看,这个context中可能没有需要的信息,比如表头是什么,需要额外传入数据进行条件判断。我这里的例子,还做了一个操作,就是把一行中从第二列开始往后的每一个单元格,前一个单元格和后面一个合并,填充的值是后面一个单元格的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| public class ReportExcelMergeStrategy implements CellWriteHandler{ private final static Logger logger= LoggerFactory.getLogger(ReportExcelMergeStrategy.class);
private JSONArray data=null;
private int dataRowNumber=0;
public ReportExcelMergeStrategy(JSONArray datalist) { this.data=datalist; } @Override public void afterCellDispose(CellWriteHandlerContext context) { Integer rowIndex=context.getRowIndex(); if (context.getHead() || rowIndex == null) { dataRowNumber=rowIndex; return; }
int dataOffset=rowIndex-dataRowNumber-1; JSONObject rowData=this.data.getJSONObject(dataOffset); Integer siteLevel=rowData.getInteger("SiteLevel"); if(siteLevel!=null&&siteLevel==1){ int cellIndex=context.getColumnIndex(); if(cellIndex==0){ return; } if(cellIndex%2==0) { Workbook workbook = context.getWriteSheetHolder().getSheet().getWorkbook(); CellStyle cellStyle = workbook.createCellStyle(); cellStyle.setAlignment(HorizontalAlignment.CENTER); Cell nowCell=context.getCell(); Double nowValue=nowCell.getNumericCellValue(); Row row=context.getRow(); Cell prevCell=row.getCell(cellIndex-1); prevCell.setCellValue(nowValue); prevCell.setCellStyle(cellStyle); Sheet sheet=context.getWriteSheetHolder().getSheet(); CellRangeAddress cellRangeAddress = new CellRangeAddress(rowIndex, rowIndex, cellIndex-1, cellIndex); sheet.addMergedRegionUnsafe(cellRangeAddress); } } } }
|
2.创建表格的时候,注册 registerWriteHandler 将自定义的写入策略注册进去
1 2 3 4 5 6
| xcelWriter excelWriter = EasyExcel .write(response.getOutputStream()) .registerWriteHandler(new ReportExcelMergeStrategy()) .build(); WriteSheet writeSheet = EasyExcel.writerSheet(1, "月度报表").head(headList).build(); excelWriter.write(dataList,writeSheet);
|
参考文章:
1.EasyExcel导出自定义合并单元格的策略 这里实现了 CellWriteHandler 接口
2.EasyExcel 实现批量合并单元格(支持自定义) 这里实现了 AbstractRowWriteHandler 类。
3.OnceAbsoluteMergeStrategy
4.EasyExcel非注解式导出、单元格合并策略及自定义样式
5.EasyExcel合并单元格(一) 这个主要讲的就是 LoopMergeStrategy 这个合并策略的使用方式
6.Class CellRangeAddress 这里是 CellRangeAddress 的使用方法
问题
(1) 输出的excel文件为空,字节为0字节
本地开发的时候没有问题,但是在上传到docker中总是出现问题,不知道为什么,还没有错误消息。
【解决方法】
在dockerfile文件中增加字体配置。
1 2 3 4 5 6 7 8 9
| RUN echo -e 'https://mirrors.aliyun.com/alpine/v3.6/main/\nhttps://mirrors.aliyun.com/alpine/v3.6/community/' > /etc/apk/repositories \ && apk update \ && apk upgrade \ && apk --no-cache add ttf-dejavu fontconfig \ && apk add --no-cache tzdata
RUN yum install dejavu-* fontconfig -y
|
参考文章:
1.EasyExcel导出的文件为空/损坏
2.踩坑:EasyExcel导出excel导出数据为空
3.EasyExcel本地导出没问题,线上环境导出却是0kb,失败! 这里也是本地没有问题,线上有问题,可能是字体的问题
4.dejavu-fonts Alpine Linux中使用:sudo apk add ttf-dejavu,CentOS系统中使用:sudo yum install -y dejavu-*