使用axios下载文件

2024-08-15 22:16:12 浏览数 (4)

使用axios下载文件

一、介绍

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

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

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

二、使用

1)下载Excel文件

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

前端界面的话,如下所示

image-20220403155847861image-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-20220407232344253image-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>

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

downloaddownload

三、主要代码

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. 由于是前后端分离项目,必定会有前后端跨域的问题,所以请注意跨域问题

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

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

0 人点赞