js文件上传 分片上传/断点续传/极速秒传

(极速秒传)利用md5判断上传的文件是否存在

MD5信息摘要算法,一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致。

每一个文件都会生成一个不同的md5编码 例:【3a94b8ca53dea8524d16c4e39dd43a69】

优点

服务器中不会出现重复文件

极速秒传

传输文件先校验md5,减小服务器压力

思路是
在文件上传到服务器前,将文件进行MD5转换,然后将转换完后的MD5码传给服务器,服务器判断当前MD5码是否存在,如果存在就代表着服务器上已经有了跟该文件相同的文件,不再需要上传文件

在这里插入图片描述

// 获取md5
import SparkMD5 from 'spark-md5';const getMd5 = async (file: File) => {let text = await file.text(); // 将文件转换成text文本return SparkMD5.hashBinary(text); // 通过插件进行MD5加密
}

分片上传

分片上传就是把一个大文件,按照一定的大小,分割成多个小文件分别进行上传,文件上传结束后,服务器对所有的文件进行合并(前端负责拆分、后端负责整合)

在这里插入图片描述

优点(分片上传/断点续传)

上传速度快

上传稳定性强

降低传输断开风险

前端拆分文件的方法

前端拿到【file】文件后,可以调用file.slice方法(Blob的方法)对文件进行拆分

在这里插入图片描述

