前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用axios下载文件

使用axios下载文件

原创
作者头像
半月无霜
发布2024-08-15 22:16:12
1960
发布2024-08-15 22:16:12
举报
文章被收录于专栏:半月无霜

使用axios下载文件

一、介绍

在前后端分离的开发项目中,我们常常有下载文件或者报表的需求。

如果只是简单的下载,我们可以简单使用a标签请求后端就可以了,不过一旦涉及到后端报错的回调、等待动画、进度条这种的,就没有任何办法了。

所以,这里可以使用axios进行请求,获取到后端的文件流后,自己进行生成文件。这样就可以完成上面的那三种情况了。

二、使用

1)下载Excel文件

我们点击下载按钮,将表单内容传入,返回一个对应的excel文件。

前端界面的话,如下所示

image-20220403155847861
image-20220403155847861

定义一下UserDTO.java,用来进行传参

代码语言:java
复制
package com.example.demo.dto;
​
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
​
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDTO {
​
    private String name;
​
    private String sex;
​
    private Integer age;
}

定义一下ResultData.java,用来统一后端的响应

代码语言:java
复制
package com.example.demo.dto;
​
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
​
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ResultData<T> {
​
    private Integer errCode;
​
    private String errMsg;
​
    private T data;
​
    public static ResultData success(){
        return new ResultData(0, "", null);
    }
​
    public static ResultData fail(String errMsg){
        return new ResultData(-1, errMsg, null);
    }
​
}

再写一个TestController.java,用来处理下载请求

代码语言:java
复制
package com.example.demo.controller;
​
import cn.hutool.poi.excel.ExcelUtil;
import cn.hutool.poi.excel.ExcelWriter;
import com.example.demo.dto.ResultData;
import com.example.demo.dto.UserDTO;
import com.example.demo.utils.MyFileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
​
import javax.servlet.http.HttpServletResponse;
​
@Slf4j
@RestController
@CrossOrigin(exposedHeaders = {"Content-disposition", "Access-Control-Allow-Origin"})
@RequestMapping("/test")
public class TestController {
​
    @RequestMapping("/download")
    public ResultData download(@RequestBody UserDTO userDTO, HttpServletResponse response){
        if(userDTO.getAge()>18)
            return ResultData.fail("愿你永远18岁");
        try {
            ExcelWriter writer = ExcelUtil.getWriter(true);
            writer.writeRow(userDTO, true);
            MyFileUtil.downloadFile(response, writer, "用户示例.xlsx");
            return null;
        } catch (Exception e) {
            log.error("出错了");
            return ResultData.fail("网络波动,请稍后再试");
        }
    }
​
}

还有一个MyFileUtil.java,用来对外输入

代码语言:java
复制
package com.example.demo.utils;
​
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.poi.excel.ExcelWriter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
​
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
​
@Slf4j
@Component
public class MyFileUtil {
​
    public static void downloadFile(HttpServletResponse response, ExcelWriter writer, String filename){
        ServletOutputStream out = null;
        try {
            out = response.getOutputStream();
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            response.setHeader("Content-Disposition","attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
​
            writer.flush(out, true);
        } catch (IOException e) {
            log.error("io异常", e);
        } finally {
            writer.close();
            IoUtil.close(out);
        }
    }
​
    public static void downloadFile(HttpServletResponse response, File file, String filename){
        OutputStream out = null;
        try {
            out = response.getOutputStream();
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition","attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
            response.setHeader("Content-Length", String.valueOf(FileUtil.size(file)));
​
            BufferedInputStream fis = new BufferedInputStream(new FileInputStream(file.getPath()));
            OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
            IoUtil.copy(fis, toClient);
            out.flush();
        } catch (Exception e) {
            log.error("io异常", e);
        } finally {
            IoUtil.close(out);
        }
    }
​
}

这样,后端就准备完成了,接下来看看前端怎么写

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
​
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>
<body>
    <div id="app">
        <h2>下载Excel</h2>
        <el-form :model="formData" label-width="80px" style="width: 300px;" size="mini">
            <el-form-item label="姓名">
                <el-input v-model="formData.name" style="width: 200px;"></el-input>
            </el-form-item>
            <el-form-item label="性别">
                <el-radio-group v-model="formData.sex">
                    <el-radio label="男">男</el-radio>
                    <el-radio label="女">女</el-radio>
                </el-radio-group>
            </el-form-item>
            <el-form-item label="年龄">
                <el-input-number v-model="formData.age" style="width: 200px;" controls-position="right" :min="1" :max="100"></el-input-number>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" @click="download">下载</el-button>
            </el-form-item>
        </el-form>
    </div>
​
    <script>
        const vm = new Vue({
            el: "#app",
            data: {
                formData: {
                    name: "半月无霜",
                    sex: "男",
                    age: 18
                },
            },
            methods: {
                download(){
                    let url = "http://localhost:8080/test/download"
                    axios.post(url, this.formData, {
                        responseType: 'arraybuffer'
                    }).then(res => {
                        window.downloadExcel(res);
                    }).catch(error => {
                        
                    })
                }
            },
        })
​
        // 得到文件流后,前端生成文件,创建出a标签进行点击
        var downloadExcel = function (res) {
            if (!res) {
                return;
            }
            const fileName = res.headers["content-disposition"].split("=")[1];
            const blob = new Blob([res.data], {
                type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8",
            });
            const url = window.URL.createObjectURL(blob);
            const aLink = document.createElement("a");
            aLink.style.display = "none";
            aLink.href = url;
            aLink.setAttribute("download", decodeURI(fileName));
            document.body.appendChild(aLink);
            aLink.click();
            document.body.removeChild(aLink);
            window.URL.revokeObjectURL(url);
        }
    </script>
</body>
</html>

前端就就是这样的,你说没有异常显示和Loading加载?这很简单,自己加上去吧

2)下载其他文件

