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>
相关推荐
老秦包你会2 分钟前
Qt第三课 ----------容器类控件
开发语言·qt
aPurpleBerry2 分钟前
JS常用数组方法 reduce filter find forEach
javascript
凤枭香5 分钟前
Python OpenCV 傅里叶变换
开发语言·图像处理·python·opencv
ULTRA??9 分钟前
C加加中的结构化绑定(解包,折叠展开)
开发语言·c++
远望清一色25 分钟前
基于MATLAB的实现垃圾分类Matlab源码
开发语言·matlab
GIS程序媛—椰子31 分钟前
【Vue 全家桶】7、Vue UI组件库(更新中)
前端·vue.js
confiself34 分钟前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
DogEgg_00137 分钟前
前端八股文(一)HTML 持续更新中。。。
前端·html
ZL不懂前端40 分钟前
Content Security Policy (CSP)
前端·javascript·面试
乐闻x43 分钟前
ESLint 使用教程(一):从零配置 ESLint
javascript·eslint