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

Vue 大文件分片上传是前端文件上传中常见的解决方案之一,用于解决大文件上传时可能遇到的性能和稳定性问题。常见的性能问题包括上传时间过长、上传失败等,而稳定性问题则是在上传过程中可能因为网络原因导致上传失败,需要支持断点续传。

  1. 什么是文件分片上传?
    文件分片上传是指将大文件分成多个较小的文件片段进行上传,上传完成后再将这些片段组合成完整的文件。这样做的好处是文件较小、上传速度较快,并且上传过程中在网络环境不佳时也具有更好的稳定性,一旦上传失败可以从上次失败的位置继续上传,实现断点续传。

  2. Vue 大文件分片上传步骤
    Vue 大文件分片上传的步骤分为以下几个:

2.1 文件切片
将文件分成大小一致的片段,并生成唯一标识符和上传地址等信息,示例代码如下:

function createChunks (file, chunk_size) {
  const chunks = []
  let start = 0
  let end = 0
  let index = 0
  while (start < file.size) {
    end = start + chunk_size
    chunks.push({
      index: index,
      total: Math.ceil(file.size / chunk_size),
      blob: file.slice(start, end),
      name: file.name,
      uid: `${file.name}_${index}_${file.size}_${Date.now()}`
    })
    index++
    start = end
  }
  return chunks
}

2.2 上传文件片段
将文件片段通过 AJAX 异步上传到服务器,并在上传完成后触发上传成功回调函数,示例代码如下:

async function uploadChunk ({ url, data, progressCallback }) {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest()
    xhr.withCredentials = true
    xhr.open('post', url, true)
    xhr.setRequestHeader('Content-Type', 'application/octet-stream')
    xhr.upload.onprogress = function (event) {
      if (event.total > 0) {
        event.percent = (event.loaded / event.total) * 100
      }
      progressCallback && progressCallback(event)
    }
    xhr.onload = function () {
      if (xhr.status === 200) {
        resolve(xhr.responseText)
      } else {
        reject(new Error('上传失败'))
      }
    }
    xhr.onerror = function (err) {
      reject(new Error('上传失败'))
    }
    xhr.send(data)
  })
}

2.3 流程控制
使用 Promise.all 控制上传过程,等待所有文件片段上传完成后再触发上传完成回调函数,示例代码如下:

Promise.all(chunks.map((chunk) => {
  return uploadChunk({
    url: upload_url,
    data: chunk.blob,
    progressCallback: updateProgress
  })
})).then((res) => {
  console.log('文件上传成功')
  // 完整的文件上传
  checkFile()
}).catch((err) => {
  console.error(err.message)
})

2.4 校验文件完整性
将所有文件片段的 md5 值上传到服务器,服务器根据 md5 值验证文件完整性,如果文件完整,则将文件合并;如果文件不完整,则删除已上传的文件片段,示例代码如下:

async function checkFile () {
  const md5Promises = chunks.map((chunk) => {
    return createFileMd5(chunk.blob)
  })
  const md5Array = await Promise.all(md5Promises)
  const md5 = md5Array.join('')
  console.log('文件MD5值为', md5)
  // 校验文件是否完整
  const res = await checkFileApi({
    md5: md5,
    size: file.size,
    name: file.name
  })
  if (res.code === 0 && res.data && res.data.uploaded) {
    console.log('秒传文件')
    // 秒传
  } else {
    console.log('开始合并文件')
    // 合并文件
    mergeChunks(md5)
  }
}

2.5 合并文件
将已上传的文件片段按照顺序合并成完整的文件,并触发文件上传完成回调函数,示例代码如下:

async function mergeChunks (md5) {
  const res = await mergeFileApi({
    md5: md5,
    name: file.name,
    chunks
  })
  if (res.code === 0) {
    console.log('文件上传成功')
    // 文件上传成功
  } else {
    console.error(res.message)
  }
}
  1. 示例说明
    下面给出两个示例说明:

3.1 Vue 大文件分片上传组件

<template>
  <div>
    <input type="file" @change="handleFileChange" />
    <el-button type="primary" @click="handleUpload">上传</el-button>
    <div class="progress">
      <el-progress :percentage="progress" :stroke-width="3"/>
    </div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      file: null,
      chunks: [],
      upload_url: 'https://example.com/api/upload',
      progress: 0
    }
  },
  methods: {
    handleFileChange (e) {
      this.file = e.target.files[0]
      this.chunks = createChunks(this.file, 2 * 1024 * 1024) // 按2MB切片
    },
    handleUpload () {
      Promise.all(this.chunks.map((chunk) => {
        return uploadChunk({
          url: this.upload_url,
          data: chunk.blob,
          progressCallback: this.updateProgress
        })
      })).then((res) => {
        console.log('文件上传成功')
        checkFile()
      }).catch((err) => {
        console.error(err.message)
      })
    },
    async checkFile () {
      const md5Promises = this.chunks.map((chunk) => {
        return createFileMd5(chunk.blob)
      })
      const md5Array = await Promise.all(md5Promises)
      const md5 = md5Array.join('')
      console.log('文件MD5值为', md5)

      const res = await checkFileApi({
        md5: md5,
        size: this.file.size,
        name: this.file.name
      })
      if (res.code === 0 && res.data && res.data.uploaded) {
        console.log('秒传文件')
        // 秒传
      } else {
        console.log('开始合并文件')
        // 合并文件
        mergeChunks(md5)
      }
    },
    async mergeChunks (md5) {
      const res = await mergeFileApi({
        md5: md5,
        name: this.file.name,
        chunks: this.chunks
      })
      if (res.code === 0) {
        console.log('文件上传成功')
        // 文件上传成功
      } else {
        console.error(res.message)
      }
    },
    updateProgress (progressEvent) {
      this.progress = Math.ceil(progressEvent.percent)
    }
  }
}
</script>

