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>
相关推荐
@大迁世界几秒前
我用 Rust 重写了一个 Java 微服务,然后丢了工作
java·开发语言·后端·微服务·rust
用户761736354012 分钟前
CSS重点知识-样式计算
前端
yoyoma4 分钟前
object 、 map 、weakmap区别
前端·javascript
himobrinehacken6 分钟前
c语言宏注意事项
c语言·开发语言
shyshi6 分钟前
vercel 部署 node 服务和解决 vercel 不可访问的问题
前端·javascript
.生产的驴6 分钟前
React 模块化Axios封装请求 统一响应格式 请求统一处理
前端·javascript·react.js·前端框架·json·ecmascript·html5
前端大神之路7 分钟前
vue2 响应式原理
前端
一个努力的小码农7 分钟前
Rust中if let与while let语法糖的工程哲学
前端·rust
雾岛听风来9 分钟前
Android开发中常用高效数据结构
前端·javascript·后端
IT_陈寒9 分钟前
Vue 3性能优化实战:这5个Composition API技巧让你的应用快30%
前端·人工智能·后端