大文件分块上传

断点续传

断点续传需要为每个分块加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);
        }
      });

      // 获取文件分块
      // 默认分块大小5m
      function 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.mfbz.cn/a/608424.html

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

相关文章

容灾演练双月报|郑大一附院数据级容灾演练切换

了解更多灾备行业动态 守护数字化时代业务连续 目录 CONTENTS 01 灾备法规政策 02 热点安全事件 03 容灾演练典型案例 01 灾备法规政策 3月19日&#xff0c;工信部发布《工业和信息化部办公厅关于做好2024年信息通信业安全生产和网络运行安全工作的通知》。明确提出“…

官宣:vAsterNOS正式发布!开放网络操作系统免费试用!

近期&#xff0c;vAsterNOS&#xff08;设备模拟器&#xff09;正式发布&#xff0c;可以满足用户快速了解 AsterNOS、体验实际操作、搭建模拟网络的需求&#xff0c;可运行在GNS3、EVE-NG等网络虚拟软件中。 AsterNOS 网络操作系统是星融元为人工智能、机器学习、高性能计算、…

java培训班还值得去培训吗?

请大家关注我的公众号&#xff1a;老胡聊Java 1 应届生或者在校生&#xff0c;如果感觉有必要&#xff0c;可以去提升下技术&#xff0c;因为应届生或在校生找工作时&#xff0c;未必要提升真实项目经验&#xff0c;所以用应届生身份学到的spring boot等java技术背面试题&#…

《二十三》Qt 简单小项目---视频播放器

QT 使用QMediaPlayer实现的简易视频播放器 效果如下&#xff1a; 功能点 播放指定视频点击屏幕暂停/播放开始/暂停/重置视频拖拽到指定位置播放 类介绍 需要在配置文件中加入Multimedia, MultimediaWidgets这俩个库。 Multimedia&#xff1a;提供了一套用于处理音频、视频…

如何开启深色模式【攻略】

如何开启深色模式【攻略】 前言版权推荐如何开启深色模式介绍手机系统手机微信手机QQ手机快手手机抖音 电脑系统电脑微信电脑QQ电脑WPS电脑浏览器 最后 前言 2024-5-9 20:48:21 深色模式给人以一种高级感。 本文介绍一些常用软件深色模式的开启 以下内容源自《【攻略】》 仅…

基于Spring Boot的酒店管理系统设计与实现

基于Spring Boot的酒店管理系统设计与实现 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea 系统部分展示 系统首页界面图&#xff0c;在系统首页可以查看首页…

【数据结构-二叉搜索树的增删查改】

&#x1f308;个人主页&#xff1a;努力学编程’ ⛅个人推荐&#xff1a;基于java提供的ArrayList实现的扑克牌游戏 |C贪吃蛇详解 ⚡学好数据结构&#xff0c;刷题刻不容缓&#xff1a;点击一起刷题 &#x1f319;心灵鸡汤&#xff1a;总有人要赢&#xff0c;为什么不能是我呢 …

python-类和对象

1、设计一个 Circle类来表示圆,这个类包含圆的半径以及求面积和周长的函数。再使用这个类创建半径为1~10的圆,并计算出相应的面积和周长。 &#xff08;1&#xff09;源代码&#xff1a; import math class Circle: def __init__(self, r): self.r r #面积 def area(self): r…

最佳实践 | 八爪鱼采集器如何用PartnerShare做全民分销?

在数字化时代&#xff0c;数据采集和分析已经成为企业运营和决策的重要一环。八爪鱼采集器作为一款领先的SaaS产品&#xff0c;凭借其强大的数据采集和处理能力&#xff0c;成为了众多企业和个人用户的心头好。为了进一步拓展市场份额&#xff0c;提升品牌影响力&#xff0c;八…

TCP通信并发:

上次的程序只能保持&#xff0c;单线程或者进程 多进程并发服务器 进程的特点&#xff08;有血缘关系&#xff09; 创建子进程&#xff1a;fork&#xff08;&#xff09;&#xff1b; 虚拟地址空间被复制 &#xff0c;从一份变成两份&#xff08;用户区和内核区&#xff09…

