摘要:上傳結(jié)構(gòu)與網(wǎng)宿云要求上傳結(jié)構(gòu)的不同上圖是翻自網(wǎng)宿云的文檔的分片上傳流程。鑒于網(wǎng)宿云的上傳一片一塊在邏輯上沒毛病,我們同樣能一塊一塊完成上傳這里注意請仔細看網(wǎng)宿云或七牛云分片上傳的文檔,了解如何分片上傳。
webuploader踩坑
webuploader是百度fex團隊開發(fā)的一個十分便捷的上傳插件,但是我們在實際生產(chǎn)中,會發(fā)現(xiàn)使用它與我們的需求有各種各樣的出入。最近做上傳功能,踩了不少坑,現(xiàn)在來記錄一下。如果我的文章中有任何不妥或者不對的地方,歡迎指正。
webuploader上傳結(jié)構(gòu)與網(wǎng)宿云要求上傳結(jié)構(gòu)的不同上圖是翻自網(wǎng)宿云的文檔的分片上傳流程。
通過該圖,我們可知網(wǎng)宿云組織上傳文件形式是
{文件[塊1(分片1,分片2,分片3,…),塊2,塊3,…]}
而webuploader對文件分片的形式如下
{文件[塊1(分片1),塊2(分片1),塊3(分片1),…]}
即一塊即是一片。鑒于網(wǎng)宿云的上傳一片一塊在邏輯上沒毛病,我們同樣能一塊一塊完成上傳
這里注意,請仔細看網(wǎng)宿云或七牛云分片上傳的文檔,了解如何分片上傳。其中一個很重要的概念是塊,片上下文,即ctx,請前往查看
webuploader上傳流程上與需求不符合的原因我們先來看webuploader一個文件上傳流程中,觸發(fā)的鉤子和事件
一個文件的上傳只觸發(fā)三個實際使用的鉤子
1. before-send-file 上傳文件前 2. before-send 上傳塊前 3. after-send-file 上傳文件結(jié)束
觸發(fā)多個事件
1. uploadStart 開始上傳前 2. uploadAccept 驗證上傳是否合法的事件,取ctx只能在這一步進行,比較悲慘 3. uploadBeforeSend 上傳文件前,對應before-send-file 4. uploadProgress 文件上傳進度事件 5. uploadSkip 跳過當前文件上傳事件,當出現(xiàn)該事件,uploader內(nèi)部標記該文件已經(jīng)上傳成功 6. stopUpload 暫停當前文件上傳時觸發(fā) 7. startUpload 恢復上傳當前文件觸發(fā),或開始上傳也會觸發(fā) 8. uploadSuccess 文件上傳成功觸發(fā) 9. uploadError 文件上傳失敗觸發(fā)
通過比對網(wǎng)宿云的分片上傳流程,我們會發(fā)現(xiàn)他遠遠不滿足我們當下需求,缺少上傳分片前的鉤子,缺少上傳分片后的鉤子,這是不同的分片姿勢決定的,目前來說除非我們自己修改widgets/upload模塊,要不沒什么好的方式解決他
所以下面是修改該模塊的內(nèi)容
// 負責將文件切片。 function CuteFile( file, chunkSize ) { ... // 七牛云,網(wǎng)宿云規(guī)定的最大的塊的大小,chunkSize不能大于它 var blockSize = 4 * 1024 * 1024 while ( index < chunks ) { len = Math.min( chunkSize, total - start ); let block = { file: file, start: start, end: chunkSize ? (start + len) : total, total: total, chunks: chunks, chunk: index, cuted: api } // 增加塊id block.blockIndex = Math.floor(block.start / blockSize); // 增加塊內(nèi)片偏移量標識 block.offset = block.start % blockSize; // 增加塊內(nèi)最后一片標識(網(wǎng)宿云要求在組合文件的時候,需要用每塊最后一片上傳成功的ctx作為參數(shù)來組合文件) block.lastChunk = block.end % blockSize === 0 || block.end === total; if (block.start % blockSize === 0) { // 增加塊頭標識 block.mkblk = true; // 計算總塊數(shù) let blocks = Math.ceil( total / opts.blockSize ); // 增加塊大小標識 block.size = (block.blockIndex + 1) === blocks ? (total - block.start) : blockSize; } pending.push(block); index++; start += len; } file.blocks = pending.concat(); file.remaning = pending.length; return api; }
這樣改過后有一個毛病,那就是由于片上傳是順序上傳,片上傳是無法并發(fā)的~這樣改的結(jié)果就是,一個文件只能順序上傳所有片了。。~本修改只是一個示例,如果真的要完全支持塊并發(fā),片順序上傳,必須要修改block的結(jié)構(gòu),讓block存儲該塊中所有片內(nèi)容。其結(jié)構(gòu)應該是
block: { ... file: 父節(jié)點的引用 cutes: [ 片1, 片2, 片3 ], percents: x, remaning: cutes.length }
除此之外,把實施上傳的主體變更為片,并實現(xiàn)或觸發(fā)一些支持分片上傳的自定義事件,這樣就可以以塊為單位,并發(fā)上傳,塊中片順序上傳了。
上傳過程中,鉤子執(zhí)行的方式和修改上傳配置所帶來的困擾通過網(wǎng)上大量的例子,如下:
uploader.register({ "before-send-file": "bsf", "before-send": "bbs", "after-send-file": "afs" }, { "bsf": function () { ... }, "bbs": function (block) { var server = ""; var D = webUploader.Deferred() if (block.chunk === 1) { uploader.options.server = "xxxx" } else { uploader.options.server = "xxxxx" } setTimeout(function () { D.resolve() }, 200) return D.promise() }, "afs": function () { ... } })
從例子看,似乎webuploader只有一個通用的options來配置服務器地址,formData, headers信息等,由于before-send-file, before-send, after-send-file三個鉤子是異步執(zhí)行的,所以在并發(fā)上傳時,修改分片上傳或mkblk操作所需的服務配置可能會給我們帶來困擾。按照這個思路,一個解決方案是實現(xiàn)一個uploadTaskManager,使用worker來進行多實例并發(fā)上傳操作。
然而近期,通過讀webuploader/widgets/upload.js的源代碼,我們發(fā)現(xiàn)以下內(nèi)容:
_doSend: function( block ) { var me = this, owner = me.owner, // 可喜可賀 opts = $.extend({}, me.options, block.options), file = block.file, tr = new Transport( opts ), data = $.extend({}, opts.formData ), headers = $.extend({}, opts.headers ), requestAccept, ret; ...
可喜可賀,我們完全可以通過直接給block增加options來保證before-send鉤子執(zhí)行時不擾亂整體options配置
// appendWidget不用管,是我添加用于追加注冊一個掛件的方法。 // 由于register方法是在webuploader實例化的時候才將注冊的掛件掛載上,所以才有了這個方法 this.$uploader.appendWidget({ "before-send-file": "bsf", "before-send": "bbs", "after-send-file": "afs", "name": "progress" }, { bsf: (file) => { // 這個也不用管,是我為vue增加的插件,每次響應get操作都返回一個webuploader.Deferred() let deferred = this.$deferred // 為webuploader增加的sha1hash計算方法 this.$uploader.sha1File(file) .progress((e) => { // console.log(file.name, e) }) .then((sha1Hash) => { file.sha1Hash = sha1Hash api.path.upload({ name: file.name, pid: file.pid, hash: file.sha1Hash }) .then((res) => { let data = res.body if (data.msg === "file already exists") { this.$uploader.skipFile(file) } else { file.token = data.token file.server = data.url } deferred.resolve() }) }) return deferred.promise() }, bbs: (block) => { let deferred = this.$deferred if (!block.options) { let file = block.file // 直接設(shè)置options來達到修改server,headers配置的目的 block.options = { headers: { "Content-Type": "application/octet-stream", "Authorization": file.token, "UploadBatch": file.source.uid } } // webuploader切出的block上沒有mkblk, blockIndex, size, offset屬性等,這是我為了支持分片上傳做的修改,請注意 if (block.mkblk) { block.options.server = file.server + "/mkblk/" + block.size + "/" + block.blockIndex } else { // 尋找當前片在整個塊中的偏移 block.options.server = file.server + "/bput/" + file.ctxs[block.chunk - 1] + "/" + block.offset } } deferred.resolve() return deferred.promise() }, afs: (file) => { let deferred = this.$deferred if (file.skipped) { deferred.resolve() } else { let server = file.server + "/mkfile/" + file.size this.$http.post(server, file.mkblkctxs.join(","), { headers: { Authorization: file.token, "Content-Type": "text/plain", UploadBatch: file.source.uid } }) .then(res => { if (res.body.code) { deferred.reject(res.body.message) } else { deferred.resolve() } }) } return deferred.promise() }, "name": "progress" })關(guān)于webuploader如何和vue組合的探索
這里用html5無依賴版本進行說明
1.html5版本沒有提供md5File的具體實現(xiàn),而是以鉤子的形式給你了,如果真的需要聚合md5計算方法,可以按照全量版本里的模塊注冊形式,依次引入md5計算輔助庫,引入全量包里的lib/md5, runtime/html5/md5, widgets/md5三個模塊,并在preset模塊中引入widgets/md5, runtime/html5/md5兩個模塊,完成模塊組合。如果不需要在內(nèi)部聚合,可以直接使用register注冊一個匿名掛件,并把md5-file這個命令鉤子所對應的函數(shù)實現(xiàn)即可。 2.無依賴版本的內(nèi)建jquery還不完全,這導致了無依賴版本無法運行,請自行為dollar-builtin模塊增加$.param, $.inArray兩個方法,并將weuploader中用到了$.map方法的地方改為$.each(內(nèi)建的jquery不支持$.map) 3.刪除所有與dom相關(guān)的依賴,只保留無dom操作相關(guān)的純邏輯模塊(其實不刪除也可以,只要不配置dom相關(guān)掛件即可) 4.將webuploader實現(xiàn)為vue的插件,可以直接為Vue.prototype添加一個uploader的實例
以下是一個內(nèi)聚實現(xiàn)七牛云qeTag hash的代碼,由于是臨時測試修改,沒有在意語法和模塊引入,見諒。
修改uploader模塊,為webuploader添加sha1File方法的命令
// 批量添加純命令式方法。 $.each({ upload: "start-upload", stop: "stop-upload", getFile: "get-file", getFiles: "get-files", addFile: "add-file", addFiles: "add-file", sort: "sort-files", removeFile: "remove-file", cancelFile: "cancel-file", skipFile: "skip-file", retry: "retry", isInProgress: "is-in-progress", makeThumb: "make-thumb", md5File: "md5-file", sha1File: "sha1-file", // 這里添加~ getDimension: "get-dimension", addButton: "add-btn", predictRuntimeType: "predict-runtime-type", refresh: "refresh", disable: "disable", enable: "enable", reset: "reset" }, function( fn, command ) { Uploader.prototype[ fn ] = function() { return this.request( command, arguments ); }; });
加入一個sha1的依賴,這里我使用的是js-sha1
實現(xiàn)/widgets/sha1,實現(xiàn)sha1File接口
/** * @fileOverview sha1計算 */ import Base from "../base" import Uploader from "../uploader" import Sha1 from "../lib/sha1" import Blob from "../lib/blob" export default Uploader.register({ name: "sha1", /** * 計算文件 sha1_hash 值,返回一個 promise 對象,可以監(jiān)聽 progress 進度。 * * * @method sha1File * @grammar sha1File( file[, start[, end]] ) => promise * @for Uploader * @example * * uploader.on( "fileQueued", function( file ) { * var $li = ...; * * uploader.sha1File( file ) * * // 及時顯示進度 * .progress(function(percentage) { * console.log("Percentage:", percentage); * }) * * // 完成 * .then(function(val) { * console.log("sha1 result:", val); * }); * * }); */ sha1File: function( file, start, end ) { var sha1 = new Sha1(), deferred = Base.Deferred(), blob = (file instanceof Blob) ? file : this.request( "get-file", file ).source; sha1.on( "progress load", function( e ) { e = e || {}; deferred.notify( e.total ? e.loaded / e.total : 1 ); }); sha1.on( "complete", function() { deferred.resolve( sha1.getResult() ); }); sha1.on( "error", function( reason ) { deferred.reject( reason ); }); if ( arguments.length > 1 ) { start = start || 0; end = end || 0; start < 0 && (start = blob.size + start); end < 0 && (end = blob.size + end); end = Math.min( end, blob.size ); blob = blob.slice( start, end ); } sha1.loadFromBlob( blob ); return deferred.promise(); } });
實現(xiàn)/lib/sha1,連接運行時sha1庫的封裝
/** * @fileOverview sha1 */ import RuntimeClient from "../runtime/client" import Mediator from "../mediator" function Sha1() { RuntimeClient.call( this, "Sha1" ); } // 讓 Sha1 具備事件功能。 Mediator.installTo( Sha1.prototype ); Sha1.prototype.loadFromBlob = function( blob ) { var me = this; if ( me.getRuid() ) { me.disconnectRuntime(); } // 連接到blob歸屬的同一個runtime. me.connectRuntime( blob.ruid, function() { me.exec("init"); me.exec( "loadFromBlob", blob ); }); }; Sha1.prototype.getResult = function() { return this.exec("getResult"); }; export default Sha1;
創(chuàng)建一個運行時庫/runtime/html5/sha1,這里使用了Crypto-JS v2.5.1進行輔助計算
/** * @fileOverview Transport flash實現(xiàn) */ import Html5Runtime from "./runtime" import Sha1 from "@/plugins/sha1" import Uploader from "../../uploader" import Crypto from "@/libs/Crypto" export default Html5Runtime.register( "Sha1", { init: function() { // do nothing. }, loadFromBlob: function( file ) { var blob = file.getSource(), chunkSize = 4 * 1024 * 1024, chunks = Math.ceil( blob.size / chunkSize ), chunk = 0, owner = this.owner, me = this, blobSlice = blob.mozSlice || blob.webkitSlice || blob.slice, loadNext, fr; var hashs = [], ret = ""; fr = new FileReader(); loadNext = function() { var start, end; start = chunk * chunkSize; end = Math.min( start + chunkSize, blob.size ); fr.onload = function( e ) { // var block = Tool.Crypto.util.bytesToWords( new Uint8Array(e.target.result)); var sha1 = Sha1.create(); var hash = sha1.update(e.target.result).digest(); hashs = hashs.concat(hash); if (end === file.size) { var perfex = 0x16; if (chunks > 1) { perfex = 0x96 sha1 = Sha1.create(); hash = sha1.update(hashs).digest() hashs = hash } hashs.unshift(perfex) ret = Crypto.util.bytesToBase64(hashs); } owner.trigger( "progress", { total: file.size, loaded: end }); }; fr.onloadend = function() { fr.onloadend = fr.onload = null; if ( ++chunk < chunks ) { setTimeout( loadNext, 1 ); } else { setTimeout(function(){ owner.trigger("load"); // 導出的是urlsafe的base64 me.result = ret.replace(///g,"_").replace(/+/g,"-"); loadNext = file = blob = hashs = null; owner.trigger("complete"); }, 50 ); } }; fr.readAsArrayBuffer( blobSlice.call( blob, start, end ) ); }; loadNext(); }, getResult: function() { return this.result; } });
為preset/html5only掛載依賴
/** * @fileOverview 只有html5實現(xiàn)的文件版本。 */ import Base from "../base" import "../widgets/widget" import "../widgets/queue" import "../widgets/runtime" import "../widgets/upload" import "../widgets/validator" import "../widgets/md5" import "../widgets/sha1" import "../runtime/html5/blob" import "../runtime/html5/transport" import "../runtime/html5/md5" import "../runtime/html5/sha1" export default Base;
如何使用?和md5File使用姿勢一模一樣
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/88811.html
摘要:簡介是由團隊開發(fā)的一個簡單的以為主,為輔的現(xiàn)代文件上傳組件。采用大文件分片并發(fā)上傳,極大的提高了文件上傳效率。另外分片傳輸能夠更加實時的跟蹤上傳進度。選擇文件的按鈕。 簡介:WebUploader是由Baidu WebFE(FEX)團隊開發(fā)的一個簡單的以HTML5為主,F(xiàn)LASH為輔的現(xiàn)代文件上傳組件。在現(xiàn)代的瀏覽器里面能充分發(fā)揮HTML5的優(yōu)勢,同時又不摒棄主流IE瀏覽器,沿用原來的...
摘要:簡介是由團隊開發(fā)的一個簡單的以為主,為輔的現(xiàn)代文件上傳組件。采用大文件分片并發(fā)上傳,極大的提高了文件上傳效率。另外分片傳輸能夠更加實時的跟蹤上傳進度。選擇文件的按鈕。 簡介:WebUploader是由Baidu WebFE(FEX)團隊開發(fā)的一個簡單的以HTML5為主,F(xiàn)LASH為輔的現(xiàn)代文件上傳組件。在現(xiàn)代的瀏覽器里面能充分發(fā)揮HTML5的優(yōu)勢,同時又不摒棄主流IE瀏覽器,沿用原來的...
摘要:簡介是由團隊開發(fā)的一個簡單的以為主,為輔的現(xiàn)代文件上傳組件。采用大文件分片并發(fā)上傳,極大的提高了文件上傳效率。另外分片傳輸能夠更加實時的跟蹤上傳進度。選擇文件的按鈕。 簡介:WebUploader是由Baidu WebFE(FEX)團隊開發(fā)的一個簡單的以HTML5為主,F(xiàn)LASH為輔的現(xiàn)代文件上傳組件。在現(xiàn)代的瀏覽器里面能充分發(fā)揮HTML5的優(yōu)勢,同時又不摒棄主流IE瀏覽器,沿用原來的...
摘要:否則強制轉(zhuǎn)換成指定的類型。是否要分片處理大文件上傳還有其他配置項上傳事件選擇需要上傳的文件后,文件就會加入文件隊列,并觸發(fā)事件上傳進度回調(diào)事件,在文件上傳中,多次調(diào)用此事件當文件上傳成功時觸發(fā)當文件上傳出錯時觸發(fā)。 WebUploader簡述 具有兩套運行時支持:HTML5與FLASH 分片、并發(fā) 預覽、壓縮 多途徑添加文件 MD5驗證 引入文件 雖然官方?jīng)]說必須要引入JQuery...
閱讀 3233·2021-11-22 12:07
閱讀 1876·2021-10-12 10:11
閱讀 1041·2019-08-30 15:44
閱讀 2935·2019-08-30 12:45
閱讀 2184·2019-08-29 16:41
閱讀 1636·2019-08-29 16:35
閱讀 2620·2019-08-29 12:57
閱讀 1147·2019-08-26 13:51