首页 > 其他分享 >大文件上传的处理方法——切片上传

大文件上传的处理方法——切片上传

时间:2023-10-11 17:56:29浏览次数:36  
标签:文件 container formData 切片 file hash 上传

本篇介绍了切片上传的基本实现方式,以及实现切片上传后的一些附加功能,切片上传原理较为简单,代码注释比较清晰就不多赘述了,后面的附加功能介绍了实现原理,并贴出了在原本代码上的改进方式。有什么错误希望大佬可以指出,感激不尽。

切片后上传
切片上传的原理较为简单,即获取文件后切片,切片后整理好每个切片的参数并发请求即可。下面直接上代码。

HTML

<template>
<div>
<input type="file" @change="handleFileChange" />
<el-button @click="handleUpload">上传</el-button>
</div>
</template>

JavaScript

<script>
const SIZE = 10 * 1024 * 1024; // 切片大小

export default {
data: () => ({
// 存放文件信息
container: {
file: null
hash: null
},
data: [] // 用于存放加工好的文件切片列表
hashPercentage: 0 // 存放hash生成进度
}),
methods: {
// 获取上传文件
handleFileChange(e) {
const [file] = e.target.files;
if (!file) {
this.container.file = null;
return;
}
this.container.file = file;
},

// 生成文件切片
createFileChunk(file, size = SIZE) {
const fileChunkList = [];
let cur = 0;
while (cur < file.size) {
fileChunkList.push({ file: file.slice(cur, cur + size) });
cur += size;
}
return fileChunkList;
},

// 生成文件hash
calculateHash(fileChunkList) {
return new Promise(resolve => {
this.container.worker = new Worker("/hash.js");
this.container.worker.postMessage({ fileChunkList });
this.container.worker.onmessage = e => {
const { percentage, hash } = e.data;
// 可以用来显示进度条
this.hashPercentage = percentage;
if (hash) {
resolve(hash);
}
};
});
},

// 切片加工(上传前预处理 为文件添加hash等)
async handleUpload() {
if (!this.container.file) return;
// 切片生成
const fileChunkList = this.createFileChunk(this.container.file);
// hash生成
this.container.hash = await this.calculateHash(fileChunkList);
this.data = fileChunkList.map(({ file },index) => ({
chunk: file,
// 这里的hash为文件名 + 切片序号,也可以用md5对文件进行加密获取唯一hash值来代替文件名
hash: this.container.hash + "-" + index
}));
await this.uploadChunks();
}

// 上传切片
async uploadChunks() {
const requestList = this.data
// 构造formData
.map(({ chunk,hash }) => {
const formData = new FormData();
formData.append("chunk", chunk);
formData.append("hash", hash);
formData.append("filename", this.container.file.name);
return { formData };
})
// 发送请求 上传切片
.map(async ({ formData }) =>
request(formData)
);
await Promise.all(requestList); // 等待全部切片上传完毕
await merge(this.container.file.name) // 发送请求合并文件
},
}
};
</script>


生成hash
无论是前端还是服务端,都必须要生成文件和切片的 hash,之前我们使用文件名 + 切片下标作为切片 hash,这样做文件名一旦修改就失去了效果,而事实上只要文件内容不变,hash 就不应该变化,所以正确的做法是根据文件内容生成 hash,所以我们修改一下 hash 的生成规则

这里用到另一个库 spark-md5,它可以根据文件内容计算出文件的 hash 值,另外考虑到如果上传一个超大文件,读取文件内容计算 hash 是非常耗费时间的,并且会引起 UI 的阻塞,导致页面假死状态,所以我们使用 web-worker 在 worker 线程计算 hash,这样用户仍可以在主界面正常的交互