3.2 上传进度条组件

<template>
  <div class="progress-wrapper">
    <div class="progress-bar" :style="{ width: percent + '%' }"></div>
  </div>
</template>

<script>
export default {
  props: {
    url: {
      type: String,
      required: true
    },
    data: {
      type: Object,
      required: true
    },
    fileName: {
      type: String,
      required: true
    },
    chunk: {
      type: Blob,
      required: true
    }
  },
  data () {
    return {
      percent: 0
    }
  },
  methods: {
    uploadChunk () {
      const xhr = new XMLHttpRequest()
      xhr.open('post', this.url, true)
      xhr.setRequestHeader('Content-Type', 'application/octet-stream')
      xhr.upload.onprogress = (e) => {
        if (e.lengthComputable) {
          this.percent = Math.ceil((e.loaded / e.total) * 100)
        }
      }
      xhr.onreadystatechange = () => {
        if (xhr.readyState === 4) {
          if (xhr.status === 200) {
            console.log(xhr.responseText)
          } else {
            console.log('上传失败')
          }
        }
      }
      const formData = new FormData()
      formData.append('file', this.chunk, `${this.fileName}_${this.data.index}`)
      formData.append('index', String(this.data.index))
      formData.append('total', String(this.data.total))
      formData.append('name', this.fileName)
      formData.append('uid', this.data.uid)
      xhr.send(formData)
    }
  },
  mounted () {
    this.uploadChunk()
  }
}
</script>

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:vue 大文件分片上传(断点续传、并发上传、秒传) - Python技术站

(0)
上一篇 2023年5月28日
下一篇 2023年5月28日

相关文章

  • Vue Prop属性功能与用法实例详解

    当我们需要将数据从父组件传递到子组件时,可以使用 Vue 中的 Prop 属性来实现。本文将详细讲解 Vue Prop 属性的功能和用法,并提供两个示例说明。 Prop 属性的基本概念 Prop 属性的作用 Prop 属性用于将数据从父组件传递到子组件,实现组件之间的数据通信。 Prop 属性的传值方式 父组件通过向子组件传递属性 props ,子组件通过 …

    Vue 2023年5月27日
    00
  • vue 组件开发原理与实现方法详解

    Vue 组件开发原理与实现方法详解 Vue 组件是 Vue.js 中的一个重要概念,允许我们将一个页面拆分成多个独立的、可复用的部分,并且每个组件拥有自己的数据、样式和行为。组件化开发大大提高了应用程序的可维护性和可扩展性。 本文将从以下三个方面介绍 Vue 组件开发的原理和实现方法: 组件的基本原理 组件的实现方法 Vue 组件的使用示例 一、组件的基本原…

    Vue 2023年5月27日
    00
  • 详解vue-cli 2.0配置文件(小结)

    下面来详细讲解“详解vue-cli 2.0配置文件(小结)”的完整攻略。 什么是vue-cli 2.0配置文件 vue-cli是Vue.js官方提供的一个基于Webpack构建工具的脚手架,用于快速搭建Vue.js项目的基础架构。在Vue.js 2.0版本之前,vue-cli默认使用的是基于Webpack1.x版本的构建工具,因此配置文件也是基于这个版本进行…

    Vue 2023年5月27日
    00
  • vue实现搜索关键词高亮的详细教程

    下面是“vue实现搜索关键词高亮的详细教程”的完整攻略。 一、需求分析 我们需要实现一个搜索功能,在用户搜索关键词后,将页面中匹配到的关键词高亮显示。 二、实现过程 1. HTML结构 首先,我们需要在HTML中准备好需要搜索的容器,并且将搜索结果渲染到容器中。 <div class="search-container"> &…

    Vue 2023年5月27日
    00
  • vue created钩子函数与mounted钩子函数的用法区别

    Vue是一种流行的JavaScript框架,提供了很多生命周期钩子函数给开发者,其中包括了created和mounted钩子函数。这两个钩子函数都会在组件被创建之后执行,但是它们有着不同的作用和使用场景。 created钩子函数 作用:created钩子函数是在Vue实例被创建之后,完成了数据观测(data observer)和事件处理(event watc…

    Vue 2023年5月28日
    00
  • 关于Node.js中的JXcore打包示例

    下面就来详细讲解“关于Node.js中的JXcore打包示例”的完整攻略。 Node.js中的JXcore打包示例 简介 JXcore是一种基于Node.js的开源项目,主要用于将Node.js项目转化为独立的应用程序,支持Node.js的所有模块和API。使用JXcore可以将原本需要使用Node.js命令行执行的代码打包成二进制文件,方便部署和使用。 安…

    Vue 2023年5月28日
    00
  • vue自定义组件(通过Vue.use()来使用)即install的用法说明

    Vue.use()的定义: Vue.use()是用来注册Vue插件的方法,本质上就是调用插件对象的install方法注册插件,所以使用Vue.use()注册插件的前提是,必须提供一个具有install方法的对象作为插件。 插件的定义: 插件其实就是一个具有install方法的JavaScript对象。这个install方法有一个Vue构造器作为参数,来给Vu…

    Vue 2023年5月27日
    00
  • vue中动态添加style样式的几种写法总结

    当我们在Vue中需要动态添加样式时,我们可以使用以下几种方法: 使用class绑定 Vue中可以使用:class或者:v-bind指令将一个变量与class属性绑定起来,这样我们可以通过改变变量的值来改变元素的class属性,从而改变样式。 <template> <div :class="{ ‘selected’: isSelec…

    Vue 2023年5月27日
    00
合作推广
合作推广
分享本页
返回顶部