手摸手,带你完成大文件分片下载
前言
之前看到,一篇大文件上传的文章,感觉上传里面的细节考虑还是很多的。
然后最近就在钻研一下文件下载的功能
描述的不好还请各位大佬见谅,萌新第一次写文章。😁
项目地址:
react 及乐1997/react-demo - 码云 - 开源中国 (gitee.com)
node big-files: nodejs的大文件分片上传下载以及秒传 (gitee.com)
整体思路
- 获取要下载文件的大小,然后除以分片的大小,明确要发送几次网络请求
- 后端获取请求头里面的range字段
- 前端把请求回来的切片做一个合并 然后创建一个URL.createObjectURL来下载
后端 express
我是用了两个接口,一个是获取文件大小的,一个是下载的
router.get('/size/:name',(req,res)=>{
//获取要下载文件的路径
let filePath = path.resolve(__dirname,distPath,req.params.name)
//console.log(filePath)
//获取文件的大小
let size = fs.statSync(filePath).size || null
console.log('下载文件大小' + size)
res.send({
msg:'ok',
data:size.toString()
})
})
下载分为三种情况,一种是不需要分片 直接去下载,第二种是 请求分片的开始位置和结束位置不对,拒绝请求, 第三种是 位置正确 返回分片给前端
router.get("/down/:name", (req, res) => {
let filename = req.params.name;
//获取文件的位置 和文件的大小
let filePath = path.resolve(__dirname, distPath, req.params.name);
let size = fs.statSync(filePath).size;
//获取请求头的range字段
let range = req.headers["range"];
let file = path.resolve(__dirname, distPath, filename);
//不使用分片下载 直接传输文件
if (!range) {
//res.set({'Accept-Ranges':'bytes'})
res.set({
"Content-Type": "application/octet-stream",
"Content-Disposition": `attachment; filename=${filename}`,
});
fs.createReadStream(file).pipe(res);
return;
}
//获取分片的开始和结束位置
let bytesV = range.split("=");
bytesV.shift()
let [start, end] = bytesV.join('').split("-");
start = Number(start)
end = Number(end)
//分片开始 结束位置不对 拒绝下载
if (start > size || end > size) {
res.set({ "Content-Range": `bytes */${size}`});
res.status(416).send(null);
return;
}
//开始分片下载
res.status(206);
res.set({
"Accept-Ranges": "bytes",
"Content-Range": `bytes ${start}-${end ? end : size}/${size}`,
});
console.log(start + '---' + end)
fs.createReadStream(file, { start, end }).pipe(res);
});
前端 react
//视图
<div>
<Input
placeholder="请输入文件名"
onChange={(e) => setname(e.target.value)}
/>
<Button type="primary" onClick={downfile}>
下载
</Button>
</div>
downfile
//下载文件
SIZE为分片大小
const SIZE = 200 * 1024 * 1024; //设置切片的大小
const downfile = async () => {
let contentLength = await filesize(name);
let chunks = Math.ceil(contentLength / SIZE);
let chunksl = [...new Array(chunks).keys()];
//用流的方式操作不行 但是如果后端把文件变成压缩包 每个分片都是一个压缩包 应该就可以 此种方法 下载下来是多个压缩包 然后压缩包数量正确才能解压 有兴趣的可以看一下streamSaver这个库 可以实现边读边下
/* for (let i of chunksl) {
let start = i * SIZE;
let end = i + 1 === chunks ? contentLength : (i + 1) * SIZE - 1;
let res = await getBinaryContent(start, end, i);
let fileStream = streamSaver.createWriteStream(name,{flags:'a',start});
if (res.data.stream().pipeTo) {
//这个用axios请求的 时候responseType设置为"blob" blob有stream这个方法
await res.data.stream().pipeTo(fileStream);
}
} */
//大文件下载 要控制并发数量 此处偷懒 使用了asyncPool这个库
let results = await asyncPool(3,chunksl,(i)=>{
let start = i * SIZE;
let end = i + 1 === chunks ? contentLength : (i + 1) * SIZE - 1;
return getBinaryContent(start, end, i);
})
results.sort((a,b)=>a.index - b.index)
let arr = results.map(r=>r.data)
//多个blob排序完合并为一个blob
let buffers = new Blob(arr)
saveAs(name,buffers)
};
filesize
//获取要下载文件的大小
const filesize = async (name) => {
let res = await http.get(`/size/${name}`);
return res.data;
};
getBinaryContent
//根据传入的参数发起范围请求
const getBinaryContent = async (start, end, i) => {
let result = await http.get(`down/${name}`, {
headers: { Range: `bytes=${start}-${end}` },
responseType: "blob",
});
return { index: i, data: result };
};
saveAs
//保存文件
const saveAs = ( name, buffers, mime = "application/octet-stream" ) => {
const blob = new Blob([buffers], { type: mime });
const blobUrl = URL.createObjectURL(blob);
const a = document.createElement("a");
a.download = name
a.href = blobUrl;
a.click();
URL.revokeObjectURL(blob);
};
参考资料
转载自:https://juejin.cn/post/7025885508748181512