前後端分離——SPA

一、 背景

1、什麼是前後端分離?

目前,大家一致認同的前後端分離的例子就是SPA(Single-pageapplication),所有用到的展現數據都是後端通過異步接口(AJAX/JSONP)的方式提供的,前端只管展現。

 

前端:負責ViewController層。

後端:只負責Model層,業務處理/數據等。

 

 

2、爲什麼要前後端分離?

1)溝通成本

 

目前的開發模式,做一些同步展現的業務效率很高,但是遇到同步異步結合的頁面,就會比較麻煩。

需要頻率與後端交互的頁面:前後端需要不斷地溝通,修改代碼、調試,開發方式過重。

 

2)前後端職責不清

 

在業務邏輯複雜的系統裏,我們最怕維護前後端混雜在一起的代碼,因爲沒有約束,M-V-C每一層都可能出現別的層的代碼,日積月累,完全沒有維護性可言。

雖然前後端分離沒辦法完全解決這種問題,但是可以大大緩解。因爲從物理層次上保證了你不可能這麼做。

現狀:業務分散在 java、php中

目標:業務集中、職責分明

 

3)開發效率問題

 

目前的架構決定了前端只能依賴後端;所以我們的開發模式依然是,前端寫好靜態demo,後端翻譯成VM模版。

現狀:

> 對前端發揮的侷限,前段無法參於頁面的組織、頁面加載的優化等。

> 後端沒法擺脫對展現層的強關注,要時不時寫一點js,從而無法專心於業務邏輯層的開發。

目標:

> 給前端自由。讓前端自由地組織頁面,控制頁面渲染的邏輯,以最優化的方式展示用戶體驗。

> 後端。再也不用碰js了,專注於後臺接口即可。

 

 

二、基礎架構

1、nodeway簡介

項目svn路徑:

http://172.16.34.106:8043/caogensvn/node/tag/nodeway_20160720

 

nodeway 基於node.js設計開發,是一個輕量級的httpsever和mvc框架,主要用於實現前後端分離。

 

Nodeway 依賴的第三方組件:

swig            一個模板渲染引擎,語法簡單,易上手

mime                 文件類型讀取插件,用於靜態文件讀取,上傳、下載文件

zlib                         文件壓縮協議,用於http server返回的數據流壓縮

formidable        node的post表單、文件上傳處理插件

log4js                   node的日誌框架

 

 

基礎架構圖:



2、設計思路

整體設計思路參考了阿里 webx、淘寶midway。

 

Ø  頁面驅動

讓前端開發、設計人員來驅動後臺開發人員,來完成頁面。

在後臺開發人員介入前,前端人員就可以進行頁面的開發(不需要後臺的支持),以保證開發進程上的並行。

 

Ø  約定優於配置

框架中減少了很多配置工作,只需要按照約定,即可輕鬆完成相應開發。

例如:

1)每一個html對應着一個url,同時又對應着一個js文件,用文件的路徑來確定了唯一的url標識,無需用編碼的方式去解析路由url;

2)每一個異步請求也對應着一個js類裏的固定的方法,通過url就能快速定位到該js文件的指定方法。

3)不同的後綴名直接映射到不同的文件,無需專門配置

 

Ø  屏蔽細節

由於nodeway是專門爲前端同學打造的,所以框架的開發過程中,屏蔽了諸如網絡通信、同步異步、請求路由、session處理、上下文對象傳遞、數據加解密等細節,使前端同學在開發過程中,可專注於數據的請求、頁面的展示。

 

Ø  靈活

nodeway在設計過程中,充分考慮了各種開發應用場景,使前端可以以簡單易懂的代碼來獲取數據、處理業務、渲染頁面,詳見開發說明章節。

 

Ø  高效

由於node.js 先天的單線程異步IO機制,所以nodeway在性能上很優越,爲前後端分離量身定製。

 

 

 

 

 

3、關於模板引擎

基於 node 的模板框架比較流行的有jade、EJS、swig等,如下圖所示:

 

