Vue 大文件切片上傳實現指南
背景
????????在Web開發(fā)中,文件上傳是一個常見的功能需求,尤其是當涉及到大文件上傳時,為了提高上傳的穩(wěn)定性和效率,文件切片上傳技術便顯得尤為重要。通過將大文件切分成多個小塊(切片)進行上傳,不僅可以有效減少單次上傳的數據量,降低網絡波動對上傳過程的影響,還能實現如斷點續(xù)傳、秒傳等高級功能。本文將以Vue
為框架,配合 Axios
進行 HTTP
請求,詳細介紹如何實現一個支持文件切片上傳的功能。
前端準備工作
????????在開始編碼之前,請確保你的項目中已經安裝了 axios
和 spark-md5
兩個庫。axios
用于發(fā)起網絡請求,spark-md5
用于計算文件的 MD5
值,從而支持秒傳和斷點續(xù)傳功能。
前端需要實現的功能
-
文件選擇和限制:
通過
<input type="file" @change="handleFileChange" accept="video/*" />
實現了文件的選擇,同時限制了用戶只能選擇視頻文件進行上傳。 -
計算文件的MD5值
在
computeFileHash
方法中,利用SparkMD5
庫計算用戶選中文件的MD5
值。這一步是為了之后能夠校驗文件的完整性和唯一性。 -
校驗文件是否需要上傳
在
checkFile
方法中,通過向服務器查詢文件的MD5
值,判斷該文件是否已經上傳過,以此實現秒傳功能,避免重復上傳相同文件。 -
文件切片
在
sliceFileAndUpload
方法中,將大文件切割成多個小片段(切片),這樣做的目的是為了支持大文件的分塊上傳,提高上傳效率,同時也便于出錯時重新上傳單個切片而不是整個文件。 -
并發(fā)上傳切片
通過
processPool
和uploadChunk
方法實現切片的并發(fā)上傳,限制了最大并發(fā)數(MAX_REQUEST)
,以免過多并發(fā)請求壓崩服務器。 -
上傳進度反饋
通過
uploadProgress
數據和模板中的進度顯示,用戶可以實時看到文件上傳的進度。 -
服務器通知合并切片
在所有切片上傳完成后,通過
notifyServerToMerge
方法向服務器發(fā)送通知,請求服務器端進行切片的合并,以重建原始文件。
后端需要支持的API接口
為了支持前端的大文件上傳和處理邏輯,后端需要提供以下API接口:
- 文件校驗API
- 功能:檢查文件的完整性和上傳狀態(tài)。這通常通過文件的唯一標識(如MD5哈希值)來實現。
- 輸入參數:文件唯一標識(如MD5哈希值)。
- 返回值:告知客戶端該文件是否已經存在,如果存在,是否完整。如果文件已經存在且不完整,則返回已上傳的切片信息。
- 切片上傳API
- 功能:接收文件的單個切片,并保存到服務器的臨時存儲位置。
- 輸入參數:文件的唯一標識,切片內容,切片的序號。
- 返回值:確認切片上傳成功或失敗的狀態(tài)。
- 切片合并API
- 功能:將所有上傳的切片合并成一個完整的文件。
- 輸入參數:文件的唯一標識,可能還包括文件名、總切片數等信息。
- 返回值:合并操作的成功或失敗狀態(tài),以及最終文件的訪問URL(可選)。
- 上傳進度查詢API
- 功能:查詢文件上傳的進度,這對于恢復上傳和提供用戶反饋非常有用。
- 輸入參數:文件的唯一標識。
- 返回值:已上傳的切片列表或上傳進度百分比。
這些API合起來支持了一個分塊上傳文件的完整流程,包括文件的校驗
、切片的上傳
、切片的合并
,以及上傳進度查詢
。這個流程可以有效地處理大文件上傳,減少網絡傳輸的負擔,提高上傳的可靠性,并允許上傳過程中的暫停和恢復
。
執(zhí)行流程
????????一開始用戶通過界面選擇一個文件進行上傳,進行文件選擇,用戶通過文件選擇框懸著一個大文件,比如視頻文件,觸發(fā)handleFileChange
方法,然后再計算這個大文件的MD5,使用computeFileHash
方法計算選中文件的MD5哈希值,計算完成后檢查文件是否需要上傳,向服務器發(fā)起請求,根據文件的MD5哈希值執(zhí)行checkFile
方法檢查文件是否已經存在,如果文件已經存在通知用戶秒傳功能
并將上傳進度設為100%,如果文件需要上傳,則使用sliceFileAndUpload
方法將文件切成很多個小塊,每個切片及其索引都被添加到requestPool
請求池中,從requestPool
中并發(fā)上傳切片processPool
方法,對每個切片調用uploadChunk
方法進行實際上傳,通過MAX_REQUEST
控制并發(fā)上傳的數量,沒上傳一個切片,uploadChunksCount
增加,并更新上傳進度。所有切片上傳完成后,通知服務器合并這些切片notifyServerToMerge
,當服務器成功合并所有切片成原始后,整個切片上傳流程完成。
實現步驟
步驟一:用戶選擇文件
????????用戶通過 <input type="file">
選擇文件后,handleFileChange
事件被觸發(fā)。在這個事件處理函數中,我們首先獲取到用戶選擇的文件,然后計算文件的 MD5
值,以此作為文件的唯一標識。這一步是實現斷點續(xù)傳
和秒傳
功能的關鍵。
<template>
<div>
<!-- 文件選擇框,僅接受視頻文件 -->
<input type="file" @change="handleFileChange" accept="video/*" />
<!-- 上傳按鈕 -->
<button @click="handleUpload">Upload</button>
<!-- 上傳進度顯示 -->
<div v-if="uploadProgress > 0">
Upload Progress: {{ uploadProgress }}%
</div>
</div>
</template>
步驟二:計算文件 MD5
????????使用 spark-md5
庫計算文件的 MD5
值。通過FileReader API
讀取文件內容,然后計算其 MD5
值。這個過程可能會花費一些時間,因此使用 Promise
來異步處理。
async computeFileHash(file) {
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
return new Promise((resolve) => {
fileReader.onload = (e) => {
spark.append(e.target.result);
const hash = spark.end();
resolve(hash);
};
fileReader.readAsArrayBuffer(file);
});
}
步驟三:檢查文件狀態(tài),檢查文件是否已經上傳還是部分上傳
????????在上傳文件之前,先向服務器發(fā)送請求,檢查這個文件是否已經部分或全部上傳過。這一步是實現斷點續(xù)傳的關鍵。服務器根據文件的 MD5
值返回已上傳的切片信息或表示文件完全上傳的狀態(tài)。
// 向服務器查詢文件是否已經部分或完全上傳
async checkFile(fileHash) {
<--- 此處應替換為你的接口調用代碼 --->
// 假設接口返回 { shouldUpload: boolean, uploadedChunks: Array<number> }
return { shouldUpload: true, uploadedChunks: [] };
},
步驟四:切片并準備上傳
????????根據服務器返回的信息,如果文件未完全上傳,我們將文件分割成多個切片。然后根據已上傳的切片信息,跳過那些已經上傳的切片,僅上傳剩余的切片。
????????切片并準備上傳在sliceFileAndUpload
方法中實現。這個方法首先計算了整個文件應該被分割成多少切片(基于設定的切片大?。?,然后根據服務器返回的已上傳切片信息(uploadedChunks)
,它會跳過這些已經上傳的切片,只將剩余的切片添加到請求池(requestPool)
中準備上傳。
// 切片并準備上傳
sliceFileAndUpload(fileHash, uploadedChunks) {
const chunkSize = 10 * 1024 * 1024; // 切片大小,這里是10MB
this.chunkCount = Math.ceil(this.selectedFile.size / chunkSize); // 計算總切片數
this.uploadProgress = 0; // 重置上傳進度
for (let i = 0; i < this.chunkCount; i++) {
if (uploadedChunks.includes(i)) continue; // 跳過已上傳的切片
const chunk = this.selectedFile.slice(i * chunkSize, (i + 1) * chunkSize); // 獲取切片
this.requestPool.push({ chunk, index: i }); // 加入請求池
}
this.processPool(fileHash); // 開始處理請求池
},
上面這段代碼中,uploadedChunks
參數是一個數組,包含了所有已上傳切片的索引。通過檢查當前切片的索引是否包含在這個數組中,代碼決定是否跳過當前切片的上傳。如果索引不在uploadedChunks
中,這意味著該切片還沒有被上傳,因此需要將其添加到requestPool
中等待上傳。這樣,只有那些未上傳的切片會被實際上傳,從而實現了斷點續(xù)傳的功能。processPool
進行并發(fā)切片上傳
步驟五:并發(fā)上傳切片
????????為了提高上傳效率,我們使用并發(fā)上傳的方式。設置最大并發(fā)數,控制同時上傳的切片數量。通過逐一上傳切片,并監(jiān)聽每個上傳請求的完成,從而動態(tài)調整并發(fā)請求。
????????并發(fā)上傳切片的邏輯主要在processPool
方法中實現。這個方法負責管理并發(fā)請求,確保同時只有一定數量的上傳請求在處理中。這通過一個簡單的請求池(requestPool)
和控制最大并發(fā)數量(MAX_REQUEST)
來實現。
// 處理請求池中的切片上傳
processPool(fileHash) {
while (this.requestPool.length > 0 && this.MAX_REQUEST > 0) {
const { chunk, index } = this.requestPool.shift(); // 取出一個待上傳的切片
this.uploadChunk(chunk, fileHash, index) // 上傳切片
.then(() => {
this.uploadedChunksCount++; // 更新已上傳切片數量
this.uploadProgress = ((this.uploadedChunksCount / this.chunkCount) * 100).toFixed(2); // 更新上傳進度
if (this.requestPool.length > 0) {
this.processPool(fileHash); // 繼續(xù)處理請求池
} else if (this.uploadedChunksCount === this.chunkCount) {
// 所有切片都已上傳,通知服務器合并
this.notifyServerToMerge(fileHash);
}
})
.finally(() => {
this.MAX_REQUEST++; // 釋放一個請求槽
});
this.MAX_REQUEST--; // 占用一個請求槽
}
},
????????在這個方法中,while
循環(huán)檢查請求池中是否還有待處理的切片,并且當前活躍的請求數量是否小于允許的最大并發(fā)數量MAX_REQUEST
。如果這兩個條件都滿足,它會從請求池中取出一個切片,并調用uploadChunk
方法來上傳它,同時減少MAX_REQUEST
的值來反映一個新的請求已經開始。
????????當一個切片上傳完成后,then
回調函數會增加已上傳切片的計數并更新上傳進度。如果請求池中還有待上傳的切片,它會遞歸調用processPool
來處理下一個切片。一旦所有切片都上傳完成,它會調用notifyServerToMerge
來通知服務器所有切片已經上傳完畢,可以合并成一個完整的文件。通過這種方式,代碼能夠在保持最大并發(fā)限制的同時,高效地處理切片的上傳。
步驟六:服務器合并切片
????????所有切片上傳完成后,客戶端向服務器發(fā)送一個合并切片的請求。服務器接收到請求后,將所有切片合并成原始文件,并返回合并結果。
// 通知服務器合并切片
notifyServerToMerge(fileHash) {
// 通知服務器合并切片,應替換為真實的合并API調用
console.log(`通知服務器將文件與哈希合并: ${fileHash}`);
},
????????一個API調用,向服務器發(fā)送一個請求來觸發(fā)合并已上傳切片的操作。這個請求通常會攜帶一些必要的信息,比如文件的唯一標識(在這個例子中是fileHash
),以及可能還有其他諸如文件名
、文件大小
、切片數量
等信息,這些信息取決于服務器端合并切片的具體要求。
????????服務器收到合并請求后,會根據提供的信息找到所有相關的切片,按正確的順序將它們合并成一個完整的文件,并將該文件存儲在服務器上的適當位置。完成這個過程后,服務器可能還會向客戶端發(fā)送一個響應,通知合并操作的結果(成功或失?。?,以及可能的后續(xù)步驟或需要的信息。
????????通過上述步驟,實現了一個高效穩(wěn)定的大文件上傳功能,極大提升了用戶體驗。文章來源:http://www.zghlxwxcb.cn/news/detail-860911.html
全部代碼
<template>
<div>
<!-- 文件選擇框,僅接受視頻文件 -->
<input type="file" @change="handleFileChange" accept="video/*" />
<!-- 上傳按鈕 -->
<button @click="handleUpload">Upload</button>
<!-- 上傳進度顯示 -->
<div v-if="uploadProgress > 0">
Upload Progress: {{ uploadProgress }}%
</div>
</div>
</template>
<script>
import axios from "axios";
import SparkMD5 from "spark-md5"; // 引入SparkMD5用于計算文件的MD5值
export default {
data() {
return {
selectedFile: null, // 用戶選擇的文件
uploadProgress: 0, // 上傳進度
requestPool: [], // 請求池,存儲待上傳的切片信息
MAX_REQUEST: 6, // 最大并發(fā)請求數量
chunkCount: 0, // 文件切片總數
uploadedChunksCount: 0, // 已上傳的切片數量
};
},
methods: {
// 處理文件選擇事件
async handleFileChange(event) {
this.selectedFile = event.target.files[0];
if (!this.selectedFile) return; // 未選擇文件則返回
// 可以在這里添加文件格式校驗
const fileHash = await this.computeFileHash(this.selectedFile); // 計算文件hash
const { shouldUpload, uploadedChunks } = await this.checkFile(fileHash); // 檢查文件是否需要上傳
if (!shouldUpload) {
alert("文件已存在,秒傳成功!");
this.uploadProgress = 100; // 直接設置進度為100%
return;
}
this.sliceFileAndUpload(fileHash, uploadedChunks); // 切片并上傳
},
// 計算文件的MD5
computeFileHash(file) {
return new Promise((resolve) => {
const spark = new SparkMD5.ArrayBuffer();
const fileReader = new FileReader();
fileReader.onload = (e) => {
spark.append(e.target.result);
const hash = spark.end();
resolve(hash); // 返回計算得到的hash值
};
fileReader.readAsArrayBuffer(file);
});
},
// 檢查文件是否已經上傳過
async checkFile(fileHash) {
// 應替換為真實的API調用來檢查文件狀態(tài)
return { shouldUpload: true, uploadedChunks: [] }; // 模擬返回值
},
// 切片并準備上傳
sliceFileAndUpload(fileHash, uploadedChunks) {
const chunkSize = 10 * 1024 * 1024; // 切片大小,這里是10MB
this.chunkCount = Math.ceil(this.selectedFile.size / chunkSize); // 計算總切片數
this.uploadProgress = 0; // 重置上傳進度
for (let i = 0; i < this.chunkCount; i++) {
if (uploadedChunks.includes(i)) continue; // 跳過已上傳的切片
const chunk = this.selectedFile.slice(i * chunkSize, (i + 1) * chunkSize); // 獲取切片
this.requestPool.push({ chunk, index: i }); // 加入請求池
}
this.processPool(fileHash); // 開始處理請求池
},
// 處理請求池中的切片上傳
processPool(fileHash) {
while (this.requestPool.length > 0 && this.MAX_REQUEST > 0) {
const { chunk, index } = this.requestPool.shift(); // 取出一個待上傳的切片
this.uploadChunk(chunk, fileHash, index) // 上傳切片
.then(() => {
this.uploadedChunksCount++; // 更新已上傳切片數量
this.uploadProgress = ((this.uploadedChunksCount / this.chunkCount) * 100).toFixed(2); // 更新上傳進度
if (this.requestPool.length > 0) {
this.processPool(fileHash); // 繼續(xù)處理請求池
} else if (this.uploadedChunksCount === this.chunkCount) {
// 所有切片都已上傳,通知服務器合并
this.notifyServerToMerge(fileHash);
}
})
.finally(() => {
this.MAX_REQUEST++; // 釋放一個請求槽
});
this.MAX_REQUEST--; // 占用一個請求槽
}
},
// 上傳單個切片
async uploadChunk(chunk, fileHash, index) {
const formData = new FormData();
formData.append("chunk", chunk);
formData.append("hash", fileHash);
formData.append("index", index);
// 替換為真實的上傳URL,并根據需要實現onUploadProgress
await axios.post("上傳URL", formData);
},
// 通知服務器合并切片
notifyServerToMerge(fileHash) {
// 通知服務器合并切片,應替換為真實的合并API調用
console.log(`通知服務器將文件與哈希合并: ${fileHash}`);
},
},
};
</script>
效果:文章來源地址http://www.zghlxwxcb.cn/news/detail-860911.html
到了這里,關于Vue 大文件切片上傳實現指南包會,含【并發(fā)上傳切片,斷點續(xù)傳,服務器合并切片,計算文件MD5,上傳進度顯示,秒傳】等功能的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!