Vue大文件上传:让你的文件秒传、断点续传、分片上传---需要后端支持--案例后端使用node

发布于:2025-06-12 ⋅ 阅读:(17) ⋅ 点赞:(0)

敲黑板:大文件上传是需要后端支持的

因为我是前端,我用的是node来说明后端怎么操作。前端自己看懂以后,方便前端工程师与Java后端沟通。

首先,大文件上传分为以下几点考量:
(1)秒传:服务器已经有该文件了,直接显示上传成功。
(2)分片上传:大文件分片传给后端,后端自己组合成一个完整的文件。
(3)断点续传:分片上传一半没成功,然后从断点处继续接着上传。

(1)秒传

a、前端思路:

  • 用户上传文件
  • 计算文件的hash值,传递给后端,后端利用hash值判断文件是否已经存在。
  • 如果存在,提示上传成功,上传结束。
  • 如果不存在,采用分片上传的方式。

前端代码: 计算hash值

使用插件: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)

b、后端思路:

  • 接收前端传递的hash值。
  • 查询数据库是否已经存在hash对应的文件。
  • 如果存在,返回数据,否则返回code告知前端。

后端代码片段

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:'服务器端错误'})
	} else {
		if(file){
			res.status(200).json({ message: '文件已存在', url: file.url })
		} else {
		res.status(404).json({ message:'文件不存在'})
		}
 	}
})

(2)分片上传

a、前端思路:

  • 将文件进行分片,使用File.slice()。
  • 依次上传每一个分片,使用Promise.all()确保所有分片上传成功,使用axios进行数据上传。
  • 同时计算上传进度,展示给用户。
  • 上传所有分片以后,由后端将各个分片进行合并。

前端分片代码

在这里插入图片描述

const slicesize=1024*1024//1MB的分片大小
const chunks = Math.ceil(filesize /slicesize)// 文件分片数
const requests=[]//分片请求的promise列表
for(leti=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.loaded11 更新已上传总进度
		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)//所有分片上传完成

b、后端思路

  • 分片上传以后先存储下来,包括分片的索引、分片总数、文件的hash值。
  • 等所有分片上传成功,在进行文件合并。
  • 将文件存储到数据库。
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(leti=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:'服务器端错误' })
		}else {
		res.status(200).json({ message: '上传成功', url: file.url })
		}
		})
	}else {
		res.status(200).json({ message:'文件块已经上传'})
	}
})

(3)断点续传

a、前端思路

  • 使用FileReader 对象读取文件内容
  • 将读取内容分成多个片段,将片段上传到后端。
  • 上传中断,下次上传直接从上次上传成功的片段之后的片段进行上传,使用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)//上传当前分片
}

b、后端思路

  • 通过文件的hash值判断是否文件已存在。
  • 如果文件在服务器上不存在,就创建一个新的文件,
  • 如果存在,就继续上传
  • 后端将上传的分片存在服务器的磁盘上,基础分片在整个文件的起始位置,最后合并文件

后端代码:

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({} else {
    res.status(200).json({ message: '分块上传成功' })
}
			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:"服务器端错误' })
			}else {
				res.status(200).json({ message: '文件上传成功', url: file.url })
			}
		})
	 }
})

网站公告

今日签到

点亮在社区的每一天
去签到