Spring boot + Vue axios 文件下载

后端代码:

@GetMapping("/{sn}")
    @ApiOperation(value = "获取文件",notes = "获取文件")
    public void downloadFile(@PathVariable String sn, HttpServletResponse response) throws Exception {
        SysFileEntity sysFile = fileService.findBySn(sn);
        if (sysFile != null){
            File file = new File(sysFile.getFilePath());
            if (file.exists()){
                // 设置强制下载不打开
                response.setContentType("application/force-download");
                // 设置文件名
                response.addHeader("Content-disposition", "attachment;fileName=" + URLEncoder.encode(file.getName(), StandardCharsets.UTF_8));
                response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
                FileInputStream fis = null;
                BufferedInputStream bis = null;
                byte[] buffer = new byte[1024];
                try {
                    fis = new FileInputStream(file);
                    bis = new BufferedInputStream(fis);
                    OutputStream os = response.getOutputStream();

                    int i = bis.read(buffer);
                    while (i != -1) {
                        os.write(buffer, 0, i);
                        i = bis.read(buffer);
                    }

                }
                finally {
                    if (bis != null) {
                        try {
                            bis.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if (fis != null) {
                        try {
                            fis.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

前端代码:

//封装的axios
this.$http.request({
        method: "get",
        url: `${this.$apis.common.file}/${this.ruleForm.fileSN}`,
        data: {},
		//此处可以无视,前端封装全局加载遮罩
        loading: false,
        responseType: 'blob'
      }).then(res => {

        const fileName = decodeURIComponent(res.headers["content-disposition"]).split("=")[1];
        const _res = res.data;
        let blob = new Blob([_res]);
        let downloadElement = document.createElement("a");
        //创建下载的链接
        let href = window.URL.createObjectURL(blob);
        downloadElement.href = href;
        //下载后文件名
        downloadElement.download = fileName;
        document.body.appendChild(downloadElement);
        //点击下载
        downloadElement.click();
        //下载完成移除元素
        document.body.removeChild(downloadElement);
        //释放掉blob对象
        window.URL.revokeObjectURL(href);

      })

常见问题总结

  1. 前端接收到的响应头中不包含 “content-disposition”,但是在浏览器的网络中可以看到,这里需要后端在响应头中增加

    response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
    
  2. 文件名返回到前端后乱码

    此处需要在后端对文件名转码

    //StandardCharsets.UTF_8 等价于"UTF8"
    URLEncoder.encode(file.getName(), StandardCharsets.UTF_8)
    

    在前端文件解码

    此处是把整个 “content-disposition”响应头的内容解码后再取文件名。也可以先取文件名再解码

    decodeURIComponent(res.headers["content-disposition"])
    
  3. 注意自己的前端框架里封装的axios是否有返回数据的拦截处理

    我这里因为需要与后端进行身份验证所以还是用的框架内封装好的请求方法。但是需要对返回的文件数据与其他区分出来

image