NestJS 執(zhí)行上下文

2023-09-08 15:09 更新

Nest 提供了幾個(gè)實(shí)用程序類,有助于輕松編寫(xiě)跨多個(gè)應(yīng)用上下文(例如,基于 Nest HTTP 服務(wù)器、微服務(wù)和 WebSockets 應(yīng)用上下文)運(yùn)行的應(yīng)用。 這些實(shí)用程序提供有關(guān)當(dāng)前執(zhí)行上下文的信息,這些信息可用于構(gòu)建通用的 guards、filters 和 interceptors,它們可以跨廣泛的控制器、方法和執(zhí)行上下文集工作。

我們?cè)诒菊轮薪榻B了兩個(gè)這樣的類: ArgumentsHost 和 ExecutionContext。

ArgumentsHost 類

ArgumentsHost 類提供了用于檢索傳遞給處理程序的參數(shù)的方法。 它允許選擇適當(dāng)?shù)纳舷挛模ɡ?HTTP、RPC(微服務(wù))或 WebSockets)以從中檢索參數(shù)。 該框架在你可能想要訪問(wèn)它的地方提供了一個(gè) ArgumentsHost 的實(shí)例,通常作為 host 參數(shù)引用。 例如,使用 ArgumentsHost 實(shí)例調(diào)用 異常過(guò)濾器 的 catch() 方法。

ArgumentsHost 只是作為處理程序參數(shù)的抽象。 例如,對(duì)于 HTTP 服務(wù)器應(yīng)用(當(dāng)使用 @nestjs/platform-express 時(shí)),host 對(duì)象封裝了 Express 的 [request, response, next] 數(shù)組,其中 request 是請(qǐng)求對(duì)象,response 是響應(yīng)對(duì)象,next 是控制應(yīng)用請(qǐng)求-響應(yīng)周期的函數(shù)。 另一方面,對(duì)于 GraphQL 應(yīng)用,host 對(duì)象包含 [root, args, context, info] 數(shù)組。

當(dāng)前應(yīng)用上下文

在構(gòu)建旨在跨多個(gè)應(yīng)用上下文運(yùn)行的通用 guards、filters 和 interceptors 時(shí),我們需要一種方法來(lái)確定我們的方法當(dāng)前運(yùn)行的應(yīng)用類型。 使用 ArgumentsHost 的 getType() 方法執(zhí)行此操作:

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 包中導(dǎo)入的。

有了可用的應(yīng)用類型,我們可以編寫(xiě)更通用的組件,如下所示。

主機(jī)處理程序參數(shù)

要檢索傳遞給處理程序的參數(shù)數(shù)組,一種方法是使用宿主對(duì)象的 getArgs() 方法。

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

你可以使用 getArgByIndex() 方法按索引提取特定參數(shù):

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

在這些示例中,我們通過(guò)索引檢索請(qǐng)求和響應(yīng)對(duì)象,這通常不被推薦,因?yàn)樗鼘?yīng)用耦合到特定的執(zhí)行上下文。 相反,你可以通過(guò)使用 host 對(duì)象的實(shí)用方法之一切換到適合你的應(yīng)用的應(yīng)用上下文,從而使你的代碼更加健壯和可重用。 上下文切換實(shí)用程序方法如下所示。

/**
 * Switch context to RPC.
 */
switchToRpc(): RpcArgumentsHost;
/**
 * Switch context to HTTP.
 */
switchToHttp(): HttpArgumentsHost;
/**
 * Switch context to WebSockets.
 */
switchToWs(): WsArgumentsHost;

讓我們使用 switchToHttp() 方法重寫(xiě)前面的示例。 host.switchToHttp() 輔助程序調(diào)用返回適合 HTTP 應(yīng)用上下文的 HttpArgumentsHost 對(duì)象。 HttpArgumentsHost 對(duì)象有兩個(gè)有用的方法可以用來(lái)提取所需的對(duì)象。 在這種情況下,我們還使用 Express 類型斷言來(lái)返回原生 Express 類型的對(duì)象:

const ctx = host.switchToHttp();
const request = ctx.getRequest<Request>();
const response = ctx.getResponse<Response>();

