守卫(Guard)与拦截器(Interceptor)的执行顺序
在 NestJS 应用中,请求处理是一个复杂的过程,涉及多个组件的协同工作。理解守卫(Guard)、拦截器(Interceptor)、管道(Pipe)和控制器(Controller)之间的执行顺序对于构建高效、安全的应用至关重要。本文将深入探讨这些组件的执行顺序以及它们在请求生命周期中的作用。
1. 请求处理生命周期
1.1 完整的执行顺序
NestJS 中请求处理的完整生命周期如下:
Incoming Request
↓
Guards (canActivate)
↓
Interceptors (before - intercept)
↓
Pipes (transform)
↓
Controller/Route Handler
↓
Interceptors (after - intercept)
↓
Exception Filters (if exception occurs)
↓
Outgoing Response1.2 执行顺序详解
typescript
// 示例:完整的请求处理流程
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before... Interceptor 1');
const now = Date.now();
return next
.handle()
.pipe(
tap(() => console.log(`After... Interceptor 1 (${Date.now() - now}ms)`)),
);
}
}
@Injectable()
export class TimingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before... Interceptor 2');
return next
.handle()
.pipe(
tap(() => console.log('After... Interceptor 2')),
);
}
}
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
console.log('Guard check...');
return true;
}
}
@UseGuards(AuthGuard)
@UseInterceptors(LoggingInterceptor, TimingInterceptor)
@Controller('users')
export class UserController {
@UsePipes(new ValidationPipe())
@Get(':id')
findOne(@Param('id') id: string) {
console.log('Controller handler...');
return { id, name: 'User Name' };
}
}执行顺序:
- Guard check...
- Before... Interceptor 1
- Before... Interceptor 2
- Controller handler...
- After... Interceptor 2
- After... Interceptor 1
2. 守卫(Guard)详解
2.1 守卫的作用
守卫主要用于确定请求是否应该被路由处理器处理:
typescript
// 基本守卫实现
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
// 执行认证逻辑
return this.validateRequest(request);
}
private validateRequest(request: any): boolean {
// 检查认证令牌
const authHeader = request.headers.authorization;
if (!authHeader) {
return false;
}
// 验证令牌
return this.verifyToken(authHeader);
}
private verifyToken(token: string): boolean {
// 实际的令牌验证逻辑
return token === 'valid-token';
}
}2.2 守卫的执行上下文
typescript
// 守卫中的上下文使用
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// 获取路由处理程序上的元数据
const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true; // 没有角色要求,允许访问
}
const { user } = context.switchToHttp().getRequest();
if (!user) {
return false;
}
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
// 使用守卫
@Controller('admin')
export class AdminController {
@Get('users')
@UseGuards(RolesGuard)
@SetMetadata('roles', ['admin'])
getAllUsers() {
return this.userService.findAll();
}
}3. 拦截器(Interceptor)详解
3.1 拦截器的作用
拦截器在请求处理前后执行,可以转换请求和响应:
typescript
// 基本拦截器实现
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('Before...');
const now = Date.now();
return next
.handle()
.pipe(
tap(() => console.log(`After... ${Date.now() - now}ms`)),
);
}
}3.2 拦截器的高级用法
typescript
// 响应转换拦截器
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map(data => ({
data,
statusCode: context.switchToHttp().getResponse().statusCode,
timestamp: new Date().toISOString(),
})),
);
}
}
// 缓存拦截器
@Injectable()
export class CacheInterceptor implements NestInterceptor {
private readonly cache = new Map<string, any>();
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const key = this.generateCacheKey(context);
if (this.cache.has(key)) {
return of(this.cache.get(key));
}
return next
.handle()
.pipe(
tap(response => {
this.cache.set(key, response);
}),
);
}
private generateCacheKey(context: ExecutionContext): string {
const request = context.switchToHttp().getRequest();
return `${request.method}:${request.url}`;
}
}4. 管道(Pipe)详解
4.1 管道的作用
管道在路由处理函数执行之前处理参数:
typescript
// 参数验证管道
@Injectable()
export class ValidationPipe implements PipeTransform {
async transform(value: any, metadata: ArgumentMetadata) {
const { metatype } = metadata;
if (!metatype || !this.toValidate(metatype)) {
return value;
}
const object = plainToClass(metatype, value);
const errors = await validate(object);
if (errors.length > 0) {
throw new BadRequestException('Validation failed');
}
return object;
}
private toValidate(metatype: Type<any>): boolean {
const types = [String, Boolean, Number, Array, Object];
return !types.includes(metatype);
}
}5. 组件协同工作示例
5.1 完整的请求处理示例
typescript
// 自定义守卫
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
console.log('1. AuthGuard: Checking authentication');
const request = context.switchToHttp().getRequest();
return !!request.headers.authorization;
}
}
// 日志拦截器
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('2. LoggingInterceptor: Before handler');
const now = Date.now();
return next
.handle()
.pipe(
tap(() => console.log(`4. LoggingInterceptor: After handler (${Date.now() - now}ms)`)),
);
}
}
// 验证管道
@Injectable()
export class ValidationPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
console.log('3. ValidationPipe: Validating input');
return value;
}
}
// 控制器
@UseGuards(AuthGuard)
@UseInterceptors(LoggingInterceptor)
@Controller('users')
export class UserController {
@Post()
@UsePipes(new ValidationPipe())
create(@Body() createUserDto: CreateUserDto) {
console.log('3.5. Controller: Handling request');
return { id: '1', ...createUserDto };
}
}执行输出:
1. AuthGuard: Checking authentication
2. LoggingInterceptor: Before handler
3. ValidationPipe: Validating input
3.5. Controller: Handling request
4. LoggingInterceptor: After handler (2ms)5.2 异常处理流程
typescript
// 异常过滤器
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
console.log('5. ExceptionFilter: Handling exception');
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
response.status(500).json({
statusCode: 500,
timestamp: new Date().toISOString(),
path: request.url,
message: 'Internal server error',
});
}
}
// 抛出异常的控制器
@Controller('users')
export class UserController {
@Get(':id')
findOne(@Param('id') id: string) {
if (id === 'error') {
throw new Error('Something went wrong');
}
return { id, name: 'User Name' };
}
}异常处理流程:
- Guards
- Interceptors (before)
- Pipes
- Controller (抛出异常)
- Exception Filters
- Interceptors (after) - 不执行,因为异常已经处理
6. 性能考虑和最佳实践
6.1 执行顺序优化
typescript
// 避免在守卫中执行重型操作
@Injectable()
export class LightweightAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// 轻量级检查,快速失败
const request = context.switchToHttp().getRequest();
const authHeader = request.headers.authorization;
if (!authHeader) {
return false; // 快速拒绝未认证请求
}
// 只进行基本格式验证,不进行完整令牌验证
return authHeader.startsWith('Bearer ');
}
}
// 在拦截器中执行重型操作
@Injectable()
export class DetailedAuthInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
// 执行完整的认证逻辑
this.verifyTokenDetails(request.headers.authorization);
return next.handle();
}
private verifyTokenDetails(token: string) {
// 完整的令牌验证逻辑
}
}6.2 条件性执行
typescript
// 条件性守卫
@Injectable()
export class ConditionalAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// 根据路由元数据决定是否需要认证
const isPublic = Reflect.getMetadata('isPublic', context.getHandler());
if (isPublic) {
return true; // 公共路由,无需认证
}
// 执行认证逻辑
const request = context.switchToHttp().getRequest();
return !!request.headers.authorization;
}
}
// 使用条件性守卫
@Controller('users')
export class UserController {
@Get('public-info')
@SetMetadata('isPublic', true)
getPublicInfo() {
return { info: 'This is public information' };
}
@Get('private-info')
getPrivateInfo() {
return { info: 'This is private information' };
}
}7. 调试和监控
7.1 执行顺序监控
typescript
// 执行顺序监控拦截器
@Injectable()
export class ExecutionOrderInterceptor implements NestInterceptor {
private static executionId = 0;
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const executionId = ++ExecutionOrderInterceptor.executionId;
const request = context.switchToHttp().getRequest();
console.log(`[${executionId}] Request: ${request.method} ${request.url}`);
console.log(`[${executionId}] Interceptor (before): Starting processing`);
const startTime = Date.now();
return next
.handle()
.pipe(
tap({
next: () => {
console.log(`[${executionId}] Interceptor (after): Processing completed in ${Date.now() - startTime}ms`);
},
error: (error) => {
console.log(`[${executionId}] Interceptor (error): Error occurred after ${Date.now() - startTime}ms`, error.message);
},
}),
);
}
}8. 总结
NestJS 请求处理组件的执行顺序是:
- Guards - 权限检查,决定是否处理请求
- Interceptors (before) - 请求前处理
- Pipes - 参数验证和转换
- Controller - 实际的业务逻辑处理
- Interceptors (after) - 响应后处理
- Exception Filters - 异常处理(如果发生异常)
理解这个执行顺序有助于我们:
- 正确设计应用的安全机制
- 优化性能,合理安排组件执行顺序
- 调试和监控应用的执行流程
- 构建更加健壮和可维护的应用
通过合理使用这些组件,我们可以构建出既安全又高效的 Web 应用程序。
至此,我们已经完成了 NestJS 专栏第三阶段的所有内容。在下一篇文章中,我们将进入第四阶段,探讨 AOP 编程:守卫、拦截器、装饰器如何实现横切关注点。