大檔案斷點續傳

2021-09-10 07:36:41 字數 3477 閱讀 2033

win10 node: v8.2.1 npm: v5.3.0 multer: v1.3.0

使用1.由於對multer v1.3.0做了修改,所以不可以通過npm install multer這種形式,需要使用到修改過multer包去覆蓋原來的。 

2.對於檔案上傳的介面,比如/upload,需要攜帶引數targetfilename和start。

targetfilename: 服務端生成目標檔案的名字。targetfilename可在seg-worker.js中匯出。seg-worker是乙個web worker。var w1 = new worker('seg-worker.js'); w1.postmessage()

start: 寫入到這個檔案中的位置。

3.如果是分段上傳,需要在multer.diskstorage中新增乙個欄位seg。

multer.diskstorage()

同時需要修改原始碼。見下文update4。

在multerv1.3.0版本中,通過multer.memorystorage和multer.diskstorage來配置檔案的destination和filename。具體怎麼寫入的這些細節multer內部做了封裝。下面是實現過程。完整流程圖在最下方。

update1

同乙個使用者可能會重複上傳看似相同實則不同的檔案。比如兩個檔案,檔名一樣、大小一樣、相關時間都一樣。但是內容不一樣。這樣伺服器會判斷出兩個檔案是一樣的,禁止使用者重複上傳。

解決這個問題是在前端使用乙個spark-md5的庫。該庫會根據檔案內容計算出檔案的md5。

update2

因為是大檔案,要做分段上傳,並且還可以續傳。比如,對於乙個2g的大檔案,如果上傳到中途因為斷網需要從頭開始上傳,這是很麻煩的事情。斷點續傳可以解決這個問題。

基於multer,最開始的做法是在檔案上傳之前,在服務端建立乙個和原始檔大小相同的新檔案(以檔案md5命名)。因 

為段上傳是乙個接乙個,當multer寫完某個段到磁碟後,該檔案追加這個段。然後響應給客戶端,客戶端再上傳下乙個段。這樣保證了順序,但是犧牲了速度。至於如何續傳,可以在上傳前去服務端或者本地快取拿到已經上傳的進度。

update3

因為瀏覽器可以同時發起多個請求,所以上述的乙個接乙個的請求沒有充分利用瀏覽器的這種特性。所以可以for迴圈,同時傳送所有ajax請求(瀏覽器會限制數量)。那麼如何保證上傳檔案的順序是個問題。這需要用到spark-md5這個庫。這需要計算出每個段的md5以及整個檔案的md5。前者在拼接檔案時用到(保證順序),後者用來標誌檔案的獨一性。

基於multer,最開始的做法是當multer寫完某個段到磁碟後,判斷是否所有的段都上傳完畢。如果上傳完畢了,就新建乙個和原始檔相同大小的檔案,然後依次將這些段追加到該檔案。因為每個段都有乙個md5,並且服務端事先取到了所有的段的md5。依次遍歷這些段的md5,就可以做到依次追加。追加完成後,刪除該段。最後理想的結果是生成乙個與原始檔同樣大小的檔案,並且所有段被刪除。

最原始版本的斷點續傳就這樣完成了。

這樣可能造成的問題有:

最多時占用兩倍服務端空間,這在使用者量大的時候是非常可怕的

寫完乙個段,然後再複製,再追加到新檔案

multer對於此的處理有乙個bug。當乙個請求被取消(重新整理頁面,xhr.abort())的時候,可能該段已經上傳到服務端一部分(幾百k),然後再次上傳,最終能得到完整的檔案。但是這些之前上傳了一部分的段沒有刪除掉。

update4

對於update3中的第三個問題,是因為上傳的檔案流filestream,也叫做源流,會被新增到目的流。即src.pipe(dest)。當有檔案上傳的時候,就將資料寫到目的流。這時候請求被取消,這個源流到目的流的關係並不會被取消,而是一直保持。只有當乙個檔案(段)上傳完畢的時候,這種關係才會結束src.unpipe(dest)。

解決這個問題,需要修改原始碼。當乙個請求被取消的時候,會觸發req的close事件。