當下node最流行的mvc框架express 默認使用的是 Jade 引擎,然而jade是一個過度設計的產品,學習成本過高,所以不採用。

 

我們對模板引擎的基本要求是:

1、所見即所得,可直接拿前端切好的頁面當作模板

2、語法簡單,學習成本低,易上手

3、基礎功能完善,如標籤轉義、字段過濾等

4、擴展性強,支持inclule 、macro等特性

5、速度不要太慢

基於以上各點,我最終選擇 swig 作來本框架的模板引擎。

 

 

 

 三、下載安裝

1、目錄結構

nodeway svn路徑:

http://172.16.34.106:8043/caogensvn/node/tag/nodeway_20160720

 

node_modules                    node的第三方庫

webapp                                   項目主路徑

           controll                         mvc中的controll層(控制層),編寫應用邏輯的地方

                     rpc                        ajax請求的處理類

                     screen                 html模塊頁的處理類

           lib                                     核心庫

           resource            靜態資源庫

           view                      mvc中的view層(展示層)

                     error                    錯誤信息頁面

                     include                頁面片面模板

                     layout                  佈局文件模板

                     macro                 宏定義模板文件

                     screen                 html模板

nodeway.json           配置文件

startup.sh/bat                    啓動腳本

 

2、安裝node

https://nodejs.org/download/release/v6.3.0/

去該網站上下載相應環境包安裝即可,

 

如何驗證安裝成功?

通過  node –v npm –v ,如果有返回值則表示安裝成功,如下圖所示

 

3、啓動項目

 

Ø  運行startup.sh(win平臺startup.bat) 即可;

Ø  帶參數的啓動

默認啓動會加載 nodeway.json配置文件 (參數項說明見註釋), 裏面的配置參數可通過命令行參數覆蓋,如:

node ./webapp/lib/main.js --model=dev--listen_port=9001 &

 

 

 

四、開發與使用

1、html請求

在 webapp/view/screen 下新建 xxx.html 文件(可含子路徑),

此時通過瀏覽器 http://localhost:9000/xxx.html 就可查看到該頁面

1)向 html頁面傳遞參數

傳參方式分get、post兩種,在 nodeway中2種方式的處理方式相同:

在 webapp/controll/screen下新建相同文件名的js文件(子路徑也要相同),

編寫以下代碼

 

module.exports = function(context,session){

   var userName = context.get('userName');

var pwd =context.get('passwrod');

//此處調用接口並進行其它業務操作

};

 

使用 context.get('參數名') 即可獲取相應的請求參數。

 

 

2)輸出內容到html

在webapp/view/screen目錄下,每一個html文件都是一個模板頁面,使用swig模板引擎渲染,我們只需要在對應的js中輸出數據,然後在html文件中用swig標籤展示數據即可,如:

使用以下代碼輸出數據到上下文對象:

context.put('address','中國');

context.put('day',‘2016-07-20’ );

 

然後在對應的 html 即可輸出:

           地址:{{ address }}<br/>

           日期:{{ day }}

 

最終瀏覽器端會展示

           地址:中國<br/>

           日期:2016-07-12

 

 

swig 使用手冊:

官方:http://paularmstrong.github.io/swig/docs

中文翻譯:http://www.cnblogs.com/elementstorm/p/3142644.html

 

3)url重寫

對於get請求,出於網站seo的需求,/xxx/xxx.html?param1=333&param2=333

這樣的請求需要轉義,去掉.html後面的參數,所以nodeway實現了url重寫的功能,具體如下:

原始url:/xxx/xxx.html?param1=p1&param2=p2

目標url:/xxx/xxx_p1_p2.html

可以看出,我們用下劃線_拼接了參數值,做爲了url的一部分,那麼怎樣去接收參數值?

首先 /xxx/xxx_p1_p2.html 會路由到 webapp/view/screen/xxx/xxx.html模板和 webapp/controll/screen/xxx/xxx.js處理類

下面xxx.js文件中的關鍵代碼:


其中:

 

以下代碼表示該類可接收 url 重寫發來的請求:

           this.urlRewrite= true;

   if(!this.urlRewrite){

       return ;

    }

 

如果 this.urlRewrite爲true,那麼我們在接收參數時,就要用數組方式接收了,如:

   var userId = context.get(0);

var userName =context.get(1);

 

 

 

2、ajax請求

在nodeway中,ajax請求分2種:.do .json,其中.do請求是在webapp/controll/rpc中自定義的,而.json請求只做了一層中轉(處理一些安全校驗的邏輯)直接從java api接口請求數據返回到瀏覽器。

 

1).do請求

該類請求只有js文件,沒有對應的html模板頁,全部以json格式輸出

例如我們要編寫/user/order/detail.do,並獲取返回值:{success:true, data:{}}

此時我們需要編寫js文件: webapp/controll/rpc/user/order.js

由此可看出 一個.do請求對應着rpc類中一個方法,detail.do對應着detail方法

在rpc中接收參數的方式與 screen相同。

唯一不同之處是需要在 rpc方法中 返回一個對象,該對象會直接以json格式輸出到瀏覽器。

 

2).json請求

該類請求無需前端同學處理,會直接發送到java服務器,並輸出數據到頁面。

 

 

3、靜態文件請求

nodeway在處理動態頁面渲染的同時,也是一個類似於nginx的http server,只需將文件放在resource目錄下,然後通過對應的url請求即可。

4、session緩存

對於有些數據,我們希望能做緩存,而不是每次請求時都去請求接口,例如用戶的登錄狀態信息。Session機制提供了會話級的緩存,在用戶清除cookie或者cookier失效前,cookie中的數據會一直存在。

session讀寫數據的方法:

//寫數據

session.put(‘name’,  ‘張三’);

session.put(‘age, 25);

//讀數據

var name = session.get(‘name’);

var age = session.get(‘age’);

 

5、網絡請求

由於只支持異常IO去做網絡請求,所以在nodeway中,所有的網絡請求、業務數據處理都是以回調函數的形式來實現的,具體如下:

注:如果是.do請求,需在最後一個 httpCallbackreturn一個對象。

 

1)單次請求

   session

             .httpRequest({

                        url:'/cgjr/account/login.json',  //請求url,可使用絕對地址

                        method: 'post',                   //可爲空,默認爲post

                        dataType: 'json',                  //可爲空,默認爲json,不爲json時返回字符串

                        param:{                                   //請求參數

                                  'account':'15722222223',

                                  'password':session.md5('123456'),

                                  'accessFrom':'nodeway'

                        }

             }).httpCallback(function(result){    //請求成功後以回調形式處理數據

            context.put('result', result.data);

             });

 

2)依賴請求

即請求B依賴請求A返回的結果。

場景舉例:在用戶登錄成功後,將用戶的訂單信息返回到頁面

  session

             .httpRequest({

                        url:'/cgjr/account/login.json',

                        method: 'post',

                        dataType: 'json',

                        param:{

                                  'account':'15722222223',

                                  'password':session.md5('123456'),

                                  'accessFrom':'nodeway'

                        }

             })

             .httpCallback(function(result){

                        if(result.success){

                                  return result;             //如果請求成功,則返回數據

                        }else{

                                  throw newError(result.errorMsg);                 //拋出異常,終止後續請求

                        }

             })

             .httpRequest({

                        url:'/cgjr/order/list.json',

                        method: 'post',

                        param:{

                                  'userId':'${data.userId}',          //引用了上一接口返回的數據 userId

                                  'token':'${data.token}',  //引用返回數據 token

                                  'accessFrom': 'nodeway'

                        }

             })

             .httpCallback(function(result){

                        context.put('orderList',result.data);     //將最終結果返回到頁面

             });

 

以上代碼中,第一個接口返回的數據是:

{"data":{"mobileNumber":15722222223,"personCard":"4205021*****86538","shortUrl":"bcAJkJn","status":2,"token":"9514f4e3092b47b29ef0831d6bd6f569","userId":191800180277,"userRealName":"張三丰"},"success":true}

3)併發請求

指在一個業務場景中,需要從多個http請求中獲取數據。

場景舉例:個人中心首頁,需要同時展示用戶基本信息和用戶的借款信息

 

session

             .httpRequest({

                        url:'/cgjr/user/get_info.json',          

                        param:{

                                   'user_id': session.get('userId'),

                'token': session.get('token'),

                'accessFrom': 'nodeway'

                        }

             })

       .httpRequest({

           url: '/cgjr/order/get_order_list.json',

           param:{

                'user_id': session.get('userId'),

                'token': session.get('token'),

                'accessFrom': 'nodeway'

           }

       })     

             .httpCallback(function(result){

           var data1 = result[0];                  //此處需用數組來獲取返回數據

                                vardata2 = result[1];

                        …….

             })

 

[ 從代碼結構上來看,只是比依賴請求少了一個 httpCallack,但本質上有很大區別 ]

 

在該模式下,若干個http請求會併發執行,直到全部獲取到數據後,纔會執行 httpCallback方法。此時返回的是數組對象,數組的長度等於http請求的數量,數組內結果的順序與httpRequest請求的順序相同

 

 

6、文件上傳下載

1)文件上傳

直接上代碼,

前端html:

 

後端js:

 

控制檯輸出:

{"size":6164,"path":"/Users/xiaobowang/upload_d04ff55a025783aa9dd1ffb8c7c12820","name":"繪圖.png","type":"image/png","mtime":"2016-07-20T11:23:03.234Z"}

 

從輸出的json可以看出nodeway上傳文件的處理很簡潔,屏蔽了複雜的文件流處理細節。

nodeway文件上傳是先將post上傳的文件存儲到本地路徑,然後再以操作本地文件的方式做後續處理。

 

 

2)文件下載

 

文件下載是在controll/screen目錄下的js裏完成的,該請求無需對應指定的html頁面,在訪問該請求後,瀏覽器端會以流的形式下載文件

 

 

 

7、核心對象說明

1)context

上下文對象,只在一次 http 請求流程中有效。

//設置上下文變量值,此方法可將key對應的值傳遞到swig頁面模板中

context.put(key, value);


//獲取上下文變量的值,變量值來自於getpost的參數值

context.get(key) ;


//刪除變量

context.remove(key) ;


//清除所有上下文變量值

context.clear();


//獲取nodeway.json中配置的全局環境變量

context.getConfig(configKey) ;


2)session
會話級緩存,基於 cookie 實現,從打開站點中任何一個頁面時,對象開始

生效,緩存時間受 session_expire 影響。

//緩存值到session,value不能是對象類型

session.put(key, value);


// session取值

session.get(key);


// session中刪除對象

session.remove(key);


// 清除session中的所有值

session.clear(key);


// 重定義向頁面,一般寫在screen對應的js

session.redirect(uri);


// 下載 downloadPath必須是一個完整的url路徑

session.download(downloadPath);


// 工具類方法:取inputmd5

session.md5(input);


//http請求方法(使用方法見 4.5:網絡請求)

session.httpRequest(cfgObj);


//http請求響應方法(使用方法見 4.5:網絡請求)

session.httpCallback(functionCallback);


//拋異常,業務出錯時執行,用於返回出錯信息至頁面,並終止後後續代碼執行

session.throwError('錯誤信息!'); 





 

五、性能測試

測試機:
vmware虛擬機(cpu: 單核2.0G, 內存:1G, 網卡:100M)



 

 

 

 

 

 

六、參考資料

nodewaysvn路徑:

http://172.16.34.106:8043/caogensvn/node/tag/nodeway_20160720

 

 

swig使用手冊:

官方:http://paularmstrong.github.io/swig/docs/

中文翻譯:http://www.cnblogs.com/elementstorm/p/3142644.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章