FORMA

拦截器 (Interceptors)

拦截器可包装响应、记录日志、映射 DTO。见 guards

拦截器是 NestJS 中一种强大的 AOP(面向切面编程)组件,可以在路由处理器的执行前后插入自定义逻辑。它们基于 RxJS 的 Observable 对象,使得你可以灵活地操纵请求-响应流。

一、拦截器的作用

功能描述
方法执行前后逻辑在控制器方法执行前/后执行额外代码(如日志、计时、事务开启/提交)
转换返回结果统一包装响应格式、自动序列化、过滤敏感字段
扩展功能实现缓存、响应的性能监测、请求/响应脱敏
处理超时或流式响应设置超时时间,或者对流式响应进行中间处理
异常映射捕获异常并转换为友好格式(通常配合异常过滤器)

二、拦截器接口与切面模式

拦截器实现 NestInterceptor 接口,其中 intercept(context: ExecutionContext, next: CallHandler) 方法返回 Observable<any>

  • context:执行上下文,类似于守卫中的 ExecutionContext,可以获取请求对象等。
  • nextCallHandler 对象,它的 handle() 方法返回一个 RxJS Observable,代表原始控制器处理器的响应流

通过 RxJS 操作符(如 maptapcatchError)可以修改响应或处理副作用。

ts
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> {
    // 请求前逻辑(例如计时开始)
    console.log("Before controller...");

    return next.handle().pipe(
      // 请求后逻辑(修改响应)
      map((data) => ({ data, code: 0, message: "success" })),
    );
  }
}

常用的 RxJS 操作符:

操作符作用
map修改响应数据(如包装格式)
tap执行副作用(如日志记录),不改变响应
catchError捕获错误,转换为自定义错误响应
timeout设置超时,抛出超时错误

三、响应映射示例:统一包装格式

很多 API 会返回固定格式 { code, data, message },可以使用全局响应拦截器统一实现。

ts
// common/interceptors/response.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from "@nestjs/common";
import { Observable } from "rxjs";
import { map } from "rxjs/operators";

export interface Response<T> {
  code: number;
  data: T;
  message: string;
}

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

然后在控制器方法中正常返回数据即可,拦截器会自动包装。

四、使用拦截器

拦截器可以通过 @UseInterceptors() 装饰器应用在方法、控制器全局范围内。

1. 方法级

ts
@Post()
@UseInterceptors(TransformInterceptor)
create(@Body() dto: CreateUserDto) {
  return this.userService.create(dto);
}

2. 控制器级

ts
@Controller('users')
@UseInterceptors(LoggingInterceptor)
export class UsersController { ... }

3. 全局级(无依赖注入)

ts
// main.ts
app.useGlobalInterceptors(new ResponseInterceptor());

4. 全局级(支持依赖注入)

使用 APP_INTERCEPTOR 令牌在模块中注册,这样拦截器就可以注入其他服务。

ts
import { Module } from "@nestjs/common";
import { APP_INTERCEPTOR } from "@nestjs/core";
import { ResponseInterceptor } from "./common/interceptors/response.interceptor";

@Module({
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: ResponseInterceptor,
    },
  ],
})
export class AppModule {}

五、内置拦截器:ClassSerializerInterceptor

@nestjs/common 提供了内置的 ClassSerializerInterceptor,它利用 class-transformer@Exclude()@Expose()@SerializeOptions() 等装饰器来控制对象序列化,常用于隐藏敏感字段(如密码)。

使用步骤

  1. 安装依赖(通常已有):
bash
npm install class-transformer
  1. 在 DTO 或实体中使用 @Exclude() 装饰器:
ts
import { Exclude } from "class-transformer";

export class UserEntity {
  id: number;
  name: string;

  @Exclude()
  password: string;
}
  1. 在控制器方法上应用 ClassSerializerInterceptor
ts
@Get(':id')
@UseInterceptors(ClassSerializerInterceptor)
findOne(@Param('id') id: number): UserEntity {
  // 返回 user 实例,password 字段会被自动排除
  return this.userService.findOne(id);
}

也可以全局注册,让所有返回的实体都自动应用序列化规则。

六、高级用例:超时处理

使用 timeout 操作符设置请求最大处理时间:

ts
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
  RequestTimeoutException,
} from "@nestjs/common";
import { Observable, throwError } from "rxjs";
import { timeout, catchError } from "rxjs/operators";

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

七、拦截器的执行顺序

多个拦截器可以同时应用,执行顺序为:

  • 请求前:按照注册顺序执行(最外层 → 内层)
  • 控制器方法执行
  • 响应后:按照注册顺序的逆序执行(内层 → 最外层)

对于全局拦截器,会先于控制器/方法级拦截器执行(在 NestJS 中,全局拦截器始终在最外层)。

八、与管道的区别

组件主要职责执行时机操作对象
管道验证和转换输入数据控制器方法执行前(参数层面)请求参数(@Body()@Query() 等)
拦截器修改输出结果、处理响应流控制器方法执行前后响应对象(整个 Observable 流)

总结

概念说明
拦截器接口实现 NestInterceptorintercept 方法 + next.handle()
切面模式使用 RxJS 操作符(maptapcatchError)处理响应流
响应映射统一包装数据结构,简化控制器
使用方式@UseInterceptors() 方法/控制器/全局(app.useGlobalInterceptorsAPP_INTERCEPTOR
内置拦截器ClassSerializerInterceptor 自动排除标记字段(配合 @Exclude()
高级功能超时控制、缓存拦截、日志记录、性能统计

掌握拦截器,你可以将横切关注点(cross-cutting concerns)从业务逻辑中抽离出来,实现更干净、可复用的代码。

参考文献

以下链接在编写时均可正常访问:

资料说明
NestJS 文档官方
Interceptors本章主题
Request lifecycle执行顺序

Series

new

12 / 19