1. 程式人生 > >SpringMVC Ajax上傳檔案例項

SpringMVC Ajax上傳檔案例項

做了一個檔案上傳模組,因為傳統的form提交會有頁面重新整理,不符合我的使用要求,所以我採用Ajax提交方式,這裡說明下,我的應用程式前端為Ajax提交,後端SpringMVC接收處理。

傳統form提交檔案方式:

<form id="uploadPic" action="/user/saveHeaderPic" method="post" enctype="multipart/form-data">
    <input type="file" name="file"/>
    <input type="submit" value="提交"/>
</form>

採用Ajax提交檔案,我先後出現瞭如下兩個問題:

Ajax post 400 (Bad Request)

HTTP Status 400 - Required CommonsMultipartFile parameter ‘pic’ is not present

這裡先解釋下錯誤原因:

問題1:

Ajax引數錯誤導致,上傳檔案的form我使用了jquery.form.js的form序列化,這樣傳輸表單到後臺很方便,但是二進位制檔案是無法用form.serialize()序列化的。

所以最終我採用了FormData的傳輸方式,XMLHttpRequest Level 2添加了一個新的介面FormData。利用FormData物件,我們可以通過JavaScript用一些鍵值對來模擬一系列表單控制元件,我們還可以使用XMLHttpRequest的send()方法來非同步的提交這個“表單”。比起普通的ajax,使用FormData的最大優點就是我們可以非同步上傳一個二進位制檔案。

但使用formData對瀏覽器有一定要求(Chrome 7+、Firefox 4+、IE 10+、Opera 12+、Safari 5+),如果程式需要相容低版本瀏覽器,可去檢視其他JS上傳控制元件或flash上傳控制元件。

formData使用參看:https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest/FormData

問題2:

這個問題是因為不細心導致的 - -,後端接收的引數名和前端file控制元件name名稱不一致導致。

好啦,下面給出我的前後端程式碼示例:

HTML:

<form id="uploadPic" action="#" enctype="multipart/form-data">
    <input type="file" name="file">
    <a href="javascript:savePic();" class="btn green"> 提交 </a>
</form>

JS指令碼:

<script type="text/javascript">
    function savePic(){
        var formData = new FormData($( "#uploadPic" )[0]);  
        var ajaxUrl = "${path}/rest/user/saveHeaderPic";
        //alert(ajaxUrl);
        //$('#uploadPic').serialize() 無法序列化二進位制檔案,這裡採用formData上傳
        //需要瀏覽器支援:Chrome 7+、Firefox 4+、IE 10+、Opera 12+、Safari 5+。
        $.ajax({
            type: "POST",
            //dataType: "text",
            url: ajaxUrl,
            data: formData,
            async: false,  
            cache: false,  
            contentType: false,  
            processData: false,
            success: function (data) {
            alert(data);
            },
            error: function(data) {
                alert("error:"+data.responseText);

             }
        });
        return false;
    }

JAVA後端:

/**
 * 頭像圖片上傳
 * @throws IOException 
 */
@RequestMapping(value = "/saveHeaderPic", method = RequestMethod.POST)
public void saveHeaderPic(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request, HttpServletResponse response) throws IOException {

        String resMsg = "";
    try {

        long  startTime=System.currentTimeMillis();

        System.out.println("fileName:"+file.getOriginalFilename());
        String path="/Users/loukai/easylife/files/"+new Date().getTime()+file.getOriginalFilename();
        System.out.println("path:" + path);

        File newFile=new File(path);
        //通過CommonsMultipartFile的方法直接寫檔案
        file.transferTo(newFile);
        long  endTime=System.currentTimeMillis();
        System.out.println("執行時間:"+String.valueOf(endTime-startTime)+"ms");
        resMsg = "1";
    } catch (IllegalStateException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
        resMsg = "0";
    }
          response.getWriter().write(resMsg);

      }

執行測試,檔案上傳成功!

 

第二部份:

<input type="file" multiple="true" id="file" name="file"/><button class="u-button u-button-primary" onclick="uploadFileMethod1()" id="doUpload">確認附件</button>

js程式碼

單個檔案上傳

function uploadFileMethodSingleFile()
  {
	 
      var FileController = $ctx+"/fankuiController/saveFiles";                    // 接收上傳檔案的後臺地址
      // FormData 物件
      var fileName = document.getElementById("file").value;
     // alert(getPath(fileObj));
      var form = new FormData();
 
      var fileObj = document.getElementById("file").files[0]; // 獲取檔案物件
       form.append("file ",fileObj );   // 檔案物件  
 
      var xhr = new XMLHttpRequest();
      xhr.open("post", FileController, true);
      xhr.send(form);
      xhr.onload = function () {
          alert("上傳完成!");
      };
  }

單個檔案上傳時,controller用MultipartFile 型別的引數接受檔案

