React+Node实现大文件分片上传、断点续传秒传思路

目录
  • 1、整体思路
  • 2、实现步骤
    • 2.1 文件切片加密
    • 2.2 查询上传文件状态
    • 2.3 秒传
    • 2.4 上传分片、断点续传
    • 2.5 合成分片还原完整文件
  • 3、总结
  • 4、后续扩展与思考
  • 5、源码

1、整体思路

  • 将文件切成多个小的文件;
  • 将切片并行上传;
  • 所有切片上传完成后,服务器端进行切片合成;
  • 当分片上传失败,可以在重新上传时进行判断,只上传上次失败的部分实现断点续传;
  • 当切片合成为完整的文件,通知客户端上传成功;
  • 已经传到服务器的完整文件,则不需要重新上传到服务器,实现秒传功能;

2、实现步骤

2.1 文件切片加密

利用MD5 , MD5 是文件的唯一标识,可以利用文件的 MD5 查询文件的上传状态;

读取进度条进度,生成MD5:

实现结果:

实现代码如下:

const md5File = (file) => {
    return new Promise((resolve, reject) => {
      // 文件截取
      let blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice,
        chunkSize = file?.size / 100,
        chunks = 100,
        currentChunk = 0,
        spark = new SparkMD5.ArrayBuffer(),
        fileReader = new FileReader();

      fileReader.onload = function (e) {
        console.log('read chunk nr', currentChunk + 1, 'of', chunks);
        spark.append(e.target.result);
        currentChunk += 1;

        if (currentChunk < chunks) {
          loadNext();
        } else {
          let result = spark.end()
          resolve(result)
        }
      };

      fileReader.onerror = function () {
        message.error('文件读取错误')
      };

      const loadNext = () => {
        const start = currentChunk * chunkSize,
          end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize;

        // 文件切片
        fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
        // 检查进度条
        dispatch({ type: 'check', checkPercent: currentChunk + 1 })
      }

      loadNext();
    })
  }

2.2 查询上传文件状态

利用当前md5去查询服务器创建的md5文件夹是否存在,如果存在则返回该目录下的所有分片;

前端只需要拿MD5和文件名去请求后端,这里就不在列出来;
node端代码逻辑:

app.get('/check/file', (req, resp) => {
  let query = req.query
  let fileName = query.fileName
  let fileMd5Value = query.fileMd5Value
  // 获取文件Chunk列表
  getChunkList(
      path.join(uploadDir, fileName),
      path.join(uploadDir, fileMd5Value),
      data => {
          resp.send(data)
      }
  )
})

// 获取文件Chunk列表
async function getChunkList(filePath, folderPath, callback) {
  let isFileExit = await isExist(filePath)
  let result = {}
  // 如果文件已在存在, 不用再继续上传, 真接秒传
  if (isFileExit) {
      result = {
          stat: 1,
          file: {
              isExist: true,
              name: filePath
          },
          desc: 'file is exist'
      }
  } else {
      let isFolderExist = await isExist(folderPath)
      // 如果文件夹(md5值后的文件)存在, 就获取已经上传的块
      let fileList = []
      if (isFolderExist) {
          fileList = await listDir(folderPath)
      }
      result = {
          stat: 1,
          chunkList: fileList,
          desc: 'folder list'
      }
  }
  callback(result)
}

2.3 秒传

如果上传的当前文件已经存在服务器目录,则秒传;

服务器端代码已给出,前端根据返回的接口做判断;

if (data?.file) {
  message.success('文件已秒传')
  return
}

实现效果:

2.4 上传分片、断点续传

检查本地切片和服务器对应的切片,如果没有当前切片则上传,实现断点续传;
同步并发上传所有的切片,维护上传进度条状态;
前端代码:

