前端通信:ajax設計方案(四)--- 集成ajax上傳技術 大文件/超大文件前端切割上傳,后端進行重組


馬上要過年了,哎,回家的心情也特別的激烈。有錢沒錢,回家過年,家永遠是舔舐傷口最好的地方。新的一年繼續加油努力。

上次做了前端的ajax的上傳文件技術,支持單文件,多文件上傳,並對文件的格式和大小進行檢查和限制。但是上次還有個心結一直沒解開,就是本來前端瀏覽器的文件切割已經好了,但是后台文件重組一直沒搞明白和處理好,所以就擱置了。主要也是對自己的代碼負責,因為自己本身都沒把這個技術搞透徹,外加各種測試都沒通過,不想這樣打臉。所以這個心結一直憋了好久,做夢都在想。終於功夫不負有心人,這周終於將這個問題干掉了,一個字:爽!!!下面咱們直接干!!

一些概念:

  前端ajax的level2的方案中可以發送很多數據類型,具體可查看ajax2的設計規范(PS:w3c的原版設計規范,打開有點慢)

  js新的技術中增加了File對象(實際上就是blob的具體化的一個東西),設計規范參考這里2個 File API 規范 和 FileSystem API 規范

  后台二進制文件重組

    1. 創建空的文件流

    2. 讀取臨時存儲切割文件的文件夾,獲得所有文件路徑  切記:一定要按順序進行排序,否則組合文件將會錯誤

    3. 按順序將切割小文件讀取成二進制流,寫進空的文件流中

    4. 寫入完成,關閉文件流,刪除臨時存儲切割文件的地方

 

工具准備:

    1. 前端代碼,包含ajax庫和測試代碼

    2. nginx服務器,做分離,反向代理后台代碼

    3. 文件MD5計算工具,檢查文件切割上傳完成之后是否改變

    4. IIS服務器,部署后台切割文件組合代碼

    5. postMan接口測試工具,測試接口狀態

 

具體思路:

    1. 前端瀏覽器選擇文件(如果默認沒有超過切割大小將使用默認上傳,否則將進行文件切割上傳)

    2. 前端使用新的特性將文件進行切割成一片一片的子文件,並每次分配一個請求

    3. 后端接受文件,將子文件進行存儲

    4. 后端根據請求參數判斷,如果為最后一次切割文件上傳,則執行切割文件重組

    5. 重組文件,並清除臨時存儲的子文件

 

前端切割文件代碼:

        //切割大文件
        cutFile:function(file,cutSize){
            var count = file.size / cutSize | 0 ,fileArr = [];
            for (var i= 0; i< count ; i++){
                fileArr.push({
                    name:file.name+".part"+(i+1),
                    file:file.slice( cutSize * i , cutSize * ( i + 1 ))
                });
            };
            fileArr.push({
                name:file.name+".part"+(count+1),
                file:file.slice(cutSize*count,file.size)
            });
            return fileArr;
        }

