什么是nodejs中间层?前端
就是前端---请求---> nodejs ----请求---->后端 ----响应--->nodejs--数据处理---响应---->前端。这么一个流程,这个流程的好处就是当业务逻辑过多,或者业务需求在不断变动的时候,前端不须要过多当去改变业务逻辑,与后端低耦合。前端即显示,渲染。后端获取和存储数据。中间层处理数据结构,返回给前端可用可渲染的数据结构。java
nodejs是起中间层的做用,即根据客户端不一样请求来作相应的处理或渲染页面,处理时能够是把获取的数据作简单的处理交由底层java那边作真正的数据持久化或数据更新,也能够是从底层获取数据作简单的处理返回给客户端。node
1.技术选型,我选择了express框架开发中间层。nginx
2.$ npm install express --save
下载express包.git
3.$npm install express-generator -g
使用express生成器,生成一个完成的项目结构。github
3.$npm install pm2 -g
下载pm2应用全局.web
4.$pm2 start express项目名称/bin/www --watch
express项目,并监听一些文件的改变,一旦代码变更,自动重启nodejs服务.express
6.开始编写这个中间件:npm
interceptor.jsjson
const log = require('../util/log');
//引用async库。
const async = require('async'),
NO_TOKEN = [
//无需 校验token的接口
'/api/login'
],
//接口列表
URL = require('../config');
/* 请求拦截器 */
const interceptor = (req, res ,next) =>{
res.result = {
code: 200,
data:""
}
console.log('请求拦截');
log.info("*********请求拦截**********");
//log.info("HOST:" + req.headers.host);
log.info("Authorization:" + req.headers['authorization']);
// res.send('**********repoert******');
try {
console.log('进入了');
const origina_url = req.originalUrl;
const method = req.method;
log.info(origina_url);
log.info(method);
let count = 0, //路由匹配计数器
matchUrl = {}; //保存匹配中的路由规则
// if(NO_TOKEN.indexOf(origina_url) < 0){
// if(req.headers){
// if(!req.headers['authorization'] || req.headers['authorization'] == ''){
// log.warn('未带上 token 请求接口');
// console.log('未带上 token 请求接口');
// res.result['code'] = 403;
// // next();
// return;
// }
// }
// }
log.info(`携带了token`);
log.info(req.query);
// 匹配: url+ 请求方式 + 参数
for(let i = 0; i < URL.length; i++){
let el = URL[i],
url = el['url'],
type = el['type'];
log.info(url);
log.info(method);
log.info(type);
log.info(url.test(origina_url));
if(url.test(origina_url) && type.test(method)){
log.info('进入了匹配')
// 对post 请求, 作参数校验
if(method !== 'GET'){
let init_params = el['params'],
init_params_length = init_params.length,
request_params = Object.keys(req.body),
request_params_length = request_params.length;
//请求参数长度 与 公共参数 长度不一致, 则跳出本次循环
if(request_params_length !== init_params_length){
log.warn("请求参数长度 与 公共参数长度 不一致 :" + request_params_length + " --- " + init_params_length);
console.log("请求参数长度 与 公共参数长度 不一致 :" + request_params_length + " --- " + init_params_length);
continue;
}
//公共参数长度 非0 才进行 请求参数 与 公共参数 的对比
//公共参数长度 为0,表示 post 请求不须要参数,看成 get 请求处理
if(init_params_length !== 0){
let params_count = 0; // 参数匹配计数器
for(let sub_i = 0; sub_i < request_params_length; sub_i++){
let item = request_params[sub_i];
//请求参数 与 公共参数中的某一个匹配了, 则计数器++;
if(init_params.indexOf(item) != -1){
params_count++;
}
}
// 参数匹配计数器 与 公共参数长度不一致, 则跳出本次循环
if(params_count !== init_params_length){
log.info('========== 请求参数 与 公共参数不匹配 ========== ');
log.info('请求参数:' + request_params);
log.info('公共参数:' + init_params);
log.info(' ========== 请求参数 与 公共参数不匹配 ========== ');
continue;
}
}
}
count++;
matchUrl = el;
break;
} else {
console.log('没进入匹配');
log.info('没进入匹配');
}
}
// 匹配项 大于 0, 则进行转发
if(count > 0){
log.info('存在匹配项目:' + matchUrl.name);
// 获取请求参数
let request_params = JSON.stringify(method == "GET" ? req.query : req.body);
//请求后台接口服务
let target = matchUrl['target'],
target_length = target.length;
//async 库控制流程
if(target_length > 0){
let async_func = [
function (cb){
let _params = JSON.parse(request_params);
log.info('传入参数:'+ request_params);
//判断是否有传入 token ,若是有传则 设置 token;
req.headers['authorization'] ?
function(){
log.info('传入的 token:' + req.headers['authorization']);
_params['authorization'] = req.headers['authorization'];
}() : function(){
log.info('未传入 token')
}();
cb(null, _params);
}
].concat(target);
log.info(target);
async_func = async_func.concat([
function(data,cb){
log.info('返回前端数据:'+ JSON.stringify(data));
cb(null, data);
}
]);
//waterfall: 按顺序依次执行一组函数。每一个函数产生的值,都将传给下一个。
async.waterfall(async_func, (err, resultend)=>{
if(err){
res.result['code'] = err['httpStatus'];
next();
}
res.result['code'] = 200;
res.result['data'] = resultend;
next();
})
}
}
}
catch(err){
log.error('======== NODE 服务器异常 =========');
log.error(err);
let errCode = err.statusCode;
switch(errCode){
case 400:
res.result['code'] = 400;
break;
default:
res.result['code'] = 500;
}
next();
}
}
module.exports = interceptor;
复制代码
index.js;
const $http = require('../util/superagent');
const ENV = require('../config/url.config')
let login = [];
login = [
{
//前端接口描述
name: '登陆',
//前端接口请求地址
url: /^\/list\/login$/i,
//请求方式
type: /^post$/i,
//公共参数
params: ['memberCellphone', 'loginPwd'],
//调用后端接口列表
target:[
function(initParam, cb){
let headers = {
"Content-Type":"application/json;",
}
$http('http://webapi.test.sxmaps.com/' + 'sapp/sapp-api/notfilter/member/login', 'post',initParam,{},function(err,res){
if(err){
cb(err,{});
return false;
}
// 这里能够对数据进行处 理, 拼接成给前端的数据
let res_data = res;
cb(null, JSON.parse(res_data.text));
})
}
]
}
]
module.exports = login;
复制代码
8.$http文件的封装. 本文使用superagent库.
superagent是一个轻量级、灵活的、易读的、低学习曲线的客户端请求代理模块,使用在NodeJS环境中。 superagent: visionmedia.github.io/superagent/
//
const superagent = require('superagent');
const NODE_ENV = process.env.NODE_ENV;
// module.exports = superagent
/**
* 请求转发 公共方法
* $http(url, method, data, callback)
* @param {String} url 请求地址
* @param {String} method 请求方式
* @param {Obejct} data 请求参数,无参传入空对象
* @param {Object} headers 请求头设置,无需设置传入空对象
* @param {Function} callback 请求回调函数
*/
const $http = (url,method,data,headers,callback)=>{
const URL = url;
if(!url || !method || !data || !callback){
throw new Error(' ========== 配置文件请求参数配置异常 ========== ');
return false;
}
let _http = superagent;
log.info('转发后台地址:' + url);
log.info('转发后台参数:' + JSON.stringify(data));
switch (method) {
case 'get':
_http = _http.get(url).query(data);
break;
case 'put':
_http = _http.put(url).send(data);
break;
case 'delete':
_http = _http.del(url).send(data);
break;
default:
_http = _http.post(url).send(data);
break;
}
_http.end((err,res)=>{
callback(null, res);
})
}
module.exports = $http;
复制代码
9.从新改造 app.js 让app使用拦截器, app.use(interceptor);
app.use(interceptor);
app.use((req,res,next)=>{
}
复制代码
例如:
app.use(interceptor);
app.use((req,res,next)=>{
let code = res.result['code'];
/* 禁止缓存 */
res.setHeader('Cache-Control', 'no-store');
switch(code){
case 403:
res.send({
code:403,
msg:'请携带token',
});
default:
res.send(res.result['data']);
break;
}
let t = new Date() - req.time,
conntectStr = req.method + ' ' + req.url + ' ' + t + ' ms';
log4js.info(conntectStr);
next();
});
复制代码
11.环境变量的设置: 在根目录的 package.json种设置命令行的运行命令,执行不一样的脚本:
在建立一个读取url地址的文件;
pm2的运行环境变量,新建一个pm2文件, 设置读取配置。
里面的watch是pm2 监听的文件,log_file是 输出到 log日志。instances是运行的线程数量
中间件的参数校验,还待完善,没有很合理。