文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2024-04-02 19:55

关注

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 上传分片、断点续传

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


  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 合成分片还原完整文件

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

前端代码:

  
  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大文件分片上传、断点续传内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-前端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