资讯专栏INFORMATION COLUMN

文件下载那点事

PascalXie / 1766人阅读

摘要:不过这种方式有问题,目前查到的大部分过程都是会在服务器新建出一个文件,等下载完毕在做删除,还没有找到可以跨过这一步的方式。

Content-Disposition / Content-Type Content-Disposition

http 头部的 Content-Disposition字段,规定了返回的内容用什么形式展示

value 含义 是否默认
inline 以网页或者页面的一部分
attachment 以附件的形式下载并保存到本地

</>复制代码

  1. http.createServer((req, res) => {
  2. res.setHeader("Content-Disposition", "attachment")
  3. res.end("123 - 321 - 1234567")
  4. })

前端需要使用window.open 形式访问 此路由就可以实现文件的下载

</>复制代码

  1. window.open(xxxx)

或者使用 H5新属性 a 标签

</>复制代码

  1. 点击下载
Content-Type

</>复制代码

  1. http.createServer((req, res) => {
  2. res.setHeader("Content-Type", "application/octet-stream")
  3. res.end("123 - 321 - 1234567")
  4. })

同上前端使用 open 或者 a标签进行处理

备注: 如果使用普通的请求,是不可以的,比如使用ajax

window.open

window.open 可以下载文件的原因是,浏览器遇到无法解析的文件就是执行下载

当使用浏览器打开文件时, 如果它无法解析,那么就会把该文件下载下来

</>复制代码

  1. http.createServer((req, res) => {
  2. const data = fs.readFileSync("./Zip.zip")
  3. res.end(data)
  4. })

拿到的data是一个buffer 对象,那么直接写一个Buffer可以实现下载么

</>复制代码

  1. data = new Buffer("我是谁,谁是我")

尝试后发现,koa是可以的。但是原生直接这么写是不行的

</>复制代码

  1. http.createServer((req, res) => {
  2. const newBuf = new Buffer("我是谁,谁是我")
  3. res.end(newBuf)
  4. })
通过接口拿到数据之后进行下载

在实际项目中,文件下载可能出现的场景

对于已经在服务器存在的文件进行下载,比如图片资源

将一些查询数据导出到本地, 比如mysql查询结果导出csv

对于已存在的资源,可以直接使用 上面说的 winodw.open 或者 a 标签进行下载,那么对于不是以文件形式存在的资源呢

data URI

经常见到使用 data URI scheme 的是图片, 一般为了减少http请求,会将图片直接以base64的形式展示在html中

</>复制代码

也可以应用到文件下载中

