一外臂、上傳之前先對文件進(jìn)行轉(zhuǎn)換校驗(yàn)文件格式
blobToString(blob) {
return new Promise(resolve => {
const reader = new FileReader()
reader.onload = function() {
const ret = reader.result.split('')
.map(v => v.charCodeAt()) // 先轉(zhuǎn)化成2進(jìn)制
.map(v=>v.toString(16).toUpperCase()) // 在轉(zhuǎn)化成16進(jìn)制
.join('')
resolve(ret)
}
reader.readAsBinaryString(blob)
})
}
將文件的進(jìn)行截取對應(yīng)的位數(shù)湿硝,傳進(jìn)去進(jìn)行判斷即可蚕捉,下面演示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
},
這樣做可以強(qiáng)校驗(yàn)文件格式奏篙,提高上傳文件的安全性。
二、文件上傳之前將文件進(jìn)行切片
//CHUNK_SIZE 默認(rèn)為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
},
三秘通、采用布隆過濾器为严,對文件進(jìn)行抽樣哈希
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) {
// 隨后一個區(qū)域塊
chunks.push(file.slice(cur, cur+offset))
} else {
// 中間的區(qū)塊夕吻,取前后兩個字節(jié)
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文件的后綴名發(fā)送給后端繁仁,后端進(jìn)行判斷文件是否已經(jīng)存在涉馅,如果不存在則會返回uploadlist便于斷點(diǎn)續(xù)傳
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進(jìn)行過濾后進(jìn)行上傳黄虱≈煽螅控制每次最多進(jìn)行兩個片段進(jìn)行上傳
async uploadChunks(uploadedList = []) {
const requests = this.chunks
.filter(chunks => uploadedList.indexOf(chunks.name) === -1) // 斷點(diǎn)續(xù)傳
.map((chunk, index) => {
// 轉(zhuǎn)成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并發(fā)數(shù)量
return new Promise((resolve, reject) => {
const len = chunks.length;
let count = 0;
const start = async () => {
// 某個任務(wù)失敗三次 直接結(jié)束上傳
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) {
// 最后一個任務(wù)
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個任務(wù)
start()
limit -= 1
}
})
},
六、文件上傳完成之后進(jìn)行文件的合并捻浦。
mergeRequest() {
this.$http.post('mergefile', {
ext: this.file.name.split('.').pop(),
size: CHUNK_SIZE,
hash: this.hash,
})
},
``