前後端數據交互,跳轉
- 2019 年 10 月 3 日
- 筆記
概述
作為一枚菜鳥,前後端交互可是大問題,經常數據交互失敗,不知道怎麼跳轉。在這分享一下交互的小心得。
我們不妨先大概了解一下整個訪問流程:
用戶從輸入網址按下回車,交互就已經開始了。
瀏覽器會將請求按照http協議(或者其他https,ftp等)將請求數據封裝包從電腦的埠發出 -> 路由器 -> 運營商(域名解析之類的)-> 目標伺服器(可能會有代理,負載均衡等等)
最終從伺服器開放的埠,進入到伺服器(TCP三次握手和四次揮手,Tomcat之類的,會根據協議的內容進行解析)-> web項目進行具體的邏輯處理 -> 返回數據 -> 瀏覽器接收數據(根據協議內容進行解析)
整個過程,協議很重要,因為客戶端和伺服器都是通過協議來解析和發送資訊的,最常見的http協議,協議頭部的參數常用的哪幾個,有什麼影響。
Http協議的簡介,說的很有意思:https://blog.csdn.net/u010256388/article/details/68491509/
還有快取問題(cookie、session、localstorage、cashe-controller等),不僅提高效率,而且有時候你更改了程式碼測試卻沒變化的重要原因:https://www.jianshu.com/p/9ed3e8759ce3
相應的後台tomcat的快取處理:http://www.360doc.com/content/17/0721/17/41344223_673116604.shtml(了解即可)
具體到使用分為:前端交互,分為同步和非同步。
同步交互
常見的from表單提交(post方式帶參數),URL直接訪問(get方式,有參數在地址欄可見,特殊符號需要轉義,不安全)。
非同步交互
關鍵是XMLHttpRequest對象,平時說的ajax只是一種交互模式,並不是什麼新技術,其原理就是對XMLHttpRequest的封裝。所以對XMLHttpRequest深入全面的了解可以幫助我們更好的掌握非同步交互。
可以看這篇精品文章了解:https://www.jianshu.com/p/b037f71af548,裡面涉及到了在傳送過程中各個參數的用法,會觸發的事件,返回值等等很實用的知識。(雖然能設置同步交互,但不推薦)
其中傳輸類型 contentType就非常重要,這個值設置決定了後台怎麼去解析http協議。
- 上圖所示,如果格式前後端發送和接收對不上,那是獲取不了數據。如圖的最後一步返回,如果想頁面跳轉,則不需要@ResponseBody,改為返回ModelAndView
- @RequestBody主要用來接收前端傳遞給後端的json字元串中的數據的(請求體中的數據的);GET方式無請求體,所以使用@RequestBody接收數據時,前端不能使用GET方式提交數據,而是用POST方式進行提交。
- @RequestParam接收的是key-value裡面的參數,所以GET方式的數據和表單提交,可以接收。
- @RequestBody和@RequestParma的使用知識,更輕鬆的全面掌握:https://blog.csdn.net/justry_deng/article/details/80972817
前端交互類
前端我創建了兩個工具類,來負責交互。
非同步交互類
我把jQuery的ajax進行了封裝,主要是獲取和計算資訊,程式碼如下:
1 /* 2 * 以下為程式錯誤碼 3 */ 4 //通用的請求失敗,包括未知原因 5 var EXPECTATION_FAILED = 417; 6 var EXPECTATION_QUERY = 404; 7 8 /** 9 * 訪問後台的對象,為ajax封裝 10 * @constructor 11 */ 12 var Query = function (url, param, callback, contentType) { 13 this.url = url; 14 15 //先確認參數存在,如果不存在則創建空map 16 if (!param) { 17 param = new Map(); 18 } 19 //注意,要根據不同的傳輸格式來確定傳輸的值的類型 20 if (contentType == Query.NOMAL_TYPE) { 21 this.param = JSON.parse(this._convertParam(param)); 22 } else { 23 this.param = this._convertParam(param); 24 } 25 26 27 this.callback = callback; 28 this.contentType = contentType; 29 //請求超時,默認5秒 30 this.timeout = 5000; 31 //是否非同步請求,默認非同步 32 this.async = true; 33 } 34 35 Query.JSON_TYPE = 'application/json'; 36 Query.NOMAL_TYPE = 'application/x-www-form-urlencoded'; 37 38 /** 39 * ajax請求的訪問 40 * @param url 要訪問的地址 41 * @param paramMap 傳給後台的Map參數,key為字元串類型 42 * @param callback 回調函數 43 * @param contentType 傳輸數據的格式 默認傳輸application/x-www-form-urlencoded格式 44 */ 45 Query.create = function (url, paramMap, callback) { 46 return new Query(url, paramMap, callback, Query.NOMAL_TYPE); 47 } 48 49 Query.createJsonType = function (url, paramMap, callback) { 50 return new Query(url, paramMap, callback, Query.JSON_TYPE); 51 } 52 53 /** 54 * 將ParamMap轉為json格式,目前只支援Map對象,以後會擴展 55 * @param paramMap 56 * @private 57 */ 58 Query.prototype._convertParam = function (param) { 59 60 if (param instanceof Map) { 61 return strMap2Json(param); 62 } 63 } 64 65 /** 66 * 對ajax回調函數的封裝 67 * @param callBack 68 * @private 69 */ 70 Query.prototype._callback = function (queryResult) { 71 72 //取消載入框 73 if (this.loadDom) { 74 $(this.loadDom).remove("#loadingDiv"); 75 } 76 77 //Query對象 78 var self = queryResult.queryObj; 79 var data = $.parseJSON(queryResult.responseText); 80 //記錄請求是否有錯誤 81 self.queryException = false; 82 var handleError; 83 84 if (queryResult.status == EXPECTATION_FAILED || queryResult.status == EXPECTATION_QUERY) { 85 var error = queryResult.responseText; 86 self.queryException = true; 87 } 88 89 //調用回調函數,如果返回結果為true,則對於出錯不會默認錯誤處理 90 if (self.callback instanceof Function) { 91 handleError = self.callback(data); 92 } 93 94 //如果出現了異常並且沒有被處理,那麼將進行默認錯誤處理 95 if (self.queryException && !handleError) { 96 window.location.href = "/system/error/" + error.code + "/" + error.msg; 97 } 98 99 //如果需要跳轉,則進行跳轉 100 if (data.redirect_url) { 101 window.location.href = data.redirect_url; 102 } 103 } 104 105 /** 106 * 正式發送ajax 107 * @private 108 */ 109 Query.prototype.sendMessage = function () { 110 var self = this; 111 var xhr = $.ajax( 112 { 113 type: "post", 114 url: this.url, 115 contentType: this.contentType, 116 data: this.param, 117 // ajax發送前調用的方法,初始化等待動畫 118 // @param XHR XMLHttpRequest對象 119 beforeSend: function (XHR) { 120 //綁定本次請求的queryObj 121 XHR.queryObj = self; 122 if (self.beforeSendFunc instanceof Function) { 123 self.beforeSendFunc(XHR); 124 } 125 126 if (self.loadDom instanceof HTMLElement) { 127 self.loadDom.innerText = ""; 128 $(self.loadDom).append("<div id='loadingDiv' class='loading'><img src='/image/loading.gif'/></div>"); 129 } else if (self.loadDom instanceof jQuery) { 130 self.loadDom.empty(); 131 self.loadDom.append("<div id='loadingDiv' class='loading'><img src='/image/loading.gif'/></div>"); 132 } 133 }, 134 complete: this._callback, 135 timeout:this.timeout, 136 async:this.async 137 } 138 ); 139 140 } 141 142 /** 143 * 檢測是否有錯誤,返回ture有錯誤,或者false 144 */ 145 Query.prototype.checkEception = function () { 146 return this.queryException; 147 } 148 149 //------------------------以下為對Query的參數設置--------------------------- 150 /** 151 * 在ajax發送前設置參數,可以有載入的動畫,並且請求完成後會自動取消 152 * @param loadDom 需要顯示動畫的dom節點 153 * @param beforeSendFunc ajax發送前的自定義函數 154 */ 155 Query.prototype.setBeforeSend = function (loadDom, beforeSendFunc) { 156 this.loadDom = loadDom; 157 this.beforeSendFunc = beforeSendFunc; 158 } 159 160 /** 161 * 設置超時時間 162 * @param timeout 163 */ 164 Query.prototype.setTimeOut = function (timeout) { 165 this.timeout = timeout; 166 } 167 168 Query.prototype.setAsync = function (async) { 169 this.async = async; 170 }
封裝的好處:
- 可以省略很多重複的程式碼,如$.ajax傳參數那一長串(111-136行)
- 可以規定統一的前端交互流程,並且修改這個流程也方便。
-
- 根據傳輸類型也對參數進行轉換(20-24行),並且_convertParam()方法能繼續擴展參數類型,直到後面可以把絕大多數參數都轉換成正確的格式(那發送基本不用考慮參數格式了,只要確定傳輸的類型就好)
- 統一回調流程(70-103行)可以對異常做統一處理(ajax)像我這樣跳轉到錯誤頁面,或者有些非同步請求返回時需要跳轉,也能統一跳轉
- 統一請求發送前的處理(119-133行)可以對請求等待統一的設置等待動畫,最後再回調流程統一取消動畫
同步交互類
from表單創建類,主要是進行有參數傳輸的頁面跳轉,因為直接URL跳轉參數會暴露不安全
1 /** 2 * 訪問後台的類,構造form表單來進行post請求 3 * @param url 4 * @param paramMap 參數map 5 * @constructor 6 */ 7 var QueryForm = function (url,paramMap) { 8 //form表單的JQ對象 9 this.form = $("<form></form>"); 10 this.form.attr("action",url); 11 this.form.attr("method","post"); 12 //遍歷Map 13 for(var [key,value] of paramMap){ 14 var inputDom = $("<input/>") ; 15 inputDom.attr("name",key); 16 inputDom.attr("value",value); 17 this.form.append(inputDom); 18 } 19 //必須要放入body裡面,不然請求發不出去 20 var bodyDom = $("body"); 21 bodyDom.append(this.form); 22 this.sendMessage(); 23 24 //發送完後銷毀 25 this.destroy(); 26 } 27 28 QueryForm.create = function (url, paramMap) { 29 return new QueryForm(url,paramMap); 30 } 31 32 QueryForm.prototype.sendMessage = function () { 33 this.form.submit(); 34 } 35 36 QueryForm.prototype.destroy = function () { 37 this.form.remove(); 38 }
後端交互類
普通的Controller,在@RequestMapping中填寫正確的路徑,根據前端傳輸的數據類型來獲取參數(見上文),根據邏輯來確定要不要@ResponseBody來返回資訊,還是ModelAndView來進行頁面跳轉
路徑
經常404怎麼辦,那就是路徑有問題。得先明白,java虛擬機運行的不是java文件而是編譯後的.calss文件,所以最先檢查的是target文件夾下是否有你URL寫的這個文件,是否路徑正確
平時網上看到的classpath,就是指target下的classes文件夾
順帶提一句,如果是eclipse,右鍵點擊新建,你會發現有幾個文件夾可以選
folder:就是普通的文件夾,它和我們window下面使用的文件夾沒有任何區別
source folder:文件夾是一種特別的文件夾,如果你用面向對象的思想去看待這個source folder,那麼他是folder的一個子集,作為子集,肯定是有folder的所有功能,而且還有自己特別的功能,他的特別之處,就是在source folder下面的java文件都會被編譯,編譯後的文件會被放在我們設置的某個文件夾下面(一般我們設置成WEB-INF/classes),source folder下面的非java文件會被copy一份放在我們的設置的文件夾下面
package:文件夾也是一種特別的文件夾,他的特別之處在於:他必須存在於source folder下面,上下級通過.來區分,他的路徑最後組成了每一個類的包路徑名
所以當出現404,請先檢查路徑,如果是springboot項目,則要遵守一些默認路徑規則,比如模板必須放在templates,靜態文件必須放在static