在测试的时候,发现了excel文件有一定的特殊性,若是平常的文件,可以这样子做。

这里以gif图片为例,来进行下载。

首先是后端,下载请求controller控制器,

代码语言:java
复制
package com.example.demo.controller;
​
import cn.hutool.core.io.FileUtil;
import com.example.demo.utils.MyFileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
​
import javax.servlet.http.HttpServletResponse;
import java.io.File;
​
@Slf4j
@RestController
@CrossOrigin(exposedHeaders = {"Content-disposition", "Access-Control-Allow-Origin"})
@RequestMapping("/test")
public class TestController {
​
    @RequestMapping("/downloadImage")
    public String downloadImage(@RequestParam String imgPath, HttpServletResponse response){
        if(FileUtil.exist(imgPath)){
            File file = new File(imgPath);
            String suffix = FileUtil.getSuffix(file);
            MyFileUtil.downloadFile(response, file, "图片文件测试." + suffix);
            return "成功";
        }
        return "失败";
    }
​
}

MyFileUtil.java就不贴出来了,上面就有

前端代码,这次responseType设置为blob

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>测试</title>
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
​
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>
<body>
    <div id="app">
        <h2>下载图片</h2>
        <form>
            图片地址:{{ imgPath }}<br>
            <el-button type="primary" @click="downloadImage" size="mini">下载</el-button>
        </form>
    </div>
​
    <script>
        const vm = new Vue({
            el: "#app",
            data: {
                imgPath: "E:\\repository\\aaa.gif"
            },
            methods: {
                downloadImage(){
                    let url = "http://localhost:8080/test/downloadImage";
                    axios({
                        url: url,
                        method: "post",
                        params: {
                            imgPath: this.imgPath
                        },
                        responseType: 'blob',
                    }).then(res => {
                        window.downloadFile(res);
                    })
                }
            },
        })
​
        var downloadFile = function (res) {
            if (!res) {
                return;
            }
            const fileName = res.headers["content-disposition"].split("=")[1];
            const blob = new Blob([res.data], {
                type: 'application/zip'
            });
            const url = window.URL.createObjectURL(blob);
            const aLink = document.createElement("a");
            aLink.style.display = "none";
            aLink.href = url;
            aLink.setAttribute("download", decodeURI(fileName));
            document.body.appendChild(aLink);
            aLink.click();
            document.body.removeChild(aLink);
            window.URL.revokeObjectURL(url);
        }
    </script>
</body>
</html>

界面是这样的,十分简单,点击按钮就可进行下载了

image-20220407232344253
image-20220407232344253

3)下载进度条

如果我们想展示下载的进度条,那该怎么办,UI样式我们就选ElementUI,这次我们需要用到axios中一个叫onDownloadProgress的参数,它允许为下载处理进度事件

修改一下后端,为后端增加一个方法

代码语言:java
复制
package com.example.demo.controller;
​
import cn.hutool.core.io.FileUtil;
import com.example.demo.utils.MyFileUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
​
import javax.servlet.http.HttpServletResponse;
import java.io.File;
​
@Slf4j
@RestController
@CrossOrigin(exposedHeaders = {"Content-disposition", "Access-Control-Allow-Origin"})
@RequestMapping("/test")
public class TestController {
​
    @RequestMapping("/downloadProgress")
    public String downloadProgress(HttpServletResponse response){
        // 尽量选择一个比较大的文件,50MB左右
        File file = new File("E:\\repository\\123.exe");
        String suffix = FileUtil.getSuffix(file);
        MyFileUtil.downloadFile(response, file, "进度条下载测试." + suffix);
        return "成功";
    }
​
}

前端的样式及请求

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
​
<head>
    <meta charset="UTF-8">
    <title>测试</title>
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
​
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>
​
<body>
    <div id="app">
        <h2>进度条</h2>
        <el-button type="primary" @click="downloadProgress" size="mini">下载</el-button>
        <el-progress :percentage="percentage"></el-progress>
    </div>
​
    <script>
        const vm = new Vue({
            el: "#app",
            data: {
                percentage: 0,
            },
            methods: {
                downloadProgress() {
                    let url = "http://localhost:8080/test/downloadProgress";
                    this.percentage = 0
                    axios({
                        url: url,
                        method: "post",
                        responseType: 'blob',
                        onDownloadProgress: (e) => {
                            console.log(e);
                            this.percentage = Math.round(e.loaded / e.total * 100);
                        }
                    }).then(res => {
                        window.downloadFile(res);
                    }).catch(error => {
​
                    })
                }
            },
        })
​
        var downloadFile = function (res) {
            if (!res) {
                return;
            }
            const fileName = res.headers["content-disposition"].split("=")[1];
            const blob = new Blob([res.data], {
                type: 'application/zip'
            });
            const url = window.URL.createObjectURL(blob);
            const aLink = document.createElement("a");
            aLink.style.display = "none";
            aLink.href = url;
            aLink.setAttribute("download", decodeURI(fileName));
            document.body.appendChild(aLink);
            aLink.click();
            document.body.removeChild(aLink);
            window.URL.revokeObjectURL(url);
        }
    </script>
</body>
​
</html>

样式就像这样,当我们点击按钮,根据下载进度展示进度条

download
download

三、主要代码

1)后端

主要是自己定义的这个MyFileUtil.java

代码语言:java
复制
package com.example.demo.utils;
​
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.poi.excel.ExcelWriter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
​
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
​
@Slf4j
@Component
public class MyFileUtil {
​
    public static void downloadFile(HttpServletResponse response, ExcelWriter writer, String filename){
        ServletOutputStream out = null;
        try {
            out = response.getOutputStream();
            response.setContentType("application/vnd.ms-excel;charset=utf-8");
            response.setHeader("Content-Disposition","attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
​
            writer.flush(out, true);
        } catch (IOException e) {
            log.error("io异常", e);
        } finally {
            writer.close();
            IoUtil.close(out);
        }
    }
​
    public static void downloadFile(HttpServletResponse response, File file, String filename){
        OutputStream out = null;
        try {
            out = response.getOutputStream();
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition","attachment;filename=" + URLEncoder.encode(filename, "UTF-8"));
            response.setHeader("Content-Length", String.valueOf(FileUtil.size(file)));
​
            BufferedInputStream fis = new BufferedInputStream(new FileInputStream(file.getPath()));
            OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
            IoUtil.copy(fis, toClient);
            out.flush();
        } catch (Exception e) {
            log.error("io异常", e);
        } finally {
            IoUtil.close(out);
        }
    }
​
}

2)前端

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
​
<head>
    <meta charset="UTF-8">
    <title>测试</title>
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
​
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
</head>
​
<body>
    <div id="app">
        <h2>下载Excel</h2>
        <el-form :model="formData" label-width="80px" style="width: 300px;" size="mini">
            <el-form-item label="姓名">
                <el-input v-model="formData.name" style="width: 200px;"></el-input>
            </el-form-item>
            <el-form-item label="性别">
                <el-radio-group v-model="formData.sex">
                    <el-radio label="男">男</el-radio>
                    <el-radio label="女">女</el-radio>
                </el-radio-group>
            </el-form-item>
            <el-form-item label="年龄">
                <el-input-number v-model="formData.age" style="width: 200px;" controls-position="right" :min="1"
                    :max="100"></el-input-number>
            </el-form-item>
            <el-form-item>
                <el-button type="primary" @click="download">下载</el-button>
            </el-form-item>
        </el-form>
​
        <hr>
        <h2>下载图片</h2>
        <form>
            图片地址:{{ imgPath }}<br>
            <el-button type="primary" @click="downloadImage" size="mini">下载</el-button>
        </form>
​
        <hr>
        <h2>进度条</h2>
        <el-button type="primary" @click="downloadProgress" size="mini">下载</el-button>
        <el-progress :percentage="percentage"></el-progress>
    </div>
