nodejs中间层实践(express)

5,367 阅读4分钟

什么是nodejs中间层?

就是前端---请求---> nodejs ----请求---->后端 ----响应--->nodejs--数据处理---响应---->前端。这么一个流程,这个流程的好处就是当业务逻辑过多,或者业务需求在不断变更的时候,前端不需要过多当去改变业务逻辑,与后端低耦合。前端即显示,渲染。后端获取和存储数据。中间层处理数据结构,返回给前端可用可渲染的数据结构。

nodejs是起中间层的作用,即根据客户端不同请求来做相应的处理或渲染页面,处理时可以是把获取的数据做简单的处理交由底层java那边做真正的数据持久化或数据更新,也可以是从底层获取数据做简单的处理返回给客户端。

项目构建:

1.技术选型,我选择了express框架开发中间层。

2.$ npm install express --save 下载express包.

3.$npm install express-generator -g 使用express生成器,生成一个完成的项目结构。

3.$npm install pm2 -g 下载pm2应用全局.

4.$pm2 start express项目名称/bin/www --watch express项目,并监听一些文件的改变,一旦代码变动,自动重启nodejs服务.

中间件创建:

  1. 在跟目录创建一个文件,来存放自己自己写的中间件,这一个用来当作拦截器(node中间层核心);

6.开始编写这个中间件:

interceptor.js

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;  

接口列表划分:

  1. 划分接口列表:

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

  1. 对拦截器返回的数据,进行处理-->>
    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是运行的线程数量

项目启动:

  1. 可以放上服务器, 如果不想端口是 nodejs的3000的话, 可以在nginx对域名进行 映射代理。

中间件的参数校验,还待完善,没有很合理。