Nest 提供了几个实用程序类,可帮助您轻松编写跨多个应用程序上下文(例如,基于 Nest HTTP 服务器、微服务和 WebSockets 应用程序上下文)运行的应用程序。这些实用程序提供有关当前执行上下文的信息,可用于构建通用 guards、filters 和 interceptors,这些 guards、filters 和 interceptors,它们可跨多种控制器、方法和执行上下文工作。
我们在本章中介绍两个这样的类:ArgumentsHost
和 ExecutionContext
。
ArgumentsHost 类
ArgumentsHost
类提供用于检索传递给处理程序的参数的方法。它允许选择适当的上下文(例如,HTTP、RPC(微服务)或 WebSockets)来从中检索参数。该框架在您可能想要访问它的地方提供了一个 ArgumentsHost
实例,通常作为 host
参数引用。例如,使用 ArgumentsHost
实例调用 异常过滤器 的 catch()
方法。
ArgumentsHost
仅充当处理程序参数的抽象。例如,对于 HTTP 服务器应用程序(当使用 @nestjs/platform-express
时),host
对象封装了 Express 的 [request, response, next]
数组,其中 request
是请求对象,response
是响应对象,next
是控制应用程序请求-响应周期的函数。另一方面,对于 GraphQL 应用程序,host
对象包含 [root, args, context, info]
数组。
当前应用程序上下文
在构建通用 guards、filters 和 interceptors(它们旨在跨多个应用程序上下文运行)时,我们需要一种方法来确定我们的方法当前正在运行的应用程序类型。使用 ArgumentsHost
的 getType()
方法执行此操作:
if (host.getType() === 'http') {
// 只在常规 HTTP 请求 (REST) 上下文中执行重要操作
}
else if (host.getType() === 'rpc') {
// 只在微服务请求上下文中执行重要操作
}
else if (host.getType<GqlContextType>() === 'graphql') {
// 只在 GraphQL 请求上下文中执行重要操作
}
GqlContextType
是从 @nestjs/graphql
包导入的。
有了应用程序类型,我们可以编写更多通用组件,如下所示。
主机处理程序参数
要检索传递给处理程序的参数数组,一种方法是使用主机对象的 getArgs()
方法。
const [req, res, next] = host.getArgs()
您可以使用 getArgByIndex()
方法通过索引获取特定参数:
const request = host.getArgByIndex(0)
const response = host.getArgByIndex(1)
在这些示例中,我们通过索引检索了请求和响应对象,这通常不推荐,因为它会将应用程序与特定的执行上下文耦合。相反,您可以使用主机
对象的某个实用方法切换到适合您应用程序的应用程序上下文,从而使您的代码更加健壮和可重用。上下文切换实用方法如下所示。
/**
* 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 类型对象:
const ctx = host.switchToHttp()
const request = ctx.getRequest<Request>()
const response = ctx.getResponse<Response>()
类似地,WsArgumentsHost
和 RpcArgumentsHost
具有在微服务和 WebSockets 上下文中返回适当对象的方法。以下是 WsArgumentsHost
的方法:
export interface WsArgumentsHost {
/**
* Returns the data object.
*/
getData: <T>() => T
/**
* Returns the client object.
*/
getClient: <T>() => T
}
以下是 RpcArgumentsHost
的方法:
export interface RpcArgumentsHost {
/**
* Returns the data object.
*/
getData: <T>() => T
/**
* Returns the context object.
*/
getContext: <T>() => T
}
ExecutionContext 类
ExecutionContext
扩展了 ArgumentsHost
,提供有关当前执行过程的更多详细信息。与 ArgumentsHost
一样,Nest 在您可能需要的地方提供了 ExecutionContext
的实例,例如在 guard 的 canActivate()
方法和 interceptor 的 intercept()
方法中。它提供了以下方法:
export interface ExecutionContext extends ArgumentsHost {
/**
* 返回当前处理程序所属的控制器类的类型。
*/
getClass: <T>() => Type<T>
/**
* 返回对请求管道中接下来要调用的处理程序(方法)的引用。
*/
getHandler: () => Function
}
getHandler()
方法返回对即将调用的处理程序的引用。getClass()
方法返回此特定处理程序所属的 Controller
类的类型。例如,在 HTTP 上下文中,如果当前处理的请求是 POST
请求,绑定到 CatsController
上的 create()
方法,则 getHandler()
返回对 create()
方法的引用,而 getClass()
返回 CatsController
类(而非实例)。
const methodKey = ctx.getHandler().name // "create"
const className = ctx.getClass().name // "CatsController"
能够访问当前类和处理程序方法的引用提供了极大的灵活性。最重要的是,它使我们有机会通过通过 Reflector#createDecorator
创建的装饰器或内置的 @SetMetadata()
装饰器从保护程序或拦截器中访问元数据集。我们将在下面介绍此用例。
反射和元数据
Nest 提供了通过通过 Reflector#createDecorator
方法创建的装饰器和内置的 @SetMetadata()
装饰器将 自定义元数据 附加到路由处理程序的能力。在本节中,让我们比较这两种方法,并了解如何从保护程序或拦截器中访问元数据。
要使用 Reflector#createDecorator
创建强类型装饰器,我们需要指定类型参数。例如,让我们创建一个以字符串数组作为参数的 Roles
装饰器。
import { Reflector } from '@nestjs/core'
export const Roles = Reflector.createDecorator<string[]>()
此处的Roles
装饰器是一个接受单个string[]
类型参数的函数。
现在,要使用此装饰器,我们只需用它来注释处理程序:
// cats.controller
@Post()
@Roles(['admin'])
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
这里我们将 Roles
装饰器元数据附加到 create()
方法,表示只有具有 admin
角色的用户才允许访问此路由。
要访问路由的角色(自定义元数据),我们将再次使用 Reflector
辅助类。Reflector
可以以正常方式注入到类中:
// roles.guard
@Injectable()
export class RolesGuard {
constructor(private reflector: Reflector) {}
}
Reflector
类是从 @nestjs/core
包导入的。
现在,要读取处理程序元数据,请使用 get()
方法:
const role = this.reflector.get(Roles, context.getHandler())
Reflector#get
方法允许我们通过传入两个参数轻松访问元数据:一个装饰器引用和一个用于从中检索元数据的 context(装饰器目标)。在此示例中,指定的 decorator 是 Roles
(请参阅上面的 roles.decorator.ts
文件)。上下文由对 context.getHandler()
的调用提供,这将提取当前处理的路由处理程序的元数据。请记住,getHandler()
为我们提供了对路由处理程序函数的 引用。
或者,我们可以通过在控制器级别应用元数据来组织我们的控制器,并将其应用于控制器类中的所有路由。
// cats.controller
@Roles(['admin'])
@Controller('cats')
export class CatsController {}
在这种情况下,为了提取控制器元数据,我们将 context.getClass()
作为第二个参数传递(以提供控制器类作为元数据提取的上下文),而不是 context.getHandler()
:
const characters = this.reflector.get(Roles, context.getClass())
鉴于能够在多个级别提供元数据,您可能需要从多个上下文中提取和合并元数据。Reflector
类提供了两种实用方法来帮助实现这一点。这些方法一次提取控制器和方法元数据,并以不同的方式组合它们。
考虑以下场景,您在两个级别都提供了 Roles
元数据。
// cats.controller
@Roles(['user'])
@Controller('cats')
export class CatsController {
@Post()
@Roles(['admin'])
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto)
}
}
如果您的意图是将用户
指定为默认角色,并有选择地为某些方法覆盖它,那么您可能会使用getAllAndOverride()
方法。
const roles = this.reflector.getAllAndOverride(Roles, [context.getHandler(), context.getClass()])
使用此代码的守卫在 create()
方法的上下文中运行,使用上述元数据,将导致 roles
包含 ['admin']
。
要获取两者的元数据并合并它(此方法合并数组和对象),请使用 getAllAndMerge()
方法:
const roles = this.reflector.getAllAndMerge(Roles, [context.getHandler(), context.getClass()])
这将导致 roles
包含 ['user', 'admin']
。
对于这两种合并方法,您都将元数据键作为第一个参数传递,并将元数据目标上下文数组(即对 getHandler()
和/或 getClass())
方法的调用)作为第二个参数传递。
低级方法
如前所述,除了使用 Reflector#createDecorator
,您还可以使用内置的 @SetMetadata()
装饰器将元数据附加到处理程序。
// cats.controller
@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
@SetMetadata()
装饰器从 @nestjs/common
包导入。
通过上面的构造,我们将 roles
元数据(roles
是元数据键,['admin']
是关联值)附加到 create()
方法。虽然这可行,但直接在路由中使用 @SetMetadata()
并不是好习惯。相反,您可以创建自己的装饰器,如下所示:
import { SetMetadata } from '@nestjs/common'
export const Roles = (...roles: string[]) => SetMetadata('roles', roles)
import { SetMetadata } from '@nestjs/common'
export const Roles = (...roles) => SetMetadata('roles', roles)
这种方法更加简洁易读,与 Reflector#createDecorator
方法有些相似。不同之处在于,使用 @SetMetadata
,您可以更好地控制元数据键和值,还可以创建采用多个参数的装饰器。
现在我们有了一个自定义的 @Roles()
装饰器,我们可以使用它来装饰 create()
方法。
// cats.controller
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
要访问路线的角色(自定义元数据),我们将再次使用Reflector
帮助类:
// roles.guard
@Injectable()
export class RolesGuard {
constructor(private reflector: Reflector) {}
}
Reflector
类从 @nestjs/core
包导入。
现在,要读取处理程序元数据,请使用 get()
方法。
const roles = this.reflector.get<string[]>('roles', context.getHandler())
这里我们不是传递装饰器引用,而是传递元数据 key 作为第一个参数(在我们的例子中是roles
)。其他一切与Reflector#createDecorator
示例中的相同。