文件上傳是一個(gè)老生常談的話題了,在文件相對(duì)比較小的情況下茵汰,可以直接把文件轉(zhuǎn)化為字節(jié)流上傳到服務(wù)器枢里,但在文件比較大的情況下,用普通的方式進(jìn)行上傳蹂午,這可不是一個(gè)好的辦法栏豺,畢竟很少有人會(huì)忍受,當(dāng)文件上傳到一半中斷后豆胸,繼續(xù)上傳卻只能重頭開(kāi)始上傳奥洼,這種讓人不爽的體驗(yàn)。那有沒(méi)有比較好的上傳體驗(yàn)?zāi)赝砗鸢赣械牧榻保褪窍逻呉榻B的幾種上傳方式。
1估盘、分片上傳
1.1 什么是分片上傳
分片上傳瓷患,就是將所要上傳的文件,按照一定的大小遣妥,將整個(gè)文件分隔成多個(gè)數(shù)據(jù)塊(我們稱之為Part)來(lái)進(jìn)行分別上傳擅编,上傳完之后再由服務(wù)端對(duì)所有上傳的文件進(jìn)行匯總整合成原始的文件。
1.2 分片上傳的場(chǎng)景
大文件上傳
網(wǎng)絡(luò)環(huán)境環(huán)境不好,存在需要重傳風(fēng)險(xiǎn)的場(chǎng)景
2斷點(diǎn)續(xù)傳
2.1 什么是斷點(diǎn)續(xù)傳
斷點(diǎn)續(xù)傳是在下載或上傳時(shí)爱态,將下載或上傳任務(wù)(一個(gè)文件或一個(gè)壓縮包)人為的劃分為幾個(gè)部分谭贪,每一個(gè)部分采用一個(gè)線程進(jìn)行上傳或下載,如果碰到網(wǎng)絡(luò)故障锦担,可以從已經(jīng)上傳或下載的部分開(kāi)始繼續(xù)上傳或者下載未完成的部分故河,而沒(méi)有必要從頭開(kāi)始上傳或者下載。
2.2 應(yīng)用場(chǎng)景
斷點(diǎn)續(xù)傳可以看成是分片上傳的一個(gè)衍生吆豹,因此可以使用分片上傳的場(chǎng)景鱼的,都可以使用斷點(diǎn)續(xù)傳。
2.3 實(shí)現(xiàn)斷點(diǎn)續(xù)傳的核心邏輯
在分片上傳的過(guò)程中痘煤,如果因?yàn)橄到y(tǒng)崩潰或者網(wǎng)絡(luò)中斷等異常因素導(dǎo)致上傳中斷凑阶,這時(shí)候客戶端需要記錄上傳的進(jìn)度。在之后支持再次上傳時(shí)衷快,可以繼續(xù)從上次上傳中斷的地方進(jìn)行繼續(xù)上傳宙橱。
為了避免客戶端在上傳之后的進(jìn)度數(shù)據(jù)被刪除而導(dǎo)致重新開(kāi)始從頭上傳的問(wèn)題,服務(wù)端也可以提供相應(yīng)的接口便于客戶端對(duì)已經(jīng)上傳的分片數(shù)據(jù)進(jìn)行查詢蘸拔,從而使客戶端知道已經(jīng)上傳的分片數(shù)據(jù)师郑,從而從下一個(gè)分片數(shù)據(jù)開(kāi)始繼續(xù)上傳。
整體的過(guò)程如下:
1调窍、前端將文件安裝百分比進(jìn)行計(jì)算,每次上傳文件的百分之一(文件分片),給文件分片做上序號(hào)
2宝冕、后端將前端每次上傳的文件,放入到緩存目錄
3、等待前端將全部的文件內(nèi)容都上傳完畢后,發(fā)送一個(gè)合并請(qǐng)求
4邓萨、后端使用RandomAccessFile進(jìn)多線程讀取所有的分片文件,一個(gè)線程一個(gè)分片
5地梨、后端每個(gè)線程按照序號(hào)將分片的文件寫(xiě)入到目標(biāo)文件中
6、在上傳文件的過(guò)程中發(fā)生斷網(wǎng)了或者手動(dòng)暫停了,下次上傳的時(shí)候發(fā)送續(xù)傳請(qǐng)求,讓后端刪除最后一個(gè)分片
7缔恳、前端重新發(fā)送上次的文件分片
2.4 實(shí)現(xiàn)流程步驟
方案一宝剖,常規(guī)步驟
將需要上傳的文件按照一定的分割規(guī)則,分割成相同大小的數(shù)據(jù)塊歉甚;
初始化一個(gè)分片上傳任務(wù)万细,返回本次分片上傳唯一標(biāo)識(shí);
按照一定的策略(串行或并行)發(fā)送各個(gè)分片數(shù)據(jù)塊纸泄;
發(fā)送完成后赖钞,服務(wù)端根據(jù)判斷數(shù)據(jù)上傳是否完整,如果完整刃滓,則進(jìn)行數(shù)據(jù)塊合成得到原始文件仁烹。
方案二、本文實(shí)現(xiàn)的步驟
前端(客戶端)需要根據(jù)固定大小對(duì)文件進(jìn)行分片咧虎,請(qǐng)求后端(服務(wù)端)時(shí)要帶上分片序號(hào)和大小卓缰。
服務(wù)端創(chuàng)建conf文件用來(lái)記錄分塊位置,conf文件長(zhǎng)度為總分片數(shù),每上傳一個(gè)分塊即向conf文件中寫(xiě)入一個(gè)127征唬,那么沒(méi)上傳的位置就是默認(rèn)的0,已上傳的就是Byte.MAX_VALUE 127(這步是實(shí)現(xiàn)斷點(diǎn)續(xù)傳和秒傳的核心步驟)
服務(wù)器按照請(qǐng)求數(shù)據(jù)中給的分片序號(hào)和每片分塊大邪葡浴(分片大小是固定且一樣的)算出開(kāi)始位置,與讀取到的文件片段數(shù)據(jù)总寒,寫(xiě)入文件扶歪。
整體的實(shí)現(xiàn)流程如下:
3、分片上傳/斷點(diǎn)上傳代碼實(shí)現(xiàn)
3.1 前端實(shí)現(xiàn)
前端的File對(duì)象是特殊類型的Blob摄闸,且可以用在任意的Blob類型的上下文中善镰。
就是說(shuō)能夠處理Blob對(duì)象的方法也能處理File對(duì)象。在Blob的方法里有有一個(gè)Slice方法可以幫完成切片年枕。
核心代碼:
當(dāng)然如果我們是vue項(xiàng)目的話還有更好的選擇炫欺,我們可以使用一些開(kāi)源的框架,本文推薦使用vue-simple-uploader 實(shí)現(xiàn)文件分片上傳熏兄、斷點(diǎn)續(xù)傳及秒傳品洛。
當(dāng)然我們也可以采用百度提供的webuploader的插件,進(jìn)行分片摩桶。操作方式也特別簡(jiǎn)單桥状,直接按照官方文檔給出的操作進(jìn)行即可。
3.2 后端寫(xiě)入文件
后端用兩種方式實(shí)現(xiàn)文件寫(xiě)入:
RandomAccessFile
MappedByteBuffer
在向下學(xué)習(xí)之前硝清,我們先簡(jiǎn)單了解一下這兩個(gè)類的使用
RandomAccessFile
Java除了File類之外辅斟,還提供了專門處理文件的類,即RandomAccessFile(隨機(jī)訪問(wèn)文件)類耍缴。
該類是Java語(yǔ)言中功能最為豐富的文件訪問(wèn)類砾肺,它提供了眾多的文件訪問(wèn)方法挽霉。RandomAccessFile類支持“隨機(jī)訪問(wèn)”方式防嗡,這里“隨機(jī)”是指可以跳轉(zhuǎn)到文件的任意位置處讀寫(xiě)數(shù)據(jù)。在訪問(wèn)一個(gè)文件的時(shí)候侠坎,不必把文件從頭讀到尾蚁趁,而是希望像訪問(wèn)一個(gè)數(shù)據(jù)庫(kù)一樣“隨心所欲”地訪問(wèn)一個(gè)文件的某個(gè)部分,這時(shí)使用RandomAccessFile類就是最佳選擇实胸。
RandomAccessFile對(duì)象類有個(gè)位置指示器他嫡,指向當(dāng)前讀寫(xiě)處的位置,當(dāng)前讀寫(xiě)n個(gè)字節(jié)后庐完,文件指示器將指向這n個(gè)字節(jié)后面的下一個(gè)字節(jié)處钢属。
剛打開(kāi)文件時(shí),文件指示器指向文件的開(kāi)頭處门躯,可以移動(dòng)文件指示器到新的位置淆党,隨后的讀寫(xiě)操作將從新的位置開(kāi)始。
RandomAccessFile類在數(shù)據(jù)等長(zhǎng)記錄格式文件的隨機(jī)(相對(duì)順序而言)讀取時(shí)有很大的優(yōu)勢(shì),但該類僅限于操作文件染乌,不能訪問(wèn)其他的I/O設(shè)備山孔,如網(wǎng)絡(luò)、內(nèi)存映像等荷憋。
RandomAccessFile類的構(gòu)造方法如下所示:
這兩個(gè)構(gòu)造方法均涉及到一個(gè)String類型的參數(shù)mode台颠,它決定隨機(jī)存儲(chǔ)文件流的操作模式,其中mode值及對(duì)應(yīng)的含義如下:
“r”:以只讀的方式打開(kāi)勒庄,調(diào)用該對(duì)象的任何write(寫(xiě))方法都會(huì)導(dǎo)致IOException異常
“rw”:以讀串前、寫(xiě)方式打開(kāi),支持文件的讀取或?qū)懭胧当巍H粑募淮嬖诶疑耄瑒t創(chuàng)建之。
“rws”:以讀盐须、寫(xiě)方式打開(kāi)玩荠,與“rw”不同的是,還要對(duì)文件內(nèi)容的每次更新都同步更新到潛在的存儲(chǔ)設(shè)備中去贼邓。這里的“s”表示synchronous(同步)的意思
“rwd”:以讀阶冈、寫(xiě)方式打開(kāi),與“rw”不同的是塑径,還要對(duì)文件內(nèi)容的每次更新都同步更新到潛在的存儲(chǔ)設(shè)備中去女坑。使用“rwd”模式僅要求將文件的內(nèi)容更新到存儲(chǔ)設(shè)備中,而使用“rws”模式除了更新文件的內(nèi)容统舀,還要更新文件的元數(shù)據(jù)(metadata)匆骗,因此至少要求1次低級(jí)別的I/O操作
MappedByteBuffer
java io操作中通常采用BufferedReader,BufferedInputStream等帶緩沖的IO類處理大文件誉简,不過(guò)java nio中引入了一種基于MappedByteBuffer操作大文件的方式碉就,其讀寫(xiě)性能極高。
3.3 進(jìn)行寫(xiě)入操作的核心代碼
為了節(jié)約文章篇幅闷串,下面我只展示核心代碼瓮钥。
RandomAccessFile實(shí)現(xiàn)方式
MappedByteBuffer實(shí)現(xiàn)方式
文件操作核心模板類代碼
上傳接口
4、秒傳
4.1 什么是秒傳
通俗的說(shuō)烹吵,你把要上傳的東西上傳碉熄,服務(wù)器會(huì)先做MD5校驗(yàn),如果服務(wù)器上有一樣的東西肋拔,它就直接給你個(gè)新地址锈津,其實(shí)你下載的都是服務(wù)器上的同一個(gè)文件,想要不秒傳凉蜂,其實(shí)只要讓MD5改變琼梆,就是對(duì)文件本身做一下修改(改名字不行)七咧,例如一個(gè)文本文件,你多加幾個(gè)字叮叹,MD5就變了艾栋,就不會(huì)秒傳了忍法。
4.2 實(shí)現(xiàn)的秒傳核心邏輯
利用redis的set方法存放文件上傳狀態(tài)阅懦,其中key為文件上傳的md5,value為是否上傳完成的標(biāo)志位掏击,當(dāng)標(biāo)志位true為上傳已經(jīng)完成携冤,此時(shí)如果有相同文件上傳悼粮,則進(jìn)入秒傳邏輯。
如果標(biāo)志位為false曾棕,則說(shuō)明還沒(méi)上傳完成扣猫,此時(shí)需要在調(diào)用set的方法,保存塊號(hào)文件記錄的路徑翘地,其中申尤,key為上傳文件md5加一個(gè)固定前綴,value為塊號(hào)文件記錄路徑
4.3 核心代碼
5衙耕、總結(jié)
在實(shí)現(xiàn)分片上傳的過(guò)程昧穿,需要前端和后端配合,比如前后端的上傳塊號(hào)的文件大小橙喘,前后端必須得要一致时鸵,否則上傳就會(huì)有問(wèn)題。
其次文件相關(guān)操作正常都是要搭建一個(gè)文件服務(wù)器的厅瞎,比如使用fastdfs饰潜、hdfs等。