国产精品爱久久久久久久小说,女人扒开腿让男人桶到爽 ,亚洲欧美国产双大乳头,国产成人精品综合久久久久,国产精品制服丝袜无码,免费无码精品黄av电影,黑色丝袜无码中中文字幕,乱熟女高潮一区二区在线

            VUE-多文件斷點續傳、秒傳、分片上傳

            2020-7-20    seo達人

            凡是要知其然知其所以然

            文件上傳相信很多朋友都有遇到過,那或許你也遇到過當上傳大文件時,上傳時間較長,且經常失敗的困擾,并且失敗后,又得重新上傳很是煩人。那我們先了解下失敗的原因吧!


            據我了解大概有以下原因:


            服務器配置:例如在PHP中默認的文件上傳大小為8M【post_max_size = 8m】,若你在一個請求體中放入8M以上的內容時,便會出現異常

            請求超時:當你設置了接口的超時時間為10s,那么上傳大文件時,一個接口響應時間超過10s,那么便會被Faild掉。

            網絡波動:這個就屬于不可控因素,也是較常見的問題。

            基于以上原因,聰明的人們就想到了,將文件拆分多個小文件,依次上傳,不就解決以上1,2問題嘛,這便是分片上傳。 網絡波動這個實在不可控,也許一陣大風刮來,就斷網了呢。那這樣好了,既然斷網無法控制,那我可以控制只上傳已經上傳的文件內容,不就好了,這樣大大加快了重新上傳的速度。所以便有了“斷點續傳”一說。此時,人群中有人插了一嘴,有些文件我已經上傳一遍了,為啥還要在上傳,能不能不浪費我流量和時間。喔...這個嘛,簡單,每次上傳時判斷下是否存在這個文件,若存在就不重新上傳便可,于是又有了“秒傳”一說。從此這"三兄弟" 便自行CP,統治了整個文件界。”

            注意文中的代碼并非實際代碼,請移步至github查看代碼

            https://github.com/pseudo-god...


            分片上傳

            HTML

            原生INPUT樣式較丑,這里通過樣式疊加的方式,放一個Button.

             <div class="btns">

               <el-button-group>

                 <el-button :disabled="changeDisabled">

                   <i class="el-icon-upload2 el-icon--left" size="mini"></i>選擇文件

                   <input

                     v-if="!changeDisabled"

                     type="file"

                     :multiple="multiple"

                     class="select-file-input"

                     :accept="accept"

                     @change="handleFileChange"

                   />

                 </el-button>

                 <el-button :disabled="uploadDisabled" @click="handleUpload()"><i class="el-icon-upload el-icon--left" size="mini"></i>上傳</el-button>

                 <el-button :disabled="pauseDisabled" @click="handlePause"><i class="el-icon-video-pause el-icon--left" size="mini"></i>暫停</el-button>

                 <el-button :disabled="resumeDisabled" @click="handleResume"><i class="el-icon-video-play el-icon--left" size="mini"></i>恢復</el-button>

                 <el-button :disabled="clearDisabled" @click="clearFiles"><i class="el-icon-video-play el-icon--left" size="mini"></i>清空</el-button>

               </el-button-group>

               <slot

               

            //data 數據


            var chunkSize = 10 * 1024 * 1024; // 切片大小

            var fileIndex = 0; // 當前正在被遍歷的文件下標


            data: () => ({

               container: {

                 files: null

               },

               tempFilesArr: [], // 存儲files信息

               cancels: [], // 存儲要取消的請求

               tempThreads: 3,

               // 默認狀態

               status: Status.wait

             }),

               

            一個稍微好看的UI就出來了。




            選擇文件

            選擇文件過程中,需要對外暴露出幾個鉤子,熟悉elementUi的同學應該很眼熟,這幾個鉤子基本與其一致。onExceed:文件超出個數限制時的鉤子、beforeUpload:文件上傳之前

            fileIndex 這個很重要,因為是多文件上傳,所以定位當前正在被上傳的文件就很重要,基本都靠它


            handleFileChange(e) {

             const files = e.target.files;

             if (!files) return;

             Object.assign(this.$data, this.$options.data()); // 重置data所有數據


             fileIndex = 0; // 重置文件下標

             this.container.files = files;

             // 判斷文件選擇的個數

             if (this.limit && this.container.files.length > this.limit) {

               this.onExceed && this.onExceed(files);

               return;

             }


             // 因filelist不可編輯,故拷貝filelist 對象

             var index = 0; // 所選文件的下標,主要用于剔除文件后,原文件list與臨時文件list不對應的情況

             for (const key in this.container.files) {

               if (this.container.files.hasOwnProperty(key)) {

                 const file = this.container.files[key];


                 if (this.beforeUpload) {

                   const before = this.beforeUpload(file);

                   if (before) {

                     this.pushTempFile(file, index);

                   }

                 }


                 if (!this.beforeUpload) {

                   this.pushTempFile(file, index);

                 }


                 index++;

               }

             }

            },

            // 存入 tempFilesArr,為了上面的鉤子,所以將代碼做了拆分

            pushTempFile(file, index) {

             // 額外的初始值

             const obj = {

               status: fileStatus.wait,

               chunkList: [],

               uploadProgress: 0,

               hashProgress: 0,

               index

             };

             for (const k in file) {

               obj[k] = file[k];

             }

             console.log('pushTempFile -> obj', obj);

             this.tempFilesArr.push(obj);

            }

            分片上傳

            創建切片,循環分解文件即可


             createFileChunk(file, size = chunkSize) {

               const fileChunkList = [];

               var count = 0;

               while (count < file.size) {

                 fileChunkList.push({

                   file: file.slice(count, count + size)

                 });

                 count += size;

               }

               return fileChunkList;

             }

            循環創建切片,既然咱們做的是多文件,所以這里就有循環去處理,依次創建文件切片,及切片的上傳。

            async handleUpload(resume) {

             if (!this.container.files) return;

             this.status = Status.uploading;

             const filesArr = this.container.files;

             var tempFilesArr = this.tempFilesArr;


             for (let i = 0; i < tempFilesArr.length; i++) {

               fileIndex = i;

               //創建切片

               const fileChunkList = this.createFileChunk(

                 filesArr[tempFilesArr[i].index]

               );

                 

               tempFilesArr[i].fileHash ='xxxx'; // 先不用看這個,后面會講,占個位置

               tempFilesArr[i].chunkList = fileChunkList.map(({ file }, index) => ({

                 fileHash: tempFilesArr[i].hash,

                 fileName: tempFilesArr[i].name,

                 index,

                 hash: tempFilesArr[i].hash + '-' + index,

                 chunk: file,

                 size: file.size,

                 uploaded: false,

                 progress: 0, // 每個塊的上傳進度

                 status: 'wait' // 上傳狀態,用作進度狀態顯示

               }));

               

               //上傳切片

               await this.uploadChunks(this.tempFilesArr[i]);

             }

            }

            上傳切片,這個里需要考慮的問題較多,也算是核心吧,uploadChunks方法只負責構造傳遞給后端的數據,核心上傳功能放到sendRequest方法中

            async uploadChunks(data) {

             var chunkData = data.chunkList;

             const requestDataList = chunkData

               .map(({ fileHash, chunk, fileName, index }) => {

                 const formData = new FormData();

                 formData.append('md5', fileHash);

                 formData.append('file', chunk);

                 formData.append('fileName', index); // 文件名使用切片的下標

                 return { formData, index, fileName };

               });


             try {

               await this.sendRequest(requestDataList, chunkData);

             } catch (error) {

               // 上傳有被reject的

               this.$message.error('親 上傳失敗了,考慮重試下呦' + error);

               return;

             }


             // 合并切片

             const isUpload = chunkData.some(item => item.uploaded === false);

             console.log('created -> isUpload', isUpload);

             if (isUpload) {

               alert('存在失敗的切片');

             } else {

               // 執行合并

               await this.mergeRequest(data);

             }

            }

            sendReques。上傳這是最重要的地方,也是容易失敗的地方,假設有10個分片,那我們若是直接發10個請求的話,很容易達到瀏覽器的瓶頸,所以需要對請求進行并發處理。


            并發處理:這里我使用for循環控制并發的初始并發數,然后在 handler 函數里調用自己,這樣就控制了并發。在handler中,通過數組API.shift模擬隊列的效果,來上傳切片。

            重試: retryArr 數組存儲每個切片文件請求的重試次數,做累加。比如[1,0,2],就是第0個文件切片報錯1次,第2個報錯2次。為保證能與文件做對應,const index = formInfo.index; 我們直接從數據中拿之前定義好的index。 若失敗后,將失敗的請求重新加入隊列即可。


            關于并發及重試我寫了一個小Demo,若不理解可以自己在研究下,文件地址:https://github.com/pseudo-god... , 重試代碼好像被我弄丟了,大家要是有需求,我再補吧!

               // 并發處理

            sendRequest(forms, chunkData) {

             var finished = 0;

             const total = forms.length;

             const that = this;

             const retryArr = []; // 數組存儲每個文件hash請求的重試次數,做累加 比如[1,0,2],就是第0個文件切片報錯1次,第2個報錯2次


             return new Promise((resolve, reject) => {

               const handler = () => {

                 if (forms.length) {

                   // 出棧

                   const formInfo = forms.shift();


                   const formData = formInfo.formData;

                   const index = formInfo.index;

                   

                   instance.post('fileChunk', formData, {

                     onUploadProgress: that.createProgresshandler(chunkData[index]),

                     cancelToken: new CancelToken(c => this.cancels.push(c)),

                     timeout: 0

                   }).then(res => {

                     console.log('handler -> res', res);

                     // 更改狀態

                     chunkData[index].uploaded = true;

                     chunkData[index].status = 'success';

                     

                     finished++;

                     handler();

                   })

                     .catch(e => {

                       // 若暫停,則禁止重試

                       if (this.status === Status.pause) return;

                       if (typeof retryArr[index] !== 'number') {

                         retryArr[index] = 0;

                       }


                       // 更新狀態

                       chunkData[index].status = 'warning';


                       // 累加錯誤次數

                       retryArr[index]++;


                       // 重試3次

                       if (retryArr[index] >= this.chunkRetry) {

                         return reject('重試失敗', retryArr);

                       }


                       this.tempThreads++; // 釋放當前占用的通道


                       // 將失敗的重新加入隊列

                       forms.push(formInfo);

                       handler();

                     });

                 }


                 if (finished >= total) {

                   resolve('done');

                 }

               };


               // 控制并發

               for (let i = 0; i < this.tempThreads; i++) {

                 handler();

               }

             });

            }

            切片的上傳進度,通過axios的onUploadProgress事件,結合createProgresshandler方法進行維護

            // 切片上傳進度

            createProgresshandler(item) {

             return p => {

               item.progress = parseInt(String((p.loaded / p.total) * 100));

               this.fileProgress();

             };

            }

            Hash計算

            其實就是算一個文件的MD5值,MD5在整個項目中用到的地方也就幾點。

            秒傳,需要通過MD5值判斷文件是否已存在。

            續傳:需要用到MD5作為key值,當唯一值使用。

            本項目主要使用worker處理,性能及速度都會有很大提升.

            由于是多文件,所以HASH的計算進度也要體現在每個文件上,所以這里使用全局變量fileIndex來定位當前正在被上傳的文件

            執行計算hash


            正在上傳文件


            // 生成文件 hash(web-worker)

            calculateHash(fileChunkList) {

             return new Promise(resolve => {

               this.container.worker = new Worker('./hash.js');

               this.container.worker.postMessage({ fileChunkList });

               this.container.worker.onmessage = e => {

                 const { percentage, hash } = e.data;

                 if (this.tempFilesArr[fileIndex]) {

                   this.tempFilesArr[fileIndex].hashProgress = Number(

                     percentage.toFixed(0)

                   );

                 }


                 if (hash) {

                   resolve(hash);

                 }

               };

             });

            }

            因使用worker,所以我們不能直接使用NPM包方式使用MD5。需要單獨去下載spark-md5.js文件,并引入


            //hash.js


            self.importScripts("/spark-md5.min.js"); // 導入腳本

            // 生成文件 hash

            self.onmessage = e => {

             const { fileChunkList } = e.data;

             const spark = new self.SparkMD5.ArrayBuffer();

             let percentage = 0;

             let count = 0;

             const loadNext = index => {

               const reader = new FileReader();

               reader.readAsArrayBuffer(fileChunkList[index].file);

               reader.onload = e => {

                 count++;

                 spark.append(e.target.result);

                 if (count === fileChunkList.length) {

                   self.postMessage({

                     percentage: 100,

                     hash: spark.end()

                   });

                   self.close();

                 } else {

                   percentage += 100 / fileChunkList.length;

                   self.postMessage({

                     percentage

                   });

                   loadNext(count);

                 }

               };

             };

             loadNext(0);

            };

            文件合并

            當我們的切片全部上傳完畢后,就需要進行文件的合并,這里我們只需要請求接口即可

            mergeRequest(data) {

              const obj = {

                md5: data.fileHash,

                fileName: data.name,

                fileChunkNum: data.chunkList.length

              };


              instance.post('fileChunk/merge', obj,

                {

                  timeout: 0

                })

                .then((res) => {

                  this.$message.success('上傳成功');

                });

            }

            Done: 至此一個分片上傳的功能便已完成

            斷點續傳

            顧名思義,就是從那斷的就從那開始,明確思路就很簡單了。一般有2種方式,一種為服務器端返回,告知我從那開始,還有一種是瀏覽器端自行處理。2種方案各有優缺點。本項目使用第二種。

            思路:已文件HASH為key值,每個切片上傳成功后,記錄下來便可。若需要續傳時,直接跳過記錄中已存在的便可。本項目將使用Localstorage進行存儲,這里我已提前封裝好addChunkStorage、getChunkStorage方法。


            存儲在Stroage的數據




            緩存處理

            在切片上傳的axios成功回調中,存儲已上傳成功的切片


            instance.post('fileChunk', formData, )

             .then(res => {

               // 存儲已上傳的切片下標

            + this.addChunkStorage(chunkData[index].fileHash, index);

               handler();

             })

            在切片上傳前,先看下localstorage中是否存在已上傳的切片,并修改uploaded


               async handleUpload(resume) {

            +      const getChunkStorage = this.getChunkStorage(tempFilesArr[i].hash);

                 tempFilesArr[i].chunkList = fileChunkList.map(({ file }, index) => ({

            +        uploaded: getChunkStorage && getChunkStorage.includes(index), // 標識:是否已完成上傳

            +        progress: getChunkStorage && getChunkStorage.includes(index) ? 100 : 0,

            +        status: getChunkStorage && getChunkStorage.includes(index)? 'success'

            +              : 'wait' // 上傳狀態,用作進度狀態顯示

                 }));


               }

            構造切片數據時,過濾掉uploaded為true的


            async uploadChunks(data) {

             var chunkData = data.chunkList;

             const requestDataList = chunkData

            +    .filter(({ uploaded }) => !uploaded)

               .map(({ fileHash, chunk, fileName, index }) => {

                 const formData = new FormData();

                 formData.append('md5', fileHash);

                 formData.append('file', chunk);

                 formData.append('fileName', index); // 文件名使用切片的下標

                 return { formData, index, fileName };

               })

            }

            垃圾文件清理

            隨著上傳文件的增多,相應的垃圾文件也會增多,比如有些時候上傳一半就不再繼續,或上傳失敗,碎片文件就會增多。解決方案我目前想了2種

            前端在localstorage設置緩存時間,超過時間就發送請求通知后端清理碎片文件,同時前端也要清理緩存。

            前后端都約定好,每個緩存從生成開始,只能存儲12小時,12小時后自動清理

            以上2中方案似乎都有點問題,極有可能造成前后端因時間差,引發切片上傳異常的問題,后面想到合適的解決方案再來更新吧。

            Done: 續傳到這里也就完成了。


            秒傳

            這算是最簡單的,只是聽起來很厲害的樣子。原理:計算整個文件的HASH,在執行上傳操作前,向服務端發送請求,傳遞MD5值,后端進行文件檢索。若服務器中已存在該文件,便不進行后續的任何操作,上傳也便直接結束。大家一看就明白

            async handleUpload(resume) {

               if (!this.container.files) return;

               const filesArr = this.container.files;

               var tempFilesArr = this.tempFilesArr;


               for (let i = 0; i < tempFilesArr.length; i++) {

                 const fileChunkList = this.createFileChunk(

                   filesArr[tempFilesArr[i].index]

                 );


                 // hash校驗,是否為秒傳

            +      tempFilesArr[i].hash = await this.calculateHash(fileChunkList);

            +      const verifyRes = await this.verifyUpload(

            +        tempFilesArr[i].name,

            +        tempFilesArr[i].hash

            +      );

            +      if (verifyRes.data.presence) {

            +       tempFilesArr[i].status = fileStatus.secondPass;

            +       tempFilesArr[i].uploadProgress = 100;

            +      } else {

                   console.log('開始上傳切片文件----》', tempFilesArr[i].name);

                   await this.uploadChunks(this.tempFilesArr[i]);

                 }

               }

             }

             // 文件上傳之前的校驗: 校驗文件是否已存在

             verifyUpload(fileName, fileHash) {

               return new Promise(resolve => {

                 const obj = {

                   md5: fileHash,

                   fileName,

                   ...this.uploadArguments //傳遞其他參數

                 };

                 instance

                   .post('fileChunk/presence', obj)

                   .then(res => {

                     resolve(res.data);

                   })

                   .catch(err => {

                     console.log('verifyUpload -> err', err);

                   });

               });

             }

            Done: 秒傳到這里也就完成了。

            后端處理

            文章好像有點長了,具體代碼邏輯就先不貼了,除非有人留言要求,嘻嘻,有時間再更新

            Node版

            請前往 https://github.com/pseudo-god... 查看

            JAVA版

            下周應該會更新處理

            PHP版

            1年多沒寫PHP了,抽空我會慢慢補上來

            待完善

            切片的大小:這個后面會做出動態計算的。需要根據當前所上傳文件的大小,自動計算合適的切片大小。避免出現切片過多的情況。

            文件追加:目前上傳文件過程中,不能繼續選擇文件加入隊列。(這個沒想好應該怎么處理。)

            更新記錄

            組件已經運行一段時間了,期間也測試出幾個問題,本來以為沒BUG的,看起來BUG都挺嚴重

            BUG-1:當同時上傳多個內容相同但是文件名稱不同的文件時,出現上傳失敗的問題。


            預期結果:第一個上傳成功后,后面相同的問文件應該直接秒傳


            實際結果:第一個上傳成功后,其余相同的文件都失敗,錯誤信息,塊數不對。


            原因:當第一個文件塊上傳完畢后,便立即進行了下一個文件的循環,導致無法及時獲取文件是否已秒傳的狀態,從而導致失敗。


            解決方案:在當前文件分片上傳完畢并且請求合并接口完畢后,再進行下一次循環。


            將子方法都改為同步方式,mergeRequest 和 uploadChunks 方法





            BUG-2: 當每次選擇相同的文件并觸發beforeUpload方法時,若第二次也選擇了相同的文件,beforeUpload方法失效,從而導致整個流程失效。

            原因:之前每次選擇文件時,沒有清空上次所選input文件的數據,相同數據的情況下,是不會觸發input的change事件。


            解決方案:每次點擊input時,清空數據即可。我順帶優化了下其他的代碼,具體看提交記錄吧。


            <input

             v-if="!changeDisabled"

             type="file"

             :multiple="multiple"

             class="select-file-input"

             :accept="accept"

            +  οnclick="f.outerHTML=f.outerHTML"

             @change="handleFileChange"/>

            重寫了暫停和恢復的功能,實際上,主要是增加了暫停和恢復的狀態





            之前的處理邏輯太簡單粗暴,存在諸多問題。現在將狀態定位在每一個文件之上,這樣恢復上傳時,直接跳過即可





            封裝組件

            寫了一大堆,其實以上代碼你直接復制也無法使用,這里我將此封裝了一個組件。大家可以去github下載文件,里面有使用案例 ,若有用記得隨手給個star,謝謝!

            偷個懶,具體封裝組件的代碼就不列出來了,大家直接去下載文件查看,若有不明白的,可留言。


            組件文檔

            Attribute

            參數 類型 說明 默認 備注

            headers Object 設置請求頭

            before-upload Function 上傳文件前的鉤子,返回false則停止上傳

            accept String 接受上傳的文件類型

            upload-arguments Object 上傳文件時攜帶的參數

            with-credentials Boolean 是否傳遞Cookie false

            limit Number 最大允許上傳個數 0 0為不限制

            on-exceed Function 文件超出個數限制時的鉤子

            multiple Boolean 是否為多選模式 true

            base-url String 由于本組件為內置的AXIOS,若你需要走代理,可以直接在這里配置你的基礎路徑

            chunk-size Number 每個切片的大小 10M

            threads Number 請求的并發數 3 并發數越高,對服務器的性能要求越高,盡可能用默認值即可

            chunk-retry Number 錯誤重試次數 3 分片請求的錯誤重試次數

            Slot

            方法名 說明 參數 備注

            header 按鈕區域 無

            tip 提示說明文字 無

            后端接口文檔:按文檔實現即可

            藍藍設計www.dzxscac.cn )是一家專注而深入的界面設計公司,為期望卓越的國內外企業提供卓越的UI界面設計、BS界面設計 、 cs界面設計 、 ipad界面設計 、 包裝設計 、 圖標定制 、 用戶體驗 、交互設計、 網站建設 平面設計服務






            日歷

            鏈接

            個人資料

            藍藍設計的小編 http://www.dzxscac.cn

            存檔

            主站蜘蛛池模板: 亚欧洲乱码视频在线观看| 手机免费看av| 2020亚洲天堂| 又粗又硬又大又爽免费视频播放| 国产视频久久| 日韩欧美www| 男受被做哭激烈娇喘gv视频| 99久久国产露脸精品| 色欲无码人妻久久精品| 性夜黄a爽影免费看| 国产激情高中生呻吟视频| 欧美不卡一区二区三区| 国产激情第一页| 国产免费无码一区二区视频| 97精品国自产在线偷拍| 亚洲成人第一页| 国产在线精品一区二区不卡麻豆 | 无码中文精品视视在线观看| 亚洲国产视频网站| 欧美激情一区二区三区在线| 国产97人妻人人做人碰人人爽| 成人在线国产| 日韩毛片在线视频| 女人爽到高潮免费视频大全 | 美女网站免费观看视频| 120秒试看无码体验区| 国产精品无码久久久久久| 尤物在线免费视频| 未满十八18禁止午夜免费网站| 蜜桃av色偷偷av老熟女| 白石茉莉奈番号| 婷婷五月综合色中文字幕| 中文字幕制服丝袜第57页| 51热门大瓜今日大瓜| 无码中文av有码中文av| 性色av无码中文av有码vr| 日本aaaa| 樱花电影最新免费观看国语版| 亚洲精品中文字幕久久久久 | 少妇人妻呻吟青椒bobx| 久久com|