🔥利用koa实现前后端文件下载[完整前后端代码]
前言
文件下载功能是一个常用的功能,一个项目中或多或少都会用到,那么这么一个常用的功能,前后端代码如何实现呢?
为什么我们不通过url的方式直接下载?
如果我们使用a标签直接下载会有哪些问题?
- 跨域下载问题:如果要下载的文件与当前页面不在同一个域下,浏览器可能会阻止跨域下载。这是因为浏览器实施了同源策略,限制跨域资源的访问。
- 兼容性问题:使用
<a>
标签直接下载文件可能在不同浏览器之间存在兼容性问题。不同浏览器对文件下载的实现方式和行为可能有所不同。有的浏览器可能就是直接打开文件
上面的问题,有什么解决办法?
- 针对第一种问题,如果是非同源url,可以使用
blob: URLs
和data: URLs
个(不建议使用base64
这种方式,因为如果文件体积大的话,那么base64转化后会变为原体积的4/3,所以base64只适合小文件或者小文本) - 第二种问题可以使用
<a>
标签模拟点击下载,这可以在一定程度上抹平浏览器差异,因为这种方法触发了浏览器的默认行为
不通过url的方式接受,那么我们就通过二进制的方式接受文件
后端是采用buffer还是stream呢?
其实两种都可以
fs和buffer,stream的关系是什么?
从Buffer
到Stream
再到fs
模块,对数据处理的粒度是越来越大的。而Node.js
在fs
模块的实现上确实也是继承了上述两个模块的api
来实现的。
-
继承
Stream
的文件流,对外暴露fs.createReadStream
与fs.createWriteStream
-
对文件的同步与异步操作
fs.readFileSync
与fs.writeFileSync
fs.readFile
与fs.writeFile
-
以上两种
fs
处理文件的形式,区别在于第一种的处理会将文件处理为流的形式。第二种会将文件视作一个整体,统一为整个文件分配内存大小,并将内容放入到一个大的缓冲区中。
前端注意设置responseType
在前端发起请求时,可以通过设置responseType
(注意分清content-type和responseType,content-type是表示请求的中的实体主体的媒体类型,会显示在请求头中。 responseType是指定服务器返回数据的类型,是请求配置信息)属性来指定服务器返回数据的类型,以便浏览器正确解析响应内容。responseType
属性可以设置为以下几种值:
""
或"text"
:默认值,将响应数据解析为字符串。"arraybuffer"
:将响应数据解析为ArrayBuffer
对象。适用于处理二进制数据,例如图片或音频。"blob"
:将响应数据解析为Blob
对象。适用于处理二进制数据,例如文件下载。"document"
:将响应数据解析为Document
对象。适用于处理XML或HTML响应。"json"
:将响应数据解析为JSON对象。适用于处理JSON格式的数据。
通过设置responseType
属性,可以告诉浏览器如何解析响应数据,以便后续对数据进行处理。例如,如果需要下载图片文件,可以将responseType
设置为"blob"
,将响应数据解析为Blob
对象,然后可以通过创建URL或者使用FileReader
等进行进一步处理。
需要注意的是,设置responseType
属性并不会自动转换响应数据的格式,仅仅是告诉浏览器如何解析响应内容。如果服务器返回的数据格式与设置的responseType
不一致,可能会导致解析错误或数据丢失。因此,需要根据服务器返回的数据类型来正确设置responseType
属性。
所以:
-
- 如果我们使用
stream
传输的话,可以设置responseType
为blob
- 如果我们使用
-
- 如果我们使用
buffer
传输的话,可以设置responseType
为arraybuffer
- 如果我们使用
把上面的概念都理清了以后,那么代码就好写了
我们先开始使用stream传输
前端代码
//返回二进制数据或文件流下载
const downLoadFile = async () => {
const res = await axios({
url: "http://localhost:9001/api/download/file",
method: "get",
responseType: "blob", // 返回的就是blob格式
});
// 获取blob
let blob = res.data;
let fileName = "12.jpeg";
//
const url = URL.createObjectURL(blob);
// 创建一个隐藏的链接,并设置其下载属性和链接地址
const link = document.createElement("a");
link.style.display = "none";
link.href = url;
link.download = fileName;
// 将链接添加到页面上,并触发点击事件进行下载
document.body.appendChild(link);
link.click();
// 下载完成后,清除链接和释放URL对象
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
后端代码
router.get('/', (ctx: any) => {
const filename = '1.jpeg';
const filePath = path.resolve(__dirname, `../../public/upload/${filename}`);
const stream = fs.createReadStream(filePath);
ctx.body = stream;
});
使用buffer传输
前端代码
const res = await axios({
url: "http://localhost:9001/api/download/file",
method: "get",
responseType: "arraybuffer",
});
// 获取buffer
let blob = new Blob([res.data]);
let fileName = "12.jpeg";
//
const url = URL.createObjectURL(blob);
// 创建一个隐藏的链接,并设置其下载属性和链接地址
const link = document.createElement("a");
link.style.display = "none";
link.href = url;
link.download = fileName;
// 将链接添加到页面上,并触发点击事件进行下载
document.body.appendChild(link);
link.click();
// 下载完成后,清除链接和释放URL对象
document.body.removeChild(link);
URL.revokeObjectURL(url);
后端代码
router.get('/', (ctx: any) => {
const filename = '1.jpeg';
const filePath = path.resolve(__dirname, `../../public/upload/${filename}`);
const file = fs.readFileSync(filePath);
ctx.body = file;
});
总结
- 上面我们讲了我们为什么不使用url的方式下载,讲了使用url下载的问题,以及针对这些问题我们应该如何去解决
- 讲解了后端采用buffer和stream两种方式获取文件二进制信息
- 讲了针对后端采用不同的方式,前端应该如何去接收
参考
转载自:https://juejin.cn/post/7291496138191061050