我的需求是将minio中存储的文件按照查询条件查询出来统一压成一个zip包然后下载下来。
思路:针对这个需求,其实可以有多个思路,不过也大同小异,一般都是后端返回流文件前端再处理下载,也有少数是压缩成zip包之后直接给下载链接返回到前端,前端收到链接url直接window.open()进行下载,不过这种下载zip包的路径要确保是在网站下,否则访问不到,还有一个缺点就是文件没法删除,占用存储空间,后期需人为动作清理,选择哪种思路就可以看具体需求啦,我选择的是第一种思路,以下就针对第一种后端返回流方式进行具体介绍。
首先说第一种方法:将需要下载的文件找到,minio中有查询方法将文件转成inputStream,这里就不多说了,拿到一组InputStream,我们就可以写入一个zip包里了,创建临时zip路径,将流遍历写入文件,读取临时zip文件再写入response中的outputStream,最后删除临时文件。
前端处理方法最后统一介绍,后端核心代码如下:
- public void downloadZip(String name, List
filePaths,HttpServletResponse response) { - File zipFile = compressedFileToZip(name,filePaths);
- ByteArrayOutputStream os = new ByteArrayOutputStream();
- try {
- FileInputStream ins = new FileInputStream(zipFile);
- WritableByteChannel writableByteChannel = Channels.newChannel(os);
- FileChannel fileChannel = ins.getChannel();
- fileChannel.transferTo(0, fileChannel.size(), writableByteChannel);
- fileChannel.close();
- response.setCharacterEncoding("UTF-8");
- name = URLEncoder.encode(name, "UTF-8");
- response.setContentType("application/octet-stream");
- response.addHeader("Content-Disposition", "attachment;filename=" + new String(name.getBytes("iso8859-1")));
- response.setContentLength(os.size());
- response.setHeader("filename", name);
- response.addHeader("Content-Length", "" + os.size());
- var outputstream = response.getOutputStream();
- os.writeTo(outputstream);
- os.flush();
- os.close();
- outputstream.flush();
- outputstream.close();
- writableByteChannel.close();
- if(zipFile.exists()){
- //删除临时文件
- zipFile.delete();
- }
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- try {
- os.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- /**
- * 构建临时zip文件
- **/
- public File compressedFileToZip(String name, List
mediaFileEntityList) { - String zipName = name.concat(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))).concat(".zip");
- //临时zip路径
- String fileZipPath = System.getProperty("user.dir").concat("/").concat(zipName);
- OutputStream os = null;
- ZipOutputStream zos = null;
- File file = new File(fileZipPath);
- try {
- if(!file.exists()){
- file.createNewFile();
- }
- os= new FileOutputStream(file);
- zos = new ZipOutputStream(os) ;
- for (MediaFileEntity entity:mediaFileEntityList
- ) {
- zos.putNextEntry(new ZipEntry(entity.getFileName()));
- //minio 获取流
- InputStream ins = ossService.getObject(OssConfiguration.bucket,entity.getObjectKey());
- FileInputStream insf=convertToFileInputStream(ins);
- WritableByteChannel writableByteChannel = Channels.newChannel(zos);
- FileChannel fileChannel = insf.getChannel();
- fileChannel.transferTo(0, fileChannel.size(), writableByteChannel);
- zos.closeEntry();
- fileChannel.close();
- ins.close();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }finally {
- if(zos != null){
- try {
- zos.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- if(os != null){
- try {
- os.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- return file;
- }
第二种方法:安装hutool依赖,调用hutool包中的ZipUtil工具类中的zip方法进行下载,。此方法需要有3个参数,分别是OutoutStream,每个流对应的文件名字符串数组,文件的InputStream数组。

首先将需要下载的文件找到,拿到一组InputStream,也就是zip方法中的第3个参数,第一个参数顾名思义就是你想要输出的地方,我们是返回给前端所以就是response.getOutputStream(),第二个参数我们遍历文件时也可以拿到,废话不多说了,上代码看吧。
在项目下安装hutool依赖
cn.hutool hutool-all 5.5.7
- /**
- * 下载多个文件转zip压缩包
- *
- * @param mediaFileEntityList
- * @param response
- * @throws Exception
- */
- public void dowloadToZip(List
mediaFileEntityList, HttpServletResponse response) throws Exception { -
- int i = 0;
- //如果有附件 进行zip处理
- if (mediaFileEntityList != null && mediaFileEntityList.size() > 0) {
- try {
- //被压缩文件流集合
- InputStream[] srcFiles = new InputStream[mediaFileEntityList.size()];
- //被压缩文件名称
- String[] srcFileNames = new String[mediaFileEntityList.size()];
- for (MediaFileEntity entity : mediaFileEntityList) {
- //以下代码为获取图片inputStream
- InputStream ins = ossService.getObject(OssConfiguration.bucket,entity.getObjectKey());
- if (ins == null) {
- continue;
- }
- //塞入流数组中
- srcFiles[i] = ins;
- srcFileNames[i] = entity.getFileName();
- i++;
- }
- response.setCharacterEncoding("UTF-8");
- response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("下载.zip", "UTF-8"));
- //多个文件压缩成压缩包返回
- ZipUtil.zip(response.getOutputStream(), srcFileNames, srcFiles);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- }
Controller这边可以直接写成没有返回值的接口,我的例子如下,仅供参考:
- @GetMapping("/{workspace_id}/fileDownList")
- @ApiOperation(value = "查询文件的下载地址")
- public void getFileStreamList(@PathVariable(name = "workspace_id") String workspaceId,
- @RequestParam(name = "ids") String ids, HttpServletResponse response) throws Exception {
-
- List
fileIds= Arrays.stream(ids.split(",")).collect(Collectors.toList()).stream() - .map(Integer::parseInt)
- .collect(Collectors.toList());
- List
mediaFileEntityList = fileService.getMediaListById(workspaceId, fileIds); - // fileUtil.downloadZip("111",mediaFileEntityList,response);//第一种方法
- fileUtil.dowloadToZip(mediaFileEntityList,response);//第二种方法
- }
到此,后端zip下载就完毕了,下面我们说说前端如何处理
网上查询前端处理大致都是如下,但是我自己使用的时候下载总是提示损坏,后找了一个工具类直接调用,就可以了,示例代码请求是get,如需调整,可根据情况自行调整
核心代码如下:
- import { saveAs } from 'file-saver';
- const baseURL = (window as any).config.VITE_APP_BASE_API; //import.meta.env.VITE_APP_BASE_API;
-
- export default {
- zip(url: string, name: string) {
- url = baseURL + url;
- axios({
- method: 'get',
- url: url,
- responseType: 'blob',
- headers: { Authorization: 'Bearer ' + getToken() },
- }).then(res => {
- const isBlob = blobValidate(res.data);
- if (isBlob) {
- const blob = new Blob([res.data], { type: 'application/zip' });
- this.saveAs(blob, name);
- } else {
- this.printErrMsg(res.data);
- }
- });
- },
- saveAs(text: any, name: string, opts?: any) {
- saveAs(text, name, opts);
- },
- async printErrMsg(data: any) {
- const resText = await data.text();
- const rspObj = JSON.parse(resText);
- const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'];
- // ElMessage.error(errMsg);
- },
- blobValidate(data: any) {
- return data.type !== 'application/json';
- }
- };
按钮绑定方法直接调用zip下载方法,传参为url和要导出zip的名称,示例如下:
- import download from '@/plugins/download';
-
- function batchDownload(){
- ElMessage.success("文件下载中,请勿重复点击!");
- download.zip(`/media/api/v1/files/${workspaceId}/fileDownList?ids=${selectlist.value.join(",")}`,"MediaFiles"+new Date().toLocaleDateString()+".zip")
-
- }