携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情
在日常的开发中,项目中基本出现的最多的应该就是上传文件,对于文件的上传,我们可以分为多种形式,单文件上传、多文件上传、进度管控、拖拽上传、大文件上传。
今天就把大文件上传来说一下,对于大文件的上传,我们需要知道有俩种做法,一种是切片上传,还有一种是断点续传:切片上传,顾名思义就是把我们的文件按
大小或者数量进行分割切成小文件,然后按片进行上传。断点续传则是当我们的文件上传一半的时候,突然断网或者刷新页面,下次再上传该文件的时候,会跳过已经上传的文件片段,从上一次上传的地方继续上传。
为什么要用hash命名文件
上传到服务器的文件,如果俩个用户上传的文件名称相同的话,可能会被识别到文件已经存在,导致后端服务不会去再次上传新的图片,这样就会造成新图片无法上传,并且显示文件已经存在。hash是对上传文件的buffer进行读取,然后通过spkmd5插件来进行转换成hash值,如果文件上传的是一样的,那么buffer的值是一样,转成出来的hash值自然也是一样的,那么上传的时候同一个文件就不会因为hash名不同而存在服务端俩次。虽然文件hash处理一般是在服务端做,但也有可能存在某些情况需要前端来做这件事情。
实现过程
<div class="container">
<div class="item">
<h3>大文件上传</h3>
<section ref="upload7" class="upload_box" id="upload7">
<input type="file" class="upload_inp" ref="upload_inp">
<div class="upload_button_box">
<button class="upload_button select" ref="upload_button_select">上传图片</button>
</div>
<div class="upload_progress" ref="upload_progress">
<div class="value" ref="upload_progress_value"></div>
</div>
</section>
</div>
</div>
切片操作
let upload7 = ref(null),
upload_inp = ref(null),
upload_button_select = ref(null),
upload_progress = ref(null),
upload_progress_value = ref(null)
function init(){
upload7 = upload7.value
upload_inp = upload_inp.value
upload_button_select = upload_button_select.value
upload_progress = upload_progress.value
upload_progress_value = upload_progress_value.value
upload_button_select.addEventListener('click',function(){
upload_inp.click()
})
upload_inp.addEventListener('change',async function(){
let file = upload_inp.files[0];
if (!file) return;
//实现切片上传
//思路,先按照最大大小切片 如果大于100片 则最多只能切100片
let max = 1024*10,
size = file.size,
count = Math.ceil(size/max),
index = 0,
chunks = [],
{
HASH,
suffix
} = await changeBuffer(file);
if(count>100){
max = size/100
count = 100
}
})
当我们获取完页面的节点后,监听上传文件的变化事件,获取file进行切片,这里的file可以打印出来看下,其实file的原型上存在着Blob,对file的切片,实际上是对Blob的切片
切片的话我们分为数量和大小进行切片,写按规定大小进行切片,如果切完数量超过最大数量,则按最大数量来进行切片
转换Buffer
const changeBuffer = file => {
return new Promise(resolve => {
let fileReader = new FileReader();
fileReader.readAsArrayBuffer(file);//读取文件转成Buffer数据
fileReader.onload = ev => {
let buffer = ev.target.result,
spark = new SparkMD5.ArrayBuffer(),
HASH,
suffix;
spark.append(buffer);
HASH = spark.end();
suffix = /\.([a-zA-Z0-9]+)$/.exec(file.name)[1];
resolve({
buffer,
HASH,
suffix,
filename: `${HASH}.${suffix}`
});
};
});
};
使用readAsArrayBuffer对文件转换成buffer
利用SparkMD5插件来对文件的内容转换成hash
转换后即使文件名换了但是图片内容不换,生成的hash都是同一个值,因为这是根据文件的内容生成的hash,这点可以有效的防止同一张图片多次存在服务器中。
存储切片
//存切片
while(index<count){
chunks.push({
file: file.slice(index * max, (index + 1) * max),
filename: `${HASH}_${index+1}.${suffix}`
})
index++
}
chunks.forEach(chunk => {
let fm = new FormData()
fm.append('file', chunk.file);
fm.append('filename', chunk.filename);
instance.post('/upload_chunk', fm).then(data => {
if (+data.code === 0) {
complate(count,HASH);
return;
}
return Promise.reject(data.codeText);
}).catch(() => {
alert('当前切片上传失败,请您稍后再试~~');
// clear();
});
});
接着上面的步骤,使用while循环把切片数据放到一个数组中,并且每次push都按照规定的切片大小进行切割,文件名自然是使用已经产生好的hash来拼接命名,当数组循环上传之后,我们可以看到浏览器网络会一个一个的发送请求
合并切片
后端通过接收切片后放到一个文件夹中,作为一个切片文件夹
前端循环切片上传到最后一个切片的时候,就去调用后端的合并切片接口,把对应hash下面的切片进行合并,生成一个正式的图片,这里后端部分暂时不做讲解,其实也是通过一些api对切片进行合并
断点续传
如果您已经理解了上面部分的内容,那么对于断点续传其实也不难理解
断点续传是针对切片上传的文件,对已经上传的切片不再重新进行上传,做法其实很简单,需要从服务端拿到已经上传的切片数组,比如服务端返回了1~10的切片,而要重新上传的文件也有一个切片数组1-100,那么上传时切片会判断1-10存在服务端,则跳过上传,做法如下
already表示已上传的切片
// 已经上传的无需在上传
if (already.length > 0 && already.includes(chunk.filename)) {
complate();
return;
}
最后
本文主要是讲清楚大文件切片上传和断点上传的内容及其实现过程,讲解了在前端方面的大文件上传的知识,本文的技术代码来自b站视频,笔者只是学习后留作笔记使用
如果对您有帮助的话,请给我点给赞吧~文章不足之处还请各位不吝赐教