參考帖子

<input type="file" id="file1" name="file1" multiple="multiple" /> <input type="file" id="file2" name="file2" multiple="multiple" /> <input type="file" id="file3" name="file3" multiple="multiple" /> <input type="file" id="file4" name="file4" multiple="multiple" /> <input type="file" id="file8" name="file8" multiple="multiple" /> <input type="file" id="file9" name="file9" multiple="multiple" /> //不需要包在form表單里面也可以 //傳輸的方式在js中指定為multipart/form-data 即可 //multiple="multiple"屬性表示一個文件筐可以上傳多個文件

console.log("-------#imgform-----"+JSON.stringify($("#imgform"))); console.log("-------#imgform-----"+$("#imgform")[0]); console.log("-------#file1-----"+$("#file1")[0].files); /*var fileForm = new FormData(); fileForm.append("file1" , $("#file1")[0].files ); fileForm.append("file2" , $("#file2")[0].files ); fileForm.append("file3" , $("#file3")[0].files ); fileForm.append("file4" , $("#file4")[0].files ); fileForm.append("file8" , $("#file8")[0].files ); fileForm.append("file9" , $("#file9")[0].files );*/ $http({ method : 'POST', url : '/omss/uploadCheckImg', data : { file1: $("#file1")[0].files, file2: $("#file2")[0].files, file3: $("#file3")[0].files, file4: $("#file4")[0].files, file8: $("#file8")[0].files, file9: $("#file9")[0].files, }, header: { 'Content-Type': 'multipart/form-data' }, transformRequest: function (data, headersGetter) { var formData = new FormData(); formData.append("new_id",$scope.employee.id); ///angular.forEach(data, function (value, key) { // debugger; // formData.append(key, value); //var formData = new FormData(); //https://stackoverflow.com/questions/32160662/upload-multiple-files-with-formdata-using-angularjs-and-asp-net-mvc for (var i = 0; i < data.file1.length; i++) { formData.append("file1", data.file1[i]); } for (var i = 0; i < data.file2.length; i++) { formData.append("file2", data.file2[i]); } for (var i = 0; i < data.file3.length; i++) { formData.append("file3", data.file3[i]); } for (var i = 0; i < data.file4.length; i++) { formData.append("file4", data.file4[i]); } for (var i = 0; i < data.file8.length; i++) { formData.append("file8", data.file8[i]); } for (var i = 0; i < data.file9.length; i++) { formData.append("file9", data.file9[i]); } //}); var headers = headersGetter(); delete headers['Content-Type']; return formData; } // transformRequest: function(data) { // var formData = new FormData(); // for (var i = 0; i < data.files.length; i++) { // formData.append("files[" + i + "]", data.files[i]); // } // return formData; // } //processData: false, // contentType: false }).success(function(data, status, headers, config) { $scope.status = status; console.log(data); //alert(data); }).error(function(data, status, headers, config) { $scope.data = data || "Request failed"; $scope.status = status; $scope.tips = '對不起,您的網絡情況不太穩定。'; });

import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper; import org.springframework.web.multipart.commons.CommonsMultipartResolver; public String uploadCheckImg(HttpServletRequest request, HttpServletResponse res, String id) { String msgString1 = ""; String msgString2 = ""; String msgString3 = ""; String msgString4 = ""; String msgString5 = ""; String msgString6= ""; String msgString7 = ""; String msgString8 = ""; String msgString9 = ""; // 解析器解析request的上下文 CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver( request.getSession().getServletContext()); // 先判斷request中是否包涵multipart類型的數據, if (multipartResolver.isMultipart(request)) { msgString1=writerImg1(request,"file1",id,"001");//寫入身份證 msgString2=writerImg1(request,"file2",id,"002");//寫入畢業證 msgString3=writerImg1(request,"file3",id,"003");//寫入學位證 msgString4=writerImg1(request,"file4",id,"004");//寫入簡歷 //msgString5=writerImg1(request,"file5",id,"005");//寫入社保卡 //msgString6=writerImg1(request,"file6",id,"006");//寫入公積金 //msgString7=writerImg1(request,"file7",id,"007");//寫入銀行卡 msgString8=writerImg1(request,"file8",id,"008");//寫入離職證明 msgString9=writerImg1(request,"file9",id,"009");//寫入個人頭像 } return msgString1 + msgString2 + msgString3 + msgString4+msgString5 + msgString6 + msgString7 + msgString8 + msgString9; } public String writerImg1(HttpServletRequest request, String inputname,String id, String fileType4){ String fileTypeName=""; switch(fileType4){ case "001" :fileTypeName="身份證";break; case "002" :fileTypeName="畢業證";break; case "003" :fileTypeName="學位證";break; case "004" :fileTypeName="簡歷";break; case "005" :fileTypeName="社保卡";break; case "006" :fileTypeName="公積金";break; case "007" :fileTypeName="銀行卡";break; case "008" :fileTypeName="離職證明";break; case "009" :fileTypeName="個人頭像";break; } String msgString4=""; String realPath = request.getSession().getServletContext() .getRealPath("/") + "img_file";// 取系統當前路徑 // 文件保存目錄URL String saveUrl = request.getContextPath() + "/img_file"; String[] fileTypes = new String[] { "jpeg", "jpg", "pdf", "doc", "docx", "gif", "png", "htm", "html", "txt" }; // 定義允許上傳的文件擴展名 long maxSize = 1024000 * 10; // 允許最大上傳文件大小 MultiPartRequestWrapper multipartRequest = (MultiPartRequestWrapper) request; File[] files4 = multipartRequest.getFiles(inputname); if (files4 == null) { } else { for (int i = 0; i < files4.length; i++) { // 得到上傳文件的擴展名 String fileName = multipartRequest.getFileNames(inputname)[i]; String fileName1 = fileName.substring(0, fileName.lastIndexOf(".")); String fileExt = fileName.substring( fileName.lastIndexOf(".") + 1).toLowerCase(); File file = files4[i]; // 檢查擴展名 if (!Arrays.asList(fileTypes).contains(fileExt)) { msgString4 = msgString4 + fileTypeName+"—" + "上傳文件擴展名是不允許的擴展名。<br>"; } // 檢查文件大小 if (file.length() > maxSize) { msgString4 = msgString4 + fileTypeName+"—" + "上傳文件大小超過限制。<br>"; } // 檢查目錄寫入權限 File uploadDir = new File(realPath); if (!uploadDir.canWrite()) { msgString4 = msgString4 + fileTypeName+"—" + "上傳目錄沒有寫入權限。<br>"; } Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat( "yyyyMMddHHmmss"); String time = sdf.format(date); String realDesc = realPath + "/" + fileType4 + "/" + fileName1 + time + i + "." + fileExt; String saveDesc = saveUrl + "/" + fileType4 + "/" + fileName1 + time + i + "." + fileExt; logger.info("-----絕對路徑realDesc------" + realDesc); if (file.renameTo(new File(realDesc))) { // 將文件的映射關系寫入數據庫 db.insertCheckImg(id, fileName, fileType4, realDesc, saveDesc); msgString4 = msgString4 + fileTypeName+"—" + "[" + fileName + "]" + "上傳成功!<br>"; } else { msgString4 = msgString4 + fileTypeName+"—" + "[" + fileName + "]" + "上傳失敗!該文件已存在,請重新命名!<br>"; } } } return msgString4; }
本文轉載自 文件上傳與 Angular
最近項目需要使用 Angular,對於初學 Angular 的我只能硬着頭皮上了,項目中有一個需求是文件上傳,磕磕絆絆之下也實現了,將實現過程中學習到的一些知識記錄下來以備將來查閱。
與表單數據編碼相關的知識
通常,我們使用 HTML 的標簽 <form>
來為用戶輸入創建一個表單,使用 <input type="file">
作為文件上傳的控件。
要將表單的數據發送給后台,不僅要通過指定 <form>
的屬性 method
來確定發送數據的 HTTP 方法而且需要通過指定 <form>
的屬性 enctype
來確定對發送數據的編碼方式。
下面對這兩個屬性進行簡單說明。
表單 form 的屬性 method
<form>
的屬性 method
規定用於發送 form-data 的 HTTP 方法,其值可以為 get
或者 post
。get
請求會將表單的數據編碼后以 name1=value1&name2=value2
的形式附加到請求的 url 后面進行發送。post
請求會將表單的數據進行編碼之后置於請求體中進行發送。
本文接下來的討論主要基於 post
請求方式。
表單 form 的屬性 enctype
<form>
標簽的屬性 entype
用來規定在發送表單數據之前應該如何對其進行編碼,其實就是用來指定請求的編碼類型。
enctype
屬性有 3 個取值,在 w3school 中對於其取值的描述如下:
取值 | 描述 |
---|---|
application/x-www-form-urlencoded | 空格轉換為 "+" 加號,特殊符號轉換為 ASCII HEX 值 |
multipart/form-data | 不對字符編碼。在使用包含文件上傳控件的表單時,必須使用該值 |
text/plain | 空格轉換為 "+" 加號,但不對特殊字符編碼 |
其中 application/x-www-form-urlencoded
是默認采用的編碼的方式,如果表單 <form>
中有用到文件上傳的控件,就要手動指定編碼為 multipart/form-data
。
下面分別對上述這幾種編碼方式進行舉例(均基於 post
請求方式)
- 編碼為
application/x-www-form-urlencoded
的情況
首先,構造一個表單:
<form method="post" action="/" enctype="application/x-www-form-urlencoded">
<input type="text" name="name1" placeholder="name1">
<input type="text" name="name2" placeholder="name2">
<input type="submit">
</form>
在輸入框內分別輸入 i'm name1
和 name@2
,根據編碼規則,提交表單的時候,表單數據會被編碼成 name1=i%27m+name1&name2=name%402
置於請求體中進行傳遞,在 chrome
瀏覽器中執行結果也正如預期所示。

application/x-www-form-urlencoded
編碼來發送的表單數據
- 編碼為
multipart/form-data
的情況
編碼為 multipart/form-data
的情況又有所不同,先來看看示例代碼的結果。
示例代碼:
<form method="post" action="/" enctype="multipart/form-data">
<input type="text" name="name1" placeholder="name1">
<input type="text" name="name2" placeholder="name2">
<input type="file" name="inputfile">
<input type="submit">
</form>
在輸入框內分別輸入 i'm name1
和 name@2
,再選擇一個名為 testfile.txt 的文件上傳,可以在 chrome
中看到發送的請求如下:

multipart/form-data
編碼來發送的表單數據
注意圖片中的紅框部分,Content-Type
值為 multipart/form-data; boundary=----WebKitFormBoundaryBdpfgMg4VKAZat6C
,其中多了一個叫做 boundary
的字段,它是由瀏覽器隨機生成的一個字符串,作為表單數據的分割邊界來使用的,在服務器端會根據這個 boundary
邊界字段來解析表單數據。
可以明顯看到,以邊界分割的每一段均對應於一項表單數據,每項數據均包含有一個 Content-Disposition
字段和一個 name
字段,而對於上傳的文件則會多一個指定上傳文件名字的 filename
的屬性和上傳文件的類型的 Content-Type
字段,由於例子中上傳的文件是 .txt
格式的文件,因此 Content-Type
的值為 text/plain
,有關文件的擴展名和 Content-Type
的對照表可以看這里。
- 編碼為
text/plain
的情況
這種情況與編碼為application/x-www-form-urlencoded
的情況類似,唯一的差別就在於text/plain
不對特殊字符進行編碼。
文件上傳的 Angular 實現
基於 FormData
的實現
實現的思路:通過 File API
獲取控件中上傳的文件,利用 FormData
類型構造表單數據上傳。
基本知識:File API
和 FormData 類型
File API
File API
(文件API)為Web 開發人員提供一種安全的方式來訪問用戶計算機中的文件,並更好地對這些文件執行操作。
具體來講,File API
在表單中的文件輸入字段的基礎上,又添加了一些直接訪問文件信息的接口。HTML5
在 DOM
中為文件輸入元素添加了一個 files
集合。在通過文件輸入字段選擇了一或多個文件時,files
集合中將包含一組 File
對象,每個 File
對象對應着一個文件。
構造一個文件上傳的表單,通過如下 jQuery
代碼:
$("input[type='file']")[0].files
在 chrome
瀏覽器控制台中可以看到獲得的信息如下:

可以看到選取的文件 testfile.txt
的相關信息,因此可以通過上述方式來獲得上傳的文件。
關於 File API
的更多敘述可以在這里獲得。
FormData
類型
FormData
是在 XMLHttpRequest Level 2
中定義的,為序列化表單以及創建與表單格式相同的數據(用於通過XHR 傳輸)提供了便利。
下面這段對於 FormData
對象的描述引用自 MDN,更多關於 FormData
類型的敘述可以在這里獲得。
XMLHttpRequest Level 2 添加了一個新的接口 FormData. 利用FormData 對象,我們可以通過 JavaScript 用一些鍵值對來模擬一系列表單控件,我們還可以使用 XMLHttpRequest 的 send() 方法來異步的提交這個"表單". 比起普通的 ajax, 使用 FormData 的最大優點就是我們可以異步上傳一個二進制文件.
可見,我們可以使用 FormData
對象來模擬實現文件上傳時候提交的表單數據,而構造提交的數據是通過 FormData
的方法 append()
實現的,它用於給當前 FormData
對象添加一個鍵/值對。
Angular 實現
有了上面所說的實現思路和基礎知識,現在可以着手進行代碼的實現了。
- 首先,編寫一個指令用來獲取上傳文件的
File
對象。
代碼如下:
.directive( "fileModel", [ "$parse", function( $parse ){ return { restrict: "A", link: function( scope, element, attrs ){ var model = $parse( attrs.fileModel ); var modelSetter = model.assign; element.bind( "change", function(){ scope.$apply( function(){ modelSetter( scope, element[0].files[0] ); // console.log( scope ); } ) } ) } } }])
這個指令的使用方式如下:
<input type="file" file-model="fileToUpload">
對於 <input>
元素,在它們失去焦點且 value 值改變時會觸發 change
事件,因此我們在指令的 link
函數中監聽元素上的 change
事件,在事件響應函數中獲取用戶上傳的文件信息,並且將該文件賦值給 $scope
對象中與指令 fileModel
綁定的屬性(上例中為 fileToUpload
)。
可以運行例子中的代碼,選擇一個文件 filetest.txt
,打印出賦值后的 $scope
對象如下:

$scope
對象
如紅框所示,$scope 的屬性 fileToUpload
即是上傳的文件 filetest.txt
的信息。
- 然后,編寫一個服務用於發送上傳文件的
multipart/form-data
請求。
代碼如下:
.service( "fileUpload", ["$http", function( $http ){ this.uploadFileToUrl = function( file, uploadUrl ){ var fd = new FormData(); fd.append( "file", file ) $http.post( uploadUrl, fd, { transformRequest: angular.identity, headers: { "Content-Type": undefined } }) .success(function(){ // blabla... }) .error( function(){ // blabla... }) } }])
在服務 fileUpload
的方法 uploadFileToUrl
中,通過 FormData
的 append()
方法將上傳的文件序列化為表單數據,然后通過 $http.post()
方法發送給后台。
Angular 默認的 transformRequest
方法會嘗試序列化我們的 FormData
對象,因此此處我們使用 angular.identity
函數來覆蓋它;另外,angular 在發送 POST 請求的時候使用的默認 Content-Type
是 application/json
,因此此處需要調整為 undefined
,這時瀏覽器會自動的幫我們設置成 multipart/form-data
的編碼方式,同時還會生成一個合適的 boundary
,如果手動設置成 multipart/form-data
的話就不會生成 boundary
字段了。
- 最后,在控制器的合適地方發送這個請求。
現在我們已經獲得了上傳的文件的相關信息,也有一個用於發送該文件的服務,那么只要在控制器中定義一個用於發送的函數,然后在合適的時機調用它即可將文件上傳到后台去了。
舉個例子,在控制器的 $scope
里面定義一個發送請求的函數 sendFile
:
.controller( "myCtrl", [ "$scope", "fileUpload", function( $scope, fileUpload ){ $scope.sendFile = function(){ var url = "/server", file = $scope.fileToUpload; if ( !file ) return; fileUpload.uploadFileToUrl( file, url ); } }])
然后我們可以定義一個按鈕,當用戶點擊這個按鈕的時候就會將上傳的文件發送出去。
<button type="button" ng-click="sendFile()">Submit</button>
結果是這樣的:

FormData
上傳文件的請求
兼容性
由於 FormData
只兼容 IE10+ ,因此上述方法也只是在 IE10+ 中可以使用。
如果你的應用需要兼容 IE8 ,老老實實封裝一個含有 iframe 的指令即可,請接着往下看。
含有 iframe 的實現
指令代碼如下
.directive( "iframeFileUpload", [function(){ var inner = "<div>"; inner += "<form action=\"/server\" method=\"post\" enctype=\"multipart/form-data\" target=\"uploadIframe\">"; inner += "<input type=\"file\" name=\"filename\">"; inner += "<input type=\"submit\">"; inner += "</form>"; inner += "<iframe id=\"uploadIframe\" name=\"uploadIframe\" style=\"display:none\"></iframe>"; inner += "</div>"; return{ restrict: "A", template: inner, // or // templateUrl: "components/iframeFileUpload.html", replace: true, scope: {}, link: function( scope, element, attrs ){ // blabla... } } }])
調用方式大概是這樣的:
<div iframe-file-upload></div>