js 大文件切片,中止上传,上传进度,断点续传

大文件切片上传
背景介绍:当涉及大文件上传时,一种有效的方法是将大文件分割成小切片并逐个上传。这种技术不仅可以减轻服务器的负担,还可以避免上传过程中的中断和内存问题。本文将介绍如何使用JavaScript实现大文件切片上传,并解释如何处理断点续传、并发控制以及上传取消等问题,用到的知识点有大文件切片、中止上传 、上传进度、断点续传、并发数量控制。

切片上传原理
大文件切片上传的基本原理是将文件分成多个固定大小的小切片,然后逐个上传到服务器。服务器收到这些切片后,可以按顺序合并它们,还原为完整的文件。这种方法不仅能提高上传效率,还能应对网络波动和服务器性能限制。

实现步骤
下面是实现大文件切片上传的基本步骤:

切片生成: 使用JavaScript将大文件切割成小切片,可以使用File API中的File.slice()方法。

切片上传: 将切片逐个上传到服务器,可以使用XMLHttpRequest或Fetch API发送切片数据。

服务器处理: 服务器接收并保存切片,并记录每个切片的索引。

切片合并: 当所有切片上传完成后,服务器按照索引顺序合并切片(或者后端提供合并接口将hash值传给后端进行合并),还原为完整文件。

断点续传
通过记录已上传切片的索引,即使上传过程中断,也可以从断点处继续上传。服务器只需判断已存在的切片,避免重新上传。

并发控制
为了避免同时上传大量切片导致网络拥塞,可以控制并发上传数。例如,一次最多只上传3个切片,等待其中一个完成后再上传下一个。

上传取消
允许用户在上传过程中取消操作是很重要的。为此,可以使用一个标记来判断是否取消上传,如果取消,则终止上传过程。

接下来,我们将深入探讨如何在JavaScript中实现这些功能。从切片生成和上传开始,逐步讲解如何处理断点续传、并发控制和上传取消。

话不多说,上代码

<template>
  <div class="manage">
    <input type="file" @change="handleFileChange" multiple />
    <button @click="stop">停止上传</button>
    <span>当前已上传:{{progress}}%</span>
  </div>
</template>

<script>
export default {
  name: 'Menu',
  props: {
  },
  mounted () {
  },
  data () {
    return {
      progress: 0,
      stopUpload: false,
      totalChunks: null,
      selectedFiles: [],
      uploadPromises: [],
      concurrentLimit: 2 // 控制并发数量
    }
  },
  methods: {
    handleFileChange (event) {
      this.selectedFiles = Array.from(event.target.files)
      this.startUpload(event.target.files)
    },
    async uploadFile (file, index, total) {
      return new Promise((resolve, reject) => {
        // 模拟上传操作,实际上传操作可能需要使用 XMLHttpRequest 或其他上传库
        // setTimeout(async () => {
        //   console.log(`Uploaded: ${file.name}`)
        //   const formData = new FormData();
        //   formData.append('fileChunk', file);
        //   formData.append('chunkIndex', index);
        //   formData.append('totalChunks', total);
        //   const response = await fetch('upload-url', {
        //     method: 'POST',
        //     body: formData
        //   });
        //   const result = await response.json();
        //   resolve(result)
        // }, 1000) // 假设上传耗时 1 秒
        setTimeout(async () => {
          console.log(`Uploaded: ${file.name}`)
          resolve()
        }, 2000) // 假设上传耗时 1 秒
      })
    },
    async startUpload (file) {
      console.log('file', file)
      this.uploadPromises = []
      const size = 1024 * 1024
      const formNum = Math.ceil(file[0].size / size)
      const hashes = []
      for (let i = 0; i <= formNum; i++) {
        const start = size * i;
        const end = size * (i + 1)
        const chunk = file[0].slice(start, end);
        const hash = await this.calculateHash(chunk);
        console.log('this.stopUpload', this.stopUpload)
        if (this.stopUpload) return false
        hashes.push(hash)
        // 将需要上传的切片和对应的哈希值添加到FormData中
        // 断点续传逻辑需要后端判断,前端根据片段生成唯一hash值,后端存储hash,续传时候需要判断若已上传直接给出进度
        const promise = this.uploadFile(chunk, i, formNum)
        this.uploadPromises.push(promise)

        if (this.uploadPromises.length >= this.concurrentLimit) {
          await Promise.race(this.uploadPromises) // 控制并发
          this.uploadPromises.shift() // 移除已完成的 Promise
        }
        this.progress = (i / formNum) * 100
      }
      console.log('hashes', hashes)
      await Promise.all(this.uploadPromises) // 等待剩余的上传完成
      console.log('全部完成上传!')
      // 调用后端提供合并接口
      // const response = await fetch('upload-merge', {
      //   method: 'POST',
      //   body: hashes
      // })
    },
    async calculateHash (chunk) {
      const buffer = await chunk.arrayBuffer()
      const hashBuffer = await crypto.subtle.digest('SHA-256', buffer)
      const hashArray = Array.from(new Uint8Array(hashBuffer))
      const hashHex = hashArray.map(byte => byte.toString(16).padStart(2, '0')).join('')
      return hashHex
    },
    stop () {
      this.stopUpload = true
    }
  }
}
</script>