国内如何访问 OpenAI 的 api

这个问题甚至我的一些大厂的朋友也不太清楚&#xff0c;所以我觉得有必备写一篇文章来简单盘盘它&#xff0c;希望能帮助到有需要的人 众所周知&#xff0c;由于大陆与 OpenAI 双方互相封锁&#xff0c;大陆是无法直接访问 OpenAI api 的 不过由于 GPT 4 的统治地位&#xff0c…

下一代自动化,国外厂商如何通过生成性AI重塑RPA?

企业自动化的未来趋势是什么&#xff1f;科技巨头们普遍认为&#xff0c;由生成性AI驱动的AI Agent将成为下一个重大发展方向。尽管“AI Agent”这一术语尚无统一定义&#xff0c;但它通常指的是那些能够根据指令通过模拟人类互动&#xff0c;在软件和网络平台上执行复杂任务的…

[C++核心编程-05]----C++类和对象之对象的初始化和清理

目录 引言 正文 01-构造函数和析构函数 ​02-构造函数的分类及调用 03-拷贝构造函数调用时机 04-构造函数调用规则 05-深拷贝与浅拷贝 06-初始化列表 07-静态成员变量 08-静态成员函数 …

Eigen求解线性方程组

1、线性方程组的应用 线性方程组可以用来解决各种涉及线性关系的问题。以下是一些通常可以用线性方程组来解决的问题&#xff1a; 在实际工程和科学计算中&#xff0c;求解多项式方程的根有着广泛的应用。 在控制系统的设计中&#xff0c;我们经常需要求解特征方程的根来分析…

【训练与预测】02 - 完整的模型验证套路

02 - 完整的模型验证套路 模型图 验证一个模型就是指使用已经训练好的模型&#xff0c;然后给它提供输入。 test.py import torch import torchvision from PIL import Imagedevice torch.device("cuda" if torch.cuda.is_available() else "cpu") ima…

C++学习笔记——仿函数

文章目录 仿函数——思维导图仿函数是什么仿函数的优势理解仿函数仿函数的原理举例 仿函数——思维导图 仿函数是什么 使用对象名调用operator&#xff08;&#xff09;函数看起来像是在使用函数一样&#xff0c;因此便有了仿函数的称呼&#xff1b;仿函数存在的意义是&#x…

Burp Suite 抓包,浏览器提示有软件正在阻止Firefox安全地连接到此网站

问题现象 有软件正在阻止Firefox安全地连接到此网站 解决办法 没有安装证书&#xff0c;在浏览器里面安装bp的证书就可以了 参考&#xff1a;教程合集 《H01-启动和激活Burp.docx》——第5步

如何防止源代码泄露?彻底解决源代码防泄密的方法

SDC沙盒系统&#xff1a;数据安全的守护者 SDC沙盒系统&#xff0c;为研发型企业设计&#xff0c;实现了对数据的代码级保护&#xff0c;同时不影响工作效率和正常使用。系统通过自动加密敏感数据&#xff0c;并配合多种管控机制&#xff0c;有效防止了数据的泄露。 涉密可信…

代码随想录算法训练营第四十二天| 01背包问题(二维、一维)、416.分割等和子集

系列文章目录 目录 系列文章目录动态规划&#xff1a;01背包理论基础①二维数组②一维数组&#xff08;滚动数组&#xff09; 416. 分割等和子集①回溯法&#xff08;超时&#xff09;②动态规划&#xff08;01背包&#xff09;未剪枝版剪枝版 动态规划&#xff1a;01背包理论基…

渗透之sql注入----二次注入

目录 二次注入的原理&#xff1a; 实战&#xff1a; 第一步&#xff1a;找注入点 找漏洞&#xff1a; 注入大概过程&#xff1a; 第二步&#xff1a;开始注入 二次注入的原理&#xff1a; 二次注入是由于对用户输入的数据过滤不严谨&#xff0c;导致存在异常的数据被出入…
最新文章