Skip to content

NestJS 中间件与守卫

Middleware、Guard、Interceptor、Pipe、Filter 的区别与实践

目录


执行顺序

请求 → Middleware → Guard → Interceptor(前) → Pipe → Handler → Interceptor(后) → Filter → 响应
组件职责典型用例
Middleware请求预处理日志、CORS、Body 解析
Guard访问控制认证、授权、角色检查
Interceptor请求/响应转换日志、缓存、响应包装
Pipe数据转换/验证DTO 验证、类型转换
Filter异常处理错误格式化、日志记录

Middleware

函数式中间件

typescript
import { Request, Response, NextFunction } from 'express';

export function loggerMiddleware(req: Request, res: Response, next: NextFunction) {
  console.log(`[${req.method}] ${req.url}`);
  next();
}

// 注册
@Module({})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(loggerMiddleware)
      .forRoutes('*');
  }
}

类式中间件

typescript
import { Injectable, NestMiddleware } from '@nestjs/common';

@Injectable()
export class AuthMiddleware implements NestMiddleware {
  constructor(private readonly authService: AuthService) {}

  use(req: Request, res: Response, next: NextFunction) {
    const token = req.headers.authorization?.split(' ')[1];
    
    if (token) {
      req['user'] = this.authService.validateToken(token);
    }
    
    next();
  }
}

// 注册
consumer
  .apply(AuthMiddleware)
  .exclude({ path: 'health', method: RequestMethod.GET })
  .forRoutes({ path: '*', method: RequestMethod.ALL });

Guard

认证守卫

typescript
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private readonly authService: AuthService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const token = request.headers.authorization?.split(' ')[1];

    if (!token) {
      throw new UnauthorizedException('No token provided');
    }

    try {
      const user = await this.authService.validateToken(token);
      request.user = user;
      return true;
    } catch {
      throw new UnauthorizedException('Invalid token');
    }
  }
}

角色守卫

typescript
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@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();
    return requiredRoles.some(role => user.roles?.includes(role));
  }
}

// 使用
@Post()
@Roles('admin')
@UseGuards(AuthGuard, RolesGuard)
create() {}

全局守卫

typescript
// main.ts
app.useGlobalGuards(new AuthGuard());

// 或通过模块注册(支持依赖注入)
@Module({
  providers: [
    {
      provide: APP_GUARD,
      useClass: AuthGuard
    }
  ]
})
export class AppModule {}

Interceptor

日志拦截器

typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const startTime = Date.now();

    console.log(`→ ${request.method} ${request.url}`);

    return next.handle().pipe(
      tap(() => {
        console.log(`← ${request.method} ${request.url} - ${Date.now() - startTime}ms`);
      })
    );
  }
}

响应转换拦截器

typescript
import { map } from 'rxjs/operators';

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
  intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
    return next.handle().pipe(
      map(data => ({
        code: 0,
        message: 'success',
        data,
        timestamp: Date.now()
      }))
    );
  }
}

缓存拦截器

typescript
@Injectable()
export class CacheInterceptor implements NestInterceptor {
  constructor(private readonly cacheService: CacheService) {}

  async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
    const request = context.switchToHttp().getRequest();
    const cacheKey = `cache:${request.url}`;

    const cached = await this.cacheService.get(cacheKey);
    if (cached) {
      return of(cached);
    }

    return next.handle().pipe(
      tap(data => this.cacheService.set(cacheKey, data, 60))
    );
  }
}

超时拦截器

typescript
import { timeout, catchError } from 'rxjs/operators';
import { TimeoutError, throwError } from 'rxjs';

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      timeout(5000),
      catchError(err => {
        if (err instanceof TimeoutError) {
          throw new RequestTimeoutException();
        }
        return throwError(() => err);
      })
    );
  }
}

Pipe

内置 Pipe

typescript
// 参数转换
@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {}

// 默认值
@Get()
findAll(@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number) {}

// UUID 验证
@Get(':uuid')
findByUuid(@Param('uuid', ParseUUIDPipe) uuid: string) {}

DTO 验证 Pipe

typescript
// main.ts
app.useGlobalPipes(new ValidationPipe({
  whitelist: true,           // 删除 DTO 中未定义的属性
  forbidNonWhitelisted: true, // 存在未定义属性时抛出错误
  transform: true,           // 自动类型转换
  transformOptions: {
    enableImplicitConversion: true
  }
}));

// create-user.dto.ts
import { IsEmail, IsString, MinLength, IsOptional } from 'class-validator';

export class CreateUserDto {
  @IsEmail()
  email: string;

  @IsString()
  @MinLength(6)
  password: string;

  @IsOptional()
  @IsString()
  name?: string;
}

自定义 Pipe

typescript
import { PipeTransform, Injectable, BadRequestException } from '@nestjs/common';

@Injectable()
export class ParseDatePipe implements PipeTransform<string, Date> {
  transform(value: string): Date {
    const date = new Date(value);
    
    if (isNaN(date.getTime())) {
      throw new BadRequestException(`Invalid date: ${value}`);
    }
    
    return date;
  }
}

// 使用
@Get()
findByDate(@Query('date', ParseDatePipe) date: Date) {}

Exception Filter

全局异常过滤器

typescript
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  constructor(private readonly logger: LoggerService) {}

  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status = exception instanceof HttpException
      ? exception.getStatus()
      : HttpStatus.INTERNAL_SERVER_ERROR;

    const message = exception instanceof HttpException
      ? exception.message
      : 'Internal server error';

    const errorResponse = {
      code: status,
      message,
      path: request.url,
      timestamp: new Date().toISOString()
    };

    this.logger.error(`${request.method} ${request.url}`, exception);

    response.status(status).json(errorResponse);
  }
}

HTTP 异常过滤器

typescript
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const status = exception.getStatus();
    const exceptionResponse = exception.getResponse();

    response.status(status).json({
      code: status,
      message: typeof exceptionResponse === 'string' 
        ? exceptionResponse 
        : (exceptionResponse as any).message,
      timestamp: Date.now()
    });
  }
}

高频面试题

Q1: Middleware 和 Interceptor 的区别?

特性MiddlewareInterceptor
访问能力req/res 对象ExecutionContext
依赖注入类式中间件支持✅ 完全支持
响应处理❌ 无法访问响应体✅ 可包装/转换响应
执行时机路由匹配前Handler 前后

Q2: Guard 和 Middleware 的区别?

特性GuardMiddleware
职责访问控制(授权)请求预处理
返回值boolean(是否放行)void(调用 next)
元数据访问✅ 可通过 Reflector
执行顺序在 Middleware 之后最先执行

Q3: 如何按顺序执行多个 Guard?

typescript
@UseGuards(AuthGuard, RolesGuard, PermissionsGuard)
  • 按声明顺序执行
  • 任一 Guard 返回 false 或抛出异常,后续不执行

Q4: Pipe 的执行顺序?

  1. 全局 Pipe (app.useGlobalPipes)
  2. Controller 级 Pipe (@UsePipes)
  3. 方法级 Pipe (@UsePipes)
  4. 参数级 Pipe (@Body(SomePipe))

最佳实践

typescript
// 推荐的组合使用方式
@Controller('users')
@UseGuards(AuthGuard)           // Controller 级守卫
@UseInterceptors(LoggingInterceptor)
export class UsersController {
  
  @Post()
  @Roles('admin')               // 方法级元数据
  @UseGuards(RolesGuard)        // 方法级守卫
  @UsePipes(ValidationPipe)     // 方法级管道
  create(@Body() dto: CreateUserDto) {
    return this.usersService.create(dto);
  }
}

前端面试知识库