@RequestMapping(method = RequestMethod.POST,value = "saveFiles")
	public @ResponseBody Map<String,Object> saveFiles(@RequestParam(value = "file") MultipartFile file, HttpServletRequest request) {
		Map<String,Object> reg=new HashMap<String,Object>();  
		//將記憶體中的資料寫入磁碟
		System.out.println("開始進行附件上傳");
		
		try {
				//String filePath = request.getSession().getServletContext().getRealPath("/");
				String filePath ="c:\\";
				MultipartFile file = fileArray[i];
				String originalFileName = file.getOriginalFilename();
				String newFileName = UUID.randomUUID()+originalFileName;
				String finalPath = filePath+newFileName;
				System.out.println(originalFileName);
				System.out.println(newFileName);
				System.out.println(finalPath);
				System.out.println("引數"+request.getParameter("json_filesNameArray"));
				System.out.println("file"+file.getContentType());
				
				File fileAttach = new File(finalPath);
				file.transferTo(fileAttach);
			
		} catch (Exception e1) {
			e1.printStackTrace();
		}
		//上傳成功後,將附件加入資料庫附件表的blob欄位中
		
		
	          
		  reg.put("result","success");
		  return reg;
	}

如果是多個檔案上傳 fomdata 中要使用formdata.append() 對多個檔案進行遍歷 然後進行上傳

function uploadFileMethod1()
  {
	 
      var FileController = $ctx+"/fankuiController/saveFiles";                    // 接收上傳檔案的後臺地址
      // FormData 物件
      var fileName = document.getElementById("file").value;
     // alert(getPath(fileObj));
      var form = new FormData();
      form.append("json_filesNameArray", fileName);                        // 可以增加表單資料
//      var fileArray = new Array();
//      for(var i=0;i<fileArray.length;i++)
//    {
//    	  var fileObj = document.getElementById("file").files[i]; // 獲取檔案物件
//    	  fileArray.push(fileObj);
//    }
      var files = document.getElementById("file").files;
      for(var i=0; i< files.length; i++){
    	  alert(files[i].name);
    	  form.append("fileArray",files[i]);   // 檔案物件  
    	  } 
      // XMLHttpRequest 物件
      //var fileObj = document.getElementById("file").files[0];
      //form.append("fileArray", fileObj);
      var xhr = new XMLHttpRequest();
      xhr.open("post", FileController, true);
      xhr.send(form);
      xhr.onload = function () {
          alert("上傳完成!");
      };
  }

多檔案上傳時,controller端,進行接收時 使用 MultipartFile[] 型別引數 接受 陣列型別的多個檔案,然後遍歷陣列進行操作