前端切割文件上傳代碼:

        /*
         *   ajax大文件切割上傳(支持單個文件)  -- level2的新特性,請保證你的項目支持新的特性再使用
         *       url                 文件上傳地址
         *       fileSelector        input=file 選擇器
         *       cutSize             切割文件大小
         *       fileType            文件限制類型 mime類型
         *       successEvent        上傳成功處理
         *       progressEvent       上傳進度事件
         *       errorEvent          上傳失敗處理
         *       timeoutEvent        超時處理事件
         *
         *   return: status:  0      請選擇文件
         *                    1      非允許文件格式
         * */
        upload_big:function(url,fileSelector,cutSize,fileType,successEvent,progressEvent,errorEvent,timeoutEvent){
            var file = document.querySelector(fileSelector).files,result ={};
            //以下為上傳文件限制檢查
            if (file.length === 1){
                if (fileType != "*"){
                    if (fileType.indexOf(file.type)=== -1 ){
                        result["status"] = 1;
                        result["errMsg"] = "非允許文件格式";
                    }
                }
            }else{
                result["status"] = 0;
                result["errMsg"] = "請選擇文件/只能上傳一個文件";
            };

       if (result.status !== undefined)  return result;   //如果有錯誤信息直接拋出去,結束運行
//判斷上傳文件是否超過需要切割的大小 if (file[0].size > cutSize){ var fileArr = tool.cutFile(file[0],cutSize); //切割文件 cutFile_upload(fileArr); }else{ return tempObj.upload(url,fileSelector,file[0].size,fileType,successEvent,errorEvent,timeoutEvent); }; /* * 切割文件上傳,配合后台接口進行對接 * 傳輸參數: * count -- 當前傳輸part的次數 * name -- 做過處理的文件名稱 * file -- 上傳的.part的切割文件 * isLast -- 是否為最后一次切割文件上傳(默認值:"true" 字符串,只有最后一次才附加) * */ function cutFile_upload(fileArr,count){ var formData = new FormData(); if (count == undefined){ count = 0; formData.append("count",count); formData.append("name",fileArr[0].name); formData.append("file".name,fileArr[0].file); }else{ if (count === fileArr.length-1){ formData.append("isLast","true") }; formData.append("count",count); formData.append("name",fileArr[count].name); formData.append("file".name,fileArr[count].file); }; var ajaxParam ={ type:"post", url:url, data:formData, isFormData:true, success:function(data){ /* * data 參數設置 需要后台接口配合 * 建議:如果后台成功保存.part文件,建議返回下次所需要的部分,比如當前發送count為0,則data返回下次為1。 * 如果保存不成功,則可false,或者返回錯誤信息,可在successEvent中處理 * * */ progressEvent(count+1,fileArr.length); //上傳進度事件,第一個參數:當前上傳次數;第二個參數:總共文件數 var currCount = Number(data); if (currCount){ if (currCount != fileArr.length){ cutFile_upload(fileArr,currCount); }; }; successEvent(data); //成功處理事件 }, error:errorEvent, timeout:timeoutEvent }; ajax.common(ajaxParam); } }

后端文件重組代碼(.NET webAPI)-- 其他任何后端語言思想是通用的:

        [Route("upload5")]
        public int Post_bigFile1()
        {
            //前端傳輸是否為切割文件最后一個小文件
            var isLast = HttpContext.Current.Request["isLast"];
            //前端傳輸當前為第幾次切割小文件
            var count = HttpContext.Current.Request["count"];
            //獲取前端處理過的傳輸文件名
            string fileName = HttpContext.Current.Request["name"];
            //存儲接受到的切割文件
            HttpPostedFile file = HttpContext.Current.Request.Files[0];

            //處理文件名稱(去除.part*,還原真實文件名稱)

            string newFileName = fileName.Substring(0, fileName.LastIndexOf('.'));
            //判斷指定目錄是否存在臨時存儲文件夾,沒有就創建
            if (!System.IO.Directory.Exists(@"D:\" + newFileName))
            {
                //不存在就創建目錄 
                System.IO.Directory.CreateDirectory(@"D:\" + newFileName);
            }
            //存儲文件
            file.SaveAs("D:\\" + newFileName + "\\" + HttpContext.Current.Request["name"]);
            //判斷是否為最后一次切割文件傳輸
            if (isLast == "true")
            {
                //判斷組合的文件是否存在
                if (File.Exists(@"L:\\" + newFileName))//如果文件存在
                {
                    File.Delete(@"L:\\" + newFileName);//先刪除,否則新文件就不能創建
                }
                //創建空的文件流
                FileStream FileOut = new FileStream(@"L:\\" + newFileName, FileMode.CreateNew,FileAccess.ReadWrite);
                BinaryWriter bw = new BinaryWriter(FileOut);
                //獲取臨時存儲目錄下的所有切割文件
                string[] allFile = Directory.GetFiles("D:\\" + newFileName);
                //將文件進行排序拼接
                allFile = allFile.OrderBy(s => int.Parse(Regex.Match(s, @"\d+$").Value)).ToArray();
                //allFile.OrderBy();
                for (int i = 0; i < allFile.Length; i++)
                {
                    FileStream FileIn = new FileStream(allFile[i], FileMode.Open);
                    BinaryReader br = new BinaryReader(FileIn);
                    byte[] data = new byte[1048576];   //流讀取,緩存空間
                    int readLen = 0;                //每次實際讀取的字節大小
                    readLen = br.Read(data,0, data.Length);
                    bw.Write(data,0, readLen);
                    //關閉輸入流
                    FileIn.Close();
                };
                //關閉二進制寫入
                bw.Close();
                FileOut.Close();
            }
            return int.Parse(count) + 1;
        }

 

以下為測試代碼:

  html頁面代碼:

選擇文件:<input type="file" id="file1" multiple accept="*"/><br />
<input type="button" id="upload" value="上傳" />

  js代碼:

$("#upload").click(function () {
   var temp = ajax.upload_big("/api/ajaxUpload/upload5/","#file1",1024*1024,"*",function(x){},function(count,all){console.log("當前傳輸進度:"+count+"/"+all);})
});

瀏覽器測試結果:

   IE10-11:

  chrome

  opera:

  火狐

  edge

  360瀏覽器

 

 代碼已集成github:https://github.com/GerryIsWarrior/ajax     點顆星星是我最大的鼓勵,有什么問題可以博客、郵箱、github上留言

這一次上傳版本,代碼做過變動,變動如下

  1. 增加大文件傳輸方法upload_big,工具類增加文件切割tool.cutFile
  2. 解決火狐瀏覽器默認要求后台返回xml類型的問題

 

遺留問題待確認:這次safair瀏覽器沒有測試,因為File對象的slice方法不支持,加各種前綴測試都不支持,主要我是window版的safair,這個問題先記着,有測試過的兄弟可以幫忙看一下,我的window版safair瀏覽器是不支持的。

 

還有最重要的一點,如果有問題歡迎指出來,我在github上維護這個庫,這段時間專注於前端的通信技術的研究,第一個階段是ajax的通信技術,后期包括服務器的SSE推送技術,還有webScoket技術。

 

其實研究這個技術最大的感慨就是,本來前端切割文件相對來說簡單,但是后台文件重組有點問題,查閱各種資料,翻牆去谷歌等等,搞了好長一段時間才把后台接口的設計和實現完善。以后技術的發展不僅僅局限於一端的,所以能全棧發展就全棧發展,新的前端技術,都是需要后台進行配合才可以溜起來。對了,其實這些東西國外11年左右就開始搞了,國內還是相對不是很跟進,這次去外面看看發現了好多東西,還是需要多看看的,增長技術視野的。哦了,吃飯去了,再不吃要餓死了。

 

我的前端分布式,容器化,組件化的框架正在從無到有,到時候歡迎大家給建議和指正,3q。

 

再啰嗦幾句,馬上要過年了,大家吃好喝好玩好,來年繼續奮戰。代碼改變世界。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM