Html + Express 实现大文件分片上传、断点续传、秒传

在日常的网页开发中,文件上传是一项常见操作。通过文件上传技术,用户可以将本地文件方便地传输到Web服务器上。这种功能在许多场景下都是必不可少的,比如上传文件到网盘或上传用户头像等。

然而,当需要上传大型文件时,可能会遇到以下问题:

1. 长时间上传:由于文件大小较大,上传过程可能会耗费较长时间。

2. 上传中断重新上传:如果在上传过程中出现意外情况导致上传中断,用户需要重新开始整个上传过程,这会增加用户的不便。

3. 服务端限制:通常,服务端会对上传的文件大小进行限制,这可能导致无法上传大型文件。

为了解决这些问题,可以采用分片上传的方式:

分片上传即将大文件分割成小块,然后分块上传到服务器。通过分片上传,可以实现以下优势:

快速上传:由于每个小块的大小相对较小,上传时间大大缩短。

断点续传:如果上传过程中出现中断,只需重新上传中断的部分,而不需要重新上传整个文件,提高了用户体验。

避免大小限制:分片上传可以避免由于文件大小限制而无法上传大文件的问题。

通过采用分片上传技术,可以提升用户体验,加快大文件上传速度,并确保上传过程的稳定性和可靠性。

原理:


分片上传的概念类似于将一个大文件分割成多个小块,然后分别上传这些小块到服务器上。
首先,将待上传的大文件划分为固定大小的小块,比如每块大小为1MB。然后逐个上传这些小块到服务器。在上传过程中,可以同时处理多个小块的上传,也可以按顺序逐一上传小块。每个小块上传完成后,服务器会妥善保存这些小块,并记录它们的顺序和位置信息。
当所有小块都上传完成后,服务器会按照预先记录的顺序和位置信息,将这些小块组合成完整的大文件。最终,整个大文件就成功地被分片上传并合并完成了。这种分片上传的方式能够有效地提升大文件上传的效率和稳定性,确保文件上传过程更加可靠和高效。

