跳至主要內容

框架对象

微信公众号:储凡About 27 min

框架对象

Egg框架中,包含了很多内置的基础对象,常用到的有:

  • Application
  • Context
  • Request
  • Response
  • Controller
  • Service
  • Helper
  • Config
  • Logger

其中,从Koa框架中继承的对象有:

  • Application
  • Context
  • Request
  • Response

下面将会以官方文档为基础,加上自己的使用、学习体验进行一一整理说明。

Application对象

Application 是全局应用对象,在一个应用中,只会实例化一个,它继承自 Koa.Application,在上面我们可以挂载一些全局的方法和对象。

支持事件

在项目入口通过app.js来启动项目,在项目运行时,会在Application对象实例上触发一些事件,应用开发者或者插件开发者可以监听这些事件做一些操作,一般在app.js中进行监听

// app.js
module.exports = app => {
  app.once('server', server => {
    // websocket
  });
  app.on('error', (err, ctx) => {
    // 监听错误信息
  });
  app.on('request', ctx => {
    // 接收request对象日志

  });
  app.on('response', ctx => {
    // 报告项目启动事件,其中ctx.start_time可以配置
    const used = Date.now() - ctx.start_time;
    // 日志记录
  });
};
  • server: 该事件一个 worker 进程只会触发一次,在 HTTP 服务完成启动后,会将 HTTP server 通过这个事件暴露出来给开发者。
  • error: 运行时有任何的异常被 onerror 插件捕获后,都会触发 error 事件,将错误对象和关联的上下文(如果有)暴露给开发者,可以进行自定义的日志记录上报等处理。
  • request 和 response: 应用收到请求和响应请求时,分别会触发 request 和 response 事件,并将当前请求上下文暴露出来,开发者可以监听这两个事件来进行日志记录。

获取方式

Application 对象几乎可以在编写应用时的任何一个地方获取到;

  • 在app.js中使用
// app.js
module.exports = app => {
  // 绑定缓存redis
  app.cache = new Cache();
};

**框架 Loader 加载的文件(Controller,Service,Schedule 等),都可以 export 一个函数,这个函数会被 Loader 调用,并使用 app 作为参数 **

  • Controller中使用
// app/controller/user.js
class UserController extends Controller {
    // 查询用户
  async getUser(){
      const {ctx,app}=this;
      const {id}=ctx.query
      ctx.body=app.cache.get(id)
  }
}

和 Koa 一样,在 Context 对象上,可以通过 ctx.app 访问到 Application 对象。上面的UserController文件也可以改为:

// app/controller/user.js
class UserController extends Controller {
  async getUser() {
    const {ctx,app}=this;
    const {id}=ctx.query
    ctx.body = ctx.app.cache.get(id);
  }
}

在继承于 Controller, Service 基类的实例中,可以通过 this.app 访问到 Application 对象。

// app/service/user.js
class UserService extends Service {
    async getUser(uid){
        const {ctx,app}=this;
        return app.cache.get(uid)
    }
};

综上,访问application对象实例可以通过ctx.app或者this.app或者app参数即可。

Context对象

Context 是一个请求级别的对象,继承自 Koa.Context。在每一次收到用户请求时,框架会实例化一个 Context 对象,这个对象封装了这次用户请求的信息,并提供了许多便捷的方法来获取请求参数或者设置响应信息。框架会将所有的 Service 挂载到 Context 实例上. 如果你考虑封装一个插件,非常建议将一些方法和对象挂载到ctx对象上,这样在调用时就非常方便,就我个人而言,* ctx的使用是明显高于app的;*

获取方式

  • 中间件里获取
'use strict'
// 中间件
async function  middleware(ctx,next){
    // 日志打印参数
    console.log(ctx.query)
}

这里跟Koa框架是非常类似的,如果没有将ctx作为参数,context对象也是可以从this对象中获取;

  • 支持Controller和Service里面获取
// 直接在this对象中拿到
const {ctx}=this
  • 创建匿名Context实例

在有些非用户请求的场景下我们需要访问 service / model 等 Context 实例上的对象,可以通过 Application.createAnonymousContext() 方法创建一个匿名 Context 实例

// 例如:app.js中
module.exports=app=>{
    app.beforeStart(async ()=>{
        const ctx=app.createAnonymousContext()

        // 使用ctx对象
        ....
    })
}

注意,createAnonymousContext()函数是在application对象提供

  • 定时任务中使用

在定时任务中的每一个 task 都接受一个 Context 实例作为参数,非常方便的帮助执行一些定时的业务逻辑

// app/schedule/refresh.js
exports.task=async ctx=>{
    // 使用ctx对象
}

Request和Response对象

这两个对象在项目中使用频率非常高,而且两者用法类似。

  • Request 是一个请求级别的对象 ,继承自 Koa.Request。封装了 Node.js 原生的 HTTP Request 对象,提供了一系列辅助方法获取 HTTP 请求常用参数。

  • Response 是一个请求级别的对象,继承自 Koa.Response。封装了 Node.js 原生的 HTTP Response 对象,提供了一系列辅助方法设置 HTTP 响应。

获取方式

可以在 Context 的实例上获取到当前请求的 Request(ctx.request) 和 Response(ctx.response) 实例

// app/controller/user.js
class UserController extends Controller {
  async getUser() {
    const { app, ctx } = this;
    // request对象获取参数
    
    // 获取query参数  get请求
    const {id} =  ctx.request.query;

    // 获取表单数据  post请求
    const {user_name}=ctx.request.body

    // response对象设置响应
    ctx.response.body = app.cache.get(id);
  }
}

值得注意的时,Koa框架会在Context对象上面代理一部分Request和Response上方法和属性,因此:

  • ctx.request.queryctx.query在使用上是等价的。

  • ctx.response.bodyctx.body在使用上是等价的。

