nestjs学习 - 拦截器(intercept)

0 阅读4分钟

拦截器是使用 @Injectable() 装饰器注解的类。拦截器应该实现 NestInterceptor 接口。

img

一、它是什么

拦截器(Interceptor) 是一个基于 面向切面编程(AOP) 思想的强大功能。它允许你在请求到达控制器(Controller)之前或之后,以及响应返回给客户端之前,插入自定义逻辑。

白话:

从上图可以看出,拦截器就是可以在 到达请求前请求返回结果后 进行拦截,做一些你想做的事情;

前端开发同学可以结合 axios 的拦截器理解,几乎就是同一个模式;

简单说:拦截器就是请求和响应路上的“把关人”,能在不修改核心业务代码的情况下,统一处理一些公共逻辑。

在下文中主要关注它的使用场景;

在框架生命周期中,它的执行时机是:

请求进入 → 中间件 → 守卫 → 拦截器 → 管道 → 控制器 → 服务 → 拦截器 → 异常过滤器 → 服务器响应

二、使用方法

在使用拦截器之前,需了解 RxJS(响应式编程库) 的使用

它底层严重依赖 RxJS,因为 intercept() 方法返回的是一个 Observable(可观察对象)。这意味着你需要对 RxJS 的操作符(如 map, tap, catchError 等)有一定了解。

1. 创建

统一响应数据格式demo:

import { NestInterceptor, CallHandler, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
​
​
interface Data<T> {
  code: number;
  message: string;
  data: T;
}
​
/**
 * 响应拦截器
 * 用于处理响应数据
 * 可以用于处理响应数据,如添加响应头,添加响应体等
 */
@Injectable()
export class ResponseInterceptor<T> implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<Data<T>> {
    // ==========================
    // 【阶段 1:控制器执行之前】
    // ==========================
    // 这里的代码会立即同步执行。
    // 此时请求刚到达拦截器,还没进控制器。
    console.log('❤️ [之前] 请求已到达拦截器');
    const startTime = Date.now();
    
    // 可以在这里做:权限预检、记录开始时间、修改请求参数等。
    // 如果在这里直接 return 一个 Observable (例如 return of({error: 'blocked'})) 
    // 而不调用 next.handle(),控制器将永远不会执行(短路)。
​
    // 调用 next.handle() 启动控制器逻辑
    // 它返回一个 Observable,代表控制器未来的执行结果(流)
    const response$ = next.handle(); 
    
    // ==========================
    // 【阶段 2:控制器执行之后】
    // ==========================
    // 这里的代码不会立即执行!
    // 它们被注册为 RxJS 的“操作符”,只有当控制器执行完毕并产生数据时,流才会流动到这里。
    return response$.pipe(
      map(data => {
        return {
          code: 200,
          message: 'success',
          data,
        };
      }),
    );
  }
}

2. 注册

有三种注册方式,作用范围依次扩大:

方法级别:

仅针对某个特定路由

@Get('users')
@UseInterceptors(LoggingInterceptor)
findAll() {
  return this.userService.findAll();
}

控制器级别:

针对该控制器下的所有路由

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

全局级别

针对整个应用的所有路由,在 main.ts 中注册:

const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());
await app.listen(3000);

三、使用场景:

  1. 统一响应格式格式化(正常数据、错误数据)

  2. 响应缓存

    对于不经常变动的数据(如配置信息、列表页),可以在拦截器中检查缓存。

    • 如果缓存命中,直接 return of(cachedData)不调用 next.handle(),从而跳过控制器逻辑,极大提升性能。
    • 如果未命中,正常执行并写入缓存。
  3. 超时处理

    如果某个请求处理时间过长,可以强制中断。

    import { timeout } from 'rxjs/operators';
    ​
    // 在 intercept 方法中
    return next.handle().pipe(
      timeout(5000), // 5秒无响应则抛出异常
    );
    
  4. 数据序列化/脱敏

    在返回给用户之前,动态修改敏感字段。

    • 例如:将用户列表中的 password 字段移除,或将手机号中间四位替换为 ****
    • 通过 map 操作符遍历返回数据并进行清洗。

四、总结:

  • 基于 AOP 思想,利用 RxJS 在请求/响应生命周期中插入逻辑的机制。
  • 它本质上是一个强大的“切面”工具,用于处理那些横跨整个应用程序的、与核心业务逻辑无关的公共关注点。

    它的精髓在于:你可以在不侵入、不修改任何一个现有控制器方法的情况下,为整个应用或特定接口批量添加上述各种功能。 这使得你的代码更加干净、可维护,并且这些横切关注点可以被轻松地复用和组合。

  • 统一返回格式、日志记录、性能监控、缓存、数据转换、超时控制。
  • 区别: 比中间件更灵活,能操作返回值;比守卫(Guard)更侧重于数据转换而非权限决策。