前端代码

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script src="https://code.jquery.com/jquery-3.6.0.min.js"></script><script src="https://cdn.jsdelivr.net/npm/spark-md5@3.0.2/spark-md5.min.js"></script><script src="https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js"></script>
</head><body><input type="file" /><script>const CHUNK_SIZE = 1024 * 1024let hashName = ''let fileName = ''$('input').change(async (e) => {const file = e.target.files[0]const chunks = shardingChunks(file) // 分片fileName = file.namehashName = await shardingHash(file) // 获取文件hash值const { data: { existFile, existChunks } } = await axios.post('http://localhost:3000/uploader/verify', { fileHash: hashName, fileName });if (existFile) return; // 如果该hash值 && file.name 存在说明该文件已经在服务器上了uploader(chunks, existChunks)})//  分片const shardingChunks = (file) => {let start = 0const chunks = []while (start < file.size) {chunks.push(file.slice(start, start + CHUNK_SIZE))start += CHUNK_SIZE}return chunks}// 获取文件hash值const shardingHash = (file) => {return new Promise((resolve) => {const fileReader = new FileReader()fileReader.readAsArrayBuffer(file)fileReader.onload = (e) => {const spark = new SparkMD5.ArrayBuffer()spark.append(e.target.result)resolve(spark.end())}})}// 分片上传const uploader = async (chunks, existChunks) => {const chunksArr = chunks.map((chunk, index) => ({fileHash: hashName,chunkHash: hashName + '-' + index,chunk}))const formDatas = chunksArr.map(item => {const formData = new FormData();formData.append("fileHash", item.fileHash);formData.append("chunkHash", item.chunkHash);formData.append("chunk", item.chunk);return formData;})let flagArr = []formDatas.forEach(async (item) => {const res = await axios.post('http://localhost:3000/uploader/upload', item, {headers: {'Content-Type': 'multipart/form-data'}})flagArr.push(res.data.success)if (flagArr.length == formDatas.length && flagArr.every(item => item == true)) {mergeFile() // 合并文件flagArr = []}})}const mergeFile = async () => {const res = await axios.post('http://localhost:3000/uploader/merge',{fileHash: hashName,fileName: fileName})if (res.data.success) return alert('上传成功')}</script>
</body></html>

后端代码(Node)

const express = require("express");
const cors = require("cors");
const bodyParser = require("body-parser");
const fse = require("fs-extra");
const path = require("path");
const multipart = require("connect-multiparty");
const multipartMiddleware = multipart();const app = express();app.use(cors());
app.use(bodyParser.json());// 所有上传的文件存放在该目录下
const UPLOADS_DIR = path.resolve("uploads");/*** 上传*/
app.post("/upload", multipartMiddleware, (req, res) => {const { fileHash, chunkHash } = req.body;// 如果临时文件夹(用于保存分片)不存在,则创建const chunkDir = path.resolve(UPLOADS_DIR, fileHash);if (!fse.existsSync(chunkDir)) {fse.mkdirSync(chunkDir);}// 如果临时文件夹里不存在该分片,则将用户上传的分片移到临时文件夹里const chunkPath = path.resolve(chunkDir, chunkHash);if (!fse.existsSync(chunkPath)) {fse.moveSync(req.files.chunk.path, chunkPath);}res.send({success: true,msg: "上传成功",});
});/*** 合并*/
app.post("/merge", async (req, res) => {const { fileHash, fileName } = req.body;// 最终合并的文件路径const filePath = path.resolve(UPLOADS_DIR, fileHash + path.extname(fileName));// 临时文件夹路径const chunkDir = path.resolve(UPLOADS_DIR, fileHash);// 读取临时文件夹,获取该文件夹下“所有文件(分片)名称”的数组对象const chunkPaths = fse.readdirSync(chunkDir);// 读取临时文件夹获得的文件(分片)名称数组可能乱序,需要重新排序chunkPaths.sort((a, b) => a.split("-")[1] - b.split("-")[1]);// 遍历文件(分片)数组,将分片追加到文件中const pool = chunkPaths.map((chunkName) =>new Promise((resolve) => {const chunkPath = path.resolve(chunkDir, chunkName);// 将分片追加到文件中fse.appendFileSync(filePath, fse.readFileSync(chunkPath));// 删除分片fse.unlinkSync(chunkPath);resolve();}));await Promise.all(pool);// 等待所有分片追加到文件后,删除临时文件夹fse.removeSync(chunkDir);res.send({success: true,msg: "合并成功",});
});/*** 校验*/
app.post("/verify", (req, res) => {const { fileHash, fileName } = req.body;// 判断服务器上是否存在该hash值的文件const filePath = path.resolve(UPLOADS_DIR, fileHash + path.extname(fileName));const existFile = fse.existsSync(filePath);// 获取已经上传到服务器的文件分片const chunkDir = path.resolve(UPLOADS_DIR, fileHash);const existChunks = [];if (fse.existsSync(chunkDir)) {existChunks.push(...fse.readdirSync(chunkDir));}res.send({success: true,msg: "校验文件",data: {existFile,existChunks,},});
});const server = app.listen(3000, () => {console.log(`Example app listening on port ${server.address().port}`);
});

效果图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/688406.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

不容错过的秘籍:JavaScript数组的创建和使用详解

在编程的世界里&#xff0c;数据是构建一切的基础。而在JavaScript中&#xff0c;有一种特殊且强大的数据结构&#xff0c;它就是——数组。 今天&#xff0c;我们就来一起探索数组的奥秘&#xff0c;从创建到使用&#xff0c;一步步掌握这个重要的工具。 一、什么是数组 数…

pycharm滚轮放大字体

进入settings&#xff0c;然后按从左到右的箭头顺序依次点击即可

Win10鼠标右键新增软件快速打开项

1、cmd 运行 regedit 2、找到该位置的shell文件夹 3、在shell文件夹下创建需要添加的软件名的文件夹&#xff0c;并修改相关信息 4、新建子文件夹command&#xff0c;并修改相关信息 5、效果

斯坦福李飞飞最新对话:AI不会对人类造成“灭绝性危机” | 最新快讯

美国斯坦福大学教授、美国国家工程院院士李飞飞&#xff08;来源&#xff1a;斯坦福大学账号&#xff09; 北京时间 5 月 10 日凌晨举行的 Bloomberg Tech 活动上&#xff0c;著名华人计算机科学家、美国斯坦福大学教授李飞飞&#xff08;Fei-Fei Li&#xff09;与彭博社 Emily…

【NodeMCU实时天气时钟温湿度项目 4】通过NTPClient库获取实时网络时间并显示在TFT屏幕上

今天是【实时天气时钟温湿度项目】第四专题&#xff0c;主要内容是&#xff1a;学习导入NTPClient库&#xff0c;通过这个库获取实时网络时间&#xff0c;显示在1.3寸TFT液晶屏幕上。此前三个专题&#xff0c;请选择查看以下链接。 第一专题内容&#xff0c;请参考 【N…

C语言神奇的经典程序

因为C语言语法格式的特殊性&#xff0c;还诞生了一个C语言乱码大赛&#xff0c;从1984年开始&#xff0c;一直办到现在。在它官网上能看到历年所有的参赛获奖的作品。 官网链接&#xff1a; https://www.ioccc.orghttps://github.com/ioccc-src/temp-test-ioccc 这个比赛的目…

美国政府发布新的国际网络空间和数字政策战略(上)

文章目录 前言一、战略内容介绍二、数字团结的含义三、如何建立数字团结前言 美国务院5月6日正式发布《美国国际网络空间和数字政策战略:迈向创新、安全和尊重权利的数字未来》,旨在指导国际社会参与技术外交并推动《美国国家安全战略》和《美国国家网络安全战略》。 美国务…

界面组件DevExpress Reporting中文教程 - 标记(可访问)PDF导出增强

DevExpress Reporting是.NET Framework下功能完善的报表平台&#xff0c;它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集&#xff0c;包括数据透视表、图表&#xff0c;因此您可以构建无与伦比、信息清晰的报表。 可访问性支持在DevExpress这里仍然是一个高优先…

深入理解卷积函数torch.nn.Conv2d的各个参数以及计算公式(看完写模型就很简单了)

代码解释帮助理解&#xff1a; torch.randn(10, 3, 32, 32)&#xff0c;初始数据&#xff1a;(10, 3, 32, 32)代表有10张图片&#xff0c;每张图片的像素点用三个数表示&#xff0c;每张图片大小为32x32。&#xff08;重点理解这个下面就好理解了&#xff09; nn.Conv2d(3, 64…

独家丨美团直播积极寻求MCN公司合作,却意外成商家刷单圣地?

图片&#xff5c;《扫黑决战》截图 ©自象限原创 作者丨薛黎 编辑丨程心 2023年4月18日&#xff0c;美团直播在“神券节”中正式上线。整整一年之后&#xff0c;美团直播又在持续加码。 据「自象限」独家获悉&#xff0c;在去年12月美团上线达人直播后&#xff0c;近期…

Verilog_学习路线(小白)

#前言&#xff1a; 自从专心学习专业课后&#xff0c;发现知识点得用&#xff0c;越用越熟练&#xff0c;工具也一样&#xff0c;高级工具的学习可帮助我们在工作中极大地提高效率&#xff0c;但这里要记住一点&#xff0c;任何工具都是为解决实际问题出现的&#xff0c;即落脚…

Spring Cloud Gateway 全局过滤器

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 全局过滤器作用于所…