效果(比较下载速度)
服务端
请求响应头
注意:
分片下载请求的状态码为206
状态码206:当客户端发送一个范围请求时,服务器可以返回206状态码,表示只返回请求范围内的部分内容。通常用于断点续传或者只需要部分内容的情况
源码
js
const express = require('express');
const path = require("path");
const fs = require("fs");
const router = express.Router();
const fileName = '下载文件.zip';
const filePath = path.join(__dirname, `../public/${fileName}`);
router.get('/size', (req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.json({code: 200, data: {fileName, fileSize: fs.statSync(filePath).size}, message: '请求成功!'});
});
router.get('/download', (req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
// acceptRanges 启用或禁用接受范围请求,默认为true。禁用此选项将不会发送Accept Ranges并忽略Range请求标头的内容。
res.download(filePath, fileName, {acceptRanges: true});
});
module.exports = router;
客户端
html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title><%= title %></title>
</head>
<style>
div.progress {
width: 0;
margin: 10px;
height: 20px;
padding: 0 10px;
font-size: 12px;
line-height: 20px;
white-space: nowrap;
border: 1px solid #cccccc;
background-color: greenyellow;
}
</style>
<body>
<button onclick="basicDownloadFile();fragmentDownloadFile();">下载文件(普通、分片)</button>
<script>
// 请求
function request({method, url, headers = {}, responseType = undefined}) {
let div = null;
let width = 0;
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(method, url);
for (const key in headers) {
xhr.setRequestHeader(key, headers[key]);
}
if (responseType) {
xhr.responseType = responseType;
}
xhr.send();
xhr.onreadystatechange = (e) => {
if (e.target.readyState === 2) {
div = document.createElement('div');
div.setAttribute('class', 'progress');
div.innerText = `用时 -> ${method} ${url} ${headers.Range || ''}`;
document.body.appendChild(div);
}
if (e.target.readyState === 3) {
width++;
div.style.width = `${width}px`;
}
// console.log(e.target, e.target.readyState)
if (e.target.readyState === 4) {
if (e.target.status >= 200 && e.target.status < 300) {
console.log(width);
resolve(e.target.response);
}
}
}
})
}
// 普通下载文件
function basicDownloadFile() {
request({
method: 'GET',
url: `http://127.0.0.1:3000/file/download?_t=${Date.now()}`,
responseType: 'arraybuffer',
})
.then(res => {
// 一个不可变、原始数据的类文件对象
const blob = new Blob([res]);
// 下载
const a = document.createElement('a');
const href = URL.createObjectURL(blob);
a.href = href;
a.download = '下载文件.zip';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(href);
console.log('下载完成01')
})
}
// 分片下载文件
async function fragmentDownloadFile() {
let res = await request({method: 'GET', url: `http://127.0.0.1:3000/file/size?_t=${Date.now()}`});
// 文件大小
const {fileSize, fileName} = JSON.parse(res).data;
console.log('文件名称', fileName);
console.log('文件大小', fileSize);
// 步长
const step = Math.pow(8, 6);
console.log('步长', step);
// 任务
let tasks = [];
// 切割任务
for (let i = 0; i < Math.ceil(fileSize / step);) {
let [start, end] = [i * step, Math.min(++i * step - 1, fileSize)];
console.log([start, end]);
tasks.push(request({
method: 'GET',
url: `http://127.0.0.1:3000/file/download?_t=${Date.now()}`,
headers: {
Range: `bytes=${start}-${end}`
},
responseType: 'arraybuffer'
}));
}
console.log('步长切割的请求集合', tasks);
// 原始二进制数据缓冲区
const arrayBuffers = await Promise.all(tasks);
console.log('原始二进制数据缓冲区集合', arrayBuffers);
// 数组类型表示一个 8 位无符号整型数组
const uint8Array = new Uint8Array(fileSize);
let offset = 0;
arrayBuffers.forEach(arrayBuffer => {
uint8Array.set(new Uint8Array(arrayBuffer), offset);
offset += arrayBuffer.byteLength;
});
console.log('ArrayBuffer', uint8Array.buffer);
// 一个不可变、原始数据的类文件对象
const blob = new Blob([uint8Array.buffer]);
// 下载
const a = document.createElement('a');
const href = URL.createObjectURL(blob);
a.href = href;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(href);
console.log('下载完成02')
}
</script>
</body>
</html>