Vue3 + Nodejs 实战 ,文件上传项目--大文件分片上传+断点续传

 

目录

1.大文件上传的场景

2.前端实现

2.1 对文件进行分片

 2.2 生成hash值(唯一标识)

2.3 发送上传文件请求

3.后端实现

 3.1 接收分片数据临时存储

3.2 合并分片

4.完成段点续传

4.1修改后端

4.2 修改前端

5.测试


博客主页:専心_前端,javascript,mysql-CSDN博客

 系列专栏:vue3+nodejs 实战--文件上传

 前端代码仓库:jiangjunjie666/my-upload: vue3+nodejs 上传文件的项目,用于学习 (github.com)

 后端代码仓库:jiangjunjie666/my-upload-server: nodejs上传文件的后端 (github.com)

 欢迎关注

 在上一篇中我们实现了文件的拖拽上传,Vue3 + Nodejs 实战 ,文件上传项目--实现拖拽上传-CSDN博客

 该篇就讲讲大文件上传的需求场景

1.大文件上传的场景

如果遇到需要上传电影或者视频之类的需求,那么上传的文件是非常大的,这个时候我们不能说用一个请求就直接将所有的文件传输过去,因为这个大文件上传时间相比较来说是比较长的,存在很多的弊端,假如用户刷新了页面之类的情况,这时候上传又需要重头开始上传,这对用户以及服务器都是不妥的。

需要解决的基本问题(其他的扩展根据需求来定)

  • 对大文件进行分片上传
  • 上传的实时进度显示
  • 上传中断后再次上传跳过已经上传的部分

2.前端实现

2.1 对文件进行分片

对文件进行分片我们用到的是  file.slice(i,j) 这个方法

其中的参数代表的意思就是切割的大小,以size单位的,i-j 就是切这一段的数据

<template><input type="file" @change="fileChange" />
</template><scritp>
import { ref } from 'vue'
const fileList = ref([])
const file = ref([])
const fileChange = (e) => {console.log(e.target.files[0])//分片file.value = e.target.files[0]for (let i = 0; i < file.value.size; i += 1024 * 1024) {fileList.value.push(file.value.slice(i, i + 1024 * 1024))}
}
</script>

这样就将文件切成了每个大小为 1024 * 1024 的大小的文件,也就是1mb

 2.2 生成hash值(唯一标识)

因为我们进行分片后的文件数据并不能单独成为一个原本的文件,一般都是二进制数据,我们在分片上传的时候需要临时存储已经上传的文件,这时候为了能对应到每个文件,需要对每个文件生成对应的文件hash值,这里需要用到hash算法,项目中大家可以直接使用spark-md5这个库生成文件hash。

总之文件hash的作用就是生成唯一的标识,该算法生成的hash值永远不会出现重复 ,但是同一文件多次生成的都是一样的。

安装 spark-md5

npm i spark-md5

引入生成hash

 import SparkMD5 from 'spark-md5'const fileMd5 = ref('')const hash = new SparkMD5.ArrayBuffer() // 构建hash值对象const fileReader = new FileReader()fileReader.onload = () => {hash.append(fileReader.result)fileMd5.value = hash.end()}fileReader.readAsArrayBuffer(file.value)

2.3 发送上传文件请求

大文件分片上传一般都是二个请求,一个是分片上传的请求,一个是分片上传完之后的合并请求。

分片上传:

const upload = async (index) => {if (index == fileList.value.length) {mergeUpload()return}const formData = new FormData()formData.append('chunk', fileList.value[index])formData.append('index', index)formData.append('name', fileMd5.value + '@' + index) // 临时的二进制文件分片formData.append('filename', fileMd5.value) // 文件名,采用hashlet res = await http.post('/api/upload_chunk1', formData)if(res.code == 200){upload(index+1)}else{upload(index) // 失败重新上传    }
}

合并分片:

const mergeUpload = async () => {//合并请求let res = await http.post('/api/merge_chunk', {filename: fileMd5.value, //最后合并的文件名extname: file.value.type.split('/').pop() //文件后缀})if ((res.code = 200)) {file.value = nullfileList.value = []fileMd5.value = ''ElMessage({type: 'success',message: '上传成功'})}
}

