概要
在前端下载文件是个很通用的需求,一般后端会提供下载的方式有两种:
- 直接返回文件的网络地址(一般用在静态文件上,比如图片以及各种音视频资源等)
- 返回文件流(一般用在动态文件上,比如根据前端选择,导出不同的统计结果 excel 等)
第一种方式比较简单,但是使用场景有限。
第二种方式通用性更好,最近再使用 antd 开发的过程中,下载文件部分折腾了一下午,于是将关键的部分和遇到的一些问题整理如下。
前端核心代码
我的前端是基于 antd pro 开发的,这里不在详细介绍 antd pro 相关的内容,只说明下封装的下载函数:
import { request } from "umi";
// 这是我在项目中封装的下载函数,有2个参数:
// 一个是文件的id,用来给后端API搜索文件用的
// 一个是文件名filename,这个给前端用的,下载时,默认保存的文件名
export async function DownloadFile(id: string, filename: string) {
return RestGet<BlobPart>(`/api/v1/file/download/${id}`, "blob").then(
(res) => {
let url = URL.createObjectURL(new Blob([res], { type: "octet/stream" }));
let a = document.createElement("a");
a.href = url;
a.download = filename;
a.click();
URL.revokeObjectURL(url);
}
);
}
// GET 方式请求后端API
export async function RestGet<T>(
url: string,
responseType: "json" | "blob" = "json"
) {
return request<T>(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
responseType: responseType,
});
}
// POST 方式请求后端API
export async function RestPost<T>(
url: string,
body: any,
responseType: "json" | "blob" = "json"
) {
return request<T>(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
data: body,
responseType: responseType,
});
}
这个下载函数本质就是创建一个 <a>
元素,然后模拟点击此 <a>
元素来完成下载。
对于 DownloadFile
函数,我是根据自己的需要封装的,不一定非得是这两个参数,可以根据自己的后端 API 来定制需要传入的参数。
将下载封装成函数之后看,前端就可以通过一个按钮来下载文件了,比如:
<Button
type="link"
key="download"
icon={<DownloadOutlined />}
onClick={() =>
DownloadFile(item.video_file_id as string, (item.name as string) + ".mp4")
}
>
下载
</Button>
我这里是 GET 方式下载文件的,如果参数比较多且复杂的话,也可以使用 POST 方式。
只要把 DownloadFile
中 RestGet<T>
改成 RestPost<T>
,并调整相应的参数即可。
遇到的问题
调试其中的下载方式就不提了,遇到的最大困难,搜索半天也没什么人提到的就是请求中的 responseType
问题。
刚开始,我没有设置 responseType
,默认值好像是 json
或者 text
。
测试时发现,下载文本类型的文件没有问题,但是下载二进制文件的话,比如视频或者图片,下载之后总是无法正常打开,前后端也没有任何错误提示。
折腾了半天,才试出 responseType
传入 blob
的话,二进制文件才能正常下载。
而且发现,设置成 blob
的话,文本类型的文件也能正常下载打开。