大文件分块上传

断点续传

断点续传需要为每个分块加md5值,如果用户取消上传,可以知道那些分块已经上传了

切块上传

只要校验整个文件的完整性就好

前端代码示例

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>大文件上传</title><style>body {padding: 24px;}button {padding: 8px 16px;cursor: pointer;border: 1px solid #409eff;border-radius: 4px;background-color: #409eff;color: #fff;margin-right: 16px;}.progress {width: 100%;height: 24px;background-color: #ebeef5;border-radius: 12px;margin-bottom: 24px;overflow: hidden;}.progress-inner {width: 0%;height: 100%;background-color: #67c23a;border-radius: 12px;text-align: right;transition: width 0.6s ease;}.progress-text {color: #fff;margin: 0 5px;display: inline-block;font-size: 12px;}</style></head><body><!-- 进度条 --><div class="progress"><div class="progress-inner"><div class="progress-text"></div></div></div><!-- 上传完显示预览地址 --><p><a id="filelink"></a></p><!-- 操作按钮 --><button id="upload"><input type="file" id="file" style="display: none" />上传文件</button><button id="download">下载文件</button><script src="https://unpkg.com/axios/dist/axios.min.js"></script><script src="https://unpkg.com/spark-md5@3.0.2/spark-md5.js"></script><script>// 上传document.querySelector("#upload").addEventListener("click", () => {document.querySelector("#file").click();});// 选中文件直接上传document.querySelector("#file").addEventListener("change", async (e) => {const file = e.target.files[0];// 获取文件的所有分块const blobList = getFileSlices(file);const uploadRequests = [];// 所有分块的md5值// 后端根据md5值存的块,请求合并根据md5值进行合并const md5ChunkList = [];let totalUploaded = 0;for (let i = 0; i < blobList.length; i++) {const blob = blobList[i];const md5 = await calculateMD5(blob);md5ChunkList.push(md5);const formData = new FormData();formData.append("file", blob);formData.append("md5", md5);const request = axios({method: "post",url: "http://localhost:3000/upload",data: formData,onUploadProgress: (progressEvent) => {totalUploaded += progressEvent.loaded;let percentCompleted = Math.floor((totalUploaded / file.size) * 100);// 为合并留1%进度条if (percentCompleted >= 100) {percentCompleted = 99;}updateProgressBar(percentCompleted);},});uploadRequests.push(request);}try {// 等待所有分块上传完成,再请求合并await Promise.all(uploadRequests);const { data } = await axios({method: "post",url: "http://localhost:3000/merge",data: { md5: md5ChunkList, fileName: file.name },});updateProgressBar(100);const url = `http://localhost:3000${data.data}`;console.log(url);const a = document.querySelector("#filelink");a.href = url;a.target = "_blank";a.innerText = url;} catch (e) {console.error(e);}});// 获取文件分块// 默认分块大小5mfunction getFileSlices(file, segmentSize = 5 * 1024 * 1024) {const fileSlices = [];let offset = 0;while (offset < file.size) {const segment = file.slice(offset, offset + segmentSize);fileSlices.push(segment);offset += segmentSize;}return fileSlices;}// 计算分块的md5值function calculateMD5(blob) {return new Promise((resolve, reject) => {const fileReader = new FileReader();let spark = new SparkMD5.ArrayBuffer();fileReader.onload = (e) => {spark.append(e.target.result);resolve(spark.end());};fileReader.onerror = () => {reject("blob读取失败");};fileReader.readAsArrayBuffer(blob);});}function updateProgressBar(percent) {const width = percent + "%";document.querySelector(".progress-inner").style.width = width;document.querySelector(".progress-text").innerText = width;}// 下载document.querySelector("#download").addEventListener("click", () => {const url = document.querySelector("#filelink").innerText;const fileName = url.substring(url.lastIndexOf("/") + 1);const download = document.createElement("a");download.href = `http://localhost:3000/download/${fileName}`;download.download = fileName;download.target = "_blank";download.click();});</script></body>
</html>

后端代码示例

import express from "express";
import cors from "cors";
import multer from "multer";
import fs from "fs";
import path from "path";
import mime from "mime-types";const app = express();
const port = 3000;
const upload = multer({ dest: "uploads/" });// 处理跨域请求
app.use(cors());
// 解析客户端请求中的JSON数据,解析后的对象将被添加到req.body
app.use(express.json());
// 启用对静态文件的服务
app.use("/uploads", express.static("uploads"));app.get("/", (req, res) => {res.send("Hello World!");
});// 上传
app.post("/upload", upload.single("file"), (req, res) => {const file = req.file;const md5 = req.body.md5;const newPath = path.join(path.dirname(file.path), md5);fs.renameSync(file.path, newPath);res.send({ code: 200 });
});// 合并
app.post("/merge", async (req, res) => {const { md5, fileName } = req.body;await concatFiles(md5, fileName);res.send({ code: 200, msg: "合并成功", data: `/uploads/${fileName}` });
});// 下载文件流
app.get("/download/:fileName", (req, res) => {const { fileName } = req.params;const file = path.join("./uploads", fileName);const type = mime.lookup(file);res.setHeader("Content-disposition","attachment; filename=" + encodeURIComponent(path.basename(file)));res.setHeader("Content-type", type);const filestream = fs.createReadStream(file);filestream.pipe(res);
});app.listen(port, () => {console.log(`listening on port ${port}`);
});// 合并切片的文件
async function concatFiles(fileChunks, name) {const filePath = path.join("./uploads", name);const writeStream = fs.createWriteStream(filePath);for (let i = 0; i < fileChunks.length; i++) {const chunkPath = path.join("./uploads", fileChunks[i]);const file = fs.createReadStream(chunkPath);file.pipe(writeStream, { end: false });// 等待当前文件读完再进行下一个await new Promise((resolve) => file.on("end", resolve));fs.unlink(chunkPath, (e) => {if (e) {console.error("删除文件切片失败", e);}});}writeStream.end();
}

参考

面试官:如何实现大文件上传
大文件分块上传

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

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

相关文章

[AIGC] 《MyBatis-Plus 结合 Spring Boot 的动态数据源介绍及 Demo 演示》

在现代的 Web 应用开发中&#xff0c;Spring Boot 已经成为了一种流行的框架选择。而 MyBatis-Plus 则为 MyBatis 框架提供了更强大的功能和便利。当它们结合使用时&#xff0c;动态数据源的运用变得更加简单和高效。 动态数据源的概念允许我们在运行时根据不同的条件或需求选…

【已解决】直接在远程新增文件本地再提交报Merge branch ‘master‘ of

【已解决】直接在远程新增文件本地再提交报Merge branch ‘master’ of … 1、问题产生背景 直接在远程仓库新建了md文件&#xff0c;本地库修改了文件已添加到暂存区之后再提交报错 2、分析 远程新建文件产生变更&#xff0c;版本号与本地拿到的不一致&#xff0c;本地再次提…

tf2使用savemodel保存之后转化为onnx适合进行om模型部署

tf2使用savemodel保存之后转化为onnx适合进行om模型部署 tf保存为kears框架h5文件将h5转化为savemodel格式&#xff0c;方便部署查看模型架构将savemodel转化为onnx格式使用netrononnx模型细微处理代码转化为om以及推理代码&#xff0c;要么使用midstudio tf保存为kears框架h5文…

中国M2总量是两个美国,意味着什么

中国人民银行公布数据&#xff1a;2月末&#xff0c;我国广义货币(M2)余额299.56万亿元&#xff0c;同比增长8.7%。 2000年末我国M2仅13万亿元&#xff0c;2013年3月达到100万亿元&#xff1b;2020年1月突破200万亿元&#xff1b;2024年2月接近300万亿元&#xff0c; 与美欧日…

CPU的星际穿越——“三维”解析“二维”之谜

文章目录 写在前面为什么三维的CPU能执行二维的指令二维指令是三维机器的抽象而已计算机所有东西都是三维的降维抽象没有软件没有指令二维到三维的总结操作系统的重塑 写在前面 以下是自己关于CPU为何能执行指令的迷惑的抽丝破茧的解答—— 困扰我的一个的问题之CPU的星际穿越…

Linux下CPU频率和核心数的锁定设置

linux下cpu频率的锁定设置 查询cpu相关信息 使用工具为&#xff1a;cpufrequtils sudo apt-get install cpufrequtils使用 cpufreq-info 命令来查看当前的 CPU 频率以及支持的频率范围 cpufreq-info设置某个CPU核心的频率 如果你想将 CPU 频率锁定在一个特定的值&#xff0…

【VLAN聚合和MUX VLAN的配置总结】

vlan聚合&#xff1a; 在一个物理网络内用多个VLAN隔离广播域&#xff0c;并将这些Sub-VLAN聚合成一个逻辑的VLAN&#xff08;称为Super-VLAN&#xff09;&#xff0c; 这些Sub-VLAN共用同一个IP子网和缺省网关&#xff0c;进而达到节约IP地址资源的目的。 案例&#xff1a;某…

【C++】每日一题 199. 二叉树的右视图

给定一个二叉树的 根节点 root&#xff0c;想象自己站在它的右侧&#xff0c;按照从顶部到底部的顺序&#xff0c;返回从右侧所能看到的节点值。 思路&#xff1a; 可以使用广度优先搜索&#xff08;BFS&#xff09;来遍历二叉树&#xff0c;但是在遍历过程中只记录每一层最右…

【Leetcode】八大排序

总述 插入排序&#xff1a;直接插入排序&#xff1b;希尔排序&#xff1b; 选择排序&#xff1a;简单选择排序&#xff1b;堆排序&#xff1b; 交换排序&#xff1a;冒泡排序&#xff1b;快速排序&#xff1b; 归并排序&#xff1b; 桶排序/基数排序&#xff1b; 直接插入排序 …

【软件工程】期末复习超全整理!!!

软件工程期末复习整理 软件工程大纲以及阅读说明用例图用例图例题1 用例文档用例文档例题1用例文档例题2 活动图活动图例题1活动图例题2活动图例题3 类图类图中的关系类图例题1类图例题2 顺序图顺序图例题1顺序图 例题2顺序图例题3顺序图--分析类顺序图例题4顺序图例题5 状态图…

重学java 33.API 4.日期相关类

任何事&#xff0c;必作于细&#xff0c;也必成于实 —— 24.5.9 一、Date日期类 1.Date类的介绍 1.概述: 表示特定的瞬间,精确到亳秒 2.常识: a.1000毫秒 1秒 b.时间原点:1970年1月1日 0时0分0秒(UNIX系统起始时间),叫做格林威治时间,在0时区上 c.时区:北京位于东八区,一个时区…

模拟实现链表的功能

1.什么是链表&#xff1f; 链表是一种物理存储结构上非连续存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的引用链接次序实现的 。 实际中链表的结构非常多样&#xff0c;以下情况组合起来就有8种链表结构&#xff1a; 单向或者双向 带头或者不带头 …

CTF-catcat-new

先判断这个题目是不是关于任意文件读取漏洞&#xff0c;读一个Linux都有的文件/etc/passwd http://61.147.171.105:60014/info?file../../../../../etc/passwd ../../../../../etc/passwd 是一个相对路径&#xff0c;用于访问文件系统中的 /etc/passwd 文件。 通过抓包发现R…

车载测试到底怎么样?真实揭秘!

什么是车载智能系统测试&#xff1f; 车载智能系统&#xff0c;是汽车智能化重要的组成部分&#xff0c;由旧有的车载资通讯系统结合联网汽车技术所演进而来&#xff0c;随着软硬件技术的不断进步&#xff0c; 让车载智能系统拥有强大的运算能力及多元化的应用功能。 车载智能…

苹果iPad M4:Console级别图形和AI强大功能

苹果iPad M4&#xff1a;Console级别图形和AI强大功能 Apple近日发布了最新的M4芯片&#xff0c;旨在为iPad Pro系列带来明显的性能提升和电池续航时间延长。在本篇报道中&#xff0c;我们将详细介绍M4芯片的特点、性能改进和为创意专业人士带来的影响。 M4芯片的强大功能 …

图解项目管理必备十大管理模型及具体应用建议

心智模型是根深蒂固存在于人们心中&#xff0c;影响人们如何理解这个世界&#xff08;包括我们自己、他人、组织和整个世界&#xff09;&#xff0c;以及如何采取行动的诸多假设、成见、逻辑、规则&#xff0c;甚至图像、印象等。本图通过对心智模型的分类和描述&#xff0c;表…

【Linux】shell基础,shell脚本

Shell Shell是一个用C语言编写的程序&#xff0c;接受用户输入的命令&#xff0c;并将其传递给操作系统内核执行。Shell还负责解释和执行命令、管理文件系统、控制进程&#xff0c;是用户使用Linux的桥梁。Shell既是一种命令语言&#xff0c;又是一种程序设计语言 Shell脚本 Sh…

上位机图像处理和嵌入式模块部署(树莓派4b和c++新版本的问题)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 自己读书的时候是03年&#xff0c;学习c也是差不多04年开始&#xff0c;到现在基本上20年了。这20年过程当中&#xff0c;其实c的语言版本一直是在…

【stomp 实战】spring websocket 接收消息源码分析

后台消息的发送过程&#xff0c;我们通过spring websocket用户消息发送源码分析已经了解了。我们再来分析一下后端接收消息的过程。这个过程和后端发送消息过程有点类似。 前端发送消息 前端发送消息给服务端的示例如下&#xff1a; 发送给目的/app/echo一个消息。 //主动发…

科林算法_3 图

一、图论基础 多对多的关系 定义&#xff1a;G(V,E) Vertex顶点 Edge边 顶点的集合V{v1,v2} 边的结合E{(v1,v2)} 无向图(1,2) 有向图<1,2> 依附&#xff1a;边(v1,v2)依附于顶点v1,v2 路径&#xff1a;&#xff08;v1,v2)(v2,v3) 无权路径最短&#xff1a;边最少…