const BIGSIZE = 1024 * 1024 * 2 // 2mb// 3. 切割文件
const getFileList = (file: File): FileList[] => {let cur = 0; // 当前分割到的位置const fileChunkList: any[] = [] // 最终的blob数组while (cur < file.size) {let chunk = file.slice(cur, cur + BIGSIZE)fileChunkList.push({chunk: chunk,index: fileChunkList.length})cur += BIGSIZE;}return fileChunkList
}
服务器合并文件的方法(node示例)
filesNameList = ['分片文件a','分片文件b',...] // 文件路径
// 创建可写流
let writeStream = fs.createWriteStream('最终合并后的文件路径', {flags: 'w+',encoding: 'base64',
})
// 合并文件
for (let i = 0; i < filesName.length; i++) {// 读取文件let val = await getFile(filesNameList[i])// 写入文件writeStream.write(val)
}
writeStream.end();// 读取文件函数
const getFile = (path) => {return new Promise(resolve => {// 创建可读流const readStream = fs.createReadStream(path, {flags: 'r',encoding: 'base64',});var count = 0;var str = '';readStream.on('data', (data) => {  //监听str += datacount++})readStream.on('end', () => {   //读取结束resolve(str)})});
}

断点续传

断点续传就是在分片上传逻辑的基础上,增加断开后继续上传的逻辑

一种实现思路是:可以将分片的文件命名为对应的索引,在文件断开后,根据索引判断有多少分片已经上传成功了,然后继续传输没有上传成功的分片

在这里插入图片描述

附代码

前端:

bigUpFile(file)
/***********************样式与提示***********************/
// 大文件上传中
const bigFileLoading = ref(false)
const loadingTextArr = ref<string[]>([])const addText = (str: string, number: undefined | number = undefined, type = 'default') => {loadingTextArr.value.push(str)if (number || number === 0) {if (type === 'add') {progressNumber.value += number} else {progressNumber.value = number}}
}/***********************上传代码***********************/
// 开始函数
const bigUpFile = async (file: File) => {addText('大文件上传中...', 0)bigFileLoading.value = true// 1 获取文件的md5addText('获取文件md5...', 5)const fileMD5 = await getMd5(file) // md5// 2. 极速秒传addText('判断是否可以进行极速秒传...', 10)let upEnd = await fastUpFile(file, fileMD5)if (upEnd) {addText('极速秒传成功!', 100)message.success('极速秒传成功!')emit("getList");return}addText('开始分割文件...', 15)// 3. 切割文件let fileChunkList = await getFileList(file)let fileNumber = fileChunkList.length // 文件分段总数addText('校验断点续传...', 18)// 4. 校验大文件分片上传文件数量let execFileList = await getExecFileList(fileMD5)if (execFileList && execFileList?.length) {let fl: FileList[] = []fileChunkList.forEach(item => {if (!execFileList.includes(item.index) && !execFileList.includes(item.index + '')) {fl.push(item)}})fileChunkList = fladdText('校验断点续传成功,继续传输...', 18)}addText('开始上传文件...', 20)// 5. 开始上传任务await createUpBigFile(fileChunkList, fileMD5, file.name, fileNumber)addText('结束...', 100)
}/**********************************************/// 1 获取文件的md5
const getMd5 = async (file: File | Blob) => {let buffer = await file.text(); // 获取bufferreturn SparkMD5.hashBinary(buffer);
}// 2. 极速秒传
const fastUpFile = async (file: File, fileMD5: string) => {// 发送md5到服务器判断是否已经存在const res = await api.upPage.execFile({md5: fileMD5});// md5已存在,更新标签if (res) {await api.upPage.updateMD5({md5: fileMD5,name: formState.name,})// 极速秒传成功return true}
}// 3. 切割文件
const getFileList = (file: File): FileList[] => {let cur = 0;const fileChunkList: any[] = [] // blob数组while (cur < file.size) {let chunk = file.slice(cur, cur + BIGSIZE)fileChunkList.push({chunk: chunk,index: fileChunkList.length})cur += BIGSIZE;}return fileChunkList
}// 4. 校验大文件分片上传文件数量
const getExecFileList = async (fileMD5: string): Promise<string | number[]> => {let res = await api.upPage.execFileList({md5: fileMD5});console.log('res', res)return res
}// 5. 开始上传任务
const createUpBigFile = async (fileList: any[], fileMD5: string, fileName: string, fileNumber: number) => {console.log('---fileList', fileList)let promiseList: any[] = []fileList.forEach(async (item, i) => {promiseList.push(up(item.chunk, item.index, fileMD5))})let num = Math.floor(70 / promiseList.length)promiseList.forEach(p => p.then(res => {addText('上传成功1个分片...', num, 'add')return res}))await Promise.all(promiseList)// 5.2 合并文件await mergeFile(fileMD5, fileNumber, fileName, fileList)
}// 5.1 上传
const up = async (file, i, fileMD5) => {let formData = new FormData();formData.append("fileName", fileMD5);formData.append("fileIndex", `${i}`);formData.append("file", file);let res = await api.upPage.saveBigFile(formData);
}// 5.2 合并文件
const mergeFile = async (fileMD5: string, fileNumber: number, fileName: string, fileList: any[]) => {addText('合并文件中...', 90)let res = await api.upPage.mergeFile({fileNumber,md5: fileMD5,name: formState.name,fileName,});console.log('res', res)if (res.type === 'add') {let indexList = res.fileListlet fl: any[] = []fileList.forEach(item => {if (!indexList.includes(item.index) && !indexList.includes(item.index + '')) {fl.push(item)}})createUpBigFile(fl, fileMD5, fileName, fileNumber)}
}

nodejs

const filePath = path.join(__dirname, './static') // 文件上传后保存的路径// 方法函数:添加文件到服务器
const addFileToServer = (file, filePath) => {// 转成文件流var _file = fs.createReadStream(file.filepath)// 存储文件_file.pipe(fs.createWriteStream(filePath))
}// 方法函数:获取文件可读流
const getFile = (path) => {return new Promise(resolve => {const readStream = fs.createReadStream(path, {flags: 'r',encoding: 'base64',});var count = 0;var str = '';readStream.on('data', (data) => {  //监听str += datacount++})readStream.on('end', () => {   //读取结束resolve(str)})});
}// 返回参数格式化
cosnt sendVal = (val) => return {...,val}/*************************************************************************/
// 校验大文件分片上传文件数量
router.post('/execFileList', async (req, res) => {const data = getReq(req) // 获取传参try {// 文件路径let dirPath = path.join(filePath, data.md5)// 判断是否有该文件夹if (fs.existsSync(dirPath)) {// 列出文件夹中文件名称const filesName = fs.readdirSync(dirPath);return res.send(sendVal(filesName))} else {return res.send(sendVal(false))}} catch (error) {return res.send(sendErr(error))}
})// 大文件上传
router.post('/saveBigFile', async (req, res) => {try {var form = new formidable.IncomingForm();form.encoding = 'utf-8';form.keepExtensions = true;//保留后缀form.parse(req, async (err, fields, files) => {if (err) {return res.send(sendErr(err))}let _filePath = path.join(filePath, fields.fileName)createPath(_filePath) // 如果没有该路径,创建路径// 添加文件到服务器addFileToServer(files.file, `${_filePath}/${fields.fileIndex}`)return res.send(sendVal(1))})} catch (error) {return res.send(sendErr(error))}
})// 大文件合并
router.post('/mergeFile', async (req, res) => {const data = getReq(req) // 获取参数// 字段校验let field = fieldVerification({md5: 'string',name: 'string',fileName: 'string',fileNumber: 'number'}, data)if (field) return res.send(sendErr(3, field))// 登陆权限校验+获取用户信息const userInfo = validateErr(req, "imgUp")if (typeof (userInfo) !== 'object') return res.send(sendErr(userInfo))try {// 获取后缀let extensions = data.fileName.replace(/^.*(\..*?)$/, '$1')// 文件路径let dirPath = path.join(filePath, data.md5)let fileName = path.join(filePath, `${data.md5}${extensions}`)const filesName = fs.readdirSync(dirPath);// 校验文件完整性if (filesName?.length !== data.fileNumber) {return res.send(sendVal({fileList: filesName,type: 'add'}))}// 创建合并流let writeStream = fs.createWriteStream(fileName, {flags: 'w+',encoding: 'base64',})writeStream.on('finish', async () => {console.log('成功');await addFileToSQL(data.md5, data.name, data.fileName, extensions, true)res.send(sendVal({type: 'success'}))})// 合并文件for (let i = 0; i < filesName.length; i++) {let _filePath = path.join(dirPath, `${i}`)// 读取文件let val = await getFile(_filePath)// 写入文件writeStream.write(val)}writeStream.end();} catch (error) {return res.send(sendErr(error))}
})

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

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

相关文章

对接第三方接口鉴权(Spring Boot+Aop+注解实现Api接口签名验证)

前言 一个web系统&#xff0c;从接口的使用范围也可以分为对内和对外两种&#xff0c;对内的接口主要限于一些我们内部系统的调用&#xff0c;多是通过内网进行调用&#xff0c;往往不用考虑太复杂的鉴权操作。但是&#xff0c;对于对外的接口&#xff0c;我们就不得不重视这个…

Elasticsearch:结合 ELSER 和 BM25 文本查询的相关搜索

Elastic Learned Spare EncodeR (ELSER) 允许你执行语义搜索以获得更相关的搜索结果。 然而&#xff0c;有时&#xff0c;将语义搜索结果与常规关键字搜索结果相结合以获得最佳结果会更有用。 问题是&#xff0c;如何结合文本和语义搜索结果&#xff1f; 首先&#xff0c;让我…

Java异常篇----第二篇

系列文章目录 文章目录 系列文章目录前言一、 Excption与Error包结构二、Thow与thorws区别三、Error与Exception区别?四、error和exception有什么区别前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女…

1213:八皇后问题 深度优先搜索算法

1213&#xff1a;八皇后问题 时间限制: 1000 ms 内存限制: 65536 KB 【题目描述】 在国际象棋棋盘上放置八个皇后&#xff0c;要求每两个皇后之间不能直接吃掉对方。 【输入】 (无) 【输出】 按给定顺序和格式输出所有八皇后问题的解&#xff08;见样例&#xff09;。 题目…

Spring源码之依赖注入(二)

书接上文 文章目录 一. Autowire底层注入逻辑1. 属性注入逻辑 一. Autowire底层注入逻辑 前面我们分析了Spring时如何找到某个目标类的所有注入点这一个核心逻辑&#xff0c;但还没又对核心注入方法inject进行详细分析&#xff0c;下面我们就来详细分析Spring拿到所有的注入点…

【CASS精品教程】CASS11计算城镇建筑密度

CASS中可以很方便计算建筑密度。 文章目录 一、建筑密度介绍二、CASS计算建筑密度1. 绘制宗地范围2. 绘制建筑物3. 计算建筑密度三、注意事项一、建筑密度介绍 建筑密度(building density;building coverage ratio),指在一定范围内,建筑物的基底面积总和与占用地面积的比…

纠删码ReedSolomon

随着大数据技术的发展&#xff0c;HDFS作为Hadoop的核心模块之一得到了广泛的应用。为了数据的可靠性&#xff0c;HDFS通过多副本机制来保证。在HDFS中的每一份数据都有两个副本&#xff0c;1TB的原始数据需要占用3TB的磁盘空间&#xff0c;存储利用率只有1/3。而且系统中大部分…

Spring Boot 2.7.11 集成 GraphQL

GraphQL介绍 GraphQL&#xff08;Graph Query Language&#xff09;是一种用于API的查询语言和运行时环境&#xff0c;由Facebook于2012年创建并在2015年公开发布。与传统的RESTful API相比&#xff0c;GraphQL提供了更灵活、高效和强大的数据查询和操作方式。 以下是GraphQL…

Spring技术内幕笔记之SpringMvc

WebApplicationContext接口的类继承关系 org.springframework.web.context.ContextLoader#initWebApplicationContext 对IOC容器的初始化 SpringMvc如何设计 DispatcherServlet类继承关系 MVC处理流程图如下&#xff1a; DispatcherServlet的工作大致可以分为两个部分&#xf…

NFC物联网开发智能衣橱解决方案

智能衣橱是智能家居的重要内容&#xff0c;现代家居市场对家居智能化控制尤为重视。但是&#xff0c;传统家居生产功能和模式已经无法满足智能化时代的需求&#xff0c;所以家居智能化成为家居行业发展的主要需求。与传统衣橱对比&#xff0c;智能衣橱的功能强大方便人们的生活…

Android--Jetpack--WorkManager详解

2024已经到来&#xff0c;愿你安睡时&#xff0c;山河入梦。愿你醒来时&#xff0c;满目春风。愿你欢笑时&#xff0c;始终如一。愿你行进时&#xff0c;前程似锦&#xff0c;坦荡从容。 编程语言的未来&#xff1f; 目录 一&#xff0c;定义 二&#xff0c;特点 三&#xff0c…

‘vue-cli-service‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。这个问题如何解决?

这个错误信息 vue-cli-service 不是内部或外部命令&#xff0c;也不是可运行的程序或批处理文件 表示 vue-cli-service 命令在你的系统上未被识别。这通常是因为 Vue CLI 没有被正确安装或其路径没有被加入到系统的环境变量中。以下是几个解决这个问题的步骤&#xff1a; 确认 …

LinkedList与ArrayList的比较

1.LinkedList 基于双向链表&#xff0c;无需连续内存 随机访问慢&#xff08;要沿着链表遍历&#xff09; 头尾插入删除性能高 占用内存多 2.ArrayList 基于数组&#xff0c;需要连续内存 随机访问快&#xff08;指根据下标访问&#xff09; 尾部插入、删除性能可以&…

从0搭建github.io网页

点击跳转到&#x1f517;我的博客文章目录 从0搭建github.io网页 文章目录 从0搭建github.io网页1.成果展示1.1 网址和源码1.2 页面展示 2.new对象2.1 创建仓库 3.github.io仓库的初始化3.1 千里之行&#xff0c;始于足下3.2 _config.yml3.3 一点杂活 4.PerCheung.github.io.p…

Linux 命令echo

命令作用 输出一行字符串在shell中&#xff0c;可以打印变量的值输出结果写入到文件在显示器上显示一段文字&#xff0c;起到提示的作用 语法 echo [选项] [字符串] 参数 字符含义-n不自动换行-e解释转义字符-E不解释转义字符 如果-e有效&#xff0c;则识别以下序列&…

SpringBoot 项目如何生成 swagger 文档

推荐使用 springdoc-openapi 的理由 1、springdoc-openapi 是 spring 官方出品&#xff0c;与 springboot 兼容更好&#xff08;springfox 兼容有坑&#xff09; 2、springdoc-openapi 社区更活跃&#xff0c;springfox 已经 2 年没更新了 3、springdoc-openapi 的注解更接近 …

(一)Matlab数值计算基础

目录 1.1Matlab命令组成 1.1.1基本符号 1.1.2功能符号 1.1.3常用命令 1.1Matlab命令组成 1.1.1基本符号 #提示运算符&#xff0c;表示软件处于准备就绪状态。在提示符号后输入一条命令或者一段程序后按Enter键&#xff0c;软件将给出相应的结果 >> *…

【Proteus仿真】【Arduino单片机】汽车尾气检测报警系统

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用按键、LCD1602液晶、蜂鸣器模块、CO、NOx、HC和PM2.5气体传感器等。 主要功能&#xff1a; 系统运行后&#xff0c;LCD1602显示CO、NOx、HC和…

手机录屏没有声音?让你的录屏有声有色

“有人知道手机录屏怎么录声音吗&#xff1f;今天录制了一个小时的直播视频&#xff0c;后面查看的时候发现没有声音&#xff0c;真的非常崩溃&#xff0c;想问问大家有没有办法&#xff0c;解决这个问题。” 在手机录屏的过程中&#xff0c;有时候我们可能会面临录制视频没有…

Spring技术内幕笔记之IOC的实现

IOC容器的实现 依赖反转&#xff1a; 依赖对象的获得被反转了&#xff0c;于是依赖反转更名为&#xff1a;依赖注入。许多应用都是由两个或者多个类通过彼此的合作来实现业务逻辑的&#xff0c;这使得每个对象都需要与其合作的对象的引用&#xff0c;如果这个获取过程需要自身…