ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
## 应用上下文 Nest提供了一些应用类来简化在不同应用上下文之间编写应用(例如Nest HTTP应用,微服务和WebSockets应用)。这些应用可以用于创建通用的守卫,过滤器和拦截器,可以工作在控制器,方法和应用上下文中。 本章包括`ArgumentsHost`和`ExecutionContext`两个类. ## `ArgumentsHost`类 `ArgumentsHost`类提供了获取传递给处理程序的参数。它允许选择合适的上下文(例如HTTP,RPC(微服务)或者Websockets)来从框架中获取参数。框架提供了`ArgumentsHost`的实例,作为`host`参数提供给需要获取的地方。例如,在异常过滤器中传入`ArgumentsHost`参数来调用`catch()`方法。 `ArgumentsHost`简单地抽象为处理程序参数。例如,在HTTP应用中(使用`@nestjs/platform-express`时),host对象封装了Express的`[request, response, next] `数组,`reuest`是一个`request`对象,`response`是一个`response`对象,`next`是控制应用的请求响应循环的函数。此外,在GraphQL应用中,host包含`[root, args, context, info]`数组。 ## 当前应用上下文 当构建通用的守卫,过滤器和拦截器时,意味着要跨应用上下文运行,我们需要一种方法来确定我们的方法当前正在运行的应用程序类型。可以使用 `ArgumentsHost`的`getType()`方法。 ```typescript if (host.getType() === 'http') { // do something that is only important in the context of regular HTTP requests (REST) } 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 } ``` > `GqlContextType`从中`@nestjs/graphql`导入。 有了可用的应用程序类型,我们可以编写更多的通用组件,如下所示。 ### `Host`处理程序参数 要获取传递给处理程序的参数数组,使用host对象的`getArgs()`方法。 ```typescript const [req, res, next] = host.getArgs(); ``` 可以使用`getArgByIndex()`根据索引获取指定参数: ```typescript const request = host.getArgByIndex(0); const response = host.getArgByIndex(1); ``` 在这些例子中我们通过索引来获取请求响应对象,这并不推荐,因为它将应用和特定上下文耦合。为了使代码鲁棒性更好,更可复用,你可以在程序中使用host对象的应用方法来切换合适的应用上下文,如下所示: ```typescript /** * Switch context to RPC. */ switchToRpc(): RpcArgumentsHost; /** * Switch context to HTTP. */ switchToHttp(): HttpArgumentsHost; /** * Switch context to WebSockets. */ switchToWs(): WsArgumentsHost; ``` 使用 `switchToHttp`() 方法重写前面的例子, `host.switchToHttp()`帮助方法调用一个HTTP应用的`HttpArgumentsHost`对象. `HttpArgumentsHost`对象有两个有用的方法,我们可以用来提取期望的对象。我们也可以使用Express类型的断言来返回原生的Express类型对象: ```typescript const ctx = host.switchToHttp(); const request = ctx.getRequest<Request>(); const response = ctx.getResponse<Response>(); ``` 类似地,`WsArgumentsHost`和`RpcArgumentsHost`有返回微服务和WebSockets上下文的方法,以下是`WsArgumentsHost`的方法: ```typescript export interface WsArgumentsHost { /** * Returns the data object. */ getData<T>(): T; /** * Returns the client object. */ getClient<T>(): T; } ``` 以下是`RpcArgumentsHost`的方法: ```typescript export interface RpcArgumentsHost { /** * Returns the data object. */ getData<T>(): T; /** * Returns the context object. */ getContext<T>(): T; } ``` ### 执行上下文类 `ExecutionContext`扩展了`ArgumentsHost`,提供有关当前执行过程的其他详细信息。和`ArgumentsHost`类似,Nest在需要的时候提供了一个`ExecutionContext`的实例, 例如守卫的`canActivate()`方法和拦截器的`intercept()`方法,它提供以下方法: ```typescript 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; } ``` `getHandler()`方法返回要调用的处理程序的引用。`getClass()`方法返回一个特定处理程序所属的控制器类。例如,一个HTTP上下文,如果当前处理的是一个POST请求,在`CatsController`中绑定`create()`方法。`getHandler()`返回`create()`方法和`getClass()`方法所在的`CatsController`类的引用(不是实例)。 ```typescript const methodKey = ctx.getHandler().name; // "create" const className = ctx.getClass().name; // "CatsController" ``` 访问对当前类和处理程序方法的引用的能力提供了极大的灵活性。最重要的是,它让我们有机会通过`@SetMetadata()`装饰器从守卫或拦截器中访问元数据集。我们将在下面介绍这个用例。 ### 反射和元数据 Nest提供了通过`@SetMetadata()`装饰器将自定义元数据附加在路径处理程序的能力。我们可以在类中获取这些元数据来执行特定决策。 >cats.controller.ts ```typescript @Post() @SetMetadata('roles', ['admin']) async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } ``` > `@SetMetadata()`装饰器从`@nestjs/common`导入。 基于上述结构,我们将`roles`元数据(`roles`是一个元数据,并且`['admin']` 是对应的值)关联到`create()`方法。在这种情况下,不推荐直接在路径中使用`@SetMetadata()`,而是应该如下创建自己的装饰器: >roles.decorator.ts ```typescript import { SetMetadata } from '@nestjs/common'; export const Roles = (...roles: string[]) => SetMetadata('roles', roles); ``` 这种方法更干净、更易读,并且是强类型的。我们现在可以使用自定义的`@Roles()`装饰器,并将其应用在`create()`方法中。 >cats.controller.ts ```typescript @Post() @Roles('admin') async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } ``` 要访问`roles`路径 (自定义元数据),我们将使用`Reflector`辅助类,它由框架提供,开箱即用,从`@nestjs/core`包导入。`Reflector`可以通过常规方式注入到类: >roles.guard.ts ```typescript @Injectable() export class RolesGuard { constructor(private reflector: Reflector) {} } ``` > `Reflector`类从`@nestjs/core`导入。 使用`get()`方法读取处理程序的元数据。 ```typescript const roles = this.reflector.get<string[]>('roles', context.getHandler()); ``` `Reflector#get`方法允许通过传递两个参数简单获取元数据:一个元数据key和一个context(装饰器对象)来获取元数据。在本例中,指定的key是`roles`(向上指回`roles.decorator.ts`以及在此处调用的`SetMetadata()`方法)。context 由`context.getHandler()`提供,用于从当前路径处理程序中获取元数据,`getHandler()`给了我们一个到路径处理函数的引用。 我们也可以组织我们的控制器,来从控制器层获取元数据,以在控制器所有路径中应用。 >cats.controller.ts ```typescript @Roles('admin') @Controller('cats') export class CatsController {} ``` 在本例中,要获取控制器元数据,将`context.getClass()`作为第二个参数(将控制器类作为上下文提供以获取元数据)来替代`context.getHandler()`: >roles.guard.ts ```typescript const roles = this.reflector.get<string[]>('roles', context.getClass()); ``` 要具备在多层提供元数据的能力,需要从多个上下文获取与合并元数据。`Reflector`类提供两个应用方法来帮助实现该功能。这些方法同时获取控制器和方法元数据,并通过不同方法来合并他们。 考虑以下场景,在两个水平应用`roles`都提供了元数据: >cats.controller.ts ```typescript @Roles('user') @Controller('cats') export class CatsController { @Post() @Roles('admin') async create(@Body() createCatDto: CreateCatDto) { this.catsService.create(createCatDto); } } ``` 如果你想将`user`指定为默认角色,并且出于特定目的有选择地进行覆盖,可以使用 `getAllAndOverride()`方法。 ```typescript const roles = this.reflector.getAllAndOverride<string[]>('roles', [ context.getHandler(), context.getClass(), ]); ``` 使用该代码编写守卫,在上下文中应用`create()`方法,采用上述元数据,将生成包含 `['admin']`的`roles`。 要获取与合并元数据(该方法合并数组和对象),使用`getAllAndMerge()`方法: ```typescript const roles = this.reflector.getAllAndMerge<string[]>('roles', [ context.getHandler(), context.getClass(), ]); ``` 这会生成包含['user', 'admin']的`roles`。 对于这两种合并方法,传输元数据作为第一个参数,数组或者元数据对象上下文(例如,调用`getHandler()`和/或`getClass()`)作为第二个参数。