拦截器(Interceptor)的概念
拦截器是使用 @Injectable() 装饰器装饰并实现 NestInterceptor 接口的类。拦截器具有一系列有用的功能:
- 在方法执行之前/之后绑定额外的逻辑
- 转换从函数返回的结果
- 转换从函数抛出的异常
- 扩展基本函数行为
- 根据所选条件完全重写函数(例如,用于缓存目的)
拦截器的基本结构
typescriptimport { 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(); } }
全局拦截器
typescriptimport { 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. 日志拦截器
typescriptimport { 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. 缓存拦截器
typescriptimport { 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. 超时拦截器
typescriptimport { 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. 响应转换拦截器
typescriptimport { 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() 装饰器装饰的类,用于捕获和处理应用程序中抛出的异常。它们允许你完全控制异常处理流程,包括响应格式、状态码等。
异常过滤器的基本结构
typescriptimport { 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 {}
全局异常过滤器
typescriptimport { 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. 全局异常过滤器
typescriptimport { 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. 自定义异常
typescriptimport { 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. 验证异常过滤器
typescriptimport { 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 中各种处理器的执行顺序如下:
- 中间件(Middleware)
- 守卫(Guards)
- 拦截器前(Interceptors before)
- 管道(Pipes)
- 控制器方法(Controller method)
- 拦截器后(Interceptors after)
- 异常过滤器(Exception filters)
最佳实践
拦截器最佳实践
- 单一职责:每个拦截器只负责一个功能
- 性能考虑:避免在拦截器中执行耗时操作
- 错误处理:在拦截器中适当处理错误
- 测试覆盖:为拦截器编写测试
- 文档化:为拦截器添加清晰的文档
异常过滤器最佳实践
- 统一响应格式:使用统一的异常响应格式
- 日志记录:在异常过滤器中记录错误日志
- 敏感信息:避免在响应中暴露敏感信息
- 自定义异常:创建自定义异常类来表示业务错误
- 全局过滤器:使用全局异常过滤器处理未捕获的异常
总结
NestJS 拦截器和异常过滤器系统提供了:
- 强大的请求/响应处理能力
- 灵活的异常处理机制
- 清晰的关注点分离
- 易于扩展和定制
- 完整的请求生命周期控制
掌握拦截器和异常过滤器是构建健壮、可维护的 NestJS 应用程序的关键。拦截器用于处理请求/响应的横切关注点,异常过滤器用于统一处理错误,它们共同构成了应用程序的错误处理和响应转换基础。