1.前言
最近遇到一个需求,就是将json转成导出到excel中导出,我查了下,主要的工具有apache poi,easypoi和easyexcel,我以前倒是使用过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); } }
|
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); })
|
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);
|
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);
|
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) 输出的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
|