基于JavaScript實現(xiàn)文件秒傳功能
背景
上傳一個100MB的視頻文件,只需要1~3秒,是真的嗎?靠譜嗎?
此前,經(jīng)常有用戶反饋在正常網(wǎng)絡(luò)下上傳一個1GB的視頻大約需要10分鐘,我們也只能回復(fù):“其實這種情況和上傳的時間、網(wǎng)絡(luò)以及文件大小有關(guān)”。
在互聯(lián)網(wǎng)高速發(fā)展的今天,文件上傳已經(jīng)成為網(wǎng)頁應(yīng)用中的一個基本功能。隨著用戶上傳文件尺寸的不斷增大、對質(zhì)量清晰度的要求也越來越高。如何提高上傳速度、優(yōu)化用戶體驗成為了前端開發(fā)者必須面對的問題。
在此之前,最常見的優(yōu)化方案就是分塊上傳、斷點續(xù)傳,在用戶因異常斷開上傳 或 刷新頁面后,能繼續(xù)在上一次的基礎(chǔ)上繼續(xù)上傳。這的確能較好的提升用戶上傳體驗,也是很有必要的優(yōu)化手段,但無法實現(xiàn)文件秒傳。
什么是文件秒傳?
文件秒傳指的是當(dāng)用戶上傳文件時,如果服務(wù)器已存在完全相同的文件,那么無需用戶再次上傳,直接使用服務(wù)器上的文件副本,實現(xiàn)瞬間完成上傳的過程。這種技術(shù)可以顯著減少不必要的數(shù)據(jù)傳輸,節(jié)省時間和帶寬資源。(服務(wù)器會根據(jù)有無hash的情況返回3種狀態(tài),下面會有詳細說明)
文件秒傳的原理
文件秒傳的核心原理是“文件指紋”。即文件唯一ID,通常是指文件的哈希值(如MD5、SHA-1等),它是通過哈希算法計算得出的一串固定長度的字符串,可以唯一標(biāo)識文件的內(nèi)容。即使文件非常龐大,其哈希值也能迅速計算出來,并且即便只是文件中的一個字節(jié)發(fā)生變化,所得到的哈希值也會完全不同。
秒傳的三種狀態(tài)處理說明
三種狀態(tài)都需要將前端計算得出的文件hash傳遞給服務(wù)端查詢獲得
狀態(tài)一(notHash):文件在服務(wù)器不存在
此時正常分塊上傳,上傳結(jié)束后返回上傳結(jié)果
狀態(tài)二(hasHash):文件在服務(wù)器存在
根據(jù)文件hash查詢到上傳結(jié)果,直接返回(實現(xiàn)秒傳)
狀態(tài)三(hashIng):新文件第一次上傳完,但后端任務(wù)未完成, 而該文件在前端又被上傳。此時輪詢后端接口,等待后端任務(wù)完成后直接上傳結(jié)果(該文件第二次或后續(xù)上傳均已實現(xiàn)秒傳)
狀態(tài)三中說的后端任務(wù)主要是:新文件上傳完后,后端并不會直接拿前端的hash存到數(shù)據(jù)庫,而是會自己在服務(wù)端根據(jù)上傳完的視頻生成hash(生成hash規(guī)則和前端一樣)再和前端比對,以確保數(shù)據(jù)的準(zhǔn)確性及唯一性。
如何在前端頁面實現(xiàn)文件秒傳?
關(guān)鍵技術(shù)點
- 文件分塊hash計算
- 拿得到的hash到后端查詢文件狀態(tài)(確定文件是否在服務(wù)端存在)
- 根據(jù)服務(wù)端返回的上傳狀態(tài),處理上傳。
1. 計算文件hash
計算hash的方法封裝,使用md5會有相對較大的概率出現(xiàn)重復(fù)hash,建議至少使用sha1的方式計算。
/**
* @description: 分塊計算文件hash
* @param {*} file 文件對象
* @param {*} chunkSize 分塊計算的文件大小,默認10MB
* @return {*}
*/
export function calculateSliceFileHash({ file, chunkSize = 10 * 1024 * 1024 }) {
let currentChunkIndex = 0;
const maxChunkCount = Math.ceil(file.size / chunkSize);
let sha1WordArray = CryptoJS.algo.SHA1.create();
const startTime = Date.now()
return new Promise((resolve, reject) => {
function loadNextChunk() {
const start = currentChunkIndex * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const reader = new FileReader();
reader.onload = function (e) {
const arrayBuffer = e.target.result;
const wordArray = CryptoJS.lib.WordArray.create(arrayBuffer);
sha1WordArray.update(wordArray);
const diffTime = Date.now() - startTime
if (currentChunkIndex < maxChunkCount - 1) {
currentChunkIndex++;
loadNextChunk()
} else {
const sha1 = sha1WordArray.finalize().toString(CryptoJS.enc.Hex); // CryptoJS.enc.Hex
resolve({ sha1, diffTime }); // 返回計算出的hash值
}
};
reader.onerror = function (error) {
console.error('Error reading file chunk:', error);
reject({ error: parseError(error) }); // 處理錯誤
};
const blobSlice = file.slice(start, end);
reader.readAsArrayBuffer(blobSlice);
}
loadNextChunk();
})
}
在需要計算的時候直接調(diào)用const {sha1} = calculateSliceFileHash({file})即可
2. 根據(jù)hash查詢后端狀態(tài)
async uploadFile(params) {
// 獲取hash
const {sha1} = await calculateSliceFileHash({ file })
// 根據(jù)hash查詢文件狀態(tài)
const {data} = await this.axios.post(`/api/xxx`, params)
const { key, bucket, region, state, url } = data.data
// state='hasHash' | 'hashIng' | 'notHash'
...
}
3. 根據(jù)服務(wù)端的狀態(tài),返回上傳結(jié)果
async uploadFile(params) {
...
// 文件存在,直接返回結(jié)果
if (state === 'hasHash') {
return Promise.resolve({url})
}
// 文件已上傳,后端處理中
if (state === 'hashIng') {
// 設(shè)置輪詢開始時間
if (!params.pollStartTs) {
params.pollStartTs = Date.now()
} else if (Date.now() - params.pollStartTs >= 60000) {
// 輪詢超過1分鐘認定為超時
return Promise.reject({ error: '文件加載超時,請稍后再試' })
}
await sleep(1000) // 每隔1s輪詢
await this.uploadFile(params)
}
// state==='notHash'時,執(zhí)行下面的上傳
return new Promise(async (resolve, reject) => {
const cos = new Cos({
getAuthorization: this.cosAuthorization({
resolve,
reject,
bucket,
key
}).bind(this)
})
cos.sliceUploadFile(
{
Bucket: bucket,
Region: region,
Key: key,
Body: file,
SliceSize: 1024 * 1024 * 10, // 超10M使用分塊(cos單個塊最大不超過5GB)
onProgress,
onTaskReady
},
(error, data) => {
if (error) {
return reject({ error })
}
resolve(data)
}
)
})
}
批量上傳中應(yīng)用秒傳
批量上傳也同樣適應(yīng),遍歷調(diào)用uploadFile({file: singleFile}),逐個處理。也可使用Promise.all(...),等待所有文件處理完后再返回結(jié)果集合。 注:建議使用遍歷逐個上傳,能有更好的用戶體驗。
存在的主要問題
上傳大文件并在瀏覽器中進行SHA1或MD5哈希計算時可能會導(dǎo)致瀏覽器崩潰,原因通常是在處理大文件時所需的計算和內(nèi)存資源超過了瀏覽器的能力。

