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>
相关推荐
milanleon6 分钟前
使用Spring Security进行登录认证
java·前端·spring
excel9 分钟前
🚀 从零开始:如何从 GPTsAPI.net 申请 API Key 并打造自己的 AI 服务
前端
小高不明29 分钟前
前缀和一维/二维-复习篇
开发语言·算法
龘龍龙32 分钟前
Python基础(八)
开发语言·python
期待のcode1 小时前
@RequestBody的伪表单提交场景
java·前端·vue.js·后端
栀秋6661 小时前
防抖 vs 节流:从百度搜索到京东电商,看前端性能优化的“节奏哲学”
前端·javascript
一颗烂土豆1 小时前
vfit.js v2.0.0 发布:精简、语义化与核心重构 🎉
前端·vue.js·响应式设计
王小菲1 小时前
《网页布局速通:8 大主流方案 + 实战案例》-pink老师现代网页布局总结
css·面试·html
有意义1 小时前
深入防抖与节流:从闭包原理到性能优化实战
前端·javascript·面试
幺零九零零1 小时前
Golang-Swagger
开发语言·后端·golang