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(单页应用的)首屏服务端渲染问题。

  持续折腾中...

posted @ 2018-11-15 15:24  尹言覃少  阅读(775)  评论(0编辑  收藏  举报