// multer/lib/make-middleware.js 96行左右

busboy.on('file', function (fieldname, filestream, filename, encoding, mimetype) )

// ......

}其中,我就新增了上述三行**req.on('close', cb)。busboy的finish事件中有乙個操作req.unpipe(busboy)。也就是src.unpipe(dest)。也就是說,當請求被取消的時候,req將busboy從目的流中移除。busboy就不會再占用這個檔案了。所以可以刪除掉了(無論是程式刪除還是手動刪除)。

對於update3中的前兩個問題,自然而然得想到了:先建立乙個和原始檔同等大小的檔案,然後當段檔案到來的時候,直接寫到新建檔案的對應位置。而不是直接寫到磁碟。

但是multer沒有提供這種操作,multer在將檔案寫入到指定資料夾後才暴露出檔案相關的資訊給使用者。所以需要改原始碼。

後端操作如下: 

1.當乙個檔案上傳前,需要請求pre-uplaod介面。通過檔名+檔案md5(也可加上使用者id)的方式來判斷該檔案是否已經上傳過。global.uploads是乙個物件,儲存著檔案相關的資訊(實際中這些資訊在資料庫)。如果檔案沒有上傳,在global.uploads上新增乙個key,值是該檔案的相關資訊。返回響應給客戶端,客戶端可以上傳檔案。如果檔案上傳了部分或上傳過,返回不同響應給客戶端。

2.檔案上傳到後端,會進入multer。multer原本的處理是:

finalpath = path.join(destination, filename)

outstream = fs.createwritestream(finalpath)12

更正的部分:

// multer/storage/disk.js 41行左右

if(that.seg) )

} else

在multer.diskstorage配置中新增了乙個seg屬性。

var storage = multer.diskstorage(); 

如果是分段上傳,就寫到目標檔案。因為是檔案上傳,前端用的是formdata。如果在formdata中新增資料,後端req.body無法拿到資料(可以借助其他包)。所以將資料放到了req.query中。targetfilename是必須的,表示要將這個段寫入到那個檔案。start也是必須的,表示要將這個段寫入到目標檔案的那個位置。所以前端需要/upload?targetfilename=***&start=***。

update5

基本功能到這裡就完成了。後面的是優化部分。使用web worker可以開闢另外乙個執行緒,防止堵塞主線程。在兩處使用了web worker。分別是給檔案分段部分,該部分使用worker意義不大,因為filereader讀取檔案的操作是非同步的。並不會堵塞主線程。第二處是分段完畢後傳送ajax請求部分,因為for迴圈傳送所有請求。檔案很大的情況下會造成堵塞。所以放到web worker。

update6

封裝–為了更方便的呼叫。

update7

在multer2.0.x版本中,暴露出了file stream api,通過這個api可以自己決定將檔案寫到**,而不是修改原始碼。

html5解決大檔案斷點續傳

1.為什麼要採用分塊上傳?試想一下,如果上傳的檔案是乙個大檔案,本來上傳時間就相對較久,再加上網路不穩定各種因素影響,容易導致傳輸中斷,使用者除了重傳整個檔案外沒有更好的選擇。採用分片上傳可以很好地解決這個問題。2.什麼是分片上傳?分塊上傳,就是把乙個大的檔案分成若干塊,一塊一塊的傳輸。如上面的ca...

Html5大檔案斷點續傳

大檔案分塊 一般常用的web伺服器都有對向伺服器端提交資料有大小限制。超過一定大小檔案伺服器端將返回拒絕資訊。當然,web伺服器都提供了配置檔案可能修改限制的大小。針對iis實現大檔案的上傳網上也有一些通過修改web伺服器限制檔案大小來實現。不過這樣對web伺服器的安全帶了問題。攻擊者很容易發乙個大...

python實現大檔案切片斷點續傳

server 能夠處理前端請求的路由 pick 是觸發檔案上傳功能的前端元素id var task id webuploader.base.guid 產生檔案唯一識別符號task id var uploader webuploader.create 上傳分片位址 pick picker auto f...