前后端分离——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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值