场景:从金山云链接中读取文件流,将文件保存到本地,然后将本地文件压缩成zip包通过接口传给前端页面
现象:图片及word显示异常
原图:
解压后:
用到工具zip4j
直接上代码
第一步,从云链接中获取输入流并保存为本地文件
/**
* 将金山云文件写入到本地
*/
public static void writeKs3FileToLocal(String ks3Url, String localFilePath) {
try {
URL url = new URL(ks3Url);
InputStream inputStream = url.openStream();
FileOutputStream fos = new FileOutputStream(localFilePath);
byte[] b = new byte[1024];
while ((inputStream.read(b)) != -1) {
fos.write(b);// 写入数据
}
inputStream.close();
fos.close();
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new BusinessException(e.getMessage());
}
}
第二步,压缩本地文件
public void exportAttachment(String encryptPerformanceId, HttpServletResponse response) {
//获取数据库中附件列表
List<ExportAttachmentDao> attachmentList = attachmentService.getAttachmentByPerformanceId(performanceId);
//添加压缩文件
log.info("生成临时压缩文件:{}", zipPath);
ZipFile zipFile = new ZipFile(zipPath);
ZipParameters parameters = new ZipParameters();
parameters.setCompressionLevel(CompressionLevel.FASTEST);//压缩比最小,速度最快
parameters.setCompressionMethod(CompressionMethod.STORE);//不压缩,防止压缩后解压打开异常
attachmentList.forEach(attachment -> {
String filePath = eachPersonFileDir + attachment.getFileName();
//将金山云文件保存成本地文件
ZipUtils.writeKs3FileToLocal(attachment.getPath(), filePath);
//将本地文件添加到zip
File localFile = new File(filePath);
zipFile.addFile(localFile, parameters);
log.info("文件[name={},path={}]将添加到压缩文件", attachment.getFileName(), attachment.getPath());
//删除本地文件
// localFile.delete();
});
String zipFileName = performance.getEid() + ".zip";
writeZip(response, zipFile.getFile(), zipFileName);
log.info("删除临时压缩文件:{}", zipPath);
// zipFile.getFile().delete();
}
第三步:返回给前端文件流
public void writeZip(HttpServletResponse resp, File zipFile, String zipFileName) {
log.info("下载文件[name={},size={}]", zipFileName, zipFile.length());
InputStream is = null;
BufferedInputStream bs = null;
is = new FileInputStream(zipFile);
bs = new BufferedInputStream(is);
resp.setHeader("Content-disposition", "attachment;filename=" + zipFileName);
resp.setContentType(MediaType.APPLICATION_OCTET_STREAM.toString());
byte[] bytes = new byte[1024 * 10];
int read = 0;
ServletOutputStream outputStream = resp.getOutputStream();
while ((read = bs.read(bytes, 0, 1024 * 10)) != -1) {
outputStream.write(bytes, 0, read);
}
}
原因分析:
1,可能是图片压缩导致的,设置了压缩参数:最快,不压缩
结果还是有问题
2,猜测是获取金山云文件流保存到本地有问题,注释了删除本地文件的代码,从服务器上下载本地文件,果然,本地文件就是异常的图片。然后分析第一步的云链接保存为本地图片,发现写法有误,应为:
public static void writeKs3FileToLocal(String ks3Url, String localFilePath) {
try {
URL url = new URL(ks3Url);
InputStream inputStream = url.openStream();
FileOutputStream fos = new FileOutputStream(localFilePath);
byte[] b = new byte[1024];
int length;
while ((length = inputStream.read(b)) > 0) {
fos.write(b, 0, length);
}
fos.close();
inputStream.close();
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new BusinessException(e.getMessage());
}
}
真实的区别在于这句
fos.write(b); <=> fos.write(b, 0, length);
这两句有啥区别呢,我们看下原码:
public void write(byte b[]) throws IOException {
writeBytes(b, 0, b.length, append);
}
public void write(byte b[], int off, int len) throws IOException {
writeBytes(b, off, len, append);
}
如果只传字节数组b,则写入的length为b.length=1024,而不是实际读取到的有效数据,而最后一次b.length=1024(固定值,定义的数组长度),length=375,写入了一些无效的字节导致图片异常,下图是最后一次读取的结果