目錄
大文件上傳優點:
大文件上傳缺點:
?大文件上傳原理:
?為什么要用md5
實現流程:
部分代碼1:
?部分代碼2:?
大文件上傳優點:
- 文件太大分片上傳能加快上傳速度,提高用戶體驗
- 能斷點續傳?如果上次上傳失敗或者中途離開的話下一次上傳過的就不用重頭開始了
- 已經上傳過的文件根據HASH查詢直接秒傳
大文件上傳缺點:
1.后臺可能設置了請求時長限制,太久會上傳失敗(解決:后端不設置上傳時長)
2.NGINX可能設置了文件上傳的最大限制導致失敗(解決:比如分片25M,nginx設置文件上傳最大限度50M)
?大文件上傳原理:
- 用戶選擇要上傳的大文件,計算整個文件的MD5。
- 前端根據分片大小將文件切分成多個小塊,計算每個分片文件的MD5。
- 逐個上傳每個小塊到服務器端。
- 服務器端接收并保存每個小塊。
- 在服務器端,根據上傳的小塊將它們合并成完整的文件。
?為什么要用md5
????????因為每個文件都會有自己專屬獨立的md5值,就像是每個人的身份證,比如我們在某個平臺發布視頻,將視頻文件二次上傳的時候就會遇到不容易過審的原因,同一個MD5就有很大的機率顯示搬運被退回。剛好后端同學也可以通過MD5這種特性來判斷上傳的文件是否完整。
如何快速計算文件的 md5 值呢? 我們使用 js-spark-md5 這個庫
實現流程:
?????????在upload組件上傳文件的鉤子函數beforeUpload() 中:
- ?獲取切片文件:設置切片文件大小、每次上傳的開始字節,每次上傳的結尾字節。文件切片的核心是使用Blob 對象的 slice 方法:
start 和 end 代表 Blob 里的下標,表示被拷貝進新的 Blob 的字節的起始位置和結束位置。contentType 會給新的 Blob 賦予一個新的文檔類型,很少使用。var blob = file.slice([start [, end [, contentType]]]};
- ?計算分片文件的MD5
- 上傳分片文件,斷點續傳(如何實現斷點續傳,關鍵點是后端需要記錄文件文件切片的信息。用戶在上傳一個文件之前,先詢問服務器,當前文件是否存在已經上傳完畢的切片,如果存在的話,需要返回切片信息。前端根據返回的信息,調整當前的進度,上傳未完成的切片)
- 檢驗分片數量及上傳的結果,全部上傳,文件合并:
- 前端發送切片完成后,發送一個合并請求,后端收到請求后,將之前上傳的切片文件合并。(上面展示的代碼采用 這個)
- 后臺記錄切片文件上傳數據,當后臺檢測到切片上傳完成后,自動完成合并。
- 創建一個和源文件大小相同的文件,根據切片文件的起止位置直接將切片寫入對應位置。
部分代碼1:
export default class Project extends React.PureComponent {construction (props){this.state({file: {},fileplanNumber: 0, // 上傳文件進度的百分比fileUploadState: '', // 上傳文件的狀態,fail失敗, success成功interFaceStart: '', // 文件上傳時的分片文件下標, 作用于斷點續傳})}beforeUpload = (file) => {this.setState({file, // 把file存起來})let reader = new FileReader();let md5 = '';reader.onload = (event) => {const spark = new SparkMD5.ArrayBuffer;spark.append(e.target.value);md5 = spark.end();axios.post('', {md5,id, // 后端需要的}).then((res) => {const { fileId, start, finish, message } = res.data.data;if(res.data.code !== 200){message.error(message);}if(finish && finish === 'true'){this.setState({file: {}, // 等于true,表示上傳完成了fileplanNumber: 0, // 上傳文件進度的百分比})message.error(message);return;}// 請求成功后this.setState({fileId,interFaceStart: start, // 文件上傳時,分片文件下標,為了斷點續傳}, () => {this.getSliceFile() // 獲取文件切片})})}reader.readArrayBufffer(file);return false; }// 獲取文件切片 getSliceFile = async () => {const { search, cataList } = this.props; const { file, fileId, interFaceStart } = this.state;const archiveId = cataList && cataList[cataList.length - 1].archiveId;const fileSize = file.size; // 文件的大小const piece = 1024 *1024 * 25; // 每片25Mlet start = 0; // 每次上傳開始字節let index = 1;let end = start + piece; // 每次上傳的結尾字節const chunksList = [];while(start < fileSize){const current = Math.min(end, fileSize) // 兩者中取最小的const blob = file.slice.call(file, start, current);// 計算分片文件的MD5let sliceFileMD5 = '';sliceFileMD5 = await this.getSliceFileMD5(blob );// 拼接分片信息數據chunksList.push({file: blod,index,sliceFileMD5,});start = current;end = start + piece;index += 1;}// 檢驗分片數量,開始上傳if (chunksList && chunksList.length) {const chunks = chunksList.slice(interFaceStart); // 分片上傳的結果let resultList = 0;// 循環分片數據for(const item of chunks){// 調用接口上傳分片內容const resultFile = await this.uploadSliceFile(item, chunks);//記錄上傳結果resultList += 1;// 一次失敗,結束后續上傳if(!resultFile){break;}}// 檢驗分片數量,分片上傳結果數量,全部上傳完成,調用合并接口if(resultList === chunks.length){const { fileList } = this.state;axios.post('', { fileId,fileName: file.name,businessId: archiveId,businessType: 'archive',}).then((res) => {if(res.data.code !== 200){message.error(res.data.message)}message.success('上傳成功!')// 調接口,刷新頁面axios.post('', {pageNum: 1,pageSize: 10,archiveId,orderBy: '',sort: '', }).then(res => {if(res.code !== 200){message.error(res.message)};})this.setState({file: {},fileId: '',fileUploadState: null,fileplanNumber: 0,fileList,interFaceStart: 0,});})} else {this.setState({fileUploadState: fail,});}} }// 計算分片文件的MD5getSliceFileMD5 = (blob) => {return new Promise((resolve, reject) => {const sliceFileReader = new FileReader();let sliceFileMD5 = '';sliceFileReader.onerror = reject;sliceFileReader.onload = (event) => {const spark = new SparkMD5.ArrayBuffer();spark.append(event.target.result)sliceFileMD5 = spark.end();// 返回分片MD5resolve(sliceFileMD5);}sliceFileReader.readAsArrayBuffer(blob);})}// 上傳分片文件uploadSliceFile = (fileMap, chunks) => {return new Promise((resolve, reject) => {const { sliceFileMD5, file, index } = fileMap;const fileBlob = new File([file], 'AAA.exe', { type: 'application/x-msdownload' })const { fileId } = this.state;const formData = new FormData();formData.append('fileId', fileId);formData.append('MD5', sliceFileMD5);formData.append('partSequence', index);formData.append('fileBlob', fileBlob);axios.post('', {formData,headers: {'Content-Type': 'multipart/form-data',}}).then(res => {if(res.data.code !== 200){message.error(res.data.message)return false;}const fileplanNumber = (index / chunks.length) * 100;this.setState({fileplanNumber,});resolve(true);})})}}
?部分代碼2: