Nest 提供了幾個(gè)實(shí)用程序類,有助于輕松編寫跨多個(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í)行上下文集工作。
我們在本章中介紹了兩個(gè)這樣的類: ArgumentsHost 和 ExecutionContext。
ArgumentsHost 類提供了用于檢索傳遞給處理程序的參數(shù)的方法。 它允許選擇適當(dāng)?shù)纳舷挛模ɡ?HTTP、RPC(微服務(wù))或 WebSockets)以從中檢索參數(shù)。 該框架在你可能想要訪問它的地方提供了一個(gè) ArgumentsHost 的實(shí)例,通常作為 host 參數(shù)引用。 例如,使用 ArgumentsHost 實(shí)例調(diào)用 異常過濾器 的 catch() 方法。
ArgumentsHost 只是作為處理程序參數(shù)的抽象。 例如,對于 HTTP 服務(wù)器應(yīng)用(當(dāng)使用 @nestjs/platform-express 時(shí)),host 對象封裝了 Express 的 [request, response, next] 數(shù)組,其中 request 是請求對象,response 是響應(yīng)對象,next 是控制應(yīng)用請求-響應(yīng)周期的函數(shù)。 另一方面,對于 GraphQL 應(yīng)用,host 對象包含 [root, args, context, info] 數(shù)組。
在構(gòu)建旨在跨多個(gè)應(yīng)用上下文運(yùn)行的通用 guards、filters 和 interceptors 時(shí),我們需要一種方法來確定我們的方法當(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)用類型,我們可以編寫更通用的組件,如下所示。
要檢索傳遞給處理程序的參數(shù)數(shù)組,一種方法是使用宿主對象的 getArgs() 方法。
const [req, res, next] = host.getArgs();
你可以使用 getArgByIndex() 方法按索引提取特定參數(shù):
const request = host.getArgByIndex(0);
const response = host.getArgByIndex(1);
在這些示例中,我們通過索引檢索請求和響應(yīng)對象,這通常不被推薦,因?yàn)樗鼘?yīng)用耦合到特定的執(zhí)行上下文。 相反,你可以通過使用 host 對象的實(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() 方法重寫前面的示例。 host.switchToHttp() 輔助程序調(diào)用返回適合 HTTP 應(yīng)用上下文的 HttpArgumentsHost 對象。 HttpArgumentsHost 對象有兩個(gè)有用的方法可以用來提取所需的對象。 在這種情況下,我們還使用 Express 類型斷言來返回原生 Express 類型的對象:
const ctx = host.switchToHttp();
const request = ctx.getRequest<Request>();
const response = ctx.getResponse<Response>();
同樣,WsArgumentsHost 和 RpcArgumentsHost 具有在微服務(wù)和 WebSockets 上下文中返回適當(dāng)對象的方法。 以下是 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 擴(kuò)展 ArgumentsHost,提供有關(guān)當(dāng)前執(zhí)行過程的更多詳細(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() 方法返回對即將被調(diào)用的處理程序的引用。 getClass() 方法返回此特定處理程序所屬的 Controller 類的類型。 例如,在 HTTP 上下文中,如果當(dāng)前處理的請求是 POST 請求,綁定到 CatsController 上的 create() 方法,getHandler() 返回 create() 方法的引用,getClass() 返回 CatsControllertype(不是實(shí)例)。
const methodKey = ctx.getHandler().name; // "create"
const className = ctx.getClass().name; // "CatsController"
訪問對當(dāng)前類和處理程序方法的引用的能力提供了極大的靈活性。 最重要的是,它使我們有機(jī)會(huì)通過 Reflector#createDecorator 創(chuàng)建的裝飾器或來自守衛(wèi)或攔截器內(nèi)的內(nèi)置 @SetMetadata() 裝飾器來訪問元數(shù)據(jù)集。 我們在下面介紹了這個(gè)用例。
Nest 提供了通過 Reflector#createDecorator 方法創(chuàng)建的裝飾器和內(nèi)置 @SetMetadata() 裝飾器將 自定義元數(shù)據(jù) 附加到路由處理程序的功能。 在本節(jié)中,我們將比較這兩種方法,并了解如何從防護(hù)程序或攔截器中訪問元數(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 角色的用戶才可以訪問此路由。
要訪問路由的角色(自定義元數(shù)據(jù)),我們將再次使用 Reflector 輔助程序類。 Reflector 可以通過正常方式注入到一個(gè)類中:
roles.guard.ts
@Injectable()
export class RolesGuard {
constructor(private reflector: Reflector) {}
}
提示Reflector 類是從 @nestjs/core 包中導(dǎo)入的。
現(xiàn)在,要讀取處理程序元數(shù)據(jù),請使用 get() 方法:
const roles = this.reflector.get(Roles, context.getHandler());
Reflector#get 方法允許我們通過傳入兩個(gè)參數(shù)輕松訪問元數(shù)據(jù): 裝飾器引用和用于檢索元數(shù)據(jù)的 context(裝飾器目標(biāo))。 在這個(gè)例子中,指定的 decorator 是 Roles(參考上面的 roles.decorator.ts 文件)。 上下文由對 context.getHandler() 的調(diào)用提供,這會(huì)導(dǎo)致為當(dāng)前處理的路由處理程序提取元數(shù)據(jù)。 請記住,getHandler() 為我們提供了路由處理程序函數(shù)的 reference。
或者,我們可以通過在控制器級別應(yīng)用元數(shù)據(jù)來組織我們的控制器,應(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è)級別提供元數(shù)據(jù)的能力,你可能需要從多個(gè)上下文中提取和合并元數(shù)據(jù)。 Reflector 類提供了兩個(gè)實(shí)用方法來幫助解決這個(gè)問題。 這些方法一次提取 both 控制器和方法元數(shù)據(jù),并以不同的方式組合它們。
考慮以下場景,你在兩個(gè)級別都提供了 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)角色,并針對某些方法有選擇地覆蓋它,你可能會(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ù)組和對象),請使用 getAllAndMerge() 方法:
const roles = this.reflector.getAllAndMerge(Roles, [context.getHandler(), context.getClass()]);
這將導(dǎo)致 roles 包含 ['user', 'admin']。
對于這兩種合并方法,你將元數(shù)據(jù)鍵作為第一個(gè)參數(shù)傳遞,并將元數(shù)據(jù)目標(biāo)上下文數(shù)組(即對 getHandler() 和/或 getClass()) 方法的調(diào)用)作為第二個(gè)參數(shù)傳遞。
如前所述,你還可以使用內(nèi)置的 @SetMetadata() 裝飾器來將元數(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)入的。
通過上面的構(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);
這種方法更簡潔、更具可讀性,并且有點(diǎn)類似于 Reflector#createDecorator 方法。 不同之處在于,使用 @SetMetadata,你可以更好地控制元數(shù)據(jù)鍵和值,并且還可以創(chuàng)建采用多個(gè)參數(shù)的裝飾器。
現(xiàn)在我們有了一個(gè)自定義的 @Roles() 裝飾器,我們可以用它來裝飾 create() 方法。
cats.controller.ts
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
要訪問路由的角色(自定義元數(shù)據(jù)),我們將再次使用 Reflector 輔助程序類:
roles.guard.ts
@Injectable()
export class RolesGuard {
constructor(private reflector: Reflector) {}
}
提示Reflector 類是從 @nestjs/core 包中導(dǎo)入的。
現(xiàn)在,要讀取處理程序元數(shù)據(jù),請使用 get() 方法。
const roles = this.reflector.get<string[]>('roles', context.getHandler());
這里我們沒有傳遞裝飾器引用,而是傳遞元數(shù)據(jù) key 作為第一個(gè)參數(shù)(在我們的例子中是 'roles')。 其他一切與 Reflector#createDecorator 示例中的相同。
更多建議: