koa的设计理念:就是koa只提供核心组件,其他自己组装,本系统也是基于koa之上使用了一些第三方的依赖而建立的系统,下图是根据理解所画的流程图: 以下全是基于流程的分析。

入口文件app.js

webserver

整个系统入口文件是app.js,先把核心代码贴出来:

var config = require('./server/config/config'),
  koaConfig = require('./server/config/koa'),
  koa = require('koa'),
  gzip = require('koa-gzip'),
  etag = require('koa-etag'),
  fresh = require('koa-fresh'),
  http = require('http'),
  app = koa();

//app.use(gzip());
//app.use(fresh());
//app.use(etag());

//koaConfig(app);

app.server = app.listen(config.app.port);

console.log('server is runing on port: ' + config.app.port);

这部分代码主要是监听配置的端口以,koa建立一个web server。

抛开次数直接使用的几个中间件不说,koaConfig是整个server的核心处理模块,此处已函数方式引入运行。

压缩

app.use(gzip()) //依赖koa-gzip

koa-gzip这个中间件主要是进行response压缩,这样能大大节省带宽。根据koa的执行顺序这也是,所有的请求都会最后走一次这个中间件,这也是为什么放在第一个。

缓存策略

app.use(fresh()); //koa-fresh
app.use(etag()); //koa-etag

koa-fresh主要是检查响应头(具体检查略不说)是否新鲜,如果新鲜则直接返回304,是客户端使用浏览器缓存。

koa-etag用于设置相应head的tag标签,以为了能判定相应的过期时间。

核心模块/server/koa.js

// == koa核心配置
module.exports = function (app) {
  // 是否开启调试
  if (config.app.env.indexOf('dev') == -1) {
    app.use(logger());
  }


  // cors配置
  app.use(cors({
    origin:'*',
    maxAge: config.app.cacheTime / 1000,
    credentials: true,
    methods: 'GET',
    headers: 'Origin'
  }));


  var sendOpts = {root: config.app.view};

  app.use(function *(next) {
    if(config.app.update){
      yield send(this, '/update.html', sendOpts);
      return;
    }

    // post处理
    if(this.method === 'POST'){
      yield next;
    }

    // get处理
    if(this.method === 'GET'){
      var pathName = this.path.split('/')[1];
      if(this.path.indexOf('.') > -1) {    /*是否为静态资源*/
        yield send(this, this.path, sendOpts);
        return;
      }else if(pathName === 'auth'){ // 如果是授权则进行相应跳转
        yield next;
      } else if(config.app.h5Router) {     /*h5路由处理*/
        var startRouter = this.path || config.app.startRouter,
          indexName = config.app.index || 'index',
          subIndexName = 'index',
          isSubapp = config.subapp && pathName in config.subapp;//是否为子应用

        this.body = isSubapp
          ? yield view.showRoot('/subapp/' + pathName + '/' + subIndexName, {startRouter: startRouter})
          : yield view.showRoot('/' + indexName, {startRouter: startRouter});

        return;
      }else{
        yield next;
      }
    }
  });


  // == session作用域
  var sessionDomain =
    config.app.view.indexOf('dev') >-1 || config.app.view.indexOf('test') >-1
      ? ''
      : '.' + config.app.domain;  /*测试*/

  app.keys = ['***'];           /*session key*/
  app.use(session(app,{
    key:'***',
    httpOnly:true,
    expries:'session',
    domain:sessionDomain
  }));


  // == 加载公共路由
  fs.readdirSync('./server/routers/public').forEach(function (file) {
    require('../routers/public/' + file)(app);
  });


  // == 加载模块路由
  loadRouters(config.app.router);
  function loadRouters(path) {
    fs.readdirSync('./server/routers/' + path).forEach(function (file) {
      if(file.indexOf('.js') >-1){
        require('../routers/'+ path +'/' + file)(app);
      }else if(file.indexOf('.DS_Store') == -1 && file){
        loadRouters(path + '/' + file);
      }
    });
  }
};

以上代码便是核心业务代码,其中一些因为放到公开部分,稍微做了下处理,先看下配置。