​
    <script>
        const vm = new Vue({
            el: "#app",
            data: {
                formData: {
                    name: "半月无霜",
                    sex: "男",
                    age: 18
                },
                imgPath: "E:\\repository\\aaa.jpg",
                percentage: 0,
            },
            methods: {
                download() {
                    let url = "http://localhost:8080/test/download";
                    let loading = this.$loading({
                        text: "正在下载"
                    });
                    axios.post(url, this.formData, {
                        responseType: 'arraybuffer'
                    }).then(res => {
                        console.log(res);
                        if (res.headers["content-type"] == "application/json") {
                            let resjson = JSON.parse(ab2str(res.data));
                            this.$message.error(resjson.errMsg);
                        } else {
                            window.downloadExcel(res);
                        }
                        loading.close();
                    }).catch(error => {
                        this.$message.error(error);
                    })
                },
                downloadImage() {
                    let url = "http://localhost:8080/test/downloadImage";
                    axios({
                        url: url,
                        method: "post",
                        params: {
                            imgPath: this.imgPath
                        },
                        responseType: 'blob',
                    }).then(res => {
                        window.downloadFile(res);
                    })
                },
                downloadProgress() {
                    let url = "http://localhost:8080/test/downloadProgress";
                    this.percentage = 0
                    axios({
                        url: url,
                        method: "post",
                        responseType: 'blob',
                        onDownloadProgress: (e) => {
                            console.log(e);
                            this.percentage = Math.round(e.loaded / e.total * 100);
                        }
                    }).then(res => {
                        window.downloadFile(res);
                    }).catch(error => {
​
                    })
                }
            },
        })
​
        var downloadExcel = function (res) {
            if (!res) {
                return;
            }
            const fileName = res.headers["content-disposition"].split("=")[1];
            const blob = new Blob([res.data], {
                type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8",
            });
            const url = window.URL.createObjectURL(blob);
            const aLink = document.createElement("a");
            aLink.style.display = "none";
            aLink.href = url;
            aLink.setAttribute("download", decodeURI(fileName));
            document.body.appendChild(aLink);
            aLink.click();
            document.body.removeChild(aLink);
            window.URL.revokeObjectURL(url);
        }
​
        var downloadFile = function (res) {
            if (!res) {
                return;
            }
            const fileName = res.headers["content-disposition"].split("=")[1];
            const blob = new Blob([res.data], {
                type: 'application/zip'
            });
            const url = window.URL.createObjectURL(blob);
            const aLink = document.createElement("a");
            aLink.style.display = "none";
            aLink.href = url;
            aLink.setAttribute("download", decodeURI(fileName));
            document.body.appendChild(aLink);
            aLink.click();
            document.body.removeChild(aLink);
            window.URL.revokeObjectURL(url);
        }
​
        function ab2str(buf) {
            let encodedString = String.fromCharCode.apply(null, new Uint8Array(buf));
            let decodedString = decodeURIComponent(escape(encodedString));
            return decodedString;
        }
​
        function ab2hex(buffer) {
            const hexArr = Array.prototype.map.call(new Uint8Array(buffer), function (bit) {
                return ('00' + bit.toString(16)).slice(-2)
            })
            return hexArr.join('');
        }
    </script>
</body>
​
</html>

四、总结

基本上来说,上面的方法步骤都是一样的,只是流的类型不同。

  1. 后端返回流,类型设置为application/vnd.ms-excel;charset=utf-8或者application/octet-stream
  2. 前端axios请求,responseType设置为arraybuffer或者blob
  3. 得到文件流后,前端生成文件,创建出模拟a标签进行点击

需要注意的点:

  1. 后端如果成功生成流并返回,controller上直接返回null即可
  2. 由于是前后端分离项目,必定会有前后端跨域的问题,所以请注意跨域问题

千万不要等用到的时候,才到处翻博客

我是半月,祝你幸福!!!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用axios下载文件
    • 一、介绍
      • 二、使用
        • 1)下载Excel文件
        • 2)下载其他文件
        • 3)下载进度条
      • 三、主要代码
        • 1)后端
        • 2)前端
      • 四、总结
      相关产品与服务
      腾讯云 BI
      腾讯云 BI(Business Intelligence,BI)提供从数据源接入、数据建模到数据可视化分析全流程的BI能力,帮助经营者快速获取决策数据依据。系统采用敏捷自助式设计,使用者仅需通过简单拖拽即可完成原本复杂的报表开发过程,并支持报表的分享、推送等企业协作场景。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档