</>复制代码

  1. router.get("/download", async (ctx, next) => {
  2. const newBUf = new Buffer("我是谁,谁是我")
  3. ctx.body = newBUf
  4. }

</>复制代码

  1. axios.get(`${path}`)
  2. .then(function ({data}) {
  3. let a = document.createElement("a");
  4. a.href = "data:text/plain;charset=utf-8," + data;
  5. a.download = "myfilename.png";
  6. a.click();
  7. // 记得处理临时元素 防止内存泄漏
  8. a = null
  9. })
Blob

Blob 是表示一个类文件对象,可以用它来表示一个文件

server 部分

</>复制代码

  1. http.createServer((req, res) => {
  2. res.setHeader("Access-Control-Allow-Origin", "*");
  3. let end = ""
  4. if (req.url.includes("/down")) {
  5. const newBUf = new Buffer("我是谁,谁是我")
  6. end = newBUf
  7. }
  8. res.end(end)
  9. }

前端

</>复制代码

  1. const xhr = new XMLHttpRequest();
  2. xhr.open("GET", path);
  3. xhr.responseType = "blob";
  4. xhr.onload = function () {
  5. const blob = xhr.response;
  6. const url = URL.createObjectURL(blob);
  7. // 通过a标签去下载
  8. const link = document.createElement("a");
  9. link.href = url;
  10. link.download = fileName;
  11. link.click();
  12. link = null
  13. URL.revokeObjectURL(url);
  14. };
  15. xhr.send();

尝试用 window.open 方式,发现不可以

目前常用的各个第三方库也支持返回内容为blob格式

</>复制代码

  1. axios.get(`${path}`, {
  2. responseType: "blob"
  3. })

或者 fetch

</>复制代码

  1. fetch(path).then(res => res.blob().then(blob =>{ ... })
window.URL

createObjectURL

用 blob 对象来创建一个 object URL(它是一个 DOMString),我们可以用这个 object URL 来表示某个 blob 对象,这个 object URL 可以用在 a 标签的 href属性上,然后触发点击事件,就可以下载文件了

revokeObjectURL

为了避免避免内存泄漏,需要手动释放创建的 object URL

缺点

构建完 blob 对象后才会转换成文件

从代码就能看出,需要先将返回内容转为blob才进行下载操作,如果用户操作的是一个很大的资源,在等待文件正式下载前,还需要一段时间等待格式的转化

实例 将mysql数据 导出 csv

这边直接用数据模拟mysql查询结果, 在网上查到两种拼接方式,肯定有其余的方案

第一种

</>复制代码

  1. const result = [
  2. ["id", "oreder", "name", "status" ],
  3. [1, "201904120201", "正在加载", "已完成"],
  4. [18, "201904120204", "测试189", "待付款"],
  5. [22, "201904120209", "蓝田日暖", "待付款"],
  6. ]
  7. // 可以看到 result 数组的第一组是 csv的各个字段标题
  8. const data = csvData.reduce((cur, next) => `${cur + next.join(",")}
  9. `, "")
  10. const blob = new Blob([data]);
  11. const url = URL.createObjectURL(blob);
  12. let a = document.createElement("a");
  13. a.href = url;
  14. a.download = filename;
  15. a.click();
  16. a = null
  17. window.URL.revokeObjectURL(url);

第二种

</>复制代码

  1. const result = [
  2. [1, "201904120201", "正在加载", "已完成"],
  3. [18, "201904120204", "测试189", "待付款"],
  4. [22, "201904120209", "蓝田日暖", "待付款"],
  5. ]
  6. // result 就是正常的数据格式
  7. // 解决乱码问题
  8. let dataType = "uFEFF"
  9. // 添加表格的头子段
  10. dataType += ([" 订单编号", "用户", "动态ID"].join(","))
  11. dataType += "
  12. "
  13. csvData.forEach(item => {
  14. dataType += ([item.id, item.order, item.name, item.staus].join(","))
  15. dataType += "
  16. "
  17. })
  18. const blob = new Blob([dataType], {type: "text/csv"})
如果有几段流文件,前端需要下载拼接成一个完整文件,如何实现

肯定是在 server端进行文件的拼接操作,然后返回到前端下载

大致过程

</>复制代码

  1. const content1 = "这里是第1段文件内容"
  2. const content2 = "这里是第2段文件内容"
  3. ctx.body = { code:200, data: content1 + content2 }

前端就是上文中的blob 下载方式了

批量下载如何处理

其实这个问题是在查询资料的过程有人提到的问题

window.open

批量写了多个 window.open 在Chrome中 可以正常使用,但是在safari中,只能打开一个窗口,下载一个文件

转为下载一个zip 文件

感觉如果将所有文件合并成为一个xxx.zip 然后对 这个zip文件做下载。不过这种方式有问题,目前查到的大部分过程都是会在服务器新建出一个 zip 文件,等下载完毕在做删除,还没有找到可以跨过这一步的方式。

参考文章

Data URI Scheme

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/103491.html

相关文章

  • 移动端键盘和光标的兼容点事

    摘要:解决方法如果使用页面数据不超过一屏禁止滚动,那么即使变成了页面也不会有什么变化。 作者:@micky思 @wupq @yewq 在H5的开发中,个人的制作页面布局习性不同,多多少少会产生在真机上input的光标和键盘的弹出会出现的各种BUG,文中整理了部分遇到的问题,欢迎新增 ios移动端输入框上浮导致输入位置偏移 问题原因:遮罩层定位为fixed,当键盘弹起时,ios11以及以下...

    XboxYan 评论0 收藏0
  • 移动端键盘和光标的兼容点事

    摘要:解决方法如果使用页面数据不超过一屏禁止滚动,那么即使变成了页面也不会有什么变化。 作者:@micky思 @wupq @yewq 在H5的开发中,个人的制作页面布局习性不同,多多少少会产生在真机上input的光标和键盘的弹出会出现的各种BUG,文中整理了部分遇到的问题,欢迎新增 ios移动端输入框上浮导致输入位置偏移 问题原因:遮罩层定位为fixed,当键盘弹起时,ios11以及以下...

    Kerr1Gan 评论0 收藏0
  • 移动端键盘和光标的兼容点事

    摘要:解决方法如果使用页面数据不超过一屏禁止滚动,那么即使变成了页面也不会有什么变化。 作者:@micky思 @wupq @yewq 在H5的开发中,个人的制作页面布局习性不同,多多少少会产生在真机上input的光标和键盘的弹出会出现的各种BUG,文中整理了部分遇到的问题,欢迎新增 ios移动端输入框上浮导致输入位置偏移 问题原因:遮罩层定位为fixed,当键盘弹起时,ios11以及以下...

    Jackwoo 评论0 收藏0
  • 关于性能优化的点事——函数防抖

    摘要:函数防抖场景假设网站有个搜索框用户输入文本我们会自动联想匹配出一些结果供用户选择我们可能首先想到的做法就是监听事件然后异步查询结果但是如果用户快速的输入了一串字符假设是个字符那么就会在瞬间触发次请求这无疑不是我们想要的我们想要的是用户停止输 函数防抖 场景 假设网站有个搜索框, 用户输入文本我们会自动联想匹配出一些结果供用户选择,我们可能首先想到的做法就是监听keypress事件, 然...

    Stardustsky 评论0 收藏0
  • 关于var,let,const的点事

    摘要:一直使用定义变量,的出现给变量定义增加了两个大将,。声明的变量,块作用域,不重复声明覆盖,限制了变量的作用域,保证变量不会去污染全局变量,所以尽量将改为用。 一直使用var定义变量,ES6的出现给变量定义增加了两个大将let,const。那它们有什么区别呢。 1、const关键字它的作用就是定义一个常量,一旦定义无法更改,不能重复声明覆盖; showImg(https://segmen...

    KavenFan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<