Express(二):文件下载 - 分片下载

效果(比较下载速度)

服务端

请求响应头

注意:分片下载请求的状态码为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>
相关推荐
咔叽布吉7 分钟前
【前端学习笔记】ES6 新特性
前端·笔记·学习
南东山人7 分钟前
python问题解决-外部模块明明安装了,却总是无法找到
开发语言·python
ThetaarSofVenice19 分钟前
【Java从入门到放弃 之 Java程序基础】
java·开发语言·python
夏子曦28 分钟前
java——Tomcat调优策略
java·开发语言·tomcat
推开世界的门40 分钟前
web 中 canvas 污染 以及解决方案
前端
会编程的果子君1 小时前
Python语法基础(一)
开发语言·python·html
星离~1 小时前
css—轮播图实现
前端·css
重生之我在字节当程序员1 小时前
c++中的lambda表达式!
开发语言·c++
ᝰꫝꪉꪯꫀ3611 小时前
JavaWeb——Mybatis
java·开发语言·后端·mybatis
八年。。1 小时前
MATLAB 中有关figure图表绘制函数设计(论文中常用)
开发语言·笔记·学习·matlab