配置

主要的配置文件是/server/config/config.js与config.json。

var platformConfig = JSON.parse(fs.readFileSync('config.json',{encoding:'utf-8'}));

module.exports = _.merge(baseConfig, platformConfig[baseConfig.app.env || (baseConfig.app.env = 'wapdev')]);

可以看过,基于NODE_ENV的值(默认为wapdev)来读取config.json的配置来与config.js的值进行合并和输出。

日志

if (config.app.env.indexOf('dev') == -1) {
    app.use(logger());
}

根据NODE_ENV的值是否含有dev也就是说其否是开发环境来开启日志,如果是开发则直接打印日志。

跨域配置

app.use(cors({
    origin:'*',
    maxAge: config.app.cacheTime / 1000,
    credentials: true,
    methods: 'GET',
    headers: 'Origin'
  }));

配置后,前端可以跨域进行访问。

(其实本站目前发现完全可以不用进行跨域设置,因为h5所有请求都是本站下的,猜测可能是为了app中请求?)

session

// == session作用域
var sessionDomain =
config.app.view.indexOf('dev') >-1 || config.app.view.indexOf('test') >-1
? ''
: '.' + config.app.domain; /*测试*/

app.keys = ['***']; /*session key*/
app.use(session(app,{
key:'***',
httpOnly:true,
expries:'session',
domain:sessionDomain
}));

正常sessio配置,需要注意的是,开发环境与生产环境的域不一样。

GET请求

if(this.method === 'GET'){
    var pathName = this.path.split('/')[1];
    if(this.path.indexOf('.') > -1) {    /*是否为静态资源*/
      yield send(this, this.path, sendOpts);
      return;
    }else if(pathName === 'auth'){ // 如果是授权则进行相应跳转
      yield next;
    } else if(config.app.h5Router) {     /*h5路由处理*/
      var startRouter = this.path || config.app.startRouter,
        indexName = config.app.index || 'index',
        subIndexName = 'index',
        isSubapp = config.subapp && pathName in config.subapp;//是否为子应用

      this.body = isSubapp
        ? yield view.showRoot('/subapp/' + pathName + '/' + subIndexName, {startRouter: startRouter})
        : yield view.showRoot('/' + indexName, {startRouter: startRouter});

      return;
    }else{
      yield next;
    }
}

如果是get请求则还分三种情况:

  1. 通过检测url中是否有“.”来判断是否请求的是静态资源,如果是则直接命中对应的静态资源。
  2. 如果url表达为鉴权,则交出执行权。
  3. 根据路径是否有/subapp/来渲染不同路径下的页面。

路由

路由部分也是命中的post请求。

// == 加载公共路由
  fs.readdirSync('./server/routers/public').forEach(function (file) {
    require('../routers/public/' + file)(app);
  });


  // == 加载模块路由
  loadRouters(config.app.router);
  function loadRouters(path) {
    fs.readdirSync('./server/routers/' + path).forEach(function (file) {
      if(file.indexOf('.js') >-1){
        require('../routers/'+ path +'/' + file)(app);
      }else if(file.indexOf('.DS_Store') == -1 && file){
        loadRouters(path + '/' + file);
      }
    });
  }

此处是先扫面路由下的文件,然后一个个去执行。以/server/routers/public/monitor.js为例其实相当于在此处这样写:

var monitor = require('../routers/public/monitor.js');
monitor(app)

每个路由下的文件都如上执行了一次,把所有的路由都挂在koa上。

mock与请求转发

我们继续以/server/routers/public/monitor.js路由为例来分析下命中post请求后,如何去获取数据及处理的。

// wap端监控接口
  router.post('/log/tranCore/BuriedPointWxService',composition.noToken(),controller());
  app.use(router.routes());

其实可以参考koa-router的使用api。

此处router.post请求的三个参数。第一个是url,第二个是一个中间件,第三个是请求完成后的回调。

在第二个中间件中,根据环境配置与参数的不同,来决定是读取mock数据还是转发请求到mserver。


篇幅编辑问题,详细解释,后续展开。

results matching ""

    No results matching ""