/**
   * 上传chunk
   * @param {*} fileMd5Value 
   * @param {*} chunkList 
   */
  async function checkAndUploadChunk(file, fileMd5Value, chunkList) {
    let chunks = Math.ceil(file.size / chunkSize)
    const requestList = []
    for (let i = 0; i < chunks; i++) {
      let exit = chunkList.indexOf(i + "") > -1
      // 如果不存在,则上传
      if (!exit) {
        requestList.push(upload({ i, file, fileMd5Value, chunks }))
      }
    }

    // 并发上传
    if (requestList?.length) {
      await Promise.all(requestList)
    }
  }

    // 上传chunk
  function upload({ i, file, fileMd5Value, chunks }) {
    current = 0
    //构造一个表单,FormData是HTML5新增的
    let end = (i + 1) * chunkSize >= file.size ? file.size : (i + 1) * chunkSize
    let form = new FormData()
    form.append("data", file.slice(i * chunkSize, end)) //file对象的slice方法用于切出文件的一部分
    form.append("total", chunks) //总片数
    form.append("index", i) //当前是第几片     
    form.append("fileMd5Value", fileMd5Value)
    return axios({
      method: 'post',
      url: BaseUrl + "/upload",
      data: form
    }).then(({ data }) => {
      if (data.stat) {
        current = current + 1
        const uploadPercent = Math.ceil((current / chunks) * 100)
        dispatch({ type: 'upload', uploadPercent })
      }
    })
  }

Node端代码:

