前端文件上傳實現(xiàn)代碼示例(文件上傳,分片上傳,斷點續(xù)傳)
普通文件上傳
思路:
首先獲取用戶選擇的文件對象,并將其添加到一個
FormData
對象中。然后,使用 axios 的post
方法將FormData
對象發(fā)送到服務器。在then
和catch
中,我們分別處理上傳成功和失敗的情況,并輸出相應的信息。需要注意,在使用 axios 進行文件上傳時,必須將數(shù)據(jù)格式設置為
multipart/form-data
,否則文件對象將無法正確傳輸。
傳統(tǒng)方式:
function handleFileSelect(e) { const formData = new FormData(); formData.append("file", file); const header={"Content-Type": "multipart/form-data;charset=UTF-8"}; axios.post("http://xxx.xxx.xx.x:xxxx/upload",formData,{ headers: header, }).then((res) => { console.log(res); }); }
封裝方法:
在大型項目中,我一般會把get,post,put,delete以及upload方法進行封裝。
//封裝upload方法 import axios from "axios"; const requests = axios.create({ //配置對象 baseURL: myBaseURL, timeout: 10000, }); const header = { "Content-Type": "multipart/form-data;charset=UTF-8", }; const http = { upload(url="",formData){ return new Promise((resolve, reject) => { requests({ url, data: formData, headers: header, method: "POST", }) .then((res) => { resolve(res.data); return res; }) .catch((err) => { reject(err); }); }); }, .... }
//封裝請求方法 apiFun.upload = (formData) => { return http.upload("/user/headshot", formData); };
//上傳文件 function handleFileSelect(e) { const formData = new FormData(); formData.append("file", file); ApiFun.upload(formData).then((res) => { console.log(res); ElMessage.success("上傳成功"); }); } }
分片上傳
分片上傳是將一個大文件切分成多個小塊,然后將這些小塊逐個上傳到服務器的一種上傳方式。
思路:
簡單來說就是三個步驟:分片=》并行/串行發(fā)送分片=》合并請求
為什么要做分片上傳?通常大家第一會想到的就是因為文件太大了。
分片上傳解決以下幾個問題:
1. 大文件上傳容易導致網(wǎng)絡傳輸中斷:當我們嘗試上傳一個大文件時,如果網(wǎng)絡連接不穩(wěn)定或上傳過程中出現(xiàn)異常,整個文件都需要重新上傳。而通過分片上傳,即使某個小塊上傳失敗,只需要重新上傳該小塊,而不必重新上傳整個文件。
2. 降低服務器壓力:如果直接上傳一個大文件,服務器需要同時處理大量的數(shù)據(jù),并且需要分配較大的內(nèi)存空間來存儲這些數(shù)據(jù)。而通過分片上傳,可以將服務器的負載分散到多個小塊的處理上,減輕了服務器的壓力。
3. 提高上傳速度和穩(wěn)定性:將大文件切分成小塊后,可以并行上傳這些小塊,從而提高上傳速度。同時,如果某個小塊上傳失敗,可以重試該小塊,而不會影響其他小塊的上傳,從而提高上傳的穩(wěn)定性。
4. 支持斷點續(xù)傳:通過分片上傳,服務器可以保存每個小塊的上傳狀態(tài),包括已上傳的字節(jié)數(shù)和已確認的小塊。如果上傳過程中斷,下次可以從上次中斷的位置繼續(xù)上傳,實現(xiàn)斷點續(xù)傳的功能。
具體實現(xiàn)思路:
- 將大文件轉(zhuǎn)換成二進制流的格式
- 利用流可以切割的屬性,將二進制流切割成多份
- 組裝和分割塊同等數(shù)量的請求塊(或同等大小的請求塊),并行或串行的形式發(fā)出請求
- 待我們監(jiān)聽到所有請求都成功發(fā)出去以后,再給服務端發(fā)出一個合并的信號
一般我會選擇在文件小于5MB時普通文件上傳,當文件大于5MB時進行分片上傳。
整體邏輯就是:
將大文件進行分片:
我這里封裝了md5計算方法
'use strict'; import '../plugins/js-spark-md5.js' export default function (file, callback) { var blobSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice, file = file, chunkSize = 4194304, // Read in chunks of 4MB 即 4 * 1024 * 1024 chunks = Math.ceil(file.size / chunkSize), currentChunk = 0, spark = new SparkMD5.ArrayBuffer(), //向上取整,因為最后一塊不一定滿4MB fileReader = new FileReader(); fileReader.onload = function (e) { console.log('read chunk nr', currentChunk + 1, 'of', chunks); spark.append(e.target.result); // Append array buffer currentChunk++; if (currentChunk < chunks) { loadNext(); } else { let data = { "etag": spark.end(), "chunks": chunks, "size": file.size, "blockToken": "", } callback(null, data); console.log('finished loading'); } }; fileReader.onerror = function () { callback('oops, something went wrong.'); }; function loadNext() { var start = currentChunk * chunkSize, end = ((start + chunkSize) >= file.size) ? file.size : start + chunkSize; fileReader.readAsArrayBuffer(blobSlice.call(file, start, end)); } loadNext(); };
因為我實現(xiàn)了多文件并行分片上傳,所以這里在將文件加入列表時我就已經(jīng)計算好了相關屬性
當md5計算完畢,文件顯示準備就緒。
創(chuàng)建切片請求:
返回結(jié)果的屬性之一exist反應該文件是否上傳過,如果存在,則可以實現(xiàn)秒傳,無需再次上傳。
如果不存在,則獲取此次上傳的目標ip和端口號
將每一個切片 并行/串行 的方式發(fā)出:
我這里使用promise實現(xiàn)了并行(如果是用循環(huán)遍歷切片串行傳輸,可以通過循環(huán)下標i的值與切片總數(shù)相等來判斷分片是否上傳完畢)
且每個分片是大小一致的。
當所有分片上傳完成后。再給服務器端發(fā)送合并請求。
文件合并請求:
斷點續(xù)傳
在分片上傳的基礎上,我們很容易考慮到斷點續(xù)傳的需求。
思路:
點擊暫停按鈕時停止上傳。點擊繼續(xù)上傳,繼續(xù)上傳剩下的分片或請求。
我們很容易想到給每個分片一個是否上傳的狀態(tài)標識來識別該分片是否上傳完成。
法1:初始時所有分片的狀態(tài)為未上傳,當一個分片上傳完成,將該標識設置為已上傳。暫停上傳時,將當前上傳的分片也設置為未上傳;重新進行上傳時,就可以繼續(xù)把其他未上傳的分片上傳了。
法2:將所有分片加入一個列表中,每當成功上傳一個分片,就將該分片從列表中刪除。而列表中剩下的分片就是還未上傳的分片。
秒傳
即前面分片上傳中提到的。發(fā)送創(chuàng)建分片請求時,讓服務器檢查該文件是否上傳過,如果是,則無需重復上傳,直接顯示上傳成功實現(xiàn)秒傳功能。
其他問題 之 上傳過程中刷新頁面怎么辦
如果在上傳過程中刷新了頁面,通常會導致上傳任務中斷。
因為刷新頁面會導致瀏覽器重新加載頁面,之前的 JavaScript 代碼和網(wǎng)絡請求都會被取消。
當頁面刷新后,可以嘗試使用以下方法來處理上傳任務的中斷情況:
1. 利用瀏覽器的緩存機制:在上傳之前,將文件對象保存到瀏覽器的本地存儲或會話存儲中。當頁面刷新后,通過讀取緩存中的文件對象信息,重新構(gòu)建上傳任務,并恢復之前的上傳進度。
2. 檢測頁面刷新事件:可以通過監(jiān)聽 `beforeunload` 事件來捕獲頁面刷新的操作。在該事件觸發(fā)時,可以彈出一個確認框,提示用戶是否繼續(xù)離開頁面。如果用戶選擇離開頁面,可以先中止當前的上傳請求,然后再進行頁面刷新。
該方法并不能完全保證上傳任務的連續(xù)性和完整性。在實際應用中,為了確保上傳任務的可靠性,通常建議在上傳過程中避免刷新頁面,或者提供其他手段來處理上傳中斷的情況,如支持斷點續(xù)傳功能。
使用 localStorage
實現(xiàn)斷點續(xù)傳的demo:
const input = document.getElementById('file-input'); const FILE_STORAGE_KEY = 'uploadedFile'; // 讀取本地存儲的文件對象信息 const storedFile = localStorage.getItem(FILE_STORAGE_KEY); let file = null; if (storedFile) { // 如果存在已上傳的文件信息,則恢復上傳任務 file = JSON.parse(storedFile); } input.addEventListener('change', function() { file = input.files[0]; // 將文件對象保存到本地存儲 localStorage.setItem(FILE_STORAGE_KEY, JSON.stringify(file)); startUpload(); }); function startUpload() { if (!file) { console.log('請先選擇文件'); return; } const formData = new FormData(); formData.append('file', file); axios.post('/upload', formData, { onUploadProgress: function(progressEvent) { // 處理上傳進度變化 if (progressEvent.lengthComputable) { const percentComplete = progressEvent.loaded / progressEvent.total * 100; console.log(percentComplete.toFixed(2) + '% 已上傳'); } } }).then(function(response) { // 處理上傳完成事件 console.log('上傳完成'); console.log(response.data); // 上傳完成后,清除本地存儲的文件信息 localStorage.removeItem(FILE_STORAGE_KEY); }).catch(function(error) { // 處理上傳錯誤事件 console.error('上傳出錯'); }); } // 監(jiān)聽頁面刷新事件 window.addEventListener('beforeunload', function(event) { if (file) { // 中止當前的上傳請求 // ... // 移除本地存儲的文件信息 localStorage.removeItem(FILE_STORAGE_KEY); } });
使用 localStorage
存儲已選擇的文件對象信息,并在頁面刷新時恢復該信息。當用戶重新選擇文件時,更新文件對象并保存到 localStorage
中。在上傳過程中,如果用戶刷新頁面,會觸發(fā) beforeunload
事件,我們可以在該事件中中止上傳請求并移除 localStorage
中的文件信息。
其他問題 之 某個切片上傳失敗怎么辦
1. 重新上傳該切片:如果上傳失敗的切片是由于網(wǎng)絡等原因?qū)е碌?,則可以嘗試重新上傳該切片。如果上傳失敗的切片數(shù)量較少,則可以通過手動重試的方式來完成。如果上傳失敗的切片數(shù)量較多,則可能需要設計一些自動化機制來處理重傳邏輯,如使用隊列等數(shù)據(jù)結(jié)構(gòu)來記錄上傳失敗的切片并進行重傳。
2. 跳過該切片:如果上傳失敗的切片數(shù)量較多,或者由于某些原因無法進行重傳,則可以考慮跳過該切片。具體實現(xiàn)方法可以根據(jù)上傳任務的特點來確定,如將上傳任務分為多個階段,每個階段上傳一定數(shù)量的切片,如果某個階段上傳失敗,則跳過該階段并記錄下失敗的切片信息,待后續(xù)再進行重傳。
3. 放棄上傳任務:如果上傳失敗的切片數(shù)量較多,或者重傳操作多次仍然無法恢復上傳任務,則可以考慮放棄上傳任務。在此情況下,可以將上傳任務標記為“失敗”狀態(tài),并記錄下失敗的切片信息。如果需要重新上傳該文件,則可以在下一次上傳任務中,首先檢查之前已上傳的切片信息,如果存在已上傳的切片,則可以直接跳過這些切片并進行后續(xù)的上傳操作。
總結(jié)
到此這篇關于前端文件上傳實現(xiàn)的文章就介紹到這了,更多相關前端文件上傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
JS實現(xiàn)點擊復選框變更DIV顯示狀態(tài)的示例代碼
下面小編就為大家分享一篇JS實現(xiàn)點擊復選框變更DIV顯示狀態(tài)的示例代碼,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2017-12-12分享10個優(yōu)化代碼的CSS和JavaScript工具
如果你想在保持文件的時候或執(zhí) 行的階段lint代碼,那么linting工具也可以如你所愿。這取決于個人的選擇。如果你正在找尋用于CSS和JavaScript最好的 linting工具,那么請繼續(xù)閱讀2016-05-05JavaScript ECMA-262-3 深入解析(二):變量對象實例詳解
這篇文章主要介紹了JavaScript ECMA-262-3變量對象,結(jié)合實例形式詳細分析了JavaScript ECMA變量對象相關概念、原理、用法及操作注意事項,需要的朋友可以參考下2020-04-04