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

news2024/12/24 10:21:55

在日常的网页开发中,文件上传是一项常见操作。通过文件上传技术,用户可以将本地文件方便地传输到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 * 1024
        let hashName = ''
        let fileName = ''

        $('input').change(async (e) => {
            const file = e.target.files[0]
            const chunks = shardingChunks(file) // 分片
            fileName = file.name
            hashName = 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 = 0
            const 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.coloradmin.cn/o/1663395.html

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

相关文章

构建第一个ArkTS应用之@AppStorage:应用全局的UI状态存储

AppStorage是应用全局的UI状态存储&#xff0c;是和应用的进程绑定的&#xff0c;由UI框架在应用程序启动时创建&#xff0c;为应用程序UI状态属性提供中央存储。 和AppStorage不同的是&#xff0c;LocalStorage是页面级的&#xff0c;通常应用于页面内的数据共享。而AppStora…

Apache Flume概述

Apache Flume概述 1.Flume定义 ​ Flume是cloudera(CDH版本的hadoop) 开发的一个分布式、可靠、高可用的海量日志收集系统。 它将各个服务器中的数据收集起来并送到指定的地方去&#xff0c;比如说送到HDFS、Hbase&#xff0c;简单来说flume就是收集日志的。 2.Flume基础架构…

导出QQ好友列表、群列表、群员列表

MENU 准备工作在浏览器地址栏中输入地址使用F12快捷键打开开发者工具(浏览器控制台)点击头像登入网站(推荐)或手机扫码登录获取群列表获取好友列表获取群员列表 准备工作 一台带有浏览器的电脑 在浏览器地址栏中输入地址 https://qun.qq.com/member.html 使用F12快捷键打开开发…

手机同步与数据安全:让手机和电脑完美结合!

在当今这个高度信息化的社会&#xff0c;手机和电脑不仅为我们提供了丰富的信息资源&#xff0c;让我们能够随时随地获取所需的信息&#xff0c;还为我们的生活带来了极大的便利。无论是工作、学习还是娱乐&#xff0c;手机和电脑都发挥着至关重要的作用。 然而&#xff0c;随…

阿里云服务器在线安装nginx

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《nginx实战》 目录 内容简介 安装步骤 1.root用户登录连接阿里云服务器 2.在usr/local下新建nginx目录 3.安装 1安装下载工具 2下载nginx压缩包 3解压 4安装nginx依赖的库 5编译并安装 6启动nginx 7开启…

【数据结构】顺序表(一)

✨✨✨专栏&#xff1a;数据结构 &#x1f9d1;‍&#x1f393;个人主页&#xff1a;SWsunlight 不怕别人看不起&#xff0c;就怕自己不争气。路是人走出来的&#xff0c;关键要靠自己闯。振作起来&#xff0c;生活的含义就是前进。 目录 一、顺序表的概念&#xff1a; 二…

东南亚服务器租用托管的优势

东南亚地区在国际贸易领域展现出了巨大的潜力和吸引力&#xff0c;其未来的外贸发展前景被认为是广阔且充满了无限商机。这一地区以其人口众多、经济快速发展的特点&#xff0c;结合独特的地理优势和丰富的自然资源&#xff0c;正在吸引全球企业的目光。今天我们一起来看看东南…

YOLOv8火焰与烟雾智能检测系统

项目概述&#xff1a; 本项目旨在开发一款高效、实时的火焰与烟雾检测系统&#xff0c;利用先进的深度学习技术——YOLOv8&#xff0c;为安全监控领域提供智能化解决方案。系统不仅能够准确识别视频流或静态图像中的火焰与烟雾&#xff0c;还配备了用户友好的图形界面&#xff…

C++ | Leetcode C++题解之第79题单词搜索

题目&#xff1a; 题解&#xff1a; class Solution { public:bool exist(vector<vector<char>>& board, string word) {rows board.size();cols board[0].size();for(int i 0; i < rows; i) {for(int j 0; j < cols; j) {if (dfs(board, word, i, …

答辩PPT制作太费时?AI工具帮你节省时间

在我原本的认知里面&#xff0c;答辩PPT是要包含论文各个章节的&#xff0c;在答辩时需要方方面面都讲到的&#xff0c;什么摘要、文献综述、实证分析、研究结果样样不落。但是&#xff0c;这大错特错&#xff01; 答辩PPT环节时长一般不超过5分钟&#xff0c;老师想要的答辩P…

精益数字化是什么

在传统的生产过程中&#xff0c;存在很多工作是重复且无价值的&#xff0c;这些工作通常需要花费大量的时间和人力&#xff0c;而且容易出现错误。例如人工测量和记录、纸质文档管理、手工排序和分类等&#xff0c;为了解决这个问题。通过引入精益生产和数字化解决方案&#xf…

springboot基本使用九(redis和springcache缓存)

为什么使用缓存: 减少数据库访问次数,从而提高应用程序的性能 redis可以缓存为啥要和spring cache一起使用? redis缓存:是内存级的缓存。它是使用单纯的内存来进行缓存 spring cache缓存:使用JVM的内存来缓存对象的,这势必会造成大量的内存消耗。但好处是显然的:使用方…

Git详解之五:分布式Git

为了便于项目中的所有开发者分享代码&#xff0c;我们准备好了一台服务器存放远程 Git 仓库。经过前面几章的学习&#xff0c;我们已经学会了一些基本的本地工作流程中所需用到的命令。接下来&#xff0c;我们要学习下如何利用 Git 来组织和完成分布式工作流程。 特别是&#…

UML之用例图

1.用例图 用例图指参与者&#xff0c;用例&#xff0c;边界以及它们之间的关系构成的用于描述系统功能的视图。说明是谁要使用系统&#xff0c;以及可以使用该系统可以做些什么。展示了一个外部用户能够观察到的系统功能模型图 2.用例图的元素 &#xff08;1&#xff09;参与…

使用Postman来调用Salesforce Bulk API 2.0的方法

简介 Bulk API 2.0 可以支持大量数据增删改查&#xff0c; 用新版的Dataloader也可以进行访问&#xff0c;但Dataloader会把CSV里的数据先转成Bean对象&#xff0c;这样会耗费大量的时间&#xff0c;而且数据量过大会卡死&#xff0c;所以直接上传CSV会节省大量时间和避免卡死风…

【Matlab】Matlab之美,抓紧来膜拜大神的创星之作(附2024Matlab教程+代码)

软件介绍 MATLAB是一款商业数学软件&#xff0c;用于算法开发、数据可视化、数据分析以及数值计算的高级技术计算语言和交互式环境&#xff0c;主要包括MATLAB和Simulink两大部分&#xff0c;可以进行矩阵运算、绘制函数和数据、实现算法、创建用户界面、连接其他编程语言的程序…

目标检测算法YOLOv8简介

YOLOv8论文尚未发布&#xff0c;YOLOv8由Ultralytics公司推出并维护&#xff0c;源码见&#xff1a;https://github.com/ultralytics/ultralytics &#xff0c;于2024年1月发布v8.1.0版本&#xff0c;最新发布版本为v8.2.0&#xff0c;License为AGPL-3.0。 以下内容主要来自&am…

ArkTS开发原生鸿蒙HarmonyOS短视频应用

HarmonyOS实战课程“2024鸿蒙零基础快速实战-仿抖音App开发&#xff08;ArkTS版&#xff09;”已经于今日上线至慕课网&#xff08;https://coding.imooc.com/class/843.html&#xff09;&#xff0c;有致力于鸿蒙生态开发的同学们可以关注一下。 课程简介 本课程以原生鸿蒙Ha…

css案例 tab上下滚动,左右滚动

效果图&#xff1a; 完整代码&#xff1a; <template><view class"content"><view class"content-item"><view class"content-title"><h4>美食热搜</h4><ul><li>火鸡面</li><li>糖…

【数据结构】第五讲:栈和队列

个人主页&#xff1a;深情秋刀鱼-CSDN博客 数据结构专栏&#xff1a;数据结构与算法 源码获取&#xff1a;数据结构: 上传我写的关于数据结构的代码 (gitee.com) 目录 一、栈 1.栈的定义 2.栈的实现 a.栈结构的定义 b.初始化 c.扩容 d.入栈 e.出栈 f.打印 g.取栈顶元素…