当前位置: 首页 > ops >正文

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

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

因为我是前端,我用的是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 = falsethis.$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 })}})}
})
http://www.xdnf.cn/news/13148.html

相关文章:

  • ArcGIS Pro 3.4 二次开发 - 流图层
  • 如何对目标检测算法RT-DETR进行创新和改进:突破瓶颈,提升性能!
  • docker compose v2版本创建和运行容器
  • HTML 列表、表格、表单 综合案例
  • ES6从入门到精通:前言
  • Linux之nginx部署网站
  • MongoDB 数据库应用
  • Win11无法安装Unity5.5.0f3怎么解决?虚拟机中如何配置?Win7怎么安装最新版VMware Tools?来这里教你完美解决!
  • windows上tensorrt国内镜像下载和安装教程
  • 金融科技的数字底座
  • Linux 信号机制深度解析:从基础概念到实战应用
  • 小程序的工具库-miniprogram-licia
  • 状态管理详解:Context API、Redux、Recoil 和 Zustand 在 React Native 中的应用
  • Stable Diffusion WebUI 本地部署指南(Windows 11 + RTX 4060 Ti)
  • 西电计组第六章-CPU
  • Flask RESTful 示例
  • 增加Label Verified
  • 《Qt C++ 与 OpenCV:解锁视频播放程序设计的奥秘》
  • 机器学习sklearn |(逻辑回归)求解器(Solver) :优化算法的实现,用于寻找模型参数的最优解
  • Spring boot应用监控集成
  • 鹰盾播放器:安全与用户体验的精妙平衡
  • 互联网大数据求职面试:从Zookeeper到数据挖掘的技术探讨
  • 基于服务器使用 apt 安装、配置 Nginx
  • 熵最小化Entropy Minimization (二): 案例实施
  • 使用 VSCode 开发 FastAPI 项目(1)
  • 从零实现富文本编辑器#5-编辑器选区模型的状态结构表达
  • 云打包生成的ipa上传构建版本经验分享
  • 游戏测试面试八股汇总(持续更新版)
  • dbeaver 查询clickhouse,数据库时间差了8小时
  • UDP(Echoserver)