3.后端实现

我这边使用的是 multiparty 中间件完成的该需求,可以安装一下

npm i multiparty

如果没有看过建的项目,可以看看第一期喔:Vue3 + Nodejs 实战 ,文件上传项目--实现图片上传-CSDN博客

 3.1 接收分片数据临时存储

首先要做的就是利用请求中携带的数据进行创建一个临时保存分片数据的目录,目录名可以使用hash值,这样可以做到唯一性,并且后面实现断点续传也能更方便。

分片上传的数据会被临时保存在 Temp 文件夹中,如果windows电脑的话,可以 win + R ,输入 %temp% 就能进入该目录,里面保存的都是临时文件。我的做法就是将这个的文件copy一份到对应的目录下,然后删除临时文件。

const multiparty = require('multiparty')
const path = require('path')
const fs = require('fs')
exports.upload_chunk1 = (req, res, next) => {// 二进制数据上传const form = new multiparty.Form()form.parse(req, (err, fields, files) => {if (err) {next(err)return}//将每一次上传的数据进行统一的存储const oldName = files.chunk[0].pathconst newName = path.join(__dirname, '../../public/upload/chunk/' + fields['filename'][0] + '/' + fields['name'][0])//创建临时存储目录fs.mkdirSync('./public/upload/chunk/' + fields['filename'][0], {recursive: true})console.log(fields)console.log(files)fs.copyFile(oldName, newName, (err) => {if (err) {console.error(err)} else {// 删除源文件fs.unlink(oldName, (err) => {if (err) {console.error(err)} else {console.log('文件复制和删除成功')}})}})res.send({code: 200,msg: '分片上传成功'})})
}

这是刚刚上传的一批二进制文件

3.2 合并分片

合并接口:

exports.merge_chunk = (req, res, next) => {const fields = req.bodyconsole.log(fields)thunkStreamMerge('../../public/upload/chunk/' + fields.filename, '../../public/upload/' + fields.filename + '.' + fields.extname)res.send({code: 200,data: '/public/upload/' + fields.filename + '.' + fields.extname})
}

编写一个thunkStreamMerge 函数用来合并分块文件。它接收两参数:源文件目录 sourceFiles 和目标文件路径 targetFile。利用 fs.readdirSync 读取源文件目录下的文件列表,并根据文件名中的序号排序,以确保按正确的顺序合并文件。接着创建一个可写流 (fileWriteStream),用于将所有分块文件的内容写入到目标文件中。这个目标文件路径是由 targetFile 指定的。

// 文件合并
function thunkStreamMerge(sourceFiles, targetFile) {const chunkFilesDir = path.join(__dirname, sourceFiles)const chunkTargetDir = path.join(__dirname, targetFile)const list = fs.readdirSync(chunkFilesDir) //读取目录中的文件const fileList = list.sort((a, b) => a.split('@')[1] * 1 - b.split('@')[1] * 1).map((name) => ({name,filePath: path.resolve(chunkFilesDir, name)}))const fileWriteStream = fs.createWriteStream(chunkTargetDir)thunkStreamMergeProgress(fileList, fileWriteStream, chunkFilesDir)
}

thunkStreamMergeProgress 函数是用来处理每个分块文件的函数。它接收三个参数:fileList,包含了分块文件信息的数组;fileWriteStream,目标文件的可写流;以及可选的 sourceFiles,用于删除临时文件目录。

首先,函数检查 fileList 是否为空,如果为空,表示所有分块文件已经合并完成。此时,它向目标文件写入一个标识('完成了'),并根据需要删除源文件目录 sourceFiles

如果 fileList 不为空,函数会从中取出第一个文件信息,并获取其文件路径。然后,它创建一个可读流 (currentReadStream) 来读取该分块文件的内容。

接下来,函数使用 .pipe 方法将当前可读流的内容传输到目标文件的可写流中,这将逐步将分块文件的内容写入目标文件。

当当前可读流读取完毕(end 事件触发)后,它递归调用 thunkStreamMergeProgress 函数,处理下一个分块文件,直到所有分块文件都合并到了目标文件中。

