eggjs 的一些使用总结

2,502 阅读1分钟
  • 路由分包管理
  • 线上监控 alinode or easy monitory
  • 接入 apollo 配置中心
  • 轻量级事件触发器 egg-bus
  • 通用的异常处理器
  • 通用的请求日志

路由分包管理

在 egg 中一个路由和控制器的绑定管理如下进行配置

// app/router.js
module.exports = app => {
  const { router, controller } = app;
  router.get('/user/:id', controller.user.info);
};

上述方式会存在一个问题,在业务推进过程中,业务越来越复杂,这样路由统一管理就会显得很杂乱,最好的方式还是采用分包管理在自己的业务域下进行管理.

${rootPath}/router.js

  const v1Router = router.namespace('/api/v1');
  const v1Controller = controller.v1;
  const v1RouterBasePath = path.resolve(__dirname, './routes/v1');
  
  const requireRouters = async (basePath, router, controller, middleware) => {
    const files = await fsPromises.readdir(basePath);
    files.forEach(file => {
      require(path.join(basePath, file))(router, controller, middleware);
    });
  };

  requireRouters(v1RouterBasePath, v1Router, v1Controller, middleware);

分别通过读取 controller 下的文件和 router 下的文件,进行绑定。

线上监控 alinode

alinode

alinode 有一些小问题,windows 上不可调试,还有就是整个环境的安装比较麻烦。我们这边在线上使用的监控还是 easy-monitor

线上监控 easy-monitor

easy-monitor 使用指南

easy-monitor 部署起来还是比较简单的,windows 可以调试。

集成 apollo 配置中心

install

npm i egg-apollojs-no-schedule --save

plugins.js

exports.apollojs = {
  enable: true,
  package: 'egg-apollojs-no-schedule',
};

app/apollo/config.js

module.exports = {
  configServerUrl: <configServerUrl>, // 配置中心地址
  appId: [appId], // appId
  clusterName: "default",
  namespaceName: 'application' // 注意这里的namespace 不能写错了,需要与 apollo 上的配置一一对应
};

preload.js

require('egg-apollojs-no-schedule')
  .init(__dirname + '/config/apollo.js');

config.default.js

// 加载process.env
require('egg-apollojs-no-schedule').apollo.setEnv();

package.json

"scripts": {
    "start": "node preload.js && egg-scripts start --daemon --title=<application name> --port=<port>"
}

使用

以 配置线上 mongodb 数据库连接地址为例

apollo 中 配置的值

mongoose.url = "mongodb://127.0.0.1/test"

在 config.default.js 中使用配置为

config.mongoose = {
    url: process.env['mongoose.url'],
    options: {
        useNewUrlParser: true,
        useCreateIndex: true,
        useUnifiedTopology: true,
        useFindAndModify: false,
        bufferMaxEntries: 0,
        poolSize: 1000,
    },
};

轻量级事件触发器 egg-bug

egg-bug

安装

npm i egg-bus --save

使用

exports.bus = {
  enable: true,
  package: 'egg-bus',
};

配置 config.default.js

// {app_root}/config/config.default.js
exports.bus = {
  debug: true, // Debug 模式下会打印更多日志信息
  concurrency: 1, // Bull 中队列处理的并发数:https://github.com/OptimalBits/bull/blob/develop/REFERENCE.md#queueprocess
  listener: {
    ignore: null, // 忽略目录中的某些文件,https://eggjs.org/zh-cn/advanced/loader.html#ignore-string
    baseDir: 'listener',
    options: { // Bull Job 配置: https://github.com/OptimalBits/bull/blob/develop/REFERENCE.md#queueadd
      attempts: 5,
      backoff: {
        delay: 3000,
        type: 'fixed',
      },
    }
  },
  job: {
    // 与 listener 一致,唯一不同的就是 默认 baseDir 的值为 `job`
  },
  bull: { // Bull 队列配置:https://github.com/OptimalBits/bull/blob/develop/REFERENCE.md#queue
    redis: {
      host: 'localhost',
      port: 6379,
      db: 0,
    },
  },

  queue: {
    default: 'default', // 默认队列名称
    prefix: 'bus', // 队列前缀
  },
  queues: { // 针对不同队列单独配置

    // 比如针对默认队列更改 redis 端口
    default: {
      redis: {
        port: 6380,
      },
    }
  },
};

通用的异常处理器

/app/middleware/error_handler.js

'use strict';

module.exports = (option, app) => {
  return async function (ctx, next) {
    try {
      await next();
    } catch (err) {
      // 生产环境时 500 错误的详细错误内容不返回给客户端,因为可能包含敏感信息
      let { code = 'Error', message = 'Error', status = 500 } = err;
      app.emit(status === 500 ? 'error' : 'warn', err, this);
      message = status === 500 && app.config.env === 'prod' ? 'Internal Server Error' : err.message;

      if (typeof code === 'number') {
        code = 'InternalError';
        message = 'The request processing has failed due to some unknown error, exception or failure.';
      }

      ctx.body = {
        code,
        message,
      };
      if (status === 422) {
        ctx.body.detail = err.errors;
      }
      ctx.status = status;
    }
  };
};

统一的处理错误,如果是在测试环境就将错误详细信息抛出,如果在生产环境就抛出统一的错误。

通用的请求日志

module.exports = () => async (ctx, next) => {
  ctx.logger.info(`
  headers:${JSON.stringify({
    referer: ctx.headers.referer,
    token: ctx.headers.token,
    accessToken: ctx.headers.accesstoken,
    openId: ctx.headers.openid,
    devieId: ctx.headers.deviceid,
  })}
  body:${JSON.stringify(ctx.request.body)}
  `);
  await next();
  ctx.logger.info(`response: ${JSON.stringify(ctx.response.body)}`);
};