前端文件上傳實(shí)現(xiàn)代碼示例(文件上傳,分片上傳,斷點(diǎn)續(xù)傳)
普通文件上傳
思路:
首先獲取用戶選擇的文件對(duì)象,并將其添加到一個(gè)
FormData對(duì)象中。然后,使用 axios 的post方法將FormData對(duì)象發(fā)送到服務(wù)器。在then和catch中,我們分別處理上傳成功和失敗的情況,并輸出相應(yīng)的信息。需要注意,在使用 axios 進(jìn)行文件上傳時(shí),必須將數(shù)據(jù)格式設(shè)置為
multipart/form-data,否則文件對(duì)象將無(wú)法正確傳輸。
傳統(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);
});
}封裝方法:
在大型項(xiàng)目中,我一般會(huì)把get,post,put,delete以及upload方法進(jìn)行封裝。
//封裝upload方法
import axios from "axios";
const requests = axios.create({
//配置對(duì)象
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);
});
});
},
....
}//封裝請(qǐng)求方法
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("上傳成功");
});
}
}分片上傳
分片上傳是將一個(gè)大文件切分成多個(gè)小塊,然后將這些小塊逐個(gè)上傳到服務(wù)器的一種上傳方式。
思路:
簡(jiǎn)單來(lái)說(shuō)就是三個(gè)步驟:分片=》并行/串行發(fā)送分片=》合并請(qǐng)求
為什么要做分片上傳?通常大家第一會(huì)想到的就是因?yàn)槲募罅恕?/p>
分片上傳解決以下幾個(gè)問(wèn)題:
1. 大文件上傳容易導(dǎo)致網(wǎng)絡(luò)傳輸中斷:當(dāng)我們嘗試上傳一個(gè)大文件時(shí),如果網(wǎng)絡(luò)連接不穩(wěn)定或上傳過(guò)程中出現(xiàn)異常,整個(gè)文件都需要重新上傳。而通過(guò)分片上傳,即使某個(gè)小塊上傳失敗,只需要重新上傳該小塊,而不必重新上傳整個(gè)文件。
2. 降低服務(wù)器壓力:如果直接上傳一個(gè)大文件,服務(wù)器需要同時(shí)處理大量的數(shù)據(jù),并且需要分配較大的內(nèi)存空間來(lái)存儲(chǔ)這些數(shù)據(jù)。而通過(guò)分片上傳,可以將服務(wù)器的負(fù)載分散到多個(gè)小塊的處理上,減輕了服務(wù)器的壓力。
3. 提高上傳速度和穩(wěn)定性:將大文件切分成小塊后,可以并行上傳這些小塊,從而提高上傳速度。同時(shí),如果某個(gè)小塊上傳失敗,可以重試該小塊,而不會(huì)影響其他小塊的上傳,從而提高上傳的穩(wěn)定性。
4. 支持?jǐn)帱c(diǎn)續(xù)傳:通過(guò)分片上傳,服務(wù)器可以保存每個(gè)小塊的上傳狀態(tài),包括已上傳的字節(jié)數(shù)和已確認(rèn)的小塊。如果上傳過(guò)程中斷,下次可以從上次中斷的位置繼續(xù)上傳,實(shí)現(xiàn)斷點(diǎn)續(xù)傳的功能。
具體實(shí)現(xiàn)思路:
- 將大文件轉(zhuǎn)換成二進(jìn)制流的格式
- 利用流可以切割的屬性,將二進(jìn)制流切割成多份
- 組裝和分割塊同等數(shù)量的請(qǐng)求塊(或同等大小的請(qǐng)求塊),并行或串行的形式發(fā)出請(qǐng)求
- 待我們監(jiān)聽(tīng)到所有請(qǐng)求都成功發(fā)出去以后,再給服務(wù)端發(fā)出一個(gè)合并的信號(hào)
一般我會(huì)選擇在文件小于5MB時(shí)普通文件上傳,當(dāng)文件大于5MB時(shí)進(jìn)行分片上傳。


整體邏輯就是:

將大文件進(jìn)行分片:
我這里封裝了md5計(jì)算方法
'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(), //向上取整,因?yàn)樽詈笠粔K不一定滿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();
};因?yàn)槲覍?shí)現(xiàn)了多文件并行分片上傳,所以這里在將文件加入列表時(shí)我就已經(jīng)計(jì)算好了相關(guān)屬性
當(dāng)md5計(jì)算完畢,文件顯示準(zhǔn)備就緒。

創(chuàng)建切片請(qǐng)求:

返回結(jié)果的屬性之一exist反應(yīng)該文件是否上傳過(guò),如果存在,則可以實(shí)現(xiàn)秒傳,無(wú)需再次上傳。
如果不存在,則獲取此次上傳的目標(biāo)ip和端口號(hào)

將每一個(gè)切片 并行/串行 的方式發(fā)出:
我這里使用promise實(shí)現(xiàn)了并行(如果是用循環(huán)遍歷切片串行傳輸,可以通過(guò)循環(huán)下標(biāo)i的值與切片總數(shù)相等來(lái)判斷分片是否上傳完畢)
且每個(gè)分片是大小一致的。

當(dāng)所有分片上傳完成后。再給服務(wù)器端發(fā)送合并請(qǐng)求。
文件合并請(qǐng)求:


斷點(diǎn)續(xù)傳
在分片上傳的基礎(chǔ)上,我們很容易考慮到斷點(diǎn)續(xù)傳的需求。
思路:
點(diǎn)擊暫停按鈕時(shí)停止上傳。點(diǎn)擊繼續(xù)上傳,繼續(xù)上傳剩下的分片或請(qǐng)求。
我們很容易想到給每個(gè)分片一個(gè)是否上傳的狀態(tài)標(biāo)識(shí)來(lái)識(shí)別該分片是否上傳完成。
法1:初始時(shí)所有分片的狀態(tài)為未上傳,當(dāng)一個(gè)分片上傳完成,將該標(biāo)識(shí)設(shè)置為已上傳。暫停上傳時(shí),將當(dāng)前上傳的分片也設(shè)置為未上傳;重新進(jìn)行上傳時(shí),就可以繼續(xù)把其他未上傳的分片上傳了。
法2:將所有分片加入一個(gè)列表中,每當(dāng)成功上傳一個(gè)分片,就將該分片從列表中刪除。而列表中剩下的分片就是還未上傳的分片。
秒傳
即前面分片上傳中提到的。發(fā)送創(chuàng)建分片請(qǐng)求時(shí),讓服務(wù)器檢查該文件是否上傳過(guò),如果是,則無(wú)需重復(fù)上傳,直接顯示上傳成功實(shí)現(xiàn)秒傳功能。
其他問(wèn)題 之 上傳過(guò)程中刷新頁(yè)面怎么辦
如果在上傳過(guò)程中刷新了頁(yè)面,通常會(huì)導(dǎo)致上傳任務(wù)中斷。
因?yàn)樗⑿马?yè)面會(huì)導(dǎo)致瀏覽器重新加載頁(yè)面,之前的 JavaScript 代碼和網(wǎng)絡(luò)請(qǐng)求都會(huì)被取消。
當(dāng)頁(yè)面刷新后,可以嘗試使用以下方法來(lái)處理上傳任務(wù)的中斷情況:
1. 利用瀏覽器的緩存機(jī)制:在上傳之前,將文件對(duì)象保存到瀏覽器的本地存儲(chǔ)或會(huì)話存儲(chǔ)中。當(dāng)頁(yè)面刷新后,通過(guò)讀取緩存中的文件對(duì)象信息,重新構(gòu)建上傳任務(wù),并恢復(fù)之前的上傳進(jìn)度。
2. 檢測(cè)頁(yè)面刷新事件:可以通過(guò)監(jiān)聽(tīng) `beforeunload` 事件來(lái)捕獲頁(yè)面刷新的操作。在該事件觸發(fā)時(shí),可以彈出一個(gè)確認(rèn)框,提示用戶是否繼續(xù)離開(kāi)頁(yè)面。如果用戶選擇離開(kāi)頁(yè)面,可以先中止當(dāng)前的上傳請(qǐng)求,然后再進(jìn)行頁(yè)面刷新。
該方法并不能完全保證上傳任務(wù)的連續(xù)性和完整性。在實(shí)際應(yīng)用中,為了確保上傳任務(wù)的可靠性,通常建議在上傳過(guò)程中避免刷新頁(yè)面,或者提供其他手段來(lái)處理上傳中斷的情況,如支持?jǐn)帱c(diǎn)續(xù)傳功能。
使用 localStorage 實(shí)現(xiàn)斷點(diǎn)續(xù)傳的demo:
const input = document.getElementById('file-input');
const FILE_STORAGE_KEY = 'uploadedFile';
// 讀取本地存儲(chǔ)的文件對(duì)象信息
const storedFile = localStorage.getItem(FILE_STORAGE_KEY);
let file = null;
if (storedFile) {
// 如果存在已上傳的文件信息,則恢復(fù)上傳任務(wù)
file = JSON.parse(storedFile);
}
input.addEventListener('change', function() {
file = input.files[0];
// 將文件對(duì)象保存到本地存儲(chǔ)
localStorage.setItem(FILE_STORAGE_KEY, JSON.stringify(file));
startUpload();
});
function startUpload() {
if (!file) {
console.log('請(qǐng)先選擇文件');
return;
}
const formData = new FormData();
formData.append('file', file);
axios.post('/upload', formData, {
onUploadProgress: function(progressEvent) {
// 處理上傳進(jìn)度變化
if (progressEvent.lengthComputable) {
const percentComplete = progressEvent.loaded / progressEvent.total * 100;
console.log(percentComplete.toFixed(2) + '% 已上傳');
}
}
}).then(function(response) {
// 處理上傳完成事件
console.log('上傳完成');
console.log(response.data);
// 上傳完成后,清除本地存儲(chǔ)的文件信息
localStorage.removeItem(FILE_STORAGE_KEY);
}).catch(function(error) {
// 處理上傳錯(cuò)誤事件
console.error('上傳出錯(cuò)');
});
}
// 監(jiān)聽(tīng)頁(yè)面刷新事件
window.addEventListener('beforeunload', function(event) {
if (file) {
// 中止當(dāng)前的上傳請(qǐng)求
// ...
// 移除本地存儲(chǔ)的文件信息
localStorage.removeItem(FILE_STORAGE_KEY);
}
});
使用 localStorage 存儲(chǔ)已選擇的文件對(duì)象信息,并在頁(yè)面刷新時(shí)恢復(fù)該信息。當(dāng)用戶重新選擇文件時(shí),更新文件對(duì)象并保存到 localStorage 中。在上傳過(guò)程中,如果用戶刷新頁(yè)面,會(huì)觸發(fā) beforeunload 事件,我們可以在該事件中中止上傳請(qǐng)求并移除 localStorage 中的文件信息。
其他問(wèn)題 之 某個(gè)切片上傳失敗怎么辦
1. 重新上傳該切片:如果上傳失敗的切片是由于網(wǎng)絡(luò)等原因?qū)е碌模瑒t可以嘗試重新上傳該切片。如果上傳失敗的切片數(shù)量較少,則可以通過(guò)手動(dòng)重試的方式來(lái)完成。如果上傳失敗的切片數(shù)量較多,則可能需要設(shè)計(jì)一些自動(dòng)化機(jī)制來(lái)處理重傳邏輯,如使用隊(duì)列等數(shù)據(jù)結(jié)構(gòu)來(lái)記錄上傳失敗的切片并進(jìn)行重傳。
2. 跳過(guò)該切片:如果上傳失敗的切片數(shù)量較多,或者由于某些原因無(wú)法進(jìn)行重傳,則可以考慮跳過(guò)該切片。具體實(shí)現(xiàn)方法可以根據(jù)上傳任務(wù)的特點(diǎn)來(lái)確定,如將上傳任務(wù)分為多個(gè)階段,每個(gè)階段上傳一定數(shù)量的切片,如果某個(gè)階段上傳失敗,則跳過(guò)該階段并記錄下失敗的切片信息,待后續(xù)再進(jìn)行重傳。
3. 放棄上傳任務(wù):如果上傳失敗的切片數(shù)量較多,或者重傳操作多次仍然無(wú)法恢復(fù)上傳任務(wù),則可以考慮放棄上傳任務(wù)。在此情況下,可以將上傳任務(wù)標(biāo)記為“失敗”狀態(tài),并記錄下失敗的切片信息。如果需要重新上傳該文件,則可以在下一次上傳任務(wù)中,首先檢查之前已上傳的切片信息,如果存在已上傳的切片,則可以直接跳過(guò)這些切片并進(jìn)行后續(xù)的上傳操作。
總結(jié)
到此這篇關(guān)于前端文件上傳實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)前端文件上傳內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript實(shí)現(xiàn)JSON合并操作示例【遞歸深度合并】
這篇文章主要介紹了JavaScript實(shí)現(xiàn)JSON合并操作,結(jié)合實(shí)例形式分析了javascript基于遞歸深度實(shí)現(xiàn)json合并操作相關(guān)實(shí)現(xiàn)技巧與注意事項(xiàng),需要的朋友可以參考下2018-09-09
微信小程序教程系列之頁(yè)面跳轉(zhuǎn)和參數(shù)傳遞(6)
這篇文章主要為大家詳細(xì)介紹了微信小程序教程系列之頁(yè)面跳轉(zhuǎn)和參數(shù)傳遞,微信小程序提供了3種頁(yè)面跳轉(zhuǎn)方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
javascript簡(jiǎn)單事件處理和with用法介紹
本文為大家介紹下javascript事件處理及with用法,主要是鼠標(biāo)點(diǎn)擊與移動(dòng),感興趣的朋友可以參考下2013-09-09
contains和compareDocumentPosition 方法來(lái)確定是否HTML節(jié)點(diǎn)間的關(guān)系
一個(gè)很棒的 blog 文章,是 PPK 兩年前寫(xiě)的,文章中解釋了 contains() 和 compareDocumentPosition() 方法運(yùn)行在他們各自的瀏覽器上。2011-09-09
JavaScript實(shí)現(xiàn)星座查詢(xún)功能 附詳細(xì)代碼
最近小編在做一個(gè)項(xiàng)目,其中涉及到一個(gè)模塊關(guān)于星座查詢(xún)功能,即在文本框中輸入一個(gè)生日值,點(diǎn)擊按鈕可以得到對(duì)應(yīng)的星座,怎么實(shí)現(xiàn)這個(gè)需求呢?下面小編通過(guò)示例代碼給大家介紹下,需要的朋友參考下吧2021-11-11
JavaScript 實(shí)現(xiàn) Tab 點(diǎn)擊切換實(shí)例代碼
Tab 選項(xiàng)卡切換效果在現(xiàn)如今的網(wǎng)頁(yè)中,運(yùn)用的也是比較多的,包括點(diǎn)擊切換、滑動(dòng)切換、延遲切換、自動(dòng)切換等多種效果,在這篇博文里,我們是通過(guò)原生 JavaScript 來(lái)實(shí)現(xiàn) Tab 點(diǎn)擊切換的效果。2017-03-03
JavaScript Generator函數(shù)使用分析
生成器Generator是JavaScript ES6引入的特性,它讓我們可以分段執(zhí)行一個(gè)函數(shù)。但是在談?wù)撋善鳎℅enerator)之前,我們要先了解迭代器Iterator2022-10-10
12個(gè)非常有創(chuàng)意的JavaScript小游戲
JavaScript 在Web開(kāi)發(fā)過(guò)程中已經(jīng)是必不可少的重要分子,他推動(dòng)著Web的交互性往越來(lái)越高的層次發(fā)展,現(xiàn)在的很多Web游戲也基于這類(lèi)語(yǔ)言開(kāi)發(fā)。2010-03-03

