执行上下文 (ExecutionContext)
当然!我们来探索一个在之前的学习中反复出现,但我们从未给过它“主角光环”的核心概念:执行上下文 (ExecutionContext)。它是 NestJS 中许多强大功能(如守卫、拦截器、自定义装饰器)的“信息中枢”和“瑞士军刀”。
NestJS 的“上帝视角”:深入解析执行上下文 (ExecutionContext)
想象一下你是一个经验丰富的活动保安(一个 守卫 Guard)。你的职责是决定是否让某位访客(一个 请求 Request)进入某个特定的房间(一个 路由处理函数 Handler)。
为了做出正确的判断,你光站在那里是不够的。你需要一个高科技的平板电脑,上面显示着关于当前情况的所有信息:
- 访客信息:这位访客是谁?(HTTP 请求的
request
对象) - 目标房间:他想进入哪个房间?(将要被执行的
Controller
方法) - 所在楼层:这个房间在哪一层?(这个方法所属的
Controller
类) - 活动类型:这是个正式晚宴,还是一个休闲派对?(是 HTTP 请求,还是 WebSocket 消息?)
在 NestJS 中,ExecutionContext
就是你手中的那台无所不知的“高科技平板电脑”。它是一个在守卫、拦截器、自定义装饰器等组件中被传递的参数,封装了当前执行操作的全部上下文信息。
1. 为什么它如此重要?—— 编写通用的“瑞士军刀”
ExecutionContext
最伟大的设计思想在于,它让 NestJS 的核心功能(守卫、拦截器等)变得与协议无关。
这意味着,你可以编写一个 RolesGuard
,它既能用于保护一个 HTTP 的 REST API 接口,也能用于保护一个 WebSocket 的实时消息处理程序。因为无论请求来自哪里,ExecutionContext
都会用一套统一的 API 把你需要的信息提供给你。
它就像一把瑞士军刀,虽然外表只有一个,但里面藏着针对不同场景的各种工具(螺丝刀、小刀、开瓶器)。
2. “瑞士军刀”里有什么工具?—— 核心方法解析
ExecutionContext
继承自 ArgumentsHost
,并提供了几个非常有用的方法来获取当前执行的详细信息。
getHandler(): Function
- 它是什么:获取即将被调用的路由处理函数(Controller 的方法)的引用。
- 它有什么用:可以用来结合
Reflector
获取附加在该方法上的元数据(比如@Roles('admin')
)。
getClass<T>(): Type<T>
- 它是什么:获取该路由处理函数所属的 Controller 类的引用。
- 它有什么用:可以用来获取附加在整个 Controller 类上的元数据。
getType<TContext extends string = ContextType>(): TContext
- 它是什么:获取当前执行上下文的类型。返回值是
'http'
,'rpc'
, 或'ws'
。 - 它有什么用:让你可以在同一个组件中,根据不同的请求类型执行不同的逻辑。
代码示例:一个“信息侦探”守卫
让我们创建一个守卫,它的唯一目的就是利用这些方法来“侦察”它所保护的路由。
src/info.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class InfoGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const contextType = context.getType();
console.log(`侦测到上下文类型: ${contextType}`);
// 只有在 HTTP 上下文中我们才能安全地获取 Controller 和 Handler
if (contextType === 'http') {
const controllerClass = context.getClass();
const handlerFunction = context.getHandler();
console.log(`目标 Controller 类名: ${controllerClass.name}`);
console.log(`目标处理函数名: ${handlerFunction.name}`);
}
return true; // 我们只是侦察,所以总是放行
}
}
在控制器中使用它:
// cats.controller.ts
@UseGuards(InfoGuard)
@Controller('cats')
export class CatsController {
@Get()
findAll() {
return 'This action returns all cats';
}
}
当你访问 /cats
时,控制台会输出:
侦测到上下文类型: http
目标 Controller 类名: CatsController
目标处理函数名: findAll
3. 打开正确的“工具”:switchToHttp()
等切换方法
getHandler()
和 getClass()
很有用,但它们没有给我们最重要的东西:原始的请求(Request)和响应(Response)对象。
这就是切换上下文方法大显身手的地方。ExecutionContext
提供了这些方法,让你从通用的“平板电脑”界面,切换到针对特定协议的“详细视图”。
switchToHttp()
: 返回一个HttpArgumentsHost
对象,可以从中获取getRequest()
和getResponse()
。switchToWs()
: 返回一个WsArgumentsHost
对象,可以获取getData()
和getClient()
。switchToRpc()
: 返回一个RpcArgumentsHost
对象,可以获取getData()
。
最常用、也最重要的就是 switchToHttp()
。
实战演练:一个真正的 AuthGuard
这个守卫需要检查 HTTP 请求的 headers
中是否包含一个有效的 Authorization
令牌。
// auth.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// 1. 从通用上下文切换到 HTTP 上下文
const httpContext = context.switchToHttp();
// 2. 从 HTTP 上下文中获取 request 对象
const request = httpContext.getRequest();
// 3. 现在你可以像在 Express 中一样操作 request 对象了!
const authToken = request.headers['authorization'];
if (authToken && authToken.startsWith('Bearer ')) {
console.log('令牌存在,验证通过!');
return true; // 实际项目中这里会验证令牌的有效性
}
console.log('未找到令牌,拒绝访问!');
return false;
}
}
这个例子完美地展示了 ExecutionContext
的核心工作流程:
- 接收通用的
context
。 - 调用
switchToHttp()
切换到 HTTP 专属的“工具集”。 - 调用
getRequest()
拿到我们最熟悉的request
对象。 - 执行具体的业务逻辑。
4. ExecutionContext
与自定义装饰器的珠联璧合
我们之前学习自定义装饰器时,其实已经和 ExecutionContext
打过照面了!创建自定义参数装饰器的工厂函数,接收的第二个参数就是 ExecutionContext
。
让我们重新审视 @User()
装饰器的实现,这次你会看得更明白。
@User()
装饰器:
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
// 这个工厂函数的第二个参数就是 ExecutionContext!
(data: unknown, ctx: ExecutionContext) => {
// 我们在这里做的,和在 Guard 里做的事情一模一样
const request = ctx.switchToHttp().getRequest();
// 装饰器的返回值,就是将被注入到 Controller 方法参数中的值
return request.user;
},
);
现在你应该恍然大悟了:自定义装饰器之所以能从请求中提取数据,正是因为 NestJS 把包含了所有请求信息的“平板电脑”——ExecutionContext
——交给了它。
总结
ExecutionContext
是 NestJS 依赖注入和请求处理流程中一个至关重要的“枢纽”。
它是什么? | 一个包含了当前执行操作所有信息的参数对象。 |
---|---|
谁在使用它? | 守卫 (Guards), 拦截器 (Interceptors), 自定义装饰器 (Custom Decorators), 以及 ModuleRef 。 |
它的超能力? | 协议无关。用一套统一的 API (getType , getClass ...) 应对不同的通信协议。 |
最核心的操作? | 通过 switchToHttp() 切换到 HTTP 上下文,然后调用 getRequest() 来获取原始的请求对象。 |
理解了 ExecutionContext
,你才算真正理解了 NestJS 的守卫和拦截器等功能是如何与底层的 HTTP 服务器(如 Express 或 Fastify)解耦,并协同工作的。它不是你每天都会直接操作的对象,但它无处不在,是你编写高级、可复用 NestJS 组件的基石。