基于JavaScript實現(xiàn)文件秒傳功能
背景
上傳一個100MB的視頻文件,只需要1~3秒,是真的嗎?靠譜嗎?
此前,經(jīng)常有用戶反饋在正常網(wǎng)絡下上傳一個1GB的視頻大約需要10分鐘,我們也只能回復:“其實這種情況和上傳的時間、網(wǎng)絡以及文件大小有關(guān)”。
在互聯(lián)網(wǎng)高速發(fā)展的今天,文件上傳已經(jīng)成為網(wǎng)頁應用中的一個基本功能。隨著用戶上傳文件尺寸的不斷增大、對質(zhì)量清晰度的要求也越來越高。如何提高上傳速度、優(yōu)化用戶體驗成為了前端開發(fā)者必須面對的問題。
在此之前,最常見的優(yōu)化方案就是分塊上傳、斷點續(xù)傳
,在用戶因異常斷開上傳 或 刷新頁面后,能繼續(xù)在上一次的基礎上繼續(xù)上傳。這的確能較好的提升用戶上傳體驗,也是很有必要的優(yōu)化手段,但無法實現(xiàn)文件秒傳。
什么是文件秒傳?
文件秒傳指的是當用戶上傳文件時,如果服務器已存在完全相同的文件,那么無需用戶再次上傳,直接使用服務器上的文件副本,實現(xiàn)瞬間完成上傳的過程。這種技術(shù)可以顯著減少不必要的數(shù)據(jù)傳輸,節(jié)省時間和帶寬資源。(服務器會根據(jù)有無hash的情況返回3種狀態(tài),下面會有詳細說明)
文件秒傳的原理
文件秒傳的核心原理是“文件指紋”。即文件唯一ID,通常是指文件的哈希值(如MD5、SHA-1等),它是通過哈希算法計算得出的一串固定長度的字符串,可以唯一標識文件的內(nèi)容。即使文件非常龐大,其哈希值也能迅速計算出來,并且即便只是文件中的一個字節(jié)發(fā)生變化,所得到的哈希值也會完全不同。
秒傳的三種狀態(tài)處理說明
三種狀態(tài)都需要將前端計算得出的文件hash傳遞給服務端查詢獲得
狀態(tài)一(notHash):文件在服務器不存在
此時正常分塊上傳,上傳結(jié)束后返回上傳結(jié)果
狀態(tài)二(hasHash):文件在服務器存在
根據(jù)文件hash查詢到上傳結(jié)果,直接返回(實現(xiàn)秒傳)
狀態(tài)三(hashIng):新文件第一次上傳完,但后端任務未完成, 而該文件在前端又被上傳。此時輪詢后端接口,等待后端任務完成后直接上傳結(jié)果(該文件第二次或后續(xù)上傳均已實現(xiàn)秒傳)
狀態(tài)三中說的后端任務主要是:新文件上傳完后,后端并不會直接拿前端的hash存到數(shù)據(jù)庫,而是會自己在服務端根據(jù)上傳完的視頻生成hash(生成hash規(guī)則和前端一樣)再和前端比對,以確保數(shù)據(jù)的準確性及唯一性。
如何在前端頁面實現(xiàn)文件秒傳?
關(guān)鍵技術(shù)點
- 文件分塊hash計算
- 拿得到的hash到后端查詢文件狀態(tài)(確定文件是否在服務端存在)
- 根據(jù)服務端返回的上傳狀態(tài),處理上傳。
1. 計算文件hash
計算hash的方法封裝,使用md5會有相對較大的概率出現(xiàn)重復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ù)服務端的狀態(tài),返回上傳結(jié)果
async uploadFile(params) { ... // 文件存在,直接返回結(jié)果 if (state === 'hasHash') { return Promise.resolve({url}) } // 文件已上傳,后端處理中 if (state === 'hashIng') { // 設置輪詢開始時間 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) } ) }) }
批量上傳中應用秒傳
批量上傳也同樣適應,遍歷調(diào)用uploadFile({file: singleFile})
,逐個處理。也可使用Promise.all(...),等待所有文件處理完后再返回結(jié)果集合。 注:建議使用遍歷逐個上傳,能有更好的用戶體驗。
存在的主要問題
上傳大文件并在瀏覽器中進行SHA1或MD5哈希計算時可能會導致瀏覽器崩潰,原因通常是在處理大文件時所需的計算和內(nèi)存資源超過了瀏覽器的能力。
如上所示,通常的處理辦法包括: 通過setTimeout
或requestAnimationFrame
分時段計算、切割合適的塊、使用Web Workers
、使用Stream Processing
優(yōu)化、優(yōu)化算法,內(nèi)存管理、在服務端計算等。
但最佳的處理辦法,是在瀏覽器中使用webworker
多線程計算hash
,同時需要兼顧其兼容性、線程數(shù)量(需根據(jù)實際應用調(diào)整),目前項目已做優(yōu)化、整體體驗尚佳。webworker
在后面的文章中會有詳細的介紹。
總結(jié)
實現(xiàn)文件秒傳能夠顯著提升用戶的上傳體驗,特別是在處理大文件上傳時(上傳1GB大概20秒左右)
。
通過文件哈希比對、分塊上傳和斷點續(xù)傳等技術(shù),可以讓用戶感受到上傳速度的極大提升。
當然,文件秒傳的具體實現(xiàn)還是有一定的復雜性,需要前后端緊密協(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-12JS實現(xiàn)的4種數(shù)字千位符格式化方法分享
這篇文章主要介紹了JS實現(xiàn)的4種數(shù)字千位符格式化方法分享,本文給出了4種千分位格式化方法并對它們的性能做了比較,需要的朋友可以參考下2015-03-03