Skip to content
On this page

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第8天,点击查看活动详情

本篇文章主要是补一下前面的坑,ArgumentsHost和ExecutionContext、装饰器SetMetadata,以及反射类Reflector

一、ArgumentsHost

ArgumentsHost提供了一系列方法用来获取传递给handler的参数,我们可以选择使用的上下文类型,包括HTTP、RPC、WebSocket等。nest框架为我们提供了很多实例入口,一般都是叫做host的一个参数,比如在ExceptionFilter接口中需要实现的catch方法第二个参数就是它:

import { ArgumentsHost } from '../features/arguments-host.interface';

export interface ExceptionFilter<T = any> {
  catch(exception: T, host: ArgumentsHost): any;
}

ArgumentsHost属于抽象级别,针对不同的协议环境有不同的含义,比如默认的使用Express驱动的应用,host对象中封装了Argument数组为[request, response, next],对于GraphQL应用,host对象中由封装了Argument数组为[root, args, context, info]等,需要根据不同情况进行不同的使用

1. 获取类型

if (host.getType() === 'http') {
  // 我们一般在这里做事,http的事,表示比较弱,哈哈
} else if (host.getType() === 'rpc') {
  // do something that is only important in the context of Microservice requests
} else if (host.getType<GqlContextType>() === 'graphql') {
  // do something that is only important in the context of GraphQL requests
}

2. 获取Arguments数组

方式一:getArgs()

const [req, res, next] = host.getArgs();

方式二:getArgByIndex(0/1/2)

const request = host.getArgByIndex(0);
const response = host.getArgByIndex(1);

方式三:使用switch(推荐使用!)

/**
 * Switch context to RPC.
 */
switchToRpc(): RpcArgumentsHost;
/**
 * Switch context to HTTP.
 */
switchToHttp(): HttpArgumentsHost;
/**
 * Switch context to WebSockets.
 */
switchToWs(): WsArgumentsHost;
// 使用Express的这两个类型
import { Request, Response } from 'express';

const ctx = host.switchToHttp();
const request = ctx.getRequest<Request>();
const response = ctx.getResponse<Response>();
  • http -> HttpArgumentsHost
  • webSocket -> WsArgumentsHost
export interface WsArgumentsHost {
  /**
   * Returns the data object.
   */
  getData<T>(): T;
  /**
   * Returns the client object.
   */
  getClient<T>(): T;
}
  • RPC -> RpcArgumentsHost
export interface RpcArgumentsHost {
  /**
   * Returns the data object.
   */
  getData<T>(): T;

  /**
   * Returns the context object.
   */
  getContext<T>(): T;
}

二、ExecutionContext

该类继承自ArgumentsHost,不过进行了扩展,我们前面讲的Guard中的canActivate()方法和interceptor中的interce()方法中都有它的身影

export interface ExecutionContext extends ArgumentsHost {
  /**
   * Returns the type of the controller class which the current handler belongs to.
   */
  getClass<T>(): Type<T>;
  /**
   * Returns a reference to the handler (method) that will be invoked next in the
   * request pipeline.
   */
  getHandler(): Function;
}

关于两个方法,举个栗子比较直观

@Controller()
export class CatsController{

  @Post()
  create(){}
}
const methodKey = ctx.getHandler().name; // "create"
const className = ctx.getClass().name; // "CatsController"

getClass得到的是类而不是它的实例,getHandler得到的是函数的引用,这有什么用呢?

可以为我们获取元数据做准备!我们可以在他们上面自定义元数据,并在guard和interpretor中获取

三、Reflection和metadata

1. SetMetadata

Nest提供了通过@SetMetadata(key, value)装饰器为route handler添加自定义元数据的方法,添加后可以通过反射取到对应的数据

@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

不过一般我们不这样做,我们会创建自定义的装饰器,把SetMetadata包装在里面,如下:

import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

这样我们就可以使用@Roles()这个装饰器装饰router handler

@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

2. Reflector

这是nest提供的一个helper class,它可以被注入到类的构造函数中

@Injectable()
export class RolesGuard {
  constructor(private reflector: Reflector) {}
}

装饰的是route handler

想要获取到route handler的元数据,可以使用get()方法

const roles = this.reflector.get<string[]>('roles', context.getHandler());

getHandler给我们返回的是route handler的方法method,这里返回的就是create这个函数引用

装饰的是Controller

如果@Role()装饰器装饰的是Controller而不是route handler,那使用getClass来获取Controller的那个class

@Roles('admin')
@Controller('cats')
export class CatsController {}
const roles = this.reflector.get<string[]>('roles', context.getClass());

route handler和Controller两者都有装饰

@Roles('user')
@Controller('cats')
export class CatsController {
  @Post()
  @Roles('admin')
  async create(@Body() createCatDto: CreateCatDto) {
    this.catsService.create(createCatDto);
  }
}

有两个方法,带来两种不同的结果

  • getAllAndOverride

这个方法会把Controller层面的作为默认值,route handler层面的作为覆盖值,会覆盖Controller层面的装饰

const roles = this.reflector.getAllAndOverride<string[]>('roles', [
  context.getHandler(),
  context.getClass(),
]);

结果是['admin']

这里有一个小坑,经过我的测试,getHandler和getClass的顺序会影响水覆盖谁,我的结论是,谁在前最后返回的就是谁,上面返回的是route handler装饰的值,如果把getClass写在前面,则返回的是Controller装饰的值

  • getAllAndMerge

保留两个层面的装饰时使用,会将两个数组进行merge

const roles = this.reflector.getAllAndMerge<string[]>('roles', [
  context.getHandler(),
  context.getClass(),
]);

结果是['user', 'admin']