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 * 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}`);
});

效果图

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

人工智能的未来:Sam Altman 揭穿搜索引擎谣言,调侃 ChatGPT 和 GPT-4 的“神奇”更新

人工智能的未来&#xff1a;Sam Altman 揭穿搜索引擎谣言&#xff0c;调侃 ChatGPT 和 GPT-4 的“神奇”更新 概述 科技界充斥着有关人工智能研究先驱组织 OpenAI 将推出类似谷歌搜索引擎的传言。然而&#xff0c;首席执行官 Sam Altman 已经平息了这些谣言&#xff0c;并透露…

机器学习:葡萄酒品质预测

说明&#xff0c;此项目是我的期末大作业&#xff0c;包括了对数据集探索&#xff0c;预处理以及分类的各个详细过程与描述&#xff0c;代码简单&#xff0c;主要是一个分类项目的流程&#xff0c;并没有对模型进行深度研究&#xff0c;因此我写在这里。 目录 一、问题介绍 …

【Win10点击任务栏刷屏,卡死转圈(亲测有效)】

计算机疑难杂症001 Win10点击任务栏刷屏&#xff0c;卡死转圈(亲测有效)1、问题状况2、问题原因3、问题解决 Win10点击任务栏刷屏&#xff0c;卡死转圈(亲测有效) 1、问题状况 在偶然间&#xff0c;发现任务栏点不动了&#xff0c;点击无反应&#xff0c;再多点击几次&#x…

.[sqlback@memeware.net].2700勒索病毒如何防护和恢复数据?

.[sqlbackmemeware.net].2700介绍&#xff1a; .[sqlbackmemeware.net].2700 勒索病毒是一种恶意软件&#xff0c;它通过加密用户文件并索要赎金来实施勒索&#xff0c;快速恢复重要数据请添加技术服务号(safe130)。以下是关于这种勒索病毒的一些关键信息&#xff1a; 病毒性质…

通用人工智能AGI,究竟是一个哲学问题还是技术问题?

引言 在探索人工智能的未来方向中&#xff0c;人工通用智能&#xff08;AGI&#xff09;的概念逐渐成为科技领域和哲学探讨的焦点。AGI旨在创建可以执行任何智能任务的机器&#xff0c;甚至在某些方面超越人类的能力。然而&#xff0c;关于AGI的研究不仅仅是技术问题&#xff…

Rust读写CSV文件 一维Vec类型元素、二维Vec类型元素写入CSV文件

本文主要介绍Rust读写CSV文件方法&#xff0c; Vec类型元素基本操作方法&#xff0c;Rust把一维Vec类型元素、二维Vec类型元素写入CSV文件方法。 实例测试&#xff1a; 要求读“log.csv”文件数据&#xff0c;把“时间”列数据和“次数”列数据写入日志处理结果1.csv文件&…

C++相关概念和易错语法(12)(迭代器、string容量调整)

1.迭代器&#xff08;以string为例&#xff09; &#xff08;1&#xff09;基本理解&#xff1a;在我们刚接触迭代器的时候&#xff0c;我们可以将迭代器理解为改造过的“指针”&#xff0c;这是一个新的类型&#xff0c;指向对应容器中的各个元素。我们可以像指针那样对迭代器…

算法详解——回溯法

一、回溯法概述——问题背景 回溯法是一种解决约束满足问题的方法&#xff0c;特别适用于解决组合问题、搜索优化问题等。它通过逐步构建候选解决方案并且在这个解决方案不再可能满足约束或条件时进行剪枝和回溯。具体来说&#xff0c;回溯法可以应用于以下类型的问题&#xff…

Windows10搭建GPU版Darknet—yolov4—VS2022+CUDA+CUDNN(亲测有效)

1 VS2019安装 网址&#xff1a;Visual Studio: 面向软件开发人员和 Teams 的 IDE 和代码编辑器 下载完成之后双击.exe文件 步骤严格如下安装 默认语音包为中文&#xff08;简体&#xff09; 安装位置可以自行选择&#xff0c;完成以后就可以点击安装了。 安装完毕以后需要重启…

如何设置cPanel的自动备份

近期我们购买了Hostease美国VPS云主机产品&#xff0c;由于需要设置服务器的自动备份&#xff0c;我们向Hostease技术团队进行了咨询&#xff0c;他们提到VPS云主机的cPanel面板包含自动备份功能&#xff0c;下面我们就介绍如何进行自动备份的设置。 首先你需要登录到WHM面板&a…

暗区突围PC测试资格获取教程 人人可领100%获取方法

暗区突围PC测试资格获取教程 人人可领100%获取方法 暗区突围国际服的上线&#xff0c;近日在游戏圈内掀起了不小的浪花。而一个暗区突围国际服的测试资格更是成为了玩家们眼中炙手可热的宝物。许多玩家不知道该如何获取游戏的测试资格&#xff0c;今天小编就为大家带来详细的教…

电脑复制和粘贴的时候会出现Hello!

电脑不管是Microsoft Excel还是Microsoft Word复制之后粘贴过来就出现HELLO&#xff0c;当复制粘贴文件的时候就会出现WINFILE&#xff1b; 具体现象看下面两个图片&#xff1a; 这是因为winfile 文件病毒&#xff08;幽灵蠕虫病毒&#xff09;,每月的28号发作&#xff1b; 症状…

【深度学习】YOLO源码中的mAP计算代码的理解笔记(大部分代码逐行+基础解释)

提示&#xff1a;本篇博客是在阅读了YOLO源码中的mAP计算方法的代码后加上官方解释以及自己的debug调试理解每一步是怎么操作的。由于是大部分代码进行了逐行解释&#xff0c;所以篇幅过长。 文章目录 前言一、输入格式处理1.1 转换公式二、init&#xff1a;初始化2.1 iouv2.2 …

2024版本idea集成SpringBoot + Ai 手写一个chatgpt 【推荐】

题目&#xff1a;SpringBoot OpenAi 在这里获取key和url&#xff1a;获取免费key base-url为这两个&#xff1a; 话不多说直接来&#xff01; 一、简介 Spring AI 是 AI 工程的应用框架。其目标是将 Spring 生态系统设计原则&#xff08;如可移植性和模块化设计&#xff…

【CAD建模号】学习笔记(四):工作平面

工作平面介绍 CAD建模号右侧导航栏提供了很多便捷的工具&#xff0c;有测量工具、坐标系、模型和图层切换、视图切换等。 1. 测量工具组 测量工具可以测量图形的几何体积&#xff0c;长度&#xff0c;角度等。工具组包含如下&#xff1a; 测量几何&#xff1a;可以测量图形的面…

C++多态实现原理详解

阅读引言&#xff1a; 我想象了一下&#xff0c; 假如人有突然问我什么是多态&#xff0c; 我该如何给别人说清楚呢&#xff1f;所以写下这篇文章&#xff0c; 希望大家看完有所收获。 ①. 开胃小菜 先看这样一个开胃小菜 这里我有点小小的疑惑&#xff0c; 大小为啥是1。 在C…

即插即用篇 | YOLOv8引入局部自注意力 HaloAttention | 为参数高效的视觉主干网络扩展局部自注意力

本改进已集成到 YOLOv8-Magic 框架。 我们提出了Axial Transformers,这是一个基于自注意力的自回归模型,用于图像和其他组织为高维张量的数据。现有的自回归模型要么因高维数据的计算资源需求过大而受到限制,要么为了减少资源需求而在分布表达性或实现的便捷性上做出妥协。相…

【知识碎片】2024_05_11

本篇记录了两个代码&#xff0c;【图片整理】是一个数组排序题&#xff0c;【寻找数组的中心下标】看起来很适合用双指针&#xff0c;但是细节多&#xff0c;最后还是没通过全部用例&#xff0c;看了题解写出来的。 C语言部分是两个知道错了之后恍然大悟的选择题。 每日代码 图…

1053: 输出利用先序遍历创建的二叉树中的指定结点的度

解法&#xff1a; c语言 #include<iostream> #include<vector> using namespace std; typedef struct tNodes{char val;tNodes* left, * right; }* tNode;void creat(tNode& t) {char ch;cin >> ch;if (ch #) t NULL;else {t new tNodes;t->val …

最少数量线段覆盖-华为OD

系列文章目录 文章目录 系列文章目录前言一、题目描述二、输入描述三、输出描述四、java代码五、测试用例 前言 本人最近再练习算法&#xff0c;所以会发布一些解题思路&#xff0c;希望大家多指教 一、题目描述 给定坐标轴上的一组线段&#xff0c;线段的起点和终点均为整数…