最近项目遇到个问题,就是利用swagger下载excel时,得到的文件打开总是乱码,首先怀疑是response的content-type有问题,将application试遍了,“x-msdownload”,“vnd.ms-excel”,“vnd.openxmlformats-officedocument.spreadsheetml.sheet”,“octet-stream”这些试了都不行,具体的类型对应文件参考一下链接:https://blog.csdn.net/xiaojia_boke/article/details/81140647
然后怀疑是不是写入文件后再回填response这种方式导致文件流操作有问题,因为写入本地和服务器的文件都是可以正常打开的,但是将文件写入response后就乱码了。于是将业务数据解析后的workbook直接写入response测试,结果还是乱码,排除这种猜测。
然后在网上查阅资料,发现下载excel方式都大同小异,都是解析出workbook然后写入response,差异无非就是是否通过写临时文件来缓存写入下。后来想到是不是自己代码的问题,然后从网上现扒个代码,然后在另外一个工程里直接测试,因为那个工程没有集成swagger,所以直接写了个get请求,然后通过浏览器访问,结果下载的excel可以正常打开,对比代码,实现方式没啥差异,于是把工程的下载excel功能也通过浏览器直接访问来测试,结果果然是好使的,于是果断怀疑到swagger身上。。
中间还用postman测试了下,之前没用postman测过这种下载文件的功能,这次新get到postman的2个使用技巧:
1、下载文件就在send旁边这个下拉框里,选择“send and download”,不过这里下载的文件名称都是默认的response,不是我后台代码定义的那个文件名。
2、后台下载的接口入参是@RequestBody对象的,postman在测试时在body-》raw-》选中json来填写入参,见上面的截图。
最后来说说swagger的配置修改,在注解swagger配置时,给Docket加上配置new Docket(DocumentationType.SWAGGER_2)
.produces(Sets.newHashSet("application/octet-stream")),说明返回的是文件流即可成功下载excel。
中间也给这个Docket改过 new Docket(DocumentationType.SWAGGER_2)
.consumes(Sets.newHashSet("application/octet-stream")),也给controller接口的@ApiOperation加过produces="application/octet-stream",后经测试发现是Docket.produces()起作用的。至于Docket的这些配置具体啥含义,需要接下来进一步研究。
最后附上后台下载的大概逻辑代码,欢迎各位提出修改意见。
1、controller的代码:
@ApiOperation(value = "下载excel接口", notes = "前端页面点击下载操作时调用接口")
@PostMapping("/downLoadRules")
public void downLoadRules(
@RequestBody @ApiParam(name = "rulesToDownLoad", value = "下载excel列表", required = true) List
HttpServletResponse response) {
if (CollectionUtils.isEmpty(rulesToDownLoad)) {
logger.error("待下载请求为空");
}
// 当前日期,用于导出文件名称
String fileName ="Download_Rule_" + DateUtil.getDate("yyyyMMddHHmmss") + ".xlsx";
boolean result = downLoadService.exportExcel(rulesToDownLoad, response, fileName);
}
2、解析后的workbook写入response:
public boolean exportExcel(List
boolean downloadResult = false;
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
XSSFWorkbook workbook = getWorkBook(ruleModelList);
if(workbook!=null){
try{
//遗留问题:文件过大,可能写入效率过低,待优化
workbook.write(response.getOutputStream());
}
catch(Exception e){
logger.error("导出excel,写入workbook异常:{}", e.getLocalizedMessage());
}finally {
try{
workbook.close();
downloadResult = true;
}
catch(IOException e){
logger.error("导出excel,关闭workbook异常:{}", e.getLocalizedMessage());
}
}
}
return downloadResult;
}
3、根据业务解析出workbook:
private XSSFWorkbook getWorkBook(List
//根据入参ruleModelList和模板templatePath解析出来的workbook对象
FileInputStream excelFileInputStream = new FileInputStream(templatePath.getPath());
XSSFWorkbook workbook = new XSSFWorkbook(excelFileInputStream);
excelFileInputStream.close();
。
。
。
return workbook;
}
后续:
前两天直接改的swagger的整体配置:
new Docket(DocumentationType.SWAGGER_2).produces(Sets.newHashSet("application/octet-stream"))
最近同事联调代码发现所有的rest请求从swagger测试请求都是流的形式,这显然是不行的,影响了别的接口功能。
后来从swagger测试,发现这里的Request Headers中Accept为“*/*”时下载的excel就不行,我在swagger配置为流"application/octet-stream"时,这里的Accept就是流的形式,然后下载的excel就是OK的。
此处怀疑swagger默认是用json还是文本或者xml给解析了,考虑是不是我能在接口层面把这个请求的Accept绑定成流的形式,然后在接口定义的@PostMapping进入源码查看有好几个属性:
感觉这个headers比较靠谱,直接给@PostMapping赋值如下:@PostMapping(value="/downLoadExcel", headers="application/octet-stream"),用swagger测试直接报错一堆js错误,还是配置不对,swagger不识别,然后在网上查阅资料,得给headers配置headers="Accept=application/octet-stream",再调试一切OK,说明这里的@PostMapping配置和swagger的配置有个对应关系,具体怎么映射,待后续研究明白了再补上~