nodejs中間層實踐(express)

Jack?發表於2019-03-27

什麼是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中間層核心);
    nodejs中間層實踐(express)

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. 劃分介面列表:

nodejs中間層實踐(express)

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

nodejs中間層實踐(express)

  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種設定命令列的執行命令,執行不同的指令碼:

nodejs中間層實踐(express)

在建立一個讀取url地址的檔案;

nodejs中間層實踐(express)

pm2的執行環境變數,新建一個pm2檔案, 設定讀取配置。

nodejs中間層實踐(express)

裡面的watch是pm2 監聽的檔案,log_file是 輸出到 log日誌。instances是執行的執行緒數量

專案啟動:

nodejs中間層實踐(express)

  1. 可以放上伺服器, 如果不想埠是 nodejs的3000的話, 可以在nginx對域名進行 對映代理。

中介軟體的引數校驗,還待完善,沒有很合理。

相關文章