乐趣区

文件下载那点事

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

value
含义
是否默认

inline
以网页或者页面的一部分

attachment
以附件的形式下载并保存到本地

http.createServer((req, res) => {
res.setHeader(“Content-Disposition”, “attachment”)
res.end(‘123 – 321 – 1234567′)
})
前端需要使用 window.open 形式访问 此路由就可以实现文件的下载
window.open(xxxx)
或者使用 H5 新属性 a 标签
<a type=’download’ href=xxx> 点击下载 </a>
Content-Type
http.createServer((req, res) => {
res.setHeader(“Content-Type”, “application/octet-stream”)
res.end(‘123 – 321 – 1234567’)
})
同上前端使用 open 或者 a 标签进行处理
备注:如果使用普通的请求,是不可以的,比如使用 ajax
window.open
window.open 可以下载文件的原因是,浏览器遇到无法解析的文件就是执行下载
当使用浏览器打开文件时,如果它无法解析,那么就会把该文件下载下来
http.createServer((req, res) => {
const data = fs.readFileSync(‘./Zip.zip’)
res.end(data)
})
拿到的 data 是一个 buffer 对象,那么直接写一个 Buffer 可以实现下载么
data = new Buffer(‘ 我是谁,谁是我 ’)
尝试后发现,koa 是可以的。但是原生直接这么写是不行的
http.createServer((req, res) => {
const newBuf = new Buffer(‘ 我是谁,谁是我 ’)
res.end(newBuf)
})
通过接口拿到数据之后进行下载
在实际项目中,文件下载可能出现的场景

对于已经在服务器存在的文件进行下载,比如图片资源
将一些查询数据导出到本地,比如 mysql 查询结果导出 csv

对于已存在的资源,可以直接使用 上面说的 winodw.open 或者 a 标签进行下载,那么对于不是以文件形式存在的资源呢
data URI
经常见到使用 data URI scheme 的是图片, 一般为了减少 http 请求,会将图片直接以 base64 的形式展示在 html 中
<img src=’data:image/png;base64,xxxxxx’/>
也可以应用到文件下载中
router.get(‘/download’, async (ctx, next) => {
const newBUf = new Buffer(‘ 我是谁,谁是我 ’)
ctx.body = newBUf
}
axios.get(`${path}`)
.then(function ({data}) {
let a = document.createElement(‘a’);
a.href = “data:text/plain;charset=utf-8,” + data;
a.download = “myfilename.png”;
a.click();
})
Blob
Blob 是表示一个类文件对象,可以用它来表示一个文件
server 部分
http.createServer((req, res) => {
res.setHeader(“Access-Control-Allow-Origin”, “*”);
let end = ”
if (req.url.includes(“/down”)) {
const newBUf = new Buffer(‘ 我是谁,谁是我 ’)
end = newBUf
}
res.end(end)
}
前端
const xhr = new XMLHttpRequest();
xhr.open(‘GET’, path);
xhr.responseType = ‘blob’;

xhr.onload = function () {
const blob = xhr.response;
const url = URL.createObjectURL(blob);
// 通过 a 标签去下载
const link = document.createElement(‘a’);
link.href = url;
link.download = fileName;
link.click();

URL.revokeObjectURL(url);
};
xhr.send();
尝试用 window.open 方式,发现不可以

目前常用的各个第三方库也支持返回内容为 blob 格式
axios.get(`${path}`, {
responseType: ‘blob’
})
或者 fetch
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 查询结果, 在网上查到两种拼接方式,肯定有其余的方案
第一种
const result = [
[‘id’, ‘oreder’, ‘name’, ‘status’],
[1, ‘201904120201’, ‘ 正在加载 ’, ‘ 已完成 ’],
[18, ‘201904120204’, ‘ 测试 189’, ‘ 待付款 ’],
[22, ‘201904120209’, ‘ 蓝田日暖 ’, ‘ 待付款 ’],
]

// 可以看到 result 数组的第一组是 csv 的各个字段标题

const data = csvData.reduce((cur, next) => `${cur + next.join(‘,’)}\n`, ”)
const blob = new Blob([data]);
const url = URL.createObjectURL(blob);
var a = document.createElement(‘a’);
a.href = url;
a.download = filename;
a.click();
window.URL.revokeObjectURL(url);
第二种
const result = [
[1, ‘201904120201’, ‘ 正在加载 ’, ‘ 已完成 ’],
[18, ‘201904120204’, ‘ 测试 189’, ‘ 待付款 ’],
[22, ‘201904120209’, ‘ 蓝田日暖 ’, ‘ 待付款 ’],
]

// result 就是正常的数据格式

// 解决乱码问题
let dataType = ‘\uFEFF’
// 添加表格的头子段
dataType += ([‘ 订单编号 ’, ‘ 用户 ’, ‘ 动态 ID’].join(‘,’))
dataType += ‘\n’

csvData.forEach(item => {
dataType += ([item.id, item.order, item.name, item.staus].join(‘,’))
dataType += ‘\n’
})

const blob = new Blob([dataType], {type: ‘text/csv’})
如果有几段流文件,前端需要下载拼接成一个完整文件,如何实现
肯定是在 server 端进行文件的拼接操作,然后返回到前端下载
大致过程
const content1 = ‘ 这里是第 1 段文件内容 ’

const content2 = ‘ 这里是第 2 段文件内容 ’

ctx.body = {code:200, data: content1 + content2}
前端就是上文中的 blob 下载方式了
参考文章
Data URI Scheme

退出移动版