Controller对象

项目结构中的controller是继承框架中的Controller基类实现的,当继承该对象,也就继承了对应的属性:

  • ctx 当前请求的 Context 实例。
  • app 应用的 Application 实例。
  • config 应用的配置。
  • service 应用所有的 service。
  • logger 为当前 controller 封装的 logger 对象。

获取框架提供的Controller对象的方案:

  • 方案一:从 egg模块上获取(推荐)
// app/controller/user.js

'use strict'
const Controller = require('egg').Controller;
// 这里可以用解构  const {Controller} =require('egg')
class UserController extends Controller {
  // 构造函数
  constructor(){
      ....
  }

  // 实际方法定义
}
module.exports = UserController;
  • 方案二:从 app 实例上获取
// app/controller/user.js
'use strict'
module.exports = app => {
  return class UserController extends app.Controller {
    // 构造函数
    constructor(){
        ....
    }

    // 实际方法定义
};

Service对象

Controller对象一样,框架也提供了Service对象,项目结构中的Service是继承框架中的Service基类实现的,当继承该对象,也就继承了对应的属性,两者的共性非常多,用法基本一致;

获取框架提供的Service对象的方案:

  • 方案一:从 egg模块上获取(推荐)
// app/service/user.js

'use strict'
const Service = require('egg').Service;
// 这里可以用解构  const {Controller} =require('egg')
class UserService extends Service {
  // 构造函数
  constructor(){
      ....
  }

  // 实际方法定义
}
module.exports = UserService;
  • 方案二:从 app 实例上获取
// app/service/user.js
'use strict'
module.exports = app => {
  return class UserService extends app.Service {
    // 构造函数
    constructor(){
        ....
    }
    // 实际方法定义
};

Helper对象

Helper 用来提供一些实用的 utility 函数。作用在于我们可以将一些常用的动作抽离在 helper.js 里面成为一个独立的函数,这样可以用 JavaScript 来写复杂的逻辑,避免逻辑分散各处,同时可以更好的编写测试用例。不通的处理;

**Helper 自身是一个类,有和 Controller 基类一样的属性,它也会在每次请求时进行实例化,因此 Helper 上的所有函数也能获取到当前请求相关的ctx上下文信息 **

获取方式

可以在 Context 的实例上获取到当前请求的 Helper(ctx.helper) 对象实例

// app/controller/user.js
class UserController extends Controller {
  async fetch() {
    const { app, ctx } = this;
    const id = ctx.query.id;
    const user = app.cache.get(id);
    ctx.body = ctx.helper.formatUser(user);
  }
}

方法自定义

在框架的设计中,helper.js文件的功能主要提供一些常用工具函数的封装,所以除了系统自带的,更多的使用场景是进行方法自定义,比如:常用正则校验、长度检测、排序处理等;

// app/extend/helper.js
module.exports = {
  // 封装统一返回
  returnFormat(code,message,result) {
    return {code,message,result}
  }
};

另外,像ctx.helper.escape()也是非常常用的,用于防止xss攻击,对标签进行序列化;

Config对象

框架提供了强大且可扩展的配置功能,可以自动合并应用、插件、框架的配置,按顺序覆盖,且可以根据环境维护不同的配置。合并后的配置可直接从app.config 获取

获取方式

  • app.config 从 Application 实例上获取到 config 对象

  • 也可以在 Controller, Service, Helper 的实例上通过 this.config 获取到 config 对象

除了框架自带的配置,其他自定义的配置,也是可以初始化到Config对象中的

Logger对象

框架内置了功能强大的日志功能,可以非常方便的打印各种级别的日志到对应的日志文件中,每一个 logger 对象都提供了 4 个级别的方法

  • logger.debug()
  • logger.info()
  • logger.warn()
  • logger.error()

Logger对象根据不同的使用场景,主要有:

  • App Logger

通过 app.logger 来获取到它,如果我们想做一些应用级别的日志记录,如记录启动阶段的一些数据信息,记录一些业务上与请求无关的信息,都可以通过 App Logger 来完成。

  • App CoreLogger

可以通过 app.coreLogger 来获取到它,一般我们在开发应用时都不应该通过 CoreLogger 打印日志,而框架和插件则需要通过它来打印应用级别的日志,这样可以更清晰的区分应用和框架打印的日志,通过 CoreLogger 打印的日志会放到和 Logger 不同的文件中。

  • Context Logger

可以通过 ctx.logger 从 Context 实例上获取到它,从访问方式上我们可以看出来,Context Logger 一定是与请求相关的,它打印的日志都会在前面带上一些当前请求相关的信息(如 [$userId/$ip/$traceId/${cost}ms $method $url] ),通过这些信息,我们可以从日志快速定位请求,并串联一次请求中的所有的日志。。

  • Context CoreLogger

可以通过 ctx.coreLogger 获取到它,和 Context Logger 的区别是一般只有插件和框架会通过它来记录日志。

  • Controller Logger & Service Logger

可以在 Controller 和 Service 实例上通过 this.logger 获取到它们,它们本质上就是一个 Context Logger,不过在打印日志的时候还会额外的加上文件路径,方便定位日志的打印位置。。

定时任务

除了上面介绍的几大对象外,框架还专门提供了处理定时任务的方案——subscription

订阅模型是一种比较常见的开发模式,譬如消息中间件的消费者或调度任务。因此提供了 Subscription 基类来规范化这个模式

简单的定时任务:

// 引用 Subscription 基类:
const {Subscription} = require('egg');

class Schedule extends Subscription {
  // 需要实现此方法,方法里面可以调用其他封装
  async subscribe() {}
}