文件下载方案

  1. 文件下载方案
    1. 单文件下载
      1. IO工具类
    2. 文件夹下载
      1. ZIP工具类
    3. 前端接收文件

文件下载方案

单文件下载

首先要设置好响应头,提供前端解析

这里由于数据库用的utf-8编码所以参数给前端需要对中文字段进行编码

响应头设置


//设置响应头
response.setCharacterEncoding("utf-8");
//比特流
response.setContentType("application/octet-stream");
//设置HTTP响应头,以允许浏览器访问服务器返回的特定的HTTP响应头
response.setHeader("Access-Control-Expose-Headers","Content-Disposition");
//设置文件名,设置字符集是避免文件名中有中文时出现乱码
String encodeFileName = null;
try {
    //UTF-8编码
    encodeFileName = URLEncoder.encode(filePo.getFileName(), StandardCharsets.UTF_8.toString());
} catch (UnsupportedEncodingException e) {
    e.printStackTrace();
}
//设置到响应头
response.addHeader("Content-Disposition", "attachment;filename=" + encodeFileName);

将文件输入流写到输出流

这里流的关闭在工具类里处理了

 OutputStream outputStream = null;
            FileInputStream inputStream = null;
            try {
                outputStream = response.getOutputStream();
                inputStream = new FileInputStream(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
 IOUtils.copy(inputStream,outputStream);

IO工具类

将读到buffer里的字节写到输出流,注意读到的字节个数以保证精确读取

public class IOUtils {

    public static void copy(InputStream inputStream, OutputStream outputStream) {
        try {
            byte[] buffer = new byte[1024];
            int read;
            while (( read = inputStream.read(buffer) ) != -1){
                outputStream.write(buffer,0,read);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                inputStream.close();
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

文件夹下载

通常浏览器下载文件夹不会返回文件夹,这里我采用了返回ZIP文件将文件夹内容打包返回给前端

构建ZIP文件集合

这里是我项目里面的一些逻辑,其实就是创建两种结点,一种文件夹结点,一种文件结点

如果没有文件夹实体的话,要整合的集合value值用null就行了。

然后文件类型的话value放File对象。

key值代表在ZIP中的路径

文件夹的话一定要以/结尾 dir1/dir2/

文件的话带上路径/文件名称 结尾即可 dir1/dir2/output.txt

//构建zip内部文件集合
Set<String> keySet = childFileMap.keySet();
for (String path : keySet) {
    //创建文件夹结点
    zipFileMap.put(path,null);
    //获取到文件持久对象列表
    List<FilePo> filePoList = childFileMap.get(path);
    for (FilePo po : filePoList) {
        //获取文件源绝对路径
        String location = locations.get(po.getSourceId());
        //创建文件对象
        File file = new File(location);
        //创建文件结点
        zipFileMap.put(path+po.getFileName(),file);
    }
}

返回给前端

try {
    //打包为zip返回前端
    ZipFileUtils.zipFilesToOutPutStream(zipFileMap,os);
} catch (IOException e) {
    e.printStackTrace();
}

ZIP工具类

package pers.os467.utils;

import java.io.*;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * by os467
 */
public class ZipFileUtils {

    /**
     * 将输出流包装为zip输出流并按照文件结构映射打包文件
     * @param files key为zip中的路径,分隔符必须使用"/" value为文件对象,若为null如果是目录则创建空目录
     *              例如
     *              新建文件夹1/ -> null
     *              新建文件夹1/新建文件夹2 -> null
     *              新建文件夹/1.txt  -> new File(".../1.txt")
     *              新建文件夹1/新建文件夹2/2.txt -> new File(".../2.txt")
     * @param out 输出流
     * @throws IOException
     */
    public static void zipFilesToOutPutStream(Map<String, File> files, OutputStream out) throws IOException {
        if (files.size() == 0){
            //创建空zip
            try (ZipOutputStream zos = new ZipOutputStream(out)){//do nothing...
                }
            return;
        }
        // 创建一个ZipOutputStream对象,它将用于写入压缩数据
        try (ZipOutputStream zos = new ZipOutputStream(out)) {
            // 遍历文件映射
            for (Map.Entry<String, File> entry : files.entrySet()) {
                // 获取文件的ZIP路径和对应的文件对象
                String name = entry.getKey();
                File file = entry.getValue();

                //如果是目录则创建空目录 例如name = dir1/dir2/
                if (file == null) {
                    zos.putNextEntry(new ZipEntry(name));
                    zos.closeEntry();
                    continue;
                }

                // 如果文件对象不是一个文件,抛出异常
                if (!file.isFile()) {
                    throw new IllegalArgumentException("Only files are supported");
                }

                // 创建一个新的ZIP条目并将其添加到ZIP输出流中  例如: name = dir1/dir2/fileName
                // 这里的name就是文件在压缩包中的路径,包括多级目录
                zos.putNextEntry(new ZipEntry(name));

                // 创建一个输入流来读取文件的内容
                try (InputStream in = new FileInputStream(file)) {
                    // 创建一个缓冲区
                    byte[] buffer = new byte[1024];
                    int len;
                    // 读取文件内容并写入ZIP输出流
                    while ((len = in.read(buffer)) != -1) {
                        zos.write(buffer, 0, len);
                    }
                }
                // 关闭当前ZIP条目,使其写入ZIP文件
                zos.closeEntry();
            }
        }
    }

}

前端接收文件

最简单的就是 location.href = 后端url 让浏览器自行处理下载即可

但是感觉不够灵活,这边写了个axios的方法

原理就是,利用html5提供给<a> 标签的download属性下载文件,浏览器会识别响应为一个可下载内容。

首先创建blob对象,然后创建一个下载链接

创建一个a标签,并附带href属性和文件名

这里文件名注意以下,因为后端用的utf-8编码后传递给前端的 %开头的那种的表示中文的utf8编码,这里先获取下content-dispostion中filename=后面的部分,就是我们设置的文件名

然后用JS的decodeURIComponent来解析即可

最后a标签加入到body然后click触发下载即可。

download: function (fid) {
  /*location.href=`${BACKEND_URL}/download/file/${fid}`*/
  axios.get(`${BACKEND_URL}/download/file/${fid}`,
      {responseType: 'blob'}
  ).then((resp) => {
    /*创建blob对象*/
    const blob = new Blob([resp.data]);
    /*创建下载链接*/
    const url = window.URL.createObjectURL(blob);
    /*创建链接标签*/
    const link = document.createElement('a');
    link.href = url;
    const fileName = resp.headers['content-disposition'].split('=')[1];
    link.setAttribute('download', decodeURIComponent(fileName));
    document.body.appendChild(link);
    link.click();
    /*移除链接标签*/
    document.body.removeChild(link);
  })
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以邮件至 1300452403@qq.com

文章标题:文件下载方案

字数:1.4k

本文作者:Os467

发布时间:2024-01-08, 02:46:12

最后更新:2024-01-08, 02:47:19

原始链接:https://os467.github.io/2024/01/08/%E6%96%87%E4%BB%B6%E4%B8%8B%E8%BD%BD%E6%96%B9%E6%A1%88/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

×

喜欢就点赞,疼爱就打赏