先描述一下业务场景,公司的电子回单是以pdf的形式一个个存储在阿里云OSS里的。财务的需求就是需要根据月份范围拿到对应的那批回单。
OK,接下就是说说我的实现思路,就是前端提供时间选择器,然后后端根据获取到的时间范围,去数据库里拿到KEY(其实就是oss里存储的文件名字),然后下载下来,并压缩到一个包里再传给前端。
任务明确了,思路也有了,那就开始干了!
(说明,文章里没有按照真实业务场景来,只是提供方案和代码……)
后端代码:
public class OSSUtil {
//阿里云API的外网域名
public static final String ENDPOINT = "按照你自己的来填入";
//阿里云API的密钥Access Key ID
public static final String ACCESS_KEY_ID = "按照你自己的来填入";
//阿里云API的密钥Access Key Secret
public static final String ACCESS_KEY_SECRET = "按照你自己的来填入";
//阿里云API的bucket名称
public static final String BACKET_NAME = "按照你自己的来填入";
/**下载并压缩返回
*
* @param fileNames 文件名称的集合
* @param response 响应
*/
public static void getFileToZip(List<String> fileNames, HttpServletResponse response) {
try {
OSSClient ossClient = new OSSClient(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);
//压缩包名称
String zipName = "test.zip";
//创建临时文件
File zipFile = File.createTempFile("test", ".zip");
FileOutputStream fileOutputStream = new FileOutputStream(zipFile);
/**
* 作用是为任何outputstream产生校验和
* 第一个参数是制定产生校验和的输出流,第二个参数是指定checksum类型(Adler32(较快)和CRC32两种)
*/
CheckedOutputStream cos = new CheckedOutputStream(fileOutputStream, new Adler32());
//用于将数据压缩成zip文件格式
ZipOutputStream zos = new ZipOutputStream(cos);
for (String fileName : fileNames) {
//获取object,返回结果ossObject对象
OSSObject ossObject = ossClient.getObject(BACKET_NAME, fileName);
//读取object内容,返回
InputStream inputStream = ossObject.getObjectContent();
// 对于每一个要被存放到压缩包的文件,都必须调用ZipOutputStream对象的putNextEntry()方法,确保压缩包里面文件不同名
zos.putNextEntry(new ZipEntry(fileName));
int bytesRead = 0;
// 向压缩文件中输出数据
while ((bytesRead = inputStream.read()) != -1) {
zos.write(bytesRead);
}
inputStream.close();
zos.closeEntry(); // 当前文件写完,定位为写入下一条项目
}
zos.close();
response.setContentType("application/octet-stream;charset=utf-8");
response.setHeader("Content-Disposition","attachment;filename=" + zipName);
FileInputStream fis = new FileInputStream(zipFile);
BufferedInputStream buff = new BufferedInputStream(fis);
BufferedOutputStream out = new BufferedOutputStream(response.getOutputStream());
byte[] car = new byte[1024];
int l = 0;
while (l < zipFile.length()) {
int j = buff.read(car, 0, 1024);
l += j;
out.write(car, 0, j);
}
// 关闭流
fis.close();
buff.close();
out.close();
ossClient.shutdown();
// 删除临时文件
zipFile.delete();
} catch (Exception e) {
}
}
整个项目是前后端分离的,前端用的是vue + axios。
前端代码
发送下载请求的JS:
export function downloadFile(data) {
return request({
url: '后端接口请求路径',
method: 'Post',
data,
headers:{
'Cache-Control': 'no-cache',
'Content-Type': 'application/x-www-form-urlencoded'
},
responseType:'blob' //这里很重要 会将我们返回的转换成Blob对象,
//不了解的.可以先去了解一下JS中Blob对象
})
}
处理返回数据的JS:
downloadFile() {
downloadFile().then(resp => {
console.log(resp) //这里可以打印看看返回的resp是什么样子
this.getBlob(resp)
})
},
//这里才是真正处理返回数据的方法
getBlob(res) {
const aLink = document.createElement('a')
var blob = new Blob([res.data],
{ type: "application/zip" } //这里是告之是zip类型的文件)
//从response的headers中获取filename, 后端response.setHeader("Content-disposition", "attachment; filename=xxxx.docx") 设置的文件名;
var patt = new RegExp('filename=([^;]+\\.[^\\.;]+);*')
var contentDisposition = decodeURI(res.headers['content-disposition'])
var result = patt.exec(contentDisposition)
var fileName = result[1]
fileName = fileName.replace(/\"/g, '')
aLink.href = URL.createObjectURL(blob)
aLink.setAttribute('download', fileName) // 设置下载文件名称
document.body.appendChild(aLink)
aLink.click()
document.body.appendChild(aLink)
}
}
可以在控制台打印看看返回的数据
这是下载下来的压缩包:
到这里基本就完成了……
遇到的问题及解决方案
问题:在处理返回数据的时候,js无法获取响应header的Content-Disposition字段。但是我在后端是有写的。
包括在控制台抓包里也能看到
后来也是看了一个大佬的文章才解决。
只需要我们在后端加上一段代码就能解决:
response.setHeader("Access-Control-Expose-Headers","Content-Disposition");
具体的原因可以去看这位大佬的文章: