1. 程式人生 > >node+koa中轉層開發實踐總結

node+koa中轉層開發實踐總結

node中轉層的意義:

  1.能解決前後端程式碼部署在不同伺服器下時的跨域問題。(實現)

  2.合併請求,業務邏輯處理。(實現)

  3.單頁應用的首屏服務端渲染。(暫未實現)

 

環境準備:

  node: ^8.11.2

  koa: ^2.6.1

  koa-router: ^7.4.0

  koa-bodyparser: ^4.2.1

 

在專案目錄下新建server目錄,新建app.js

const Koa = require('koa');
const bodyParser = require('koa-bodyparser');
const apiControler = require('./apiControler');

let app = new Koa();
global.hostname = "172.16.16.113";
global.port = 8070;

app.use(async (ctx, next) => {
  await next();
  console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
});

//bodyParser必須在router之前註冊到app上
app.use(bodyParser());

//使用路由處理中介軟體
app.use(apiControler());

app.listen(8899);

在server目錄下新建業務介面目錄,本例目錄名為apiControlers,拿登入模組為例,新建一個login.js,裡面包含登入模組所需要的所有介面。(獲取驗證碼、登入、獲取選單許可權)

login.js

const http = require('http');
const hostname = global.hostname;
const port = global.port;
let tokenStr = "";

/*獲取圖形驗證碼*/
let getAuthCoedFn = async (ctx, next) => {
  let data = await asyncGetAuthCode();
  ctx.set("Content-Type", "application/json");
  ctx.body = JSON.parse(data);
  await next();
};
function asyncGetAuthCode() {
  return new Promise((resolve, reject)=> {
    let authCodedData = "";
    let req = http.request({
      path: '/api/backstage/authCode',
      port: port,
      method: 'GET',
      hostname: hostname
    },(res)=> {
      res.on('data', (chunk)=> {
        authCodedData += chunk
      });
      res.on('end', ()=> {
        authCodedData = JSON.stringify(authCodedData)
        resolve(authCodedData)
      })
    });
    req.on("error", (e)=> {
      console.log("api:/backstage/authCode error")
      reject(e.message)
    });
    req.end();
  })
}

/*登入*/
let loginFn = async (ctx, next) => {
  let param = ctx.request.body;
  let authcodekey = ctx.request.header.authcodekey;
  let postData = {
    userName: param.userName,
    authCode: param.authCode,
    password: param.password
  };
  let loginData = await asyncPostLogin(authcodekey, JSON.stringify(postData));
  ctx.set("Content-Type", "application/json");
  ctx.set("Connection", "keep-alive");
  ctx.body = JSON.parse(loginData);
  next()
};
function asyncPostLogin(authcodekey, postData) {
  return new Promise((resolve, reject)=> {
    let loginData = "";
    let req = http.request({
      path: '/api/backstage/login',
      port: port,
      method: 'POST',
      hostname: hostname,
      headers: {
        'Content-Type': 'application/json',
        'authCodeKey': authcodekey
      }
    },(res)=> {
      res.on('data', (chunk)=> {
        loginData += chunk
      }).on('end', ()=> {
        loginData = JSON.stringify(loginData);
        tokenStr = res.headers['set-cookie'];
        resolve(loginData)
      })
    });
    req.on('error', (e)=> {
      console.log("api:/backstage/login error");
      reject(e.message)
    });
    req.write(postData);
    req.end();
  })
}

/*獲取選單及許可權列表*/
let getPowerListFn = async (ctx, next) => {
  let menuList = await asyncGetPowerList();
  ctx.body = JSON.parse(menuList);
  next()
};
function asyncGetPowerList() {
  return new Promise((resolve, reject)=> {
    let listData = "";
    let req = http.request({
      path: '/api/backstage/getPowerList',
      method: 'get',
      port: port,
      hostname: hostname,
      headers: {
        'Cookie': tokenStr.toString()
      }
    },(res)=> {
      res.on('data', (chunk)=> {
        listData += chunk;
      }).on('end', ()=> {
        listData = JSON.stringify(listData);
        resolve(listData)
      })
    });
    req.on("error", (e)=> {
      console.log("api: /backstage/getPowerList error");
      reject(e.message)
    });
    req.end()
  })
}


module.exports = {
  'GET/api/backstage/authCode': getAuthCoedFn,
  'POST/api/backstage/login': loginFn,
  'GET/api/backstage/getPowerList': getPowerListFn
}

以介面功能宣告一個函式,在此函式中通過node的http模組傳送請求。需要注意的是http.request請求獲取響應頭cookie的方式是tokenStr = res.headers['set-cookie']

每一個業務功能js最後暴露出內部所有以介面請求方式+介面地址為key,以對應功能函式為value的物件。

 

在server目錄下新建一個apiControler.js中介軟體(有返回值的函式)。此中介軟體的功能一是讀取apiControlers目錄下的所有業務js,並引入;二是設定介面請求方式與執行函式的對映關係。

最後暴露出一個函式返回所有請求介面路徑的集合。

apiControler.js

const fs = require("fs");

function readApiFiles(router, dir = '/apiControlers') {
  fs.readdirSync(__dirname + dir).filter((f)=> {
    return f.endsWith('.js')
  }).forEach(f => {
    console.log(`process controller: ${f}...`);
    let mapping = require(__dirname + dir + '/' + f);
    addMapping(router, mapping)
  });
}

function addMapping(router, mapping) {
  for(let url in mapping) {
    if(url.startsWith('GET')) {
      let path = url.substring(3);
      router.get(path, mapping[url]);
    }else if(url.startsWith('POST')) {
      let path = url.substring(4);
      router.post(path, mapping[url]);
    }else{
      router.get(url, mapping[url]);
      console.log(`無效的URL: ${url}`);
    }
  }
}

module.exports = function (dir) {
  let controllers_dir = dir || '/apiControlers';
  let router = require('koa-router')();
  readApiFiles(router, controllers_dir);
  return router.routes();
};

 

最後回到app.js,引入apiControler.js中介軟體並註冊到app上。需要注意的是bodyParser中介軟體必須在router之前註冊到app上。

 

後續

  此例目前只能用作介面轉發、合併請求和解決跨域問題,終極目標是能解決SPA(單頁應用的)首屏服務端渲染問題。

  持續折騰中...