跳到主要内容

执行上下文

Nest提供了几个实用类,帮助轻松编写可以跨多个应用程序上下文 (例如,基于Nest HTTP服务器、微服务和WebSockets应用程序上下文)运行的应用程序。 这些实用程序提供有关当前执行上下文的信息,可用于构建通用的守卫过滤器拦截器, 可以在广泛的控制器、方法和执行上下文中工作。

在本章中,我们涵盖了两个这样的类:ArgumentsHostExecutionContext

ArgumentsHost类

ArgumentsHost类提供了检索传递给处理程序的参数的方法。 它允许选择适当的上下文(例如,HTTP、RPC(微服务)或WebSockets)来从中检索参数。 框架在需要访问ArgumentsHost实例的地方通常提供ArgumentsHost实例,通常被引用为host参数。 例如,异常过滤器catch()方法就是使用ArgumentsHost实例调用的。

ArgumentsHost简单地充当处理程序参数的抽象。 例如,对于HTTP服务器应用程序(当使用@nestjs/platform-express时), host对象封装了Express的[request、response、next]数组,其中request是请求对象, response是响应对象,而next是一个控制应用程序请求-响应循环的函数。 另一方面,对于GraphQL应用程序,host对象包含[root、args、context、info]数组。

当前应用程序上下文

在构建通用的守卫、过滤器和拦截器时,这些组件旨在在多个应用程序上下文中运行, 我们需要一种方法来确定我们的方法当前正在运行的应用程序类型。 使用ArgumentsHostgetType()方法来实现:

if (host.getType() === 'http') {
// 在常规HTTP请求(REST)上下文中执行的特定操作
} else if (host.getType() === 'rpc') {
// 在微服务请求上下文中执行的特定操作
} else if (host.getType<GqlContextType>() === 'graphql') {
// 在GraphQL请求上下文中执行的特定操作
}
提示

GqlContextType是从@nestjs/graphql包中导入的。

有了应用程序类型,我们可以编写更通用的组件,如下所示。

处理程序参数

为了检索传递给处理程序的参数数组,一种方法是使用host对象的getArgs()方法。

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

您可以使用getArgByIndex()方法按索引提取特定参数。

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

在这些示例中,我们通过索引检索了请求和响应对象,这通常不建议, 因为它将应用程序与特定的执行上下文耦合在一起。 相反,您可以通过使用host对象的实用方法之一,在应用程序的适当应用程序上下文中切换, 使您的代码更健壮和可重用。以下是上下文切换实用方法。

切换上下文到RPC
switchToRpc(): RpcArgumentsHost;

切换上下文到HTTP
switchToHttp(): HttpArgumentsHost;

切换上下文到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>();

同样,WsArgumentsHostRpcArgumentsHost有方法以在微服务和WebSockets上下文中返回适当的对象。 以下是WsArgumentsHost的方法:

export interface WsArgumentsHost {
/**
* 返回数据对象。
*/
getData<T>(): T;
/**
* 返回客户端对象。
*/
getClient<T>(): T;
}

以下是RpcArgumentsHost的方法:

export interface RpcArgumentsHost {
/**
* 返回数据对象。
*/
getData<T>(): T;

/**
* 返回上下文对象。
*/
getContext<T>(): T;
}

ExecutionContext类

ExecutionContext扩展了ArgumentsHost,提供有关当前执行过程的额外详细信息。 与ArgumentsHost一样,Nest在可能需要的地方提供了ExecutionContext的实例, 例如在守卫的canActivate()方法和拦截器的intercept()方法中。它提供了以下方法:

export interface ExecutionContext extends ArgumentsHost {
/**
* 返回当前处理程序所属的控制器类的类型。
*/
getClass<T>(): Type<T>;
/**
* 返回对将在请求管道中接下来调用的处理程序(方法)的引用。
*/
getHandler(): Function;
}

getHandler()方法返回一个对即将被调用的处理程序的引用。 getClass()方法返回此特定处理程序所属的Controller类的类型。 例如,在HTTP上下文中,如果当前处理的请求是一个绑定到CatsControllercreate()方法的POST请求, 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[]的单一参数。

现在,要使用这个装饰器,我们只需在处理程序上注释它:

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

在这里,我们将Roles装饰器元数据附加到create()方法, 表示只有具有admin角色的用户才能访问此路由。

要访问路由的角色(自定义元数据),我们将再次使用Reflector助手类。 可以正常方式将Reflector注入到类中:

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

Reflector类是从@nestjs/core包中导入的。

现在,要读取处理程序元数据,使用get()方法:

const roles = this.reflector.get(Roles, context.getHandler());

Reflector#get方法允许我们通过传递两个参数轻松访问元数据: 装饰器引用和上下文(装饰器目标),以从中检索元数据。 在这个例子中,指定的装饰器是Roles(参见上面的roles.decorator.ts文件)。 上下文由调用context.getHandler()提供,这导致提取当前处理的路由处理程序的元数据。 请记住,getHandler()给我们提供了对路由处理程序函数的引用。

或者,我们可以通过在控制器级别应用元数据来组织我们的控制器,应用于控制器类中的所有路由。

@Roles(['admin'])
@Controller('cats')
export class CatsController {}

在这种情况下,为了提取控制器元数据, 我们将context.getClass()作为第二个参数传递(以提供控制器类作为元数据提取的上下文), 而不是context.getHandler()

const roles = this.reflector.get(Roles, context.getClass());

由于可以在多个级别提供元数据,您可能需要从几个上下文中提取并合并元数据。 Reflector类提供了两个用于帮助处理的实用方法。 这些方法一次性提取控制器和方法元数据,并以不同的方式组合它们。

考虑以下场景,您在两个级别都提供了Roles元数据。

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

如果您的目的是将'user'指定为默认角色,并有选择地在某些方法中覆盖它, 您可能会使用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()方法的调用)作为第二个参数。

低级方法

如前所述,您可以使用内置的@SetMetadata()装饰器而不是使用Reflector#createDecorator来附加元数据到处理程序。

@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);

这种方法更清晰、更可读,并且在某种程度上类似于Reflector#createDecorator方法。 区别在于,使用@SetMetadata,您可以更好地控制元数据键和值,并且还可以创建接受多个参数的装饰器。

现在我们有了一个自定义的@Roles()装饰器,我们可以用它来装饰create()方法。

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

要访问路由的角色(自定义元数据),我们将再次使用Reflector助手类:

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

Reflector类是从@nestjs/core包中导入的。

现在,要读取处理程序元数据,使用get()方法。

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

在这里,我们传递的是元数据键作为第一个参数(在我们的例子中是'roles'), 而其他的都与Reflector#createDecorator示例中一样。