前后端分离——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¶m2=333
这样的请求需要转义,去掉.html后面的参数,所以nodeway实现了url重写的功能,具体如下:
原始url:/xxx/xxx.html?param1=p1¶m2=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