同樣,WsArgumentsHost 和 RpcArgumentsHost 具有在微服務(wù)和 WebSockets 上下文中返回適當(dāng)對(duì)象的方法。 以下是 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;
}

執(zhí)行上下文類

ExecutionContext 擴(kuò)展 ArgumentsHost,提供有關(guān)當(dāng)前執(zhí)行過(guò)程的更多詳細(xì)信息。 與 ArgumentsHost 一樣,Nest 在你可能需要的地方提供了 ExecutionContext 的實(shí)例,例如 guard 的 canActivate() 方法和 interceptor 的 intercept() 方法。 它提供了以下方法:

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() 方法返回對(duì)即將被調(diào)用的處理程序的引用。 getClass() 方法返回此特定處理程序所屬的 Controller 類的類型。 例如,在 HTTP 上下文中,如果當(dāng)前處理的請(qǐng)求是 POST 請(qǐng)求,綁定到 CatsController 上的 create() 方法,getHandler() 返回 create() 方法的引用,getClass() 返回 CatsControllertype(不是實(shí)例)。

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

訪問(wèn)對(duì)當(dāng)前類和處理程序方法的引用的能力提供了極大的靈活性。 最重要的是,它使我們有機(jī)會(huì)通過(guò) Reflector#createDecorator 創(chuàng)建的裝飾器或來(lái)自守衛(wèi)或攔截器內(nèi)的內(nèi)置 @SetMetadata() 裝飾器來(lái)訪問(wèn)元數(shù)據(jù)集。 我們?cè)谙旅娼榻B了這個(gè)用例。

反射和元數(shù)據(jù)

Nest 提供了通過(guò) Reflector#createDecorator 方法創(chuàng)建的裝飾器和內(nèi)置 @SetMetadata() 裝飾器將 自定義元數(shù)據(jù) 附加到路由處理程序的功能。 在本節(jié)中,我們將比較這兩種方法,并了解如何從防護(hù)程序或攔截器中訪問(wèn)元數(shù)據(jù)。

要使用 Reflector#createDecorator 創(chuàng)建強(qiáng)類型裝飾器,我們需要指定類型參數(shù)。 例如,讓我們創(chuàng)建一個(gè) Roles 裝飾器,它將字符串?dāng)?shù)組作為參數(shù)。

roles.decorator.ts

import { Reflector } from '@nestjs/core';
export const Roles = Reflector.createDecorator<string[]>();

這里的 Roles 裝飾器是一個(gè)接受 string[] 類型的單個(gè)參數(shù)的函數(shù)。

現(xiàn)在,要使用這個(gè)裝飾器,我們只需用它注釋處理程序:

cats.controller.ts

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

這里我們將 Roles 裝飾器元數(shù)據(jù)附加到 create() 方法,表明只有具有 admin 角色的用戶才可以訪問(wèn)此路由。

要訪問(wèn)路由的角色(自定義元數(shù)據(jù)),我們將再次使用 Reflector 輔助程序類。 Reflector 可以通過(guò)正常方式注入到一個(gè)類中:

roles.guard.ts

@Injectable()
export class RolesGuard {
  constructor(private reflector: Reflector) {}
}
提示Reflector 類是從 @nestjs/core 包中導(dǎo)入的。

現(xiàn)在,要讀取處理程序元數(shù)據(jù),請(qǐng)使用 get() 方法:

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

Reflector#get 方法允許我們通過(guò)傳入兩個(gè)參數(shù)輕松訪問(wèn)元數(shù)據(jù): 裝飾器引用和用于檢索元數(shù)據(jù)的 context(裝飾器目標(biāo))。 在這個(gè)例子中,指定的 decorator 是 Roles(參考上面的 roles.decorator.ts 文件)。 上下文由對(duì) context.getHandler() 的調(diào)用提供,這會(huì)導(dǎo)致為當(dāng)前處理的路由處理程序提取元數(shù)據(jù)。 請(qǐng)記住,getHandler() 為我們提供了路由處理程序函數(shù)的 reference。

或者,我們可以通過(guò)在控制器級(jí)別應(yīng)用元數(shù)據(jù)來(lái)組織我們的控制器,應(yīng)用于控制器類中的所有路由。

cats.controller.ts

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

在這種情況下,為了提取控制器元數(shù)據(jù),我們將 context.getClass() 作為第二個(gè)參數(shù)傳遞(以提供控制器類作為元數(shù)據(jù)提取的上下文)而不是 context.getHandler():

roles.guard.ts

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

鑒于在多個(gè)級(jí)別提供元數(shù)據(jù)的能力,你可能需要從多個(gè)上下文中提取和合并元數(shù)據(jù)。 Reflector 類提供了兩個(gè)實(shí)用方法來(lái)幫助解決這個(gè)問(wèn)題。 這些方法一次提取 both 控制器和方法元數(shù)據(jù),并以不同的方式組合它們。

考慮以下場(chǎng)景,你在兩個(gè)級(jí)別都提供了 Roles 元數(shù)據(jù)。

cats.controller.ts

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

如果你打算將 'user' 指定為默認(rèn)角色,并針對(duì)某些方法有選擇地覆蓋它,你可能會(huì)使用 getAllAndOverride() 方法。

const roles = this.reflector.getAllAndOverride(Roles, [context.getHandler(), context.getClass()]);

使用此代碼的守衛(wèi),在具有上述元數(shù)據(jù)的 create() 方法的上下文中運(yùn)行,將導(dǎo)致 roles 包含 ['admin']。

要獲取兩者的元數(shù)據(jù)并將其合并(此方法合并數(shù)組和對(duì)象),請(qǐng)使用 getAllAndMerge() 方法:

const roles = this.reflector.getAllAndMerge(Roles, [context.getHandler(), context.getClass()]);

這將導(dǎo)致 roles 包含 ['user', 'admin']。

對(duì)于這兩種合并方法,你將元數(shù)據(jù)鍵作為第一個(gè)參數(shù)傳遞,并將元數(shù)據(jù)目標(biāo)上下文數(shù)組(即對(duì) getHandler() 和/或 getClass()) 方法的調(diào)用)作為第二個(gè)參數(shù)傳遞。

底層方法#

如前所述,你還可以使用內(nèi)置的 @SetMetadata() 裝飾器來(lái)將元數(shù)據(jù)附加到處理程序,而不是使用 Reflector#createDecorator。

cats.controller.ts

@Post()
@SetMetadata('roles', ['admin'])
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}
提示@SetMetadata() 裝飾器是從 @nestjs/common 包中導(dǎo)入的。

通過(guò)上面的構(gòu)造,我們將 roles 元數(shù)據(jù)(roles 是元數(shù)據(jù)鍵,['admin'] 是關(guān)聯(lián)值)附加到 create() 方法。 雖然這可行,但在你的路由中直接使用 @SetMetadata() 并不是好的做法。 相反,你可以創(chuàng)建自己的裝飾器,如下所示:

roles.decorator.ts

import { SetMetadata } from '@nestjs/common';
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

這種方法更簡(jiǎn)潔、更具可讀性,并且有點(diǎn)類似于 Reflector#createDecorator 方法。 不同之處在于,使用 @SetMetadata,你可以更好地控制元數(shù)據(jù)鍵和值,并且還可以創(chuàng)建采用多個(gè)參數(shù)的裝飾器。

現(xiàn)在我們有了一個(gè)自定義的 @Roles() 裝飾器,我們可以用它來(lái)裝飾 create() 方法。

cats.controller.ts

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

要訪問(wèn)路由的角色(自定義元數(shù)據(jù)),我們將再次使用 Reflector 輔助程序類:

roles.guard.ts

@Injectable()
export class RolesGuard {
  constructor(private reflector: Reflector) {}
}
提示Reflector 類是從 @nestjs/core 包中導(dǎo)入的。

現(xiàn)在,要讀取處理程序元數(shù)據(jù),請(qǐng)使用 get() 方法。

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

這里我們沒(méi)有傳遞裝飾器引用,而是傳遞元數(shù)據(jù) key 作為第一個(gè)參數(shù)(在我們的例子中是 'roles')。 其他一切與 Reflector#createDecorator 示例中的相同。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)