@RequestMapping(method = RequestMethod.POST,value = "saveFiles")
	public @ResponseBody Map<String,Object> saveFiles(@RequestParam(value = "fileArray") MultipartFile[] fileArray, HttpServletRequest request) {
		Map<String,Object> reg=new HashMap<String,Object>();  
		//將記憶體中的資料寫入磁碟
		System.out.println("開始進行附件上傳");
		System.out.println(fileArray.length);
		try {
			for(int i=0;i<fileArray.length;i++)
			{
				//String filePath = request.getSession().getServletContext().getRealPath("/");
				String filePath ="c:\\";
				MultipartFile file = fileArray[i];
				String originalFileName = file.getOriginalFilename();
				String newFileName = UUID.randomUUID()+originalFileName;
				String finalPath = filePath+newFileName;
				System.out.println(originalFileName);
				System.out.println(newFileName);
				System.out.println(finalPath);
				System.out.println("引數"+request.getParameter("json_filesNameArray"));
				System.out.println("file"+file.getContentType());
				
				File fileAttach = new File(finalPath);
				file.transferTo(fileAttach);
			}
			
		} catch (Exception e1) {
			e1.printStackTrace();
		}

第三部份:

頁面表單:

<form id="frm_identityA" action="" enctype="multipart/form-data">
	<span style="display:none">
		<input type="file" id="identityA" name="identityA" value="">
	</span>
	<input type="hidden" name="mobile" value="***********">
</form>

頁面圖片標籤和按鈕:

<img id="img_identityA" src="app/images/icon011.png" alt="" name="img_identityA"/>
<button type="button" id="button_identityA" class="btn btn-primary btn-lg btn-block">點選上傳圖片</button>

頁面操作:

// 點選按鈕的時候選擇圖片
$("#button_identityA").click(function(){
	$("#identityA").click();
});
// input框改變的時候將圖片傳送給後臺
$("#identityA").change(function() {
	var formData = new FormData($("#frm_identityA")[0]);
	$.ajax({
		url : "userregeste/file/upload.do", // 自行按需配置好完整的url,包括ip和埠號
		type : "POST",
		data : formData,
		async : false,
		cache : false,
		contentType : false,
		processData : false,
		success : function(returndata) {
			alert("success");
			$("#img_identityA").attr("src","userregeste/file/showImages.do?mobile=***********&name=identityA&"+new Date().toTimeString());
			$("#img_identityA").attr("width","124");
			$("#img_identityA").attr("height","124");
		},
		error : function(returndata) {
			alert("error");
			$("#img_identityA").attr("src","app/images/icon011.png");
		}
	});
});

Spring配置:

<!-- 圖片獲取 maxUploadSize:設定最大限制 位元組為單位-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	<property name="maxUploadSize" value="1024000"></property>
</bean>
<!-- SpringMVC檔案上傳 -->
    <bean id="multipartResolver"
        class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--defaultEncoding:請求的編碼格式必須和使用者JSP的編碼一致,以便正確讀取表單中的內容。 
            uploadTempDir:檔案上傳過程中的臨時目錄,上傳完成後,臨時檔案會自動刪除 
            maxUploadSize:設定檔案上傳大小上限(單位為位元組) -->
        <property name="defaultEncoding" value="UTF-8" />
        <property name="maxUploadSize" value="102400000" />
        <!-- uploadTempDir可以不做設定,有預設的路徑,上傳完畢會臨時檔案會自動被清理掉 -->
        <property name="uploadTempDir" value="upload/temp"></property>
    </bean>

1.defaultEncoding:表示用來解析request請求的預設編碼格式,當沒有指定的時候根據Servlet規範會使用預設值 ISO-8859-1 。當request自己指明瞭它的編碼格式的時候就會忽略這裡指定的defaultEncoding。

2.uploadTempDir:設定上傳檔案時的臨時目錄,預設是Servlet容器的臨時目錄。

3.maxUploadSize:設定允許上傳的最大檔案大小,以位元組為單位計算。當設為-1時表示無限制,預設是-1。

4.maxInMemorySize:設定在檔案上傳時允許寫到記憶體中的最大值,以位元組為單位計算,預設是10240。

Action中的程式碼:
1、圖片上傳

@Controller
@RequestMapping("/userregeste")
public class ImageUpLoadAction {
	
	/**
	 * 存放上傳的圖片資訊
	 */
	private static Map<String,byte[]> images;
	
	static {
		images = new HashMap<String, byte[]>();
	}
	
	@RequestMapping("/file/upload")
	public ProcessResultModel upLoad(HttpServletRequest request,HttpServletResponse response) {
		// 從請求中獲取到檔案資訊需要將請求轉換為MultipartHttpServletRequest型別
		MultipartHttpServletRequest MulRequest = request instanceof MultipartHttpServletRequest ? (MultipartHttpServletRequest) request : null;
		request.getParameter("mobile");// 依然可以從請求中獲取到除圖片之外的引數
		Iterator<String> fileNames = MulRequest.getFileNames();
		if (fileNames.hasNext()) { // 遍歷請求中的圖片資訊
			String fileName = fileNames.next(); // 圖片對應的引數名
			log.debug("fileName:" + fileName);
			file = MulRequest.getFile(fileName); // 獲取到圖片
			if (file != null) {
				log.debug("file.getSize():" + file.getSize()); // 圖片大小
				file.getBytes();// 可以獲取到圖片的位元組陣列
				images.put(fileName,file.getBytes());// 獲取到圖片以位元組陣列形式儲存在伺服器記憶體中
			}
		}
	}
}

2、圖片顯示
Ajax請求傳送成功之後的方法中的操作,
$("#img_identityA").attr("src","userregeste/file/showImages.do?mobile=***********&name=identityA&"+new Date().toTimeString());
值得一提的是,img標籤中的src屬性,對於UI人員來說就是存放靜態圖片資源的,但是對於程式設計師來說,應該要知道img標籤實際上會根據src屬性去傳送一次請求。同時,瀏覽器對於img的src屬性的處理方式是如果src屬性值不變,只會傳送一次請求,所有加上new Date().toTimeString(),使每次的請求都不相同。
程式碼如下:

@RequestMapping("/file/showImages")
public String showImages(HttpServletRequest request,HttpServletResponse response) throws IOException {
	log.debug("請求地址:"+request.getRequestURI()+",開始獲取圖片");
	OutputStream sout = null;
	String mobile = request.getParameter("mobile");
	String name = request.getParameter("name"); // 圖片名稱
	log.debug("mobile:"+mobile+"	name:"+name);
	if (mobile == null || name == null) {
		log.debug("手機號或圖片名為空,獲取圖片失敗!");
		return null;
	}
	byte[] pictrue = null;
	// 從本地Map中去獲取images圖片
	pictrue = images.get(name);
	log.debug("圖片大小:"+pictrue.length);
	try {
		if (pictrue != null) {
			response.setContentType("text/html");
			sout = response.getOutputStream();
			sout.write(pictrue);
			sout.flush();
			sout.close();
			sout = null;
		} else {
			return null;
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		if (sout != null) {
			sout.close();
			sout = null;
		}
	}
	log.debug("返回圖片成功!");
	return null;
}

如此,頁面上會成功的顯示圖片。

*需要的jar包除了SpringMVC的所有jar包之外,還需要
commons-fileupload-1.3.1.jar
commons-io.jar
*設定enctype="multipart/form-data"後,表單資料是以二進位制形式進行傳輸的,commons-fileupload-1.3.1.jar 即是幫我們解析二進位制資料並且封裝到parameter裡面,不新增這個包,從HttpServletRequest獲取不到引數,可以獲取二進位制流資料自行解析。
*以上的實現動態的將圖片傳送到後臺,可以在後臺對圖片進行一系列處理,效率比較高。

頁面效果: