一、上传之前先对文件进行转换校验文件格式
blobToString(blob) {
return new Promise(resolve => {
const reader = new FileReader()
reader.onload = function() {
const ret = reader.result.split('')
.map(v => v.charCodeAt()) // 先转化成2进制
.map(v=>v.toString(16).toUpperCase()) // 在转化成16进制
.join('')
resolve(ret)
}
reader.readAsBinaryString(blob)
})
}
将文件的进行截取对应的位数,传进去进行判断即可,下面演示gif的判断逻辑
async isGif(file) {
const ret = this.blobToString(file.slice(0, 6))
const isGif = (ret == '47 49 46 38 39 61') || (ret == '47 49 46 38 37 61')
return isGif
},
这样做可以强校验文件格式,提高上传文件的安全性。
二、文件上传之前将文件进行切片
//CHUNK_SIZE 默认为0.5m 0.5 * 1024 * 1024
createFileChunk(file, size=CHUNK_SIZE) {
const chunks = []
let cur = 0
while (cur < this.file.size) {
chunks.push({index: cur, file: this.file.slice(cur, cur+size)})
cur+=size
}
return chunks
},
三、采用布隆过滤器,对文件进行抽样哈希
async calculateHashSample() {
return new Promise(resolve => {
// 布容过滤器,前面,中间,后面
// 布隆过滤器,牺牲精度,换取速度
const spark = new sparkMD5.ArrayBuffer();
const reader = new FileReader();
const file = this.file;
const size = this.size;
const offset = 0.5 * 1024 * 1024;
let chunks = [file.slice(0, offset)];
let cur = offset;
while (cur < size) {
if(cur+offset >= size) {
// 随后一个区域块
chunks.push(file.slice(cur, cur+offset))
} else {
// 中间的区块,取前后两个字节
const mid = cur+offset/2
const end = cur+offset;
chunks.push(file.slice(cur, cur+2));
chunks.push(file.slice(mid, mid+2));
chunks.push(file.slice(end-2,end));
}
cur += offset;
}
reader.readAsArrayBuffer(new Blob(chunks))
reader.onload = e => {
spark.append(e.target.result);
this.hashProgress = 100;
resolve(spark.end())
}
})
},
四、将hash文件的后缀名发送给后端,后端进行判断文件是否已经存在,如果不存在则会返回uploadlist便于断点续传
const {data:{uploaded, uploadedList}} = await this.$http.post('/checkfile', {
hash: this.hash,
ext: this.file.name.split('.').pop()
})
// console.log('hash:', hash)
// console.log('hash1:', hash1)
// console.log('hash2:', hash2)
if(uploaded) {
return this.$message.success('秒传成功')
}
this.chunks = chunks.map((chunk, index) => {
// 切片的名字 hash+index
const name = hash + '-' + index;
return {
hash,
name,
index,
chunk: chunk.file
}
})
await this.uploadChunks(uploadedList)
五、uploadChunks会将后端返回的uploadlist进行过滤后进行上传。控制每次最多进行两个片段进行上传
async uploadChunks(uploadedList = []) {
const requests = this.chunks
.filter(chunks => uploadedList.indexOf(chunks.name) === -1) // 断点续传
.map((chunk, index) => {
// 转成promise
const form = new FormData();
form.append('chunk', chunk.chunk);
form.append('hash', chunk.hash);
form.append('name', chunk.name);
return {form, index:chunk.index}
})
await this.sendRequest(requests)
await this.mergeRequest()
},
async sendRequest(chunks, limit = 2) {
//limit并发数量
return new Promise((resolve, reject) => {
const len = chunks.length;
let count = 0;
const start = async () => {
// 某个任务失败三次 直接结束上传
if(stop)return
const task = chunks.shift();
if(task) {
const {form, index} = task;
try {
await this.$http.post('/uploadFile', form, {
onUploadProgress: progress => {
this.chunks[index].progress = Number(((progress.loaded/progress.total) * 100).toFixed(2))
}
})
if(count == len - 1) {
// 最后一个任务
resolve()
} else {
count++;
start()
}
} catch(e) {
// 失败重试
this.chunks[i].progress=-1 // 步骤条为红色
if(task.error<3){
task.error++
req.unshift(task)
start()
}else{
// 失败三次
stop=true
reject()
}
}
}
}
while (limit > 0) {
// 启动limit个任务
start()
limit -= 1
}
})
},
六、文件上传完成之后进行文件的合并。
mergeRequest() {
this.$http.post('mergefile', {
ext: this.file.name.split('.').pop(),
size: CHUNK_SIZE,
hash: this.hash,
})
},
``