参考文章:http://blog.ncmem.com/wordpress/2023/10/21/js-%e5%a4%a7%e6%96%87%e4%bb%b6%e5%88%87%e7%89%87%ef%bc%8c%e4%b8%ad%e6%ad%a2%e4%b8%8a%e4%bc%a0%ef%bc%8c%e4%b8%8a%e4%bc%a0%e8%bf%9b%e5%ba%a6%ef%bc%8c%e6%96%ad%e7%82%b9%e7%bb%ad%e4%bc%a0/
欢迎入群一起讨论

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个使用 Node.js 实现大文件切片上传断点续传的功能示例: ```javascript const http = require('http'); const fs = require('fs'); const path = require('path'); const PORT = 8000; const UPLOAD_DIR = path.resolve(__dirname, './uploads'); // 创建上传目录 if (!fs.existsSync(UPLOAD_DIR)) { fs.mkdirSync(UPLOAD_DIR); } // 上传文件处理函数 const handleUpload = (req, res) => { const { filename, chunkIndex, totalChunks } = req.headers; const chunkDir = path.resolve(UPLOAD_DIR, filename); // 如果是第一个分片,创建文件夹 if (chunkIndex === '0') { fs.mkdirSync(chunkDir); } // 获取上传的分片数据 const chunksData = []; req.on('data', (chunk) => { chunksData.push(chunk); }); req.on('end', () => { const buffer = Buffer.concat(chunksData); // 写入分片文件 fs.writeFileSync(path.resolve(chunkDir, chunkIndex), buffer); // 如果当前分片是最后一个分片,则合并文件 if (Number(chunkIndex) === Number(totalChunks) - 1) { const filePath = path.resolve(UPLOAD_DIR, filename); const chunks = fs.readdirSync(chunkDir); const writeStream = fs.createWriteStream(filePath); chunks.forEach((chunk) => { const chunkPath = path.resolve(chunkDir, chunk); const chunkBuffer = fs.readFileSync(chunkPath); fs.unlinkSync(chunkPath); // 删除分片文件 writeStream.write(chunkBuffer); }); writeStream.end(() => { res.end('upload success'); }); } else { res.end('chunk upload success'); } }); }; // 断点续传处理函数 const handleResumeUpload = (req, res) => { const { filename } = req.headers; const filePath = path.resolve(UPLOAD_DIR, filename); const fileStat = fs.statSync(filePath); res.setHeader('Content-Length', fileStat.size); res.setHeader('Content-Type', 'application/octet-stream'); res.setHeader('Accept-Ranges', 'bytes'); const range = req.headers.range || 'bytes=0-'; const positions = range.replace(/bytes=/, '').split('-'); const start = parseInt(positions[0], 10); const end = positions[1] ? parseInt(positions[1], 10) : fileStat.size - 1; const chunkSize = end - start + 1; res.setHeader('Content-Range', `bytes ${start}-${end}/${fileStat.size}`); res.setHeader('Cache-Control', 'no-cache'); const readStream = fs.createReadStream(filePath, { start, end }); readStream.on('open', () => { readStream.pipe(res); }); readStream.on('error', () => { res.end('Error'); }); }; // 创建 HTTP 服务器 const server = http.createServer((req, res) => { if (req.url === '/upload' && req.method === 'POST') { handleUpload(req, res); } else if (req.url === '/resume-upload' && req.method === 'GET') { handleResumeUpload(req, res); } else { res.end('Hello World!'); } }); // 启动服务器 server.listen(PORT, () => { console.log(`Server is listening on port ${PORT}`); }); ``` 使用示例: 1. 开启服务器:`node server.js` 2. 上传文件:使用 POST 请求发送文件分片(每个分片的大小可以自定义),请求头需要包含 `filename`(文件名)、`chunkIndex`(当前分片索引,从 0 开始)、`totalChunks`(总分片数)三个字段。 3. 断点续传:使用 GET 请求获取已上传文件,请求头需要包含 `filename`(文件名)字段和 `range`(请求的字节范围)字段。如果请求头中没有 `range` 字段,则返回整个文件的内容。如果请求头中有 `range` 字段,则只返回指定字节范围内的文件内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值