//合并每一个分片
function thunkStreamMergeProgress(fileList, fileWriteStream, sourceFiles) {if (!fileList.length) {// thunkStreamMergeProgress(fileList)fileWriteStream.end('完成了')// 删除临时目录if (sourceFiles) fs.rmdirSync(sourceFiles, { recursive: true, force: true })return}const data = fileList.shift() // 取第一个数据const { filePath: chunkFilePath } = dataconst currentReadStream = fs.createReadStream(chunkFilePath) // 读取文件// 把结果往最终的生成文件上进行拼接currentReadStream.pipe(fileWriteStream, { end: false })currentReadStream.on('end', () => {// console.log(chunkFilePath);// 拼接完之后进入下一次循环thunkStreamMergeProgress(fileList, fileWriteStream, sourceFiles)})
}

这样做完后就能实现基本的文件分片上传以及合并分片了

这是上传的一个视频

4.完成段点续传

4.1修改后端

 断点续传的情况就是文件上传至一半时突然做了其他的交互让上传停止了,那么下次再上传时就需要重新开始从头上传,这样非常消耗时间,对用户的体验与服务器的维护都不友好,这时候就要判断是否有对应的文件数据,直接将对应的索引传给客户端,客户端直接从该分片开始传输即可,因为我们之前给每个临时文件都加了 hash+@+index,我们可以读取文件夹目录查找最后的index传输给客户端即可。

当然这种解决方法是因为我没有采用数据库的情况,如果采用数据库或者其他的需求,基本的实现思路都差不多,利用唯一值 hash 进行传输。

后端接口加上一个判断,是否为段点传输

exports.upload_chunk1 = (req, res, next) => {// 二进制数据上传const form = new multiparty.Form()form.parse(req, (err, fields, files) => {if (err) {next(err)return}let pa = path.join(__dirname, '../../public/upload/chunk/' + fields['filename'][0])console.log(pa)//判断是否为断点续传if (fs.existsSync(pa) && parseInt(fields.index[0]) === 0) {//存在该目录//返回最大的索引let maxIndex = 0let arr = fs.readdirSync(pa)for (let i = 0; i < arr.length; i++) {let str = parseInt(arr[i].split('@')[1])console.log(str)if (str > maxIndex) {maxIndex = str}}res.send({code: 300,msg: '存在该目录,请继续上传',index: maxIndex})} else {//将每一次上传的数据进行统一的存储const oldName = files.chunk[0].pathconst newName = path.join(__dirname, '../../public/upload/chunk/' + fields['filename'][0] + '/' + fields['name'][0])//创建临时存储目录fs.mkdirSync('./public/upload/chunk/' + fields['filename'][0], {recursive: true})console.log(fields)console.log(files)fs.copyFile(oldName, newName, (err) => {if (err) {console.error(err)} else {// 删除源文件fs.unlink(oldName, (err) => {if (err) {console.error(err)} else {console.log('文件复制和删除成功')}})}})res.send({code: 200,msg: '分片上传成功'})}})
}

4.2 修改前端

前端加上进度条,发送请求时对相应的数据进行判断即可完成此需求了

分片请求,合并请求不变

const upload = async (index) => {if (index == fileList.value.length) {mergeUpload()return}const formData = new FormData()formData.append('chunk', fileList.value[index])formData.append('index', index)formData.append('name', fileMd5.value + '@' + index) // 名字formData.append('filename', fileMd5.value) // 文件名let res = await http.post('/api/upload_chunk1', formData)console.log(res)if (res.code == 300) {//证明已经存在部分文件percentage.value = ((res.index / fileList.value.length) * 100).toFixed(2)upload(res.index + 1)} else if (res.code == 200) {percentage.value = (((index + 1) / fileList.value.length) * 100).toFixed(2)upload(index + 1)} else {upload(index)}
}

加上进度条

<template><div><input type="file" @change="fileChange" /><div class="progress"><el-progress :text-inside="true" :stroke-width="24" :percentage="percentage" status="success" /></div></div>
</template><script>let percentage = ref(0)</script>

5.测试

我这里选择一个 624Mb的视频

我在上传进度到一半时刷新了页面

 分片文件也只有450个

这时候重新选择该视频重新上传

这时候进度条很快就能到对应的进度

后端也没有上传多余的文件,直接接着传输,速度很快就传输完并且合并成了视频

到这基本的分片传输 + 断点续传就实现了,可能还存在者一些小问题,这个大家再项目中根据需求来做相应的改变。

具体的详细代码请大家到仓库下载,或者可以去我的主页资源中下载源码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/111414.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Python】文件操作

一、文件的编码 思考:计算机只能识别:0和1&#xff0c;那么我们丰富的文本文件是如何被计算机识别&#xff0c;并存储在硬盘中呢? 答案:使用编码技术( 密码本)将内容翻译成0和1存入 编码技术即:翻译的规则&#xff0c;记录了如何将内容翻译成二进制&#xff0c;以及如何将二…

人人开源前后端分离开源项目启动流程(超详细)

renren-security是一个轻量级的&#xff0c;前后端分离的Java快速开发平台&#xff0c;能快速开发项目并交付【接私活利器】采用SpringBoot、Shiro、MyBatis-Plus、Vue3、TypeScript、Element Plus、Vue Router、Pinia、Axios、Vite框架&#xff0c;开发的一套权限系统&#xf…

【计算机网络笔记】OSI参考模型基本概念

系列文章目录 什么是计算机网络&#xff1f; 什么是网络协议&#xff1f; 计算机网络的结构 数据交换之电路交换 数据交换之报文交换和分组交换 分组交换 vs 电路交换 计算机网络性能&#xff08;1&#xff09;——速率、带宽、延迟 计算机网络性能&#xff08;2&#xff09;…

(H5轮播)vue一个轮播里显示多个内容/一屏展示两个半内容

效果图 : html: <div class"content"><van-swipeclass"my-swipe com-long-swipe-indicator":autoplay"2500"indicator-color"#00C4FF"><van-swipe-itemclass"flex-row-wrap"v-for"(items, index) in M…

【Git】升级MacOS系统,git命令无法使用

终端执行git命令报错 xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun安装这个东东&#xff0c;&#xff1f;需要42小时 最终解决&#xff1a; 下载安装 https…

微信小程序开发的OA会议之会议个人中心的页面搭建及模板,自定义组件的学习

目录 一.自定义组件及会议效果编写 效果显示 二.个人中心布局 编写结果 ​编辑 一.自定义组件及会议效果编写 在页面中创建一个以components命名的项目来存放组件 再在components文件夹中创建一个组件&#xff0c;名为 :tabs &#xff0c;创建操作如图所示 刚刚创建好会报…

山海鲸可视化B/S架构应用

一、什么是B/S架构 BS架构&#xff08;Browser-Server架构&#xff09;是一种常见的软件架构模式&#xff0c;其中系统的核心业务逻辑和数据处理都发生在服务器端&#xff08;Server&#xff09;&#xff0c;而客户端&#xff08;Browser&#xff09;主要负责显示和用户交互。…

客户端post请求,服务器收到{}数据解决方法

当我们发起登录请求时&#xff0c;后台接收到的为{}数据 原因&#xff1a;传送过去的对象格式不对 解决方案&#xff1a; 引入qs npm install qs 在data中格式化数据 const res await axios({url:http://127.0.0.1:3000/post,method:post,data:Qs.stringify({username:te…

阿里云服务器x86计算架构ECS规格大全

阿里云企业级服务器基于X86架构的实例规格&#xff0c;每一个vCPU都对应一个处理器核心的超线程&#xff0c;基于ARM架构的实例规格&#xff0c;每一个vCPU都对应一个处理器的物理核心&#xff0c;具有性能稳定且资源独享的特点。阿里云服务器网aliyunfuwuqi.com分享阿里云企业…

【高等数学】导数与微分

文章目录 1、导数的概念1.1、引例1.1.1、变速直线运动瞬时速度1.1.2、曲线的切线 1.2、导数的定义1.3、证明常用导数1.4、导数的几何意义1.5、可导与连续的关系 2、函数的求导法则2.1、函数的和、差、积、商的求导法则2.2、反函数的求导法则2.3、复合函数的求导法则2.4、基本初…

github: kex_exchange_identification: Connection closed by remote host

问题描述 (base) ➜ test git:(dev) git pull kex_exchange_identification: Connection closed by remote host Connection closed by 192.30.255.113 port 22 致命错误&#xff1a;无法读取远程仓库。解决方案 参照下边文档 https://docs.github.com/en/authentication/tr…

基于SSM的工资管理系统

基于SSM的工资管理系统 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringSpringMVCMyBatisVue工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 登录界面 管理员界面 通知公告 考勤管理 工资管理 请假管理 摘要 基于SSM&#xff08;Spring、S…

【每日一题】根据规则将箱子分类

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;分类讨论 其他语言cpython3 写在最后 Tag 【分类讨论】【2023-10-20】 题目来源 2525. 根据规则将箱子分类 题目解读 题目意思明确&#xff0c;根据条件判断箱子的类别。 解题思路 方法一&#xff1a;分类讨论 根据…

【小白专用】安装Apache2.4+ 安装PHP8.2+ php与sql server 2008 r2连接测试教程

PHP安装 1、PHP下载 PHP For Windows: Binaries and sources Releases 注意&#xff1a; 1.要下载Thread Safe&#xff0c;否则没有php7apache2_4.dll这个文件 2.如果是64位系统要下载x64的&#xff0c;x86的不行 3.下载Zip 2、PHP解压安装 将Zip进行解压&#xff0c;里…

docker全家桶(基本命令、dockerhub、docker-compose)

概念 应用场景&#xff1a; Web 应用的自动化打包和发布。自动化测试和持续集成、发布。在服务型环境中部署和调整数据库或其他的后台应用。从头编译或者扩展现有的 OpenShift 或 Cloud Foundry 平台来搭建自己的 PaaS 环境。 作用&#xff1a;Docker 使您能够将应用程序与基…

2023年信息院学生科协第二次硬件培训

2023年信息院学生科协第二次硬件培训 前言一、51单片机简介1、什么是单片机2、主流单片机及其编程语言3、单片机的应用4、单片机开发软件 二、GPIO&#xff08;点亮LED&#xff09;1、GPIO简介2、LED简介3、硬件设计4、软件设计 三、GPIO&#xff08;独立按键&#xff09;1、按…

2023.10(u盘刻录iso)主机,vmware,virtualbox安装linux/ubuntu/kali/centos stream9/arch

download 1 kali官网 2 ubuntu官网 3vmware workstation pro(最新版17pro) 4 virtualbox for linux sudo apt install virtualbox-ext-pack 5 win32 disk imger linux dd 刻录iso到u盘 #查看U盘路径 fdisk -l #图形界面 以kali为例会在桌面出现挂载图标 点开之后输入pwd寻…

CVPR 2018 基于累积注意力的视觉定位 Visual Grounding via Accumulated Attention 详解

Abstract&#xff1a; VG面临的主要挑战有3个&#xff1a;1 )查询的主要焦点是什么&#xff1b;2 )如何理解图像&#xff1b;3 )如何定位物体。 在本文中&#xff0c;我们将这些挑战形式化为三个注意力问题&#xff0c;并提出了一个累积注意力( A-ATT )机制来共同推理其中的挑战…

【微信小程序】6天精准入门(第3天:小程序flex布局、轮播图组件及mock运用以及综合案例)附源码

一、flex布局 布局的传统解决方案&#xff0c;基于[盒状模型]&#xff0c;依赖display属性 position属性 float属性 1、什么是flex布局&#xff1f; Flex是Flexible Box的缩写&#xff0c;意为”弹性布局”&#xff0c;用来为盒状模型提供最大的灵活性。任何一个容器都可以…

智能变电站自动化系统的应用与产品选型

摘要&#xff1a;现如今&#xff0c;智能变电站发展已经成为了电力系统发展过程中的内容&#xff0c;如何提高智能变电站的运行效率也成为电力系统发展的一个重要目标&#xff0c;为了能够更好地促进电力系统安全稳定运行&#xff0c;本文则就智能变电站自动化系统的实现进行了…