文件分片上傳通過將大文件切分為小塊提升上傳效率與穩定性。其核心實現步驟如下:1.前端使用filereader和slice()方法進行文件切割;2.利用fetch或xmlhttprequest逐個上傳分片,并附帶分片索引等元數據;3.通過localstorage記錄已上傳分片實現斷點續傳,后端需存儲并驗證分片狀態;4.服務器接收所有分片后按序合并成完整文件;5.合理選擇4mb-8mb的分片大小以平衡請求次數與失敗率;6.上傳失敗時采用重試機制,設置最大重試次數與延遲間隔;7.監聽上傳進度事件實現ui實時反饋,從而優化用戶體驗。
文件分片上傳,簡單來說,就是把一個大文件切成很多小塊,一塊一塊地傳到服務器。這樣做的好處顯而易見:提升上傳速度,降低單次上傳失敗的風險,還能實現斷點續傳。
JS實現文件分片上傳與斷點續傳,核心在于前端的文件切割、上傳控制,以及后端的分片合并。
解決方案
-
前端文件切割: 使用FileReader API讀取文件,然后用slice()方法將文件切割成指定大小的塊。
async function sliceFile(file, chunkSize) { const chunks = []; let offset = 0; while (offset < file.size) { const chunk = file.slice(offset, offset + chunkSize); chunks.push(chunk); offset += chunkSize; } return chunks; }
-
上傳分片: 使用XMLHttpRequest或fetch API將每個分片上傳到服務器。 每個分片都作為一個獨立的請求發送。
async function uploadChunk(chunk, fileId, chunkIndex, totalChunks) { const formData = new FormData(); formData.append('file', chunk); formData.append('fileId', fileId); formData.append('chunkIndex', chunkIndex); formData.append('totalChunks', totalChunks); try { const response = await fetch('/upload', { method: 'POST', body: formData, }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data; // 假設后端返回上傳結果 } catch (error) { console.error('上傳分片失敗:', error); throw error; // 向上拋出錯誤,便于后續處理 } }
-
斷點續傳: 在上傳過程中,記錄已上傳的分片信息。如果上傳中斷,下次可以從上次中斷的位置繼續上傳。這需要后端配合,記錄已上傳的分片。前端也需要存儲這些信息,例如使用localStorage。
// 示例:使用localStorage存儲已上傳的分片 function saveUploadedChunk(fileId, chunkIndex) { let uploadedChunks = JSON.parse(localStorage.getItem(fileId) || '[]'); uploadedChunks.push(chunkIndex); localStorage.setItem(fileId, JSON.stringify(uploadedChunks)); } function getUploadedChunks(fileId) { return JSON.parse(localStorage.getItem(fileId) || '[]'); }
-
后端分片合并: 服務器接收到所有分片后,按照分片順序將它們合并成完整的文件。
如何選擇合適的分片大小?
分片大小的選擇直接影響上傳效率和用戶體驗。 太小的分片會導致過多的請求,增加服務器壓力;太大的分片則可能因為網絡不穩定導致單次上傳失敗。一般來說,4MB-8MB是一個比較合適的范圍。 實際應用中,可以根據網絡環境和文件大小進行調整。
如何處理上傳錯誤和重試機制?
上傳過程中,網絡波動、服務器故障等都可能導致上傳失敗。為了提高上傳成功率,需要實現錯誤處理和重試機制。可以設置最大重試次數,每次重試之間增加一定的延遲。 超過最大重試次數后,可以提示用戶稍后重試。
async function retryUpload(chunk, fileId, chunkIndex, totalChunks, maxRetries = 3, delay = 1000) { let retries = 0; while (retries < maxRetries) { try { return await uploadChunk(chunk, fileId, chunkIndex, totalChunks); } catch (error) { console.warn(`分片 ${chunkIndex} 上傳失敗,正在重試 (${retries + 1}/${maxRetries})...`); retries++; await new Promise(resolve => setTimeout(resolve, delay)); // 延遲一段時間后重試 } } throw new Error(`分片 ${chunkIndex} 上傳達到最大重試次數,上傳失敗。`); }
如何實現上傳進度的實時展示?
實時展示上傳進度可以顯著提升用戶體驗。 可以通過監聽XMLHttpRequest的progress事件,或者fetch API返回的ReadableStream來獲取上傳進度。 將進度信息更新到UI界面上,讓用戶了解上傳狀態。
// 使用 fetch API 監聽上傳進度 async function uploadWithProgress(chunk, fileId, chunkIndex, totalChunks, progressCallback) { const formData = new FormData(); formData.append('file', chunk); formData.append('fileId', fileId); formData.append('chunkIndex', chunkIndex); formData.append('totalChunks', totalChunks); const response = await fetch('/upload', { method: 'POST', body: formData, duplex: 'half', // 允許讀取請求體 signal: AbortSignal.timeout(60000) // 設置超時時間 }); if (!response.body) { throw new Error('Response body is empty.'); } const reader = response.body.getReader(); const contentLength = response.headers.get('Content-Length'); let receivedLength = 0; while (true) { const { done, value } = await reader.read(); if (done) { break; } receivedLength += value.length; const progress = contentLength ? receivedLength / Number(contentLength) : 0; progressCallback(progress); // 調用回調函數更新進度 } if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data; }