檔案各種上傳,離不開的表單
閱讀目錄
作為程式設計師的我們,經常會要用到檔案的上傳和下載功能。到了需要用的時候,各種查資料。有木有..有木有...。為了方便下次使用,這裡來做個總結和備忘。
利用表單實現檔案上傳
最原始、最簡單、最粗暴的檔案上傳。
前端程式碼:
//方式1 <form action="/Home/SaveFile1" method="post" enctype="multipart/form-data"> <input type="file" class="file1" name="file1" /> <button type="submit" class="but1">上傳</button> </form>
【注意】
- 1、需要post提交
- 2、enctype="multipart/form-data" (傳輸檔案)
- 3、需要提交的表單元素需要設定 name 屬性
後臺程式碼:
public ActionResult SaveFile1() { if (Request.Files.Count > 0) { Request.Files[0].SaveAs(Server.MapPath("~/App_Data/") + Request.Files[0].FileName); return Content("儲存成功"); } return Content("沒有讀到檔案"); }
表單非同步上傳(jquery.form外掛)
雖然上面的方式簡單粗暴,但是不夠友好。頁面必然會重新整理。難以實現停留在當前頁面,並給出檔案上傳成功的提示。
隨著時間的流逝,技術日新月異。ajax的出現,使得非同步檔案提交變得更加容易。
下面我們利用jquery.form外掛來實現檔案的非同步上傳。
首先我們需要匯入jquery.js和jquery.form.js
前端程式碼:
<form id="form2" action="/Home/SaveFile2" method="post" enctype="multipart/form-data"> <input type="file" class="file1" name="file1" /> <button type="submit" class="but1">上傳1</button> <button type="button" class="but2">上傳2</button> </form>
//方式2(通過ajaxForm繫結ajax操作) $(function () { $('#form2').ajaxForm({ success: function (responseText) { alert(responseText); } }); });
//方式3(通過ajaxSubmit直接執行ajax操作) $(function () { $(".but2").click(function () { $('#form2').ajaxSubmit({ success: function (responseText) { alert(responseText); } }); }); });
後臺程式碼:
public string SaveFile2() { if (Request.Files.Count > 0) { Request.Files[0].SaveAs(Server.MapPath("~/App_Data/") + Path.GetFileName(Request.Files[0].FileName)); return "儲存成功"; } return "沒有讀到檔案"; }
原理:
我們很多時候使用了外掛,就不管其他三七二十一呢。
如果有點好奇心,想想這個外掛是怎麼實現的。隨便看了看原始碼一千五百多行。我的媽呀,不就是個非同步上傳嗎,怎麼這麼複雜。
難以看出個什麼鬼來,直接斷點除錯下吧。
原來外掛內部有iframe和FormData不同方式來上傳,來適應更多版本瀏覽器。
模擬表單資料上傳(FormData)
iframe這東西太噁心。我們看到上面可以利用FormData來上傳檔案,這個是Html 5 才有的。下面我們自己也來試試把。
前端程式碼:
<input id="fileinfo" type="file" class="notFormFile" /> <button type="button" class="btnNotForm">上傳4</button>
//方式4 $(".btnNotForm").click(function () { var formData = new FormData();//初始化一個FormData物件 formData.append("files", $(".notFormFile")[0].files[0]);//將檔案塞入FormData $.ajax({ url: "/Home/SaveFile2", type: "POST", data: formData, processData: false, // 告訴jQuery不要去處理髮送的資料 contentType: false, // 告訴jQuery不要去設定Content-Type請求頭 success: function (responseText) { alert(responseText); } }); });
後的程式碼:(不變,還是上例程式碼)
public string SaveFile2() { if (Request.Files.Count > 0) { Request.Files[0].SaveAs(Server.MapPath("~/App_Data/") + Path.GetFileName(Request.Files[0].FileName)); return "儲存成功"; } return "沒有讀到檔案"; }
我們看到,FormData物件也只是在模擬一個原始的表單格式的資料。那有沒有可能不利用表單或表單格式來上傳檔案呢?答案是肯定的。(下面馬上揭曉)
前端程式碼:
<input type="file" id="file5" multiple> <button type="button" class="btnFile5">上傳5</button>
//方式5 $(".btnFile5").click(function () { $.ajax({ url: "/Home/SaveFile4", type: "POST", data: $("#file5")[0].files[0], processData: false, // 告訴jQuery不要去處理髮送的資料 contentType: false, // 告訴jQuery不要去設定Content-Type請求頭 success: function (responseText) { alert(responseText); } });; });
後臺程式碼:
public string SaveFile4() { //這裡發現只能得到一個網路流,沒有其他資訊了。(比如,檔案大小、檔案格式、檔名等) Request.SaveAs(Server.MapPath("~/App_Data/SaveFile4.data") + "", false); return "儲存成功"; }
細心的你發現沒有了表單格式,我們除了可以上傳檔案流資料外,不能再告訴後臺其他資訊了(如檔案格式)。
到這裡,我似乎明白了以前上傳檔案為什麼非得要用個form包起來,原來這只是和後臺約定的一個傳輸格式而已。
其實我們單純的用jq的ajax傳輸文字資料的時候,最後也是組裝成了form格式的資料,如:
$.ajax({ data: { "userName": "張三" }
分片上傳
在知道了上面的各種上傳之後,我們是不是就滿於現狀了呢?no,很多時候我們需要傳輸大檔案,一般伺服器都會有一定的大小限制。
某天,你發現了一個激情小電影想要分享個大家。無奈,高清檔案太大傳不了,怎麼辦?我們可以化整為零,一部分一部分的傳嘛,也就是所謂的分片上傳。
前端程式碼:
<input type="file" id="file6" multiple> <button type="button" class="btnFile6">分片上傳6</button> <div class="result"></div>
//方式6 $(".btnFile6").click(function () { var upload = function (file, skip) { var formData = new FormData();//初始化一個FormData物件 var blockSize = 1000000;//每塊的大小 var nextSize = Math.min((skip + 1) * blockSize, file.size);//讀取到結束位置 var fileData = file.slice(skip * blockSize, nextSize);//擷取 部分檔案 塊 formData.append("file", fileData);//將 部分檔案 塞入FormData formData.append("fileName", file.name);//儲存檔名字 $.ajax({ url: "/Home/SaveFile6", type: "POST", data: formData, processData: false, // 告訴jQuery不要去處理髮送的資料 contentType: false, // 告訴jQuery不要去設定Content-Type請求頭 success: function (responseText) { $(".result").html("已經上傳了" + (skip + 1) + "塊檔案"); if (file.size <= nextSize) {//如果上傳完成,則跳出繼續上傳 alert("上傳完成"); return; } upload(file, ++skip);//遞迴呼叫 } }); }; var file = $("#file6")[0].files[0]; upload(file, 0); });
後臺程式碼:
public string SaveFile6() { //儲存檔案到根目錄 App_Data + 獲取檔名稱和格式 var filePath = Server.MapPath("~/App_Data/") + Request.Form["fileName"]; //建立一個追加(FileMode.Append)方式的檔案流 using (FileStream fs = new FileStream(filePath, FileMode.Append, FileAccess.Write)) { using (BinaryWriter bw = new BinaryWriter(fs)) { //讀取檔案流 BinaryReader br = new BinaryReader(Request.Files[0].InputStream); //將檔案留轉成位元組陣列 byte[] bytes = br.ReadBytes((int)Request.Files[0].InputStream.Length); //將位元組陣列追加到檔案 bw.Write(bytes); } } return "儲存成功"; }
相對而言,程式碼量多了一點,複雜了一點。不過相對於網上的其他分片上傳的程式碼應該要簡單得多(因為這裡沒有考慮多檔案塊同時上傳、斷點續傳。那樣就需要在後臺把檔案塊排序,然後上傳完成按序合併,然後刪除原來的臨時檔案
有興趣的同學可以自己試試,稍候在分析上傳外掛webuploader的時候也會實現)。
效果圖:
【說明】:如果我們想要上傳多個檔案怎麼辦?其實H5中也提供了非常簡單的方式。直接在input裡面標記multiple,<input type="file" id="file6" multiple>,然後我們後臺接收的也是一個陣列Request.Files。
使用HTML5 拖拽、貼上上傳
只能說H5真是強大啊,許可權越來越大,操作越來越牛逼。
前端程式碼(拖拽上傳):
<textarea class="divFile7" style="min-width:800px;height:150px" placeholder="請將檔案拖拽或直接貼上到這裡"></textarea>
//方式7 $(".divFile7")[0].ondrop = function (event) { event.preventDefault();//不要執行與事件關聯的預設動作 var files = event.dataTransfer.files;//獲取拖上來的檔案 //以下程式碼不變 var formData = new FormData();//初始化一個FormData物件 formData.append("files", files[0]);//將檔案塞入FormData $.ajax({ url: "/Home/SaveFile2", type: "POST", data: formData, processData: false, // 告訴jQuery不要去處理髮送的資料 contentType: false, // 告訴jQuery不要去設定Content-Type請求頭 success: function (responseText) { alert(responseText); } }); };
後臺程式碼:
略(和之前的SaveFile2一樣)
前端程式碼(貼上上傳 限圖片格式):
//方式8 $(".divFile7")[0].onpaste = function (event) { event.preventDefault();//不要執行與事件關聯的預設動作 var clipboard = event.clipboardData.items[0];//剪貼簿資料 if (clipboard.kind == 'file' || clipboard.type.indexOf('image') > -1) {//判斷是圖片格式 var imageFile = clipboard.getAsFile();//獲取檔案 //以下程式碼不變 var formData = new FormData; formData.append('files', imageFile); formData.append('fileName', "temp.png");//這裡給檔案命個名(或者直接在後臺儲存的時候命名) $.ajax({ url: "/Home/SaveFile8", type: "POST", data: formData, processData: false, // 告訴jQuery不要去處理髮送的資料 contentType: false, // 告訴jQuery不要去設定Content-Type請求頭 success: function (responseText) { alert(responseText); } }); } };
後臺程式碼:
public string SaveFile8() { //儲存檔案到根目錄 App_Data + 獲取檔名稱和格式 var filePath = Server.MapPath("~/App_Data/") + Request.Form["fileName"]; if (Request.Files.Count > 0) { Request.Files[0].SaveAs(filePath); return "儲存成功"; } return "沒有讀到檔案"; }
效果圖:
上傳外掛(WebUploader)
已經列舉分析了多種上傳檔案的方式,我想總有一種適合你。不過,上傳這個功能比較通用,而我們自己寫的可能好多情況沒有考慮到。接下來簡單介紹下百度的WebUploader外掛。
比起我們自己寫的簡單上傳,它的優勢:穩定、相容性好(有flash切換,所以支援IE)、功能多、併發上傳、斷點續傳(主要還是靠後臺配合)。
官網:http://fex.baidu.com/webuploader/
外掛下載:https://github.com/fex-team/webuploader/releases/download/0.1.5/webuploader-0.1.5.zip
下面開始對WebUploader的使用
第一種,簡單粗暴
前端程式碼:
<div id="picker">選擇檔案</div> <button id="ctlBtn" class="btn btn-default">開始上傳</button>
<!--引用webuploader的js和css--> <link href="~/Scripts/webuploader-0.1.5/webuploader.css" rel="stylesheet" /> <script src="~/Scripts/webuploader-0.1.5/webuploader.js"></script> <script type="text/javascript"> var uploader = WebUploader.create({ // (如果是新瀏覽器 可以不用 flash) //swf: '/Scripts/webuploader-0.1.5/Uploader.swf', // 檔案接收服務端。 server: '/Webuploader/SaveFile', // 選擇檔案的按鈕。可選。 // 內部根據當前執行是建立,可能是input元素,也可能是flash. pick: '#picker' }); $("#ctlBtn").click(function () { uploader.upload(); }); uploader.on('uploadSuccess', function (file) { alert("上傳成功"); }); </script>
後臺程式碼:
public string SaveFile() { if (Request.Files.Count > 0) { Request.Files[0].SaveAs(Server.MapPath("~/App_Data/") + Path.GetFileName(Request.Files[0].FileName)); return "儲存成功"; } return "沒有讀到檔案"; }
第二種,分片上傳。和我們之前自己寫的效果差不多。
前端程式碼:
var uploader = WebUploader.create({ //相容老版本IE swf: '/Scripts/webuploader-0.1.5/Uploader.swf', // 檔案接收服務端。 server: '/Webuploader/SveFile2', // 開起分片上傳。 chunked: true, //分片大小 chunkSize: 1000000, //上傳併發數 threads: 1, // 選擇檔案的按鈕。 pick: '#picker' }); // 點選觸發上傳 $("#ctlBtn").click(function () { uploader.upload(); }); uploader.on('uploadSuccess', function (file) { alert("上傳成功"); });
後臺程式碼:
public string SveFile2() { //儲存檔案到根目錄 App_Data + 獲取檔名稱和格式 var filePath = Server.MapPath("~/App_Data/") + Path.GetFileName(Request.Files[0].FileName); //建立一個追加(FileMode.Append)方式的檔案流 using (FileStream fs = new FileStream(filePath, FileMode.Append, FileAccess.Write)) { using (BinaryWriter bw = new BinaryWriter(fs)) { //讀取檔案流 BinaryReader br = new BinaryReader(Request.Files[0].InputStream); //將檔案留轉成位元組陣列 byte[] bytes = br.ReadBytes((int)Request.Files[0].InputStream.Length); //將位元組陣列追加到檔案 bw.Write(bytes); } } return "儲存成功"; }
我們看到了有個引數threads: 1上傳併發數,如果我們改成大於1會怎樣?前端會同時發起多個檔案片上傳。後臺就會報錯,多個程式同時操作一個檔案。
那如果我們想要多執行緒上傳怎麼辦?改程式碼吧(主要是後臺邏輯)。
前端程式碼:
//併發上傳(多執行緒上傳) var uploader = WebUploader.create({ //相容老版本IE swf: '/Scripts/webuploader-0.1.5/Uploader.swf', // 檔案接收服務端。 server: '/Webuploader/SveFile3', // 開起分片上傳。 chunked: true, //分片大小 chunkSize: 1000000, //上傳併發數 threads: 10, // 選擇檔案的按鈕。 pick: '#picker' }); // 點選觸發上傳 $("#ctlBtn").click(function () { uploader.upload(); }); uploader.on('uploadSuccess', function (file) { //上傳完成後,給後臺傳送一個合併檔案的命令 $.ajax({ url: "/Webuploader/FileMerge", data: { "fileName": file.name }, type: "post", success: function () { alert("上傳成功"); } }); });
後臺程式碼:
public string SveFile3() { var chunk = Request.Form["chunk"];//當前是第多少片 var path = Server.MapPath("~/App_Data/") + Path.GetFileNameWithoutExtension(Request.Files if (!Directory.Exists(path))//判斷是否存在此路徑,如果不存在則建立 { Directory.CreateDirectory(path); } //儲存檔案到根目錄 App_Data + 獲取檔名稱和格式 var filePath = path + "/" + chunk; //建立一個追加(FileMode.Append)方式的檔案流 using (FileStream fs = new FileStream(filePath, FileMode.Append, FileAccess.Write)) { using (BinaryWriter bw = new BinaryWriter(fs)) { //讀取檔案流 BinaryReader br = new BinaryReader(Request.Files[0].InputStream); //將檔案留轉成位元組陣列 byte[] bytes = br.ReadBytes((int)Request.Files[0].InputStream.Length); //將位元組陣列追加到檔案 bw.Write(bytes); } } return "儲存成功"; }
/// <summary> /// 合併檔案 /// </summary> /// <param name="path"></param> /// <returns></returns> public bool FileMerge() { var fileName = Request.Form["fileName"]; var path = Server.MapPath("~/App_Data/") + Path.GetFileNameWithoutExtension(fileName); //這裡排序一定要正確,轉成數字後排序(字串會按1 10 11排序,預設10比2小) foreach (var filePath in Directory.GetFiles(path).OrderBy(t => int.Parse(Path.GetFileNameWithoutExtension(t)))) { using (FileStream fs = new FileStream(Server.MapPath("~/App_Data/") + fileName, FileMode.Append, FileAccess.Write)) { byte[] bytes = System.IO.File.ReadAllBytes(filePath);//讀取檔案到位元組陣列 fs.Write(bytes, 0, bytes.Length);//寫入檔案 } System.IO.File.Delete(filePath); } Directory.Delete(path); return true; }
到這裡你以為就結束了嗎?錯,還有好多情況沒有考慮到。如果多個使用者上傳的檔名字一樣會怎樣?如何實現斷點續傳?還沒實現選擇多個檔案?不過,這裡不打算繼續貼程式碼了(再貼下去,程式碼量越來越多了),自己也來練習練習吧。
提供一個思路,上傳前先往資料庫插入一條資料。資料包含檔案要存的路徑、檔名(用GUID命名,防止同名檔案衝突)、檔案MD5(用來識別下次續傳和秒傳)、臨時檔案塊存放路徑、檔案是否完整上傳成功等資訊。
然後如果我們斷網後再傳,首先獲取檔案MD5值,看資料庫裡面有沒上傳完成的檔案,如果有就實現秒傳。如果沒有,看是不是有上傳了部分的。如果有接著傳,如果沒有則重新傳一個新的檔案。
總結
之前我一直很疑惑,為什麼上傳檔案一定要用form包起來,現在算是大概明白了。
最開始在javascript還不流行時,我們就可以直接使用submit按鈕提交表單資料了。表單裡面可以包含文字和檔案。然後隨著js和ajax的流行,可以利用ajax直接非同步提交部分表單資料。這裡開始我就糾結了,為什麼ajax可以提交自己組裝的資料。那為什麼不能直接提交檔案呢。這裡我錯了,ajax提交的並不是隨意的資料,最後還是組裝成了表單格式(因為後臺技術對錶單格式資料的支援比較普及)。但是現有的技術還不能通過js組裝一個檔案格式的表單資料。直到H5中的FormData出現,讓前端js組裝一個包含檔案的表單格式資料成為了可能。所以說表單只是為了滿足和後臺“約定”的資料格式而已。
相關推薦
- http://www.cnblogs.com/fish-li/archive/2011/07/17/2108884.html
- http://javascript.ruanyifeng.com/htmlapi/file.html
demo
相關文章
- post 表單大檔案上傳
- 單個檔案上傳和批量檔案上傳
- 檔案上傳的幾種方式
- SpringMVC 單檔案上傳與多檔案上傳SpringMVC
- jqm檔案上傳,上傳圖片,jqm的表單操作,jqm的ajax的使用,jqm檔案操作大全,檔案操作demo
- php單個檔案上傳PHP
- Django檔案上傳 -- 適用於單一小檔案上傳Django
- 檔案上傳頁面有多個form表單,在火狐下無法上傳ORM
- Spring MVC 檔案上傳、Restful、表單校驗框架SpringMVCREST框架
- 前端頁面上實現表單提交檔案上傳功能前端
- RMAN簡單演示 備份各種檔案
- C# exe上傳檔案和提交表單資料的方法C#
- 為什麼檔案上傳表單是主要的安全威脅
- Python 不寫硬碟上傳檔案Python硬碟
- http不使用Form表單傳送檔案資料和非檔案資料(上傳篇)HTTPORM
- 各種檔案系統的允許的單個檔案最大大小
- android intent開啟各種檔案的方法AndroidIntent
- php檔案上傳之多檔案上傳PHP
- SpringMVC檔案上傳下載(單檔案、多檔案)SpringMVC
- 簡單檔案的上傳與儲存
- 微信開發 檔案上傳
- PHP實現單檔案、多檔案上傳 封裝 物件導向實現檔案上傳PHP封裝物件
- Spring Boot + Vue 前後端分離,兩種檔案上傳方式總結Spring BootVue後端
- 檔案上傳之三基於flash的檔案上傳
- 史上最全然oophperphp檔案上傳之檔案型別相應表,ie,火狐各一份。OOPPHP型別
- 幾種windows到linux上傳檔案的方式WindowsLinux
- .NetCore上傳多檔案的幾種示例NetCore
- iOS開發-NSURLSession檔案上傳iOSSession
- 上傳本地.CSV檔案到內表中
- 檔案上傳
- 偶得各種檔案開啟方式 記之
- 上傳檔案的陷阱
- Asp.net中關於上傳檔案的各項基本操作ASP.NET
- 各種型別檔案頭型別
- Knife4j檔案上傳不顯示上傳選擇文字域
- 檔案上傳的單元測試怎麼寫?
- PHP 檔案操作的各種姿勢PHP
- c# 對檔案的各種操作C#