前後端數據交互,跳轉

  • 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協議。

json交互 

  • 上圖所示,如果格式前後端發送和接收對不上,那是獲取不了數據。如圖的最後一步返回,如果想頁面跳轉,則不需要@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行)
  • 可以規定統一的前端交互流程,並且修改這個流程也方便。
    1. 根據傳輸類型也對參數進行轉換(20-24行),並且_convertParam()方法能繼續擴展參數類型,直到後面可以把絕大多數參數都轉換成正確的格式(那發送基本不用考慮參數格式了,只要確定傳輸的類型就好)              
    2. 統一回調流程(70-103行)可以對異常做統一處理(ajax)像我這樣跳轉到錯誤頁面,或者有些非同步請求返回時需要跳轉,也能統一跳轉
    3. 統一請求發送前的處理(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