记一次后端生成Zip文件问题
- 前言
- 问题出现
- 排查
- 一、流没有关好
- 二、写入了空白字节
- 三、没有flush
- 定位环节
- 一、生成
- 二、通过SwaggerUI、PostMan进行下载
- 三、结论
- 解决
- 方法
前言
在项目上线前夕,临时添加了个数据导出的接口,需求是导出压缩包,选择了项目中正常使用的下载接口改造,只是生成文件函数内添加了文件压缩功能
问题出现
但是在其他地方正常下载的接口,下载的压缩包却无法打开,提示
压缩包损坏
,不可预料的压缩文件末端
,生成的压缩包为205kb
,下载后为370kb
排查
通过面向百度,得到几个答案流没关好
,写入使用了字节数组导致多写入空字节
,流没有flush
等
一、流没有关好
1.检查程序输出流是否关闭
2.流的关闭顺序是否正确
但是我的流使用的是try-with-resource
方法,不用操作流关闭啊
try (
// 1.读取要下载的内容
BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file));
// 将要下载的文件内容通过输出流写到浏览器
ServletOutputStream outputStream = response.getOutputStream()) {
//do something
} catch (IOException e) {
e.printStackTrace();
}
扩展: 不使用
try-with-resource
方法的可以看看这篇文章,避免因为流问题导致
java创建的zip无法打开或打开显示不可预料的压缩文件(https://blog.csdn.net/freedom_zzc/article/details/118930027)
二、写入了空白字节
如果通过流写入时,写入方法不对会出现最后一次写入时,出现空字节写入进文件中,导致文件无法打开,
错误写法:
不能直接用output.write(buffer)
。否则如果最后的流不能完全填充buffer时写的字节会比实际的字节多
byte[] b = new byte[2048];
int len;
while ((len = inputStream.read(b)) > 0) {
outputStream.write(b);
}
正确写法:
byte[] b = new byte[2048];
int len;
while ((len = inputStream.read(b)) > 0) {
outputStream.write(b, 0, len);
}
三、没有flush
如果没有flush流,数据还一直在文件缓冲区
,数据还没有被真正的写入到物理介质
,如果服务挂掉会出现文件丢失情况。
但是如果直接调用内部的close方法,内部是会先调用flush方法的
其实可以直接使用工具类的拷贝,避免上述问题,而且代码更显简介
hutool包中工具类
IoUtil.copy(inputStream, outputStream);
所以我的问题和流没有关系
此时问题陷入了僵局
定位环节
决定排查下看看是哪个环节出问题在进行修改
一、生成
通过手动下载服务器上程序生成的压缩包到本地,打开发现没有问题,不会报错,确定生成环节
没有问题,继续往下
二、通过SwaggerUI、PostMan进行下载
通过工具下载,发现文件大小正常,可以正常打开,没有报错,确定下载接口
没有问题
三、结论
目前可以确定问题出现在前台调用中,后续通过修改前端调用接口解决了下载压缩包问题
解决
最后解决办法为前台调用接口添加responseType: ‘blob‘
参数解决
代码实例如下:
- 前端blob下载,responseType: ‘blob‘(https://blog.csdn.net/weixin_40994437/article/details/122425671)
- 导出文件类型为responseType:blob的问题(https://blog.csdn.net/weixin_43123717/article/details/116125289)
方法
一开始方法就不对,不应该直接就修改后端代码,经验主义害死人,习惯性的以为是写文件出了问题(之前下载word时出现了类似的问题)。应该先定位环节再进行解决问题。
- 首先使用Postman下载或导出文件,如果无法打开,则在后端代码中寻找问题,否则定位前端调用
- 如果服务器本地文件就无法打开,则在生成代码中寻找问题,否则定位下载接口