乐闻世界logo
搜索文章和话题

NestJS 拦截器和异常过滤器的作用是什么?

2月17日 22:32

拦截器(Interceptor)的概念

拦截器是使用 @Injectable() 装饰器装饰并实现 NestInterceptor 接口的类。拦截器具有一系列有用的功能:

  1. 在方法执行之前/之后绑定额外的逻辑
  2. 转换从函数返回的结果
  3. 转换从函数抛出的异常
  4. 扩展基本函数行为
  5. 根据所选条件完全重写函数(例如,用于缓存目的)

拦截器的基本结构

typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @Injectable() export class TransformInterceptor implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable<any> { return next.handle().pipe( map(data => ({ data, timestamp: new Date().toISOString() })) ); } }

使用拦截器

在控制器上使用拦截器

typescript
@Controller('cats') @UseInterceptors(TransformInterceptor) export class CatsController {}

在方法上使用拦截器

typescript
@Controller('cats') export class CatsController { @Get() @UseInterceptors(TransformInterceptor) findAll() { return this.catsService.findAll(); } }

全局拦截器

typescript
import { Module } from '@nestjs/common'; import { APP_INTERCEPTOR } from '@nestjs/core'; import { TransformInterceptor } from './common/interceptors/transform.interceptor'; @Module({ providers: [ { provide: APP_INTERCEPTOR, useClass: TransformInterceptor, }, ], }) export class AppModule {}

常见拦截器用例

1. 日志拦截器

typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, Logger } from '@nestjs/common'; import { Observable } from 'rxjs'; import { tap } from 'rxjs/operators'; @Injectable() export class LoggingInterceptor implements NestInterceptor { private readonly logger = new Logger(LoggingInterceptor.name); intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); const { method, url, body } = request; const now = Date.now(); this.logger.log(`Incoming request: ${method} ${url}`); return next.handle().pipe( tap(() => { const response = context.switchToHttp().getResponse(); const { statusCode } = response; const delay = Date.now() - now; this.logger.log(`Outgoing response: ${statusCode} - ${delay}ms`); }), ); } }

2. 缓存拦截器

typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable, of } from 'rxjs'; @Injectable() export class CacheInterceptor implements NestInterceptor { private cache = new Map(); intercept(context: ExecutionContext, next: CallHandler): Observable<any> { const request = context.switchToHttp().getRequest(); const cacheKey = request.url; if (this.cache.has(cacheKey)) { return of(this.cache.get(cacheKey)); } return next.handle().pipe( tap(response => { this.cache.set(cacheKey, response); }), ); } }

3. 超时拦截器

typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler, RequestTimeoutException } from '@nestjs/common'; import { Observable, throwError, TimeoutError } from 'rxjs'; import { catchError, timeout } from 'rxjs/operators'; @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) { return throwError(() => new RequestTimeoutException()); } return throwError(() => err); }), ); } }

4. 响应转换拦截器

typescript
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; export interface Response<T> { success: boolean; data: T; message: string; timestamp: string; } @Injectable() export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> { intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> { return next.handle().pipe( map(data => ({ success: true, data, message: 'Success', timestamp: new Date().toISOString(), })), ); } }

异常过滤器(Exception Filter)的概念

异常过滤器是使用 @Catch() 装饰器装饰的类,用于捕获和处理应用程序中抛出的异常。它们允许你完全控制异常处理流程,包括响应格式、状态码等。

异常过滤器的基本结构

typescript
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common'; @Catch(HttpException) export class HttpExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); const status = exception.getStatus(); response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, message: exception.message, }); } }

使用异常过滤器

在方法上使用异常过滤器

typescript
@Post() @UseFilters(HttpExceptionFilter) create(@Body() createCatDto: CreateCatDto) { return this.catsService.create(createCatDto); }

在控制器上使用异常过滤器

typescript
@Controller('cats') @UseFilters(HttpExceptionFilter) export class CatsController {}

全局异常过滤器

typescript
import { Module } from '@nestjs/common'; import { APP_FILTER } from '@nestjs/core'; import { HttpExceptionFilter } from './common/filters/http-exception.filter'; @Module({ providers: [ { provide: APP_FILTER, useClass: HttpExceptionFilter, }, ], }) export class AppModule {}

常见异常过滤器

1. 全局异常过滤器

typescript
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common'; import { Request, Response } from 'express'; @Catch() export class AllExceptionsFilter implements ExceptionFilter { catch(exception: unknown, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; const message = exception instanceof HttpException ? exception.message : 'Internal server error'; response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, message, }); } }

2. 自定义异常

typescript
import { HttpException, HttpStatus } from '@nestjs/common'; export class BusinessException extends HttpException { constructor(message: string, status: HttpStatus = HttpStatus.BAD_REQUEST) { super( { statusCode: status, message, error: 'Business Error', }, status, ); } } // 使用自定义异常 @Get() findAll() { if (someCondition) { throw new BusinessException('Invalid operation'); } return this.catsService.findAll(); }

3. 验证异常过滤器

typescript
import { ExceptionFilter, Catch, ArgumentsHost, BadRequestException } from '@nestjs/common'; import { ValidationError } from 'class-validator'; @Catch(BadRequestException) export class ValidationExceptionFilter implements ExceptionFilter { catch(exception: BadRequestException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); const status = exception.getStatus(); const exceptionResponse = exception.getResponse(); let errors = []; if (typeof exceptionResponse === 'object' && 'message' in exceptionResponse) { errors = (exceptionResponse as any).message; } response.status(status).json({ statusCode: status, timestamp: new Date().toISOString(), path: request.url, message: 'Validation failed', errors, }); } }

执行顺序

NestJS 中各种处理器的执行顺序如下:

  1. 中间件(Middleware)
  2. 守卫(Guards)
  3. 拦截器前(Interceptors before)
  4. 管道(Pipes)
  5. 控制器方法(Controller method)
  6. 拦截器后(Interceptors after)
  7. 异常过滤器(Exception filters)

最佳实践

拦截器最佳实践

  1. 单一职责:每个拦截器只负责一个功能
  2. 性能考虑:避免在拦截器中执行耗时操作
  3. 错误处理:在拦截器中适当处理错误
  4. 测试覆盖:为拦截器编写测试
  5. 文档化:为拦截器添加清晰的文档

异常过滤器最佳实践

  1. 统一响应格式:使用统一的异常响应格式
  2. 日志记录:在异常过滤器中记录错误日志
  3. 敏感信息:避免在响应中暴露敏感信息
  4. 自定义异常:创建自定义异常类来表示业务错误
  5. 全局过滤器:使用全局异常过滤器处理未捕获的异常

总结

NestJS 拦截器和异常过滤器系统提供了:

  • 强大的请求/响应处理能力
  • 灵活的异常处理机制
  • 清晰的关注点分离
  • 易于扩展和定制
  • 完整的请求生命周期控制

掌握拦截器和异常过滤器是构建健壮、可维护的 NestJS 应用程序的关键。拦截器用于处理请求/响应的横切关注点,异常过滤器用于统一处理错误,它们共同构成了应用程序的错误处理和响应转换基础。

标签:NestJS