如上所示,通常的處理辦法包括: 通過setTimeout或requestAnimationFrame分時段計算、切割合適的塊、使用Web Workers、使用Stream Processing優(yōu)化、優(yōu)化算法,內(nèi)存管理、在服務(wù)端計算等。
但最佳的處理辦法,是在瀏覽器中使用webworker多線程計算hash,同時需要兼顧其兼容性、線程數(shù)量(需根據(jù)實際應(yīng)用調(diào)整),目前項目已做優(yōu)化、整體體驗尚佳。webworker在后面的文章中會有詳細的介紹。
總結(jié)
實現(xiàn)文件秒傳能夠顯著提升用戶的上傳體驗,特別是在處理大文件上傳時(上傳1GB大概20秒左右)。
通過文件哈希比對、分塊上傳和斷點續(xù)傳等技術(shù),可以讓用戶感受到上傳速度的極大提升。
當(dāng)然,文件秒傳的具體實現(xiàn)還是有一定的復(fù)雜性,需要前后端緊密協(xié)作,確保整個上傳過程的穩(wěn)定性和安全性。隨著技術(shù)的不斷進步,相信未來的文件上傳體驗將會更加流暢,讓用戶真正體驗到“秒傳”的魔力。
以上就是基于JavaScript實現(xiàn)文件秒傳功能的詳細內(nèi)容,更多關(guān)于JavaScript實現(xiàn)文件秒傳的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JavaScript簡單遍歷DOM對象所有屬性的實現(xiàn)方法
這篇文章主要介紹了JavaScript簡單遍歷DOM對象所有屬性的實現(xiàn)方法,涉及JavaScript針對頁面元素屬性操作的相關(guān)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-10-10
基于javascript的COOkie的操作實現(xiàn)只能點一次
這篇文章主要介紹了基于javascript的COOkie的操作實現(xiàn)只能點一次,需要的朋友可以參考下2014-12-12
JS實現(xiàn)的4種數(shù)字千位符格式化方法分享
這篇文章主要介紹了JS實現(xiàn)的4種數(shù)字千位符格式化方法分享,本文給出了4種千分位格式化方法并對它們的性能做了比較,需要的朋友可以參考下2015-03-03

