当前位置:
首页
文章
前端
详情

Vue3大文件上传:秒传、断点续传、分片上传

开篇

你是否曾经遇到过上传大文件时,进度条缓慢前进的煎熬?或者上传中断后,又要重新开始上传的烦恼?别担心,Vue3大文件上传攻略来了!本文将教你如何实现秒传、断点续传和分片上传,让你的文件飞快地传!快来跟着小编的步伐,让我们一起解决这个烦人的问题吧!

首先需要明确,大文件上传需要用到后端的支持。本文中我将选择使用Node.js框架下的 Express 来搭建我们的后台。

秒传

秒传指的是已经上传过的文件,不必重复传输,可以直接从后台取回文件资源。

前端实现思路如下:

  • 用户选择要上传的文件
  • 计算文件的hash值,利用hash值判断该文件是否已经存在
  • 如果已经存在,直接从后台获取该文件资源,上传结束
  • 如果不存在,则走正常的分片上传流程

在Vue3中可以通过使用第三方库spark-md5来计算文件的hash值。

安装库:

npm install spark-md5

在页面中引用:

import SparkMD5 from 'spark-md5'

// 计算hash值
const fileReader = new FileReader() // 文件读取器
fileReader.onload = function() {
  const spark = new SparkMD5.ArrayBuffer() // 构建hash值对象
  spark.append(fileReader.result) // 添加文件二进制内容
  const hash = spark.end() // 计算hash值
  console.log(hash)
}
fileReader.readAsArrayBuffer(file)

后端实现思路:

  • 前端传来文件的hash值
  • 后台查询数据库中是否存在该hash对应的文件
  • 如果存在,返回文件资源,否则报错

在Node.js中可以通过使用 mongoose 这个库来连接数据库,并且使用findOne这个方法来查询数据库中是否存在某个hash值对应的文件。

const mongoose = require('mongoose')
const Schema = mongoose.Schema

// 定义文件资源的数据结构
const fileSchema = new Schema({
  name: { type: String, required: true },
  hash: { type: String, required: true },
  size: { type: Number, required: true },
  type: { type: String, required: true },
  createTime: { type: Date, default: Date.now },
  updateTime: { type: Date, default: Date.now }
})

// 定义文件资源模型
const File = mongoose.model('File', fileSchema)

// 查询是否存在该hash值对应的文件
File.findOne({ hash: req.query.hash }, (err, file) => {
  if (err) {
    console.error(err)
    res.status(500).json({ message: 'Internal Server Error' })
  } else {
    if (file) {
      res.status(200).json({ message: 'File exists', url: file.url })
    } else {
      res.status(404).json({ message: 'File not found' })
    }
  }
})

断点续传

断点续传指的是上传文件时,如果因为网络等原因导致上传中断,下次上传可以从中断处继续进行,而不需要重新上传整个文件。

前端实现思路:

  • 使用FileReader对象读取文件内容
  • 将读取内容分为多个片段,并将每个片段上传到后台
  • 若上传中断,则在下次上传时从最后一次成功上传的片段之后继续上传,而不是重新上传整个文件

在Vue3中,可以使用Promise和async/await来实现断点续传功能。

async uploadFileChunk(file, start, end) {
  const chunk = file.slice(start, end) // 获取当前分片
  const formData = new FormData() // 构建formdata用于上传文件
  formData.append('chunk', chunk)
  formData.append('hash', this.hash)
  formData.append('name', file.name)
  formData.append('start', start)
  const config = {
    headers: {
      'Content-Type': 'multipart/form-data'
    }
  }
  try {
    const response = await axios.post(`http://localhost:3000/upload`, formData, config)
    if (response.data.message === 'Chunk uploaded') { // 如果分片上传成功
      this.uploadFile(this.file, end) // 上传完当前分片后,继续上传下一个分片
    } else if (response.data.message === 'Upload successful') { // 如果整个文件上传成功
      this.uploading = false
      this.$emit('uploadFinish', response.data.url)
    }
  } catch (error) {
    console.error(error)
    this.uploading = false
  }
},
async uploadFile(file, start = 0) {
  const end = start + this.chunkSize // 当前分片的终止位置
  await this.uploadFileChunk(file, start, end) // 上传当前分片
}

后端实现思路:

  • 通过文件的hash值来判断服务器上是否存在该文件,从而实现断点续传。如果该文件在服务器上不存在,则新建一个文件,否则继续上传该文件。
  • 将上传的分片存储在服务器的磁盘上,并记录该分片在整个文件中的起始位置,方便合并文件。

在Node.js中,可以使用formidable这个库来解析前端发送过来的formdata数据,并且将上传的每个分片暂存在服务器上。当所有分片上传完成后,可以根据分片信息将所有分片合并成一个完整的文件。同时,可以使用fs模块实现断点续传的功能,通过判断文件在服务器上的大小来确定从哪一个位置继续上传。

const formidable = require('formidable')
const fs = require('fs')
const path = require('path')