app.all('/upload', (req, resp) => {
  const form = new formidable.IncomingForm({
      uploadDir: 'nodeServer/tmp'
  })
  form.parse(req, function(err, fields, file) {
      let index = fields.index
      let fileMd5Value = fields.fileMd5Value
      let folder = path.resolve(__dirname, 'nodeServer/uploads', fileMd5Value)
      folderIsExit(folder).then(val => {
          let destFile = path.resolve(folder, fields.index)
          copyFile(file.data.path, destFile).then(
              successLog => {
                  resp.send({
                      stat: 1,
                      desc: index
                  })
              },
              errorLog => {
                  resp.send({
                      stat: 0,
                      desc: 'Error'
                  })
              }
          )
      })
  })

实现效果:

存储形式:

2.5 合成分片还原完整文件

当所有的分片上传完成,前端通知服务器端分片上传完成,准备合成;

前端代码:

  /**
   * 所有的分片上传完成,准备合成
   * @param {*} file 
   * @param {*} fileMd5Value 
   */
  function notifyServer(file, fileMd5Value) {
    let url = BaseUrl + '/merge?md5=' + fileMd5Value + "&fileName=" + file.name + "&size=" + file.size
    axios.get(url).then(({ data }) => {
      if (data.stat) {
        message.success('上传成功')
      } else {
        message.error('上传失败')
      }
    })
  }

Node端代码:

// 合成
app.all('/merge', (req, resp) => {
  let query = req.query
  let md5 = query.md5
  let fileName = query.fileName
  console.log(md5, fileName)
  mergeFiles(path.join(uploadDir, md5), uploadDir, fileName)
  resp.send({
      stat: 1
  })
})

// 合并文件
async function mergeFiles(srcDir, targetDir, newFileName) {
  let fileArr = await listDir(srcDir)
  fileArr.sort((x,y) => {
      return x-y;
  })
  // 把文件名加上文件夹的前缀
  for (let i = 0; i < fileArr.length; i++) {
      fileArr[i] = srcDir + '/' + fileArr[i]
  }
  concat(fileArr, path.join(targetDir, newFileName), () => {
      console.log('合成成功!')
  })
}

请求实现:

合成文件效果:

3、总结

将文件切片,并发上传切片,切片合成完整文件,实现分片上传;
使用MD5标识文件夹,得到唯一标识;
分片上传前通过文件 MD5 查询已上传切片列表,上传时只上传未上传过的切片,实现断点续传;
检查当前上传文件,如果已存在服务器,则不需要再次上传,实现秒传;

4、后续扩展与思考

使用时间切片计算hash

当文件过大时需要计算很久的hash,页面不能做其他的操作,所以考虑使用React-Fiber的架构理念,利用浏览器空闲时间去计算hash。考虑使用window.requestIdleCallback()函数;

请求并发控制

假如一个文件过大,就会切割成许多的碎片,一次性发几百个请求,这显然是不行的;所以要考虑请求并发数控制;

5、源码

地址:https://github.com/linhexs/file-upload.git

到此这篇关于React+Node实现大文件分片上传、断点续传秒传思路的文章就介绍到这了,更多相关React Node大文件分片上传、断点续传内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Node.js断点续传的实现

    前言 平常业务需求:上传图片.Excel等,毕竟几M的大小可以很快就上传到服务器. 针对于上传视频等大文件几百M或者几G的大小,就需要等待比较长的时间. 这就产生了对应的解决方法,对于大文件上传时的暂停.断网.网络较差的情况下,  使用切片+断点续传就能够很好的应对上述的情况 方案分析 切片 就是对上传视频进行切分,具体操作为: File.slice(start,end):返回新的blob对象 拷贝blob的起始字节 拷贝blob的结束字节 断点续传 每次切片上传之前,请求服务器接口,读取相同文

  • Node中文件断点续传原理和方法总结

    目录 原理介绍 普通上传 大文件上传 断点续传 方法总结 普通文件 前端部分 大文件 实战演练 普通文件 导语:之前做过一个小项目,其中用到了文件上传,在大文件上面使用了断点续传,降低了服务器方面的压力,现在就这个开发经验做一个细致的总结. 原理介绍 这里先介绍一下文件上传的原理,帮助理清这个头绪. 普通上传 一般网站上都是普通上传的比较多,大多数都是上传一些用户的头像,用户的动态评论附带图片什么的,所以先来说一下这个的原理. 用户选择文件后,js检测文件大小是否超出限制和格式是否正确: 检查后

  • Node.js实现断点续传

    目录 方案分析 切片 断点续传 具体解决流程 逻辑分析 前端 服务端 小结 方案分析 切片 就是对上传视频进行切分,具体操作为: File.slice(start,end):返回新的blob对象 拷贝blob的起始字节 拷贝blob的结束字节 断点续传 每次切片上传之前,请求服务器接口,读取相同文件的已上传切片数 上传的是新文件,服务端则返回0,否则返回已上传切片数 具体解决流程 该demo提供关键点思路及方法,其他功能如:文件限制,lastModifiedDate校验文件重复性,缓存文件定期清

  • 基于Node.js的大文件分片上传示例

    我们在做文件上传的时候,如果文件过大,可能会导致请求超时的情况.所以,在遇到需要对大文件进行上传的时候,就需要对文件进行分片上传的操作.同时如果文件过大,在网络不佳的情况下,如何做到断点续传?也是需要记录当前上传文件,然后在下一次进行上传请求的时候去做判断. 先上代码:代码仓库地址 前端 1. index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8

  • React+Node实现大文件分片上传、断点续传秒传思路

    目录 1.整体思路 2.实现步骤 2.1 文件切片加密 2.2 查询上传文件状态 2.3 秒传 2.4 上传分片.断点续传 2.5 合成分片还原完整文件 3.总结 4.后续扩展与思考 5.源码 1.整体思路 将文件切成多个小的文件: 将切片并行上传: 所有切片上传完成后,服务器端进行切片合成: 当分片上传失败,可以在重新上传时进行判断,只上传上次失败的部分实现断点续传: 当切片合成为完整的文件,通知客户端上传成功: 已经传到服务器的完整文件,则不需要重新上传到服务器,实现秒传功能: 2.实现步骤

  • JavaScript实现大文件分片上传处理

    很多时候我们在处理文件上传时,如视频文件,小则几十M,大则 1G+,以一般的HTTP请求发送数据的方式的话,会遇到的问题: 1.文件过大,超出服务端的请求大小限制: 2.请求时间过长,请求超时: 3.传输中断,必须重新上传导致前功尽弃 这些问题很影响用户的体验感,所以下面介绍一种基于原生JavaScript进行文件分片处理上传的方案,具体实现过程如下: 1.通过dom获取文件对象,并且对文件进行MD5加密(文件内容+文件标题形式),采用SparkMD5进行文件加密: 2.进行分片设置,文件Fil

  • webuploader在springMVC+jquery+Java开发环境下的大文件分片上传的实例代码

    注意: 1,webuploader上传组件会和jQuery自带的上传组件冲突,所以不要使用<form>标签中添加上传文件的属性; enctype="multipart/form-data" 2.并且屏蔽ApplicationContext-mvc.xml里面的拦截配置! <!-- 上传拦截,如最大上传值及最小上传值 --> <!--新增加的webuploader上传组件,必须要屏蔽这里的拦截机制 <bean id="multipartRes

  • PHP大文件分片上传的实现方法

    一.前言 在网站开发中,经常会有上传文件的需求,有的文件size太大直接上传,经常会导致上传过程中耗时太久,大量占用带宽资源,因此有了分片上传. 分片上传主要是前端将一个较大的文件分成等分的几片,标识当前分片是第几片和总共几片,待所有的分片均上传成功的时候,在后台进行合成文件即可. 二.开发过程中遇到的问题 分片的时候每片该分多大size?太大会出现"413 request entity too large" 分片上传的时候并不是严格按照分片的序号顺序上传,如何判断所有的分片均上传成功

  • .NET Core Web APi大文件分片上传研究实现

    前言 前两天发表利用FormData进行文件上传,然后有人问要是大文件几个G上传怎么搞,常见的不就是分片再搞下断点续传,动动手差不多也能搞出来,只不过要深入的话,考虑的东西还是很多.由于断点续传之前写个几篇,这里试试利用FormData来进行分片上传. .NET Core Web APi文件分片上传 这里我们依然是使用FormData来上传,只不过在上传之前对文件进行分片处理,如下HTML代码 <div class="form-horizontal" style="ma

  • Java实现浏览器端大文件分片上传

    目录 背景介绍 项目介绍 需要知识点 启动项目 项目示范 核心讲解 核心原理 功能分析 分块上传 秒传功能 断点续传 总结 参考文献 背景介绍   Breakpoint-http,是不是觉得这个名字有点low,break point断点.这是一个大文件上传的一种实现.因为本来很久没写过前端了,本来想自己好好写一番js,可惜因为种种原因而作罢了.该项目是基于一款百度开源的前端上传控件:WebUploader(百度开源的东西文档一如既往的差,哈哈.或者是我理解能力差).   Breakpoint-h

  • Java超详细大文件分片上传代码

    目录 Java 大文件分片上传 首先是交互的控制器 上传文件分片参数接收 大文件分片上传服务类实现 文件分片上传定义公共服务类接口 文件分片上传文件操作接口实现类 OSS阿里云对象存储分片上传实现 京东云对象存储实现 腾讯云对象存储分片上传 分片上传前端代码实现 Java 大文件分片上传 原理:前端通过js读取文件,并将大文件按照指定大小拆分成多个分片,并且计算每个分片的MD5值.前端将每个分片分别上传到后端,后端在接收到文件之后验证当前分片的MD5值是否与上传的MD5一致,待所有分片上传完成之

  • vue 大文件分片上传(断点续传、并发上传、秒传)

    对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送.接收都是不可取,很容易导致内存问题.所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线程并发上传能够达到最大效率. 本文是基于 springboot + vue 实现的文件上传,本文主要介绍vue实现文件上传的步骤及代码实现,服务端(springboot)的实现步骤及实现请移步本人的另一篇文章: springboot 大文件上传.分片上传.断点续传.秒传 上传分步: 本人分析上传总共分为: MD5读取文件,获取文件的

  • 前端使用koa实现大文件分片上传

    目录 引言 前端 拆分上传的文件流 后端 接收文件片段 合并文件片段 总结 引言 一个文件资源服务器,很多时候需要保存的不只是图片,文本之类的体积相对较小的文件,有时候,也会需要保存音视频之类的大文件.在上传这些大文件的时候,我们不可能一次性将这些文件数据全部发送,网络带宽很多时候不允许我们这么做,而且这样也极度浪费网络资源. 因此,对于这些大文件的上传,往往会考虑用到分片传输. 分片传输,顾名思义,也就是将文件拆分成若干个文件片段,然后一个片段一个片段的上传,服务器也一个片段一个片段的接收,最

  • 利用Vue3+Element-plus实现大文件分片上传组件

    目录 一.背景 二.技术栈 三.核心代码实现 四.总结 一.背景 实际项目中遇到需要上传几十个G的3d模型文件,传统上传就不适用了. 结合element提供的上传组件自己封装了文件分片上传的组件. 思路: 把文件拆分成若干分片 依次上传分片(每次上传前可校验该分片是否已经上传) 发起合并分片的请求 二.技术栈 Vue3+Ts+Element-Plus 其他库:spark-md5 后端接口: 上传分片接口 校验分片是否已上传接口 合并分片接口 三.核心代码实现 Element组件基础配置 <el-

随机推荐