1. 程式人生 > >HTML5 jQuery+FormData 非同步上傳檔案,帶進度條

HTML5 jQuery+FormData 非同步上傳檔案,帶進度條

利用jQuery和html5的FormData非同步上傳檔案的好處是:

  • 實現很簡單
  • 很方便地支援進度條
  • 很方便地進行擴充套件和美化

先看看效果圖:


圖片上傳後的結果:


實現步驟如下:

第二步:上傳頁面的html程式碼:

[html] view plain copy print?
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <link href="../resources/css/common.css" rel="stylesheet" />
  6. <script
    src="../resources/js/jquery-2.1.4.js"></script>
  7. </head>
  8. <body>
  9. <h2>HTML5非同步上傳檔案,帶進度條</h2>
  10. <form method="post" enctype="multipart/form-data">
  11. 其他需要提交的資訊:<input type="text" name="otherInfo"/><br/><br/>
  12. 選擇要上傳的檔案:<br/>
  13. <input type="file" name="file"
    /><span></span><br/>
  14. <input type="file" name="file" /><span></span><br/>
  15. </form>
  16. <br/><br/>
  17. <input type="button" value="上傳吧" onclick="upload()"/>
  18. <br/><br/>
  19. 上傳進度:<progress></progress><br/>
  20. <p id="progress">
    0 bytes</p>
  21. <p id="info"></p>
  22. </body>
  23. </html>
<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<link href="../resources/css/common.css" rel="stylesheet" />
	<script src="../resources/js/jquery-2.1.4.js"></script>
	
</head>

<body>
	<h2>HTML5非同步上傳檔案,帶進度條</h2>
	<form method="post" enctype="multipart/form-data">
		其他需要提交的資訊:<input type="text" name="otherInfo"/><br/><br/>
		選擇要上傳的檔案:<br/>
		<input type="file" name="file" /><span></span><br/>
		<input type="file" name="file" /><span></span><br/>
	</form>
	
	<br/><br/>
	<input type="button" value="上傳吧" onclick="upload()"/>
	<br/><br/>
	上傳進度:<progress></progress><br/>
	<p id="progress">0 bytes</p>
	<p id="info"></p>
</body>
</html>


第三步:非同步上傳的JavaScript程式碼(註釋很詳細):

[javascript] view plain copy print?
  1. <script>
  2. var totalSize = 0;
  3. //繫結所有type=file的元素的onchange事件的處理函式
  4. $(':file').change(function() {
  5. var file = this.files[0]; //假設file標籤沒開啟multiple屬性,那麼只取第一個檔案就行了
  6. name = file.name;
  7. size = file.size;
  8. type = file.type;
  9. url = window.URL.createObjectURL(file); //獲取本地檔案的url,如果是圖片檔案,可用於預覽圖片
  10. $(this).next().html("檔名:" + name + " 檔案型別:" + type + " 檔案大小:" + size + " url: " + url);
  11. totalSize += size;
  12. $("#info").html("總大小: " + totalSize + "bytes");
  13. });
  14. function upload() {
  15. //建立FormData物件,初始化為form表單中的資料。需要新增其他資料可使用formData.append("property", "value");
  16. var formData = new FormData($('form')[0]);
  17. //ajax非同步上傳
  18. $.ajax({
  19. url: "http://localhost:8080/MyJavaStudio/servlet/file/upload",
  20. type: "POST",
  21. data: formData,
  22. xhr: function(){ //獲取ajaxSettings中的xhr物件,為它的upload屬性繫結progress事件的處理函式
  23. myXhr = $.ajaxSettings.xhr();
  24. if(myXhr.upload){ //檢查upload屬性是否存在
  25. //繫結progress事件的回撥函式
  26. myXhr.upload.addEventListener('progress',progressHandlingFunction, false);
  27. }
  28. return myXhr; //xhr物件返回給jQuery使用
  29. },
  30. success: function(result){
  31. $("#result").html(result.data);
  32. },
  33. contentType: false, //必須false才會自動加上正確的Content-Type
  34. processData: false //必須false才會避開jQuery對 formdata 的預設處理
  35. });
  36. }
  37. //上傳進度回撥函式:
  38. function progressHandlingFunction(e) {
  39. if (e.lengthComputable) {
  40. $('progress').attr({value : e.loaded, max : e.total}); //更新資料到進度條
  41. var percent = e.loaded/e.total*100;
  42. $('#progress').html(e.loaded + "/" + e.total+" bytes. " + percent.toFixed(2) + "%");
  43. }
  44. }
  45. </script>
<script>
	var totalSize = 0;
	
	//繫結所有type=file的元素的onchange事件的處理函式
	$(':file').change(function() {
		var file = this.files[0]; //假設file標籤沒開啟multiple屬性,那麼只取第一個檔案就行了
		name = file.name;
		size = file.size;
		type = file.type;
		url = window.URL.createObjectURL(file); //獲取本地檔案的url,如果是圖片檔案,可用於預覽圖片
		
		$(this).next().html("檔名:" + name + " 檔案型別:" + type + " 檔案大小:" + size + " url: " + url);
		
		totalSize += size;
		
		$("#info").html("總大小: " + totalSize + "bytes");
		
	});

	function upload() {
		//建立FormData物件,初始化為form表單中的資料。需要新增其他資料可使用formData.append("property", "value");
		var formData = new FormData($('form')[0]);
		
		//ajax非同步上傳
		$.ajax({
			url: "http://localhost:8080/MyJavaStudio/servlet/file/upload",
			type: "POST",
			data: formData,
			xhr: function(){ //獲取ajaxSettings中的xhr物件,為它的upload屬性繫結progress事件的處理函式
			
				myXhr = $.ajaxSettings.xhr();
				if(myXhr.upload){ //檢查upload屬性是否存在
					//繫結progress事件的回撥函式
					myXhr.upload.addEventListener('progress',progressHandlingFunction, false); 
				}
				return myXhr; //xhr物件返回給jQuery使用
			},
			success: function(result){
				$("#result").html(result.data);
			},
			contentType: false, //必須false才會自動加上正確的Content-Type
			processData: false  //必須false才會避開jQuery對 formdata 的預設處理
		});
	}		

	//上傳進度回撥函式:
	function progressHandlingFunction(e) {
		if (e.lengthComputable) {
			$('progress').attr({value : e.loaded, max : e.total}); //更新資料到進度條
			var percent = e.loaded/e.total*100;
			$('#progress').html(e.loaded + "/" + e.total+" bytes. " + percent.toFixed(2) + "%");
		}
	}
</script>

第四步:SpringMVC寫好接受和保持檔案的Controller方法: [java] view plain copy print?
  1. /**
  2. * 檔案上傳
  3. * @author XuJijun
  4. *
  5. */
  6. @RestController
  7. @RequestMapping("/servlet/file")
  8. public class FileUploadController {
  9. /**
  10. * 儲存檔案的目錄,放在web目錄、或一個指定的絕對目錄下
  11. */
  12. private static final String SAVE_DIR = "uploadFiles";
  13. /**
  14. *
  15. * @param request
  16. * @param response
  17. * @param p form表單中,type="text"的input控制元件,內容通過這個引數傳送過來,以input控制元件中的name屬性來區分
  18. * @return JSON表示的處理結果
  19. * @throws ServletException
  20. * @throws IOException
  21. */
  22. @RequestMapping("/upload")
  23. public JsonResult upload(HttpServletRequest request, HttpServletResponse response, @RequestParam Map<String, Object> p)
  24. throws ServletException, IOException {
  25. // 獲取 web application的絕對路徑
  26. String appPath = request.getServletContext().getRealPath("");
  27. // 構造檔案存放的路徑
  28. String savePath = appPath + File.separator + SAVE_DIR;
  29. // 如果檔案存放路徑不存在,則mkdir一個
  30. File fileSaveDir = new File(savePath);
  31. if (!fileSaveDir.exists()) {
  32. fileSaveDir.mkdirs();
  33. }
  34. List<String> fileNames = new ArrayList<>();
  35. //迴圈所有的part,把part中的檔案儲存到硬碟中
  36. for (Part part : request.getParts()) {
  37. String fileName = part.getSubmittedFileName();
  38. //form表單中的每個input,都在一個不同的part中,
  39. //所以需要判斷通過fileName是否為空,過濾掉其他型別的input(比如type="text"):
  40. if(!StringUtils.isEmpty(fileName)){
  41. part.write(savePath + File.separator + fileName);
  42. fileNames.add(fileName);
  43. }
  44. }
  45. Map<String, Object> resultData = new HashMap<>();
  46. resultData.put("savePath", savePath);
  47. resultData.put("files", fileNames);
  48. return new JsonResult("200", "檔案上傳成功!", resultData);
  49. }
  50. /**
  51. * 從content-disposition頭中獲取原始檔名
  52. *
  53. * content-disposition頭的格式如下:
  54. * form-data; name="dataFile"; filename="PHOTO.JPG"
  55. *
  56. * @param part
  57. * @return
  58. */
  59. @SuppressWarnings("unused")
  60. private String extractFileName(Part part) {
  61. String contentDisp = part.getHeader("content-disposition");
  62. String[] items = contentDisp.split(";");
  63. for (String s : items) {
  64. if (s.trim().startsWith("filename")) {
  65. return s.substring(s.indexOf("=") + 2, s.length()-1);
  66. }
  67. }
  68. return "";
  69. }
  70. }
/**
 * 檔案上傳
 * @author XuJijun
 *
 */
@RestController
@RequestMapping("/servlet/file")
public class FileUploadController {
	
	/**
	 * 儲存檔案的目錄,放在web目錄、或一個指定的絕對目錄下
	 */
	 private static final String SAVE_DIR = "uploadFiles";
	
	 /**
	  * 
	  * @param request
	  * @param response
	  * @param p form表單中,type="text"的input控制元件,內容通過這個引數傳送過來,以input控制元件中的name屬性來區分
	  * @return JSON表示的處理結果
	  * @throws ServletException
	  * @throws IOException
	  */
	@RequestMapping("/upload")
	public JsonResult upload(HttpServletRequest request, HttpServletResponse response, @RequestParam Map<String, Object> p)
			throws ServletException, IOException {

		// 獲取 web application的絕對路徑
		String appPath = request.getServletContext().getRealPath("");
		
		// 構造檔案存放的路徑
		String savePath = appPath + File.separator + SAVE_DIR;

		// 如果檔案存放路徑不存在,則mkdir一個
		File fileSaveDir = new File(savePath);
		if (!fileSaveDir.exists()) {
			fileSaveDir.mkdirs();
		}

		List<String> fileNames = new ArrayList<>();
		
		//迴圈所有的part,把part中的檔案儲存到硬碟中
		for (Part part : request.getParts()) {
			String fileName = part.getSubmittedFileName();
			
			//form表單中的每個input,都在一個不同的part中,
			//所以需要判斷通過fileName是否為空,過濾掉其他型別的input(比如type="text"):
			if(!StringUtils.isEmpty(fileName)){ 
				part.write(savePath + File.separator + fileName);
				fileNames.add(fileName);
			}
		}

		Map<String, Object> resultData = new HashMap<>();
		resultData.put("savePath", savePath);
		resultData.put("files", fileNames);
		
		return new JsonResult("200", "檔案上傳成功!", resultData);
	}
	 
	/**
	 * 從content-disposition頭中獲取原始檔名
	 * 
	 * content-disposition頭的格式如下:
	 * form-data; name="dataFile"; filename="PHOTO.JPG"
	 * 
	 * @param part
	 * @return
	 */
	@SuppressWarnings("unused")
	private String extractFileName(Part part) {
	    String contentDisp = part.getHeader("content-disposition");
	    String[] items = contentDisp.split(";");
	    for (String s : items) {
	        if (s.trim().startsWith("filename")) {
	            return s.substring(s.indexOf("=") + 2, s.length()-1);
	        }
	    }
	    return "";
	}

}

最後那個私有方法可以不用的,只是為了演示如何直接獲取request header中的資料。

最後,驗證上傳過程中的網路訊息:

上傳的訊息頭和資料:


可見,對於表單中的3個input,http request payload中對應有3個part來上傳資料。

Controller處理後的返回結果(JSON格式):


總結:程式碼很簡單,結果很友好。感謝HTML5和SpringMVC!