前后端分离——SPA

一、 背景

1、什么是前后端分离?

目前,大家一致认同的前后端分离的例子就是SPA(Single-pageapplication),所有用到的展现数据都是后端通过异步接口(AJAX/JSONP)的方式提供的,前端只管展现。

前端:负责View和Controller层。

后端:只负责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请求,需在最后一个 httpCallback里return一个对象。

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);

//获取上下文变量的值,变量值来自于get或post的参数值

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);

// 工具类方法:取input的md5值

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

(0)

相关推荐