const fileDir = path.resolve('./uploads')
if (!fs.existsSync(fileDir)) fs.mkdirSync(fileDir)
let start = 0 // 文件上传的起始位置
form.on('field', (name, value) => {
  if (name === 'hash') {
    hash = value
  } else if (name === 'name') {
    name = value
  } else if (name === 'start') {
    start = Number(value)
  }
})
form.on('file', (_, file) => {
  const filePath = path.join(fileDir, name)
  const stream = fs.createWriteStream(filePath, { start, flags: 'a' })
  fs.createReadStream(file.path).pipe(stream) // 将当前分片写入指定文件
  stream.on('close', () => {
    res.status(200).json({ message: 'Chunk uploaded' })
  })
})
form.on('end', () => {
  if (fs.statSync(path.join(fileDir, name)).size === size) { // 如果文件已上传完成
    // 将文件信息存入数据库
    const newFile = new File({
      name: name,
      hash: hash,
      size: size,
      type: type,
      url: `/uploads/${name}`
    })
    newFile.save((err, file) => {
      if (err) {
        console.error(err)
        res.status(500).json({ message: 'Internal Server Error' })
      } else {
        res.status(200).json({ message: 'Upload successful', url: file.url })
      }
    })
  }
})

分片上传

前端实现思路:

  • 将文件进行分片,每个分片大小一般为1MB
  • 上传每个分片
  • 所有分片上传完成后,后台将分片合并成文件

在Vue3中,可以使用File.slice()方法将文件进行分片,同时使用axios库来发送分片数据到后台。

const sliceSize = 1024 * 1024 // 1MB的分片大小
const chunks = Math.ceil(fileSize / sliceSize) // 文件分片数
const requests = [] // 分片请求的promise列表
for (let i = 0; i < chunks; i++) {
  const start = i * sliceSize // 当前分片在文件中的起始位置
  const end = Math.min(start + sliceSize, fileSize) // 当前分片在文件中的终止位置
  const chunk = file.slice(start, end) // 获取当前分片
  const formData = new FormData() // 构建formdata用于上传文件
  formData.append('chunk', chunk)
  formData.append('hash', hash)
  formData.append('name', file.name)
  formData.append('chunkIndex', i)
  formData.append('chunks', chunks)
  const config = {
    headers: {
      'Content-Type': 'multipart/form-data'
    },
    onUploadProgress: progressEvent => {
      const uploaded = start + progressEvent.loaded // 已上传的大小
      const total = fileSize // 文件总大小
      const percentCompleted = Math.floor((uploaded / total) * 100) // 计算上传进度
      // 更新当前分片的上传进度
      this.$set(this.chunks, i, percentCompleted)
      // 更新已上传总大小
      this.uploadedSize += progressEvent.loaded
      // 更新已上传总进度
      this.totalPercentCompleted = Math.floor((this.uploadedSize / this.fileSize) * 100)
    }
  }
  const request = axios.post(`http://localhost:3000/upload`, formData, config)
  requests.push(request)
}
Promise.all(requests) // 所有分片上传完成

后端实现思路:

  • 分片上传时,存储每个分片,以及该分片所属文件的hash值、分片索引、分片总数等信息
  • 如果所有分片上传完成,将分片合并成一个完整的文件
  • 将文件信息存入数据库

在Node.js中,可以使用formidable这个库来解析前端发送过来的formdata数据,并且将上传的每个分片暂存在服务器上。当所有分片上传完成后,可以根据分片信息将所有分片合并成一个完整的文件。

const formidable = require('formidable')
const fs = require('fs')
const path = require('path')

// 存储分片文件
const fileDir = path.resolve('./uploads')
if (!fs.existsSync(fileDir)) fs.mkdirSync(fileDir)
form.on('field', (name, value) => {
  if (name === 'chunkIndex') {
    chunkInfo.index = Number(value)
  } else if (name === 'chunks') {
    chunkInfo.total = Number(value)
  }
})
form.on('file', (_, file) => {
  const chunkPath = path.join(fileDir, `${hash}_${chunkInfo.index}`)
  fs.renameSync(file.path, chunkPath) // 将分片文件存入指定目录
})
form.on('end', () => {
  // 如果所有分片上传完成
  if (chunkInfo.total - 1 === chunkInfo.index) {
    const file = fs.createWriteStream(path.join(fileDir, name)) // 创建新文件流
    for (let i = 0; i < chunkInfo.total; i++) {
      const chunkPath = path.join(fileDir, `${hash}_${i}`)
      const chunk = fs.readFileSync(chunkPath) // 读取分片内容
      file.write(chunk) // 将分片内容写入新文件流
      fs.unlinkSync(chunkPath) // 删除该分片
    }
    file.end()
    // 将文件信息存入数据库
    const newFile = new File({
      name: name,
      hash: hash,
      size: size,
      type: type,
      url: `/uploads/${name}`
    })
    newFile.save((err, file) => {
      if (err) {
        console.error(err)
        res.status(500).json({ message: 'Internal Server Error' })
      } else {
        res.status(200).json({ message: 'Upload successful', url: file.url })
      }
    })
  } else {
    res.status(200).json({ message: 'Chunk uploaded' })
  }
})

如果本文中有任何错误或不足之处,还请各位大佬多多指教,让我们一起共同进步!

结束

现在,你已经掌握了Vue3大文件上传的技巧,让你的文件不再受限于大小和速度!快来试试吧,让你的文件像闪电一样飞快传输!记得要分享给你的小伙伴哦,让他们也能享受到这个神奇的技能!

免责申明:本站发布的内容(图片、视频和文字)以转载和分享为主,文章观点不代表本站立场,如涉及侵权请联系站长邮箱:xbc-online@qq.com进行反馈,一经查实,将立刻删除涉嫌侵权内容。