由于实例化 web-worker 时,参数是一个 js 文件路径且不能跨域,所以我们单独创建一个 hash.js 文件放在 public 目录下,另外在 worker 中也是不允许访问 dom 的,但它提供了importScripts`函数用于导入外部脚本,通过它导入 spark-md5

// /public/hash.js
self.importScripts("/spark-md5.min.js"); // 导入脚本

// 生成文件 hash
self.onmessage = e => {
const { fileChunkList } = e.data;
const spark = new self.SparkMD5.ArrayBuffer();
let percentage = 0;
let count = 0;
const loadNext = index => {
// 新建读取器
const reader = new FileReader();
// 设定读取数据格式并开始读取
reader.readAsArrayBuffer(fileChunkList[index].file);
// 监听读取完成
reader.onload = e => {
count++;
// 获取读取结果并交给spark计算hash
spark.append(e.target.result);
if (count === fileChunkList.length) {
self.postMessage({
percentage: 100,
// 获取最终hash
hash: spark.end()
});
self.close();
} else {
percentage += 100 / fileChunkList.length;
self.postMessage({
percentage
});
// 递归计算下一个切片
loadNext(count);
}
};
};
loadNext(0);
};


总结
获取上传文件
文件切片后存入数组 fileChunkList.push({ file: file.slice(cur, cur + size) });
生成文件hash(非必须)
根据文件切片列表生成请求列表
并发请求
待全部请求完成后发送合并请求
文件秒传
实际是障眼法,用来欺骗用户的。

原理:在文件上传之前先计算出文件的hash,然后发送给后端进行验证,看后端是否存在这个hash,如果存在,则证明这个文件上传过,则直接提示用户秒传成功

// 切片加工(上传前预处理 为文件添加hash等)
async handleUpload() {
if (!this.container.file) return;
// 切片生成
const fileChunkList = this.createFileChunk(this.container.file);
// hash生成
this.container.hash = await this.calculateHash(fileChunkList);

// hash验证 (verify为后端验证接口请求)
const { haveExisetd } = await verify(this.container.hash)
// 判断
if(haveExisetd) {
this.$message.success("秒传:上传成功")
return
}

this.data = fileChunkList.map(({ file },index) => ({
chunk: file,
// 这里的hash为文件名 + 切片序号,也可以用md5对文件进行加密获取唯一hash值来代替文件名
hash: this.container.hash + "-" + index
}));
await this.uploadChunks();
}


暂停上传
原理:将所有的切片存在一个数组中,每当一个切片上传完毕,从数组中移除,这样就可以实现用一个数组只保存上传中的文件。此外,因为要暂停上传,所以需要中断请求 axios中断请求可以利用AbortController

中断请求示例
const controller = new AbortController()

axios({
signal: controller.signal
}).then(() => {});

// 取消请求
controller.abort()

添加暂停上传功能
// 上传切片
async uploadChunks() {
// 需要把requestList放到全局,因为要通过操控requestList来实现中断
this.requestList = this.data
// 构造formData
.map(({ chunk,hash }) => {
const formData = new FormData();
formData.append("chunk", chunk);
formData.append("hash", hash);
formData.append("filename", this.container.file.name);
return { formData };
})
// 发送请求 上传切片
.map(async ({ formData }, index) =>
request(formData).then(() => {
// 将请求成功的请求剥离出requestList
this.requestList.splice(index, 1)
})
);
await Promise.all(this.requestList); // 等待全部切片上传完毕
await merge(this.container.file.name) // 发送请求合并文件
},
// 暂停上传
handlePause() {
this.requestList.forEach((req) => {
// 为每个请求新建一个AbortController实例
const controller = new AbortController();
req.signal = controller.signal
controller.abort()
})
}


恢复上传
原理:上传切片之前,向后台发送请求,接口将已上传的切片列表返回,通过切片hash将后台已存在的切片过滤,只上传未存在的切片

// 切片加工(上传前预处理 为文件添加hash等)
async handleUpload() {
if (!this.container.file) return;
// 切片生成
const fileChunkList = this.createFileChunk(this.container.file);
// 文件hash生成
this.container.hash = await this.calculateHash(fileChunkList);

// hash验证 (verify为后端验证接口请求)
const { haveExisetd, uploadedList } = await verify(this.container.hash)
// 判断
if(haveExisetd) {
this.$message.success("秒传:上传成功")
return
}

this.data = fileChunkList.map(({ file },index) => ({
chunk: file,
// 注:这个是切片hash 这里的hash为文件名 + 切片序号,也可以用md5对文件进行加密获取唯一hash值来代替文件名
hash: this.container.hash + "-" + index
}));
await this.uploadChunks(uploadedList);
}


// 上传切片
async uploadChunks(uploadedList = []) {
// 需要把requestList放到全局,因为要通过操控requestList来实现中断
this.requestList = this.data
// 过滤出来未上传的切片
.filter(({ hash }) => !uploadedList.includes(hash))
// 构造formData
.map(({ chunk,hash }) => {
const formData = new FormData();
formData.append("chunk", chunk);
formData.append("hash", hash);
formData.append("filename", this.container.file.name);
return { formData };
})
// 发送请求 上传切片
.map(async ({ formData }, index) =>
request(formData).then(() => {
// 将请求成功的请求剥离出requestList
this.requestList.splice(index, 1)
})
);
await Promise.all(this.requestList); // 等待全部切片上传完毕
// 合并之前添加一层验证 验证全部切片传送完毕
if(uploadedList.length + this.requestList.length == this.data.length){
await merge(this.container.file.name) // 发送请求合并文件
}
},

// 暂停上传
handlePause() {
this.requestList.forEach((req) => {
// 为每个请求新建一个AbortController实例
const controller = new AbortController();
req.signal = controller.signal
controller.abort()
})
}

// 恢复上传
async handleRecovery() {
//获取已上传切片列表 (verify为后端验证接口请求)
const { uploadedList } = await verify(this.container.hash)
await uploadChunks(uploadedList)
}


添加功能总结
1.文件秒传其实就是一个简单的验证,把文件的hash发送给后端,后端验证是否存在该文件后将结果返回,如果存在则提示文件秒传成功

2.断点传送分为两步,暂停上传和恢复上传。暂停上传是通过获取到未上传完毕切片列表(完整切片列表剥离请求已完成的切片后形成),对列表请求进行请求中断实现的。恢复上传实质也是一层验证,在上传文件之前,将文件的hash发送给后端,后端返回已经上传完毕的切片列表,然后根据切片hash将后端返回的切片列表中的切片过滤出去,只上传未上传完成的切片。

参考文章:http://blog.ncmem.com/wordpress/2023/10/11/%e5%a4%a7%e6%96%87%e4%bb%b6%e4%b8%8a%e4%bc%a0%e7%9a%84%e5%a4%84%e7%90%86%e6%96%b9%e6%b3%95-%e5%88%87%e7%89%87%e4%b8%8a%e4%bc%a0/

欢迎入群一起讨论

 

 

标签:文件,container,formData,切片,file,hash,上传
From: https://www.cnblogs.com/songsu/p/17757820.html

相关文章

  • js 多文件下载打包zip 重名问题
    这里用jszip实现多文件下载并打包zip,但是这里有个问题,项目是可以上传重名文件,也就是下载也必须全部下载。jszipblob以文件名称(filename)下载的话,同名多个文件是会被忽略掉的,也就是只下载一个,而我们的需求是全部下载,问题也是困扰了很久。如果名称可以重复,那么可以试试用地址(file......
  • Linux批量替换文件内容
    示例方法:Linux下批量替换多个文件中的字符串的简单方法。用sed命令可以批量替换多个文件中的字符串。用sed命令可以批量替换多个文件中的字符串。sed-i"s/原字符串/新字符串/g"`grep原字符串-rl所在目录`例如:我要把mahuinan替换为huinanma,执行命令:sed-i"s/mahui......
  • Microsoft SQL Server导出数据为sql文件以及sql文件的执行
    一.MicrosoftSQLServer导出数据为sql文件1.在数据库上右击选择“任务”/“生成脚本” 2.在选择对象对话框中选择你要导出的实例 3.在“高级脚本编写选项”的“常规”的“编写脚本的数据类型”设置为“架构和数据/仅限数据/仅限架构” 4.单机下一步完成 二.sql文件的......
  • 文件上传
    文件包含漏洞文件包含漏洞是指利用文件包含函数包含了存在恶意代码的文件,并且解析执行了其中的恶意代码造成的漏洞。在php中常用的文函数有include()、require()、include_once()、require_once()。include()函数并不在意被包含的文件是什么类型,只要有php代码,都会被解析出来......
  • Nginx 做前端代理,上传文件速度慢
    同一台服务器,前端部署在nginx,在上传33M的文件时,耗时36s,但是通过IIS或者swagger上传文件,只需要8s解决方案:将Nginx配置文件中 proxy_passhttp://139.196.154.85:54730/;改为localhost: proxy_passhttp://localhost:54730/; 此方案只适用于前后端部署在同一台服务器的情况......
  • 前端大文件上传如何做到刷新续传?
    前言这两天在学习阿里云oss上传。踩了不少坑,终于实现了大文件分片、断点续传的功能。这篇文章主要分享学习笔记,希望能给大家一些帮助。先看效果 技术栈1.前端:react+Ts+axios上传文件2.Node部分:定义接口、阿里云oss3.socket.io:实时同步上传进度特别说明axios中on......
  • 监听上传的服务器文件是否改变,从而刷新页面
     监听上传的服务器文件是否改变,从而刷新页面=>interfaceOptions{timer?:number;}classUpdater{oldScript:string[];//存储第一次值也就是script的hash信息newScript:string[];//获取新的值也就是新的script的hash信息dispatch:Record<string,Fun......
  • 前台首页,导出项目依赖,git介绍和安装,git和其他相关介绍,git工作流程,git常用命令,git忽略
    1前台首页⛺1.1Header.vue<template><divclass="header"><divclass="slogan"><p>老男孩IT教育|帮助有志向的年轻人通过努力学习获得体面的工作和生活</p></div><divclass="nav"><ulclass=......
  • C++ - 文件读写
    5文件操作 程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放通过文件可以将数据持久化C++中对文件操作需要包含头文件==<fstream>== 文件类型分为两种:文本文件-文件以文本的ASCII码形式存储在计算机中二进制文件-文件以文本的二进制形式存储......
  • 导出excel文件总结
    使用alibaba的easyExcel一定要给要导出的类加注解,例:@ExcelProperty(value="文件传输结果(1=成功,0=失败,2=未传输)")导出的excel文件如果是只有属性没有值,查看自己的查询条件是否由默认值影响了异步导出,先确定好list是否为0,再返回成功如果导出的文件卡住了,也没有异......