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

node中轉層的意義:javascript

  1.能解決先後端代碼部署在不一樣服務器下時的跨域問題。(實現)java

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

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

 

環境準備:後端

  node: ^8.11.2api

  koa: ^2.6.1跨域

  koa-router: ^7.4.0服務器

  koa-bodyparser: ^4.2.1cookie

 

在項目目錄下新建server目錄,新建app.jsapp

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(單頁應用的)首屏服務端渲染問題。

  持續折騰中...

相關文章
相關標籤/搜索