普通视图

发现新文章,点击刷新页面。
昨天 — 2026年3月20日首页

nestjs学习 - 拦截器(intercept)

作者 web_bee
2026年3月20日 11:04

拦截器是使用 @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)更侧重于数据转换而非权限决策。
昨天以前首页

nestjs学习 - 守卫

作者 web_bee
2026年3月18日 16:40

NestJS 守卫是一个实现了 CanActivate 接口的类。

一、它是什么?

在 NestJS 里,「守卫(Guard)」是一种用来控制请求是否能进入路由处理器(Controller 方法) 的机制。

通俗点说:

守卫就是“门卫”——每次请求进来之前,它会先检查一下你有没有资格进去。

  • 通过进入下一步
  • 未通过❌,请求拒绝(比如返回 403 Forbidden)

核心职责:主要关注 授权(Authorization) 。虽然也可以做认证(Authentication),但通常认证由中间件或 Passport 策略处理,而守卫用于更细粒度的权限控制(如:只有管理员才能删除文章)。

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

请求进入 → 中间件 → 守卫 → 拦截器 → 管道 → 控制器 → 服务

二、怎么用?

创建守卫

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

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();

    // 简单示例:如果请求头中有 token,就放行
    const token = request.headers['authorization'];
    if (token) {
      return true; // 放行
    }

    // 否则拒绝
    return false;
  }
}

canActivate() 方法返回:

  • true → 允许进入控制器;
  • false → 阻止访问(会返回 403 Forbidden);
  • 也可以返回一个 Promise<boolean>Observable<boolean>(支持异步)。

应用守卫

你可以在三个层级使用守卫:

1. 方法级

import { UseGuards, Controller, Get } from '@nestjs/common';
import { AuthGuard } from './auth.guard';

@Controller('user')
export class UserController {
  @Get('profile')
  @UseGuards(AuthGuard)
  getProfile() {
    return { msg: '用户资料' };
  }
}

2. 控制器级

@UseGuards(AuthGuard)
@Controller('admin')
export class AdminController {
  @Get()
  getAdminData() {
    return '后台数据';
  }
}

3. 全局守卫

// main.ts
import { AppModule } from './app.module';
import { AuthGuard } from './auth.guard';
import { NestFactory } from '@nestjs/core';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalGuards(new AuthGuard());
  await app.listen(3000);
}
bootstrap();

三、使用场景

守卫最常见的用途是:权限控制 / 身份验证

  1. 身份验证(Authentication) :检查用户是否已登录(例如验证 JWT Token 是否存在且有效)。
  2. 角色授权(Role-based Authorization) :检查当前用户是否拥有特定角色(如 admin, editor)。
  3. 权限控制(Permission-based Authorization) :检查用户是否有执行特定操作的权限(如:user:delete)。
  4. IP 地址过滤:只允许特定 IP 段的请求访问。
  5. 功能开关:根据配置动态开启或关闭某些接口。
  6. 请求时间限制(比如只允许工作时间访问)

你可以把它理解为:

在“进入接口之前”的最后一道防线。

四、中间件 vs 守卫

1. 中间件

中间件 是通用的“流水线工人”,负责处理请求的通用逻辑(如日志、解析数据),它不知道具体的业务逻辑是什么。

中间件的盲区: 当中间件运行时,NestJS 还没有确定最终由哪个 Controller 的哪个方法来处理请求。因此,中间件无法知道当前请求是否需要“管理员权限”。你无法在中间件里写:“如果这个路由用了 @Roles('admin') 装饰器,则检查角色”。

2. 守卫:

守卫 是专业的“安检员”,专门负责授权决策(能不能进),它完全知道即将执行哪个具体的控制器方法,并能根据元数据做判断。

守卫的全知视角

守卫接收 ExecutionContext 对象。通过这个对象,你可以拿到:

  • context.getClass(): 当前的 Controller 类。
  • context.getHandler(): 当前正在执行的方法。
  • 结合 Reflector,你可以读取该方法上所有的装饰器元数据(例如 @Roles('admin'))。
  • 结论:凡是需要根据路由元数据(装饰器)来做判断的逻辑,必须用守卫。

3. 核心区别对比表

特性 中间件 (Middleware) 守卫 (Guard)
主要职责 通用逻辑:日志、压缩、Cookie 解析、原始请求预处理。 授权 (Authorization) :决定请求是否允许执行特定的 Handler。
执行时机 最早。在守卫、拦截器、管道之前执行。 中间。在中间件之后,拦截器和管道之前执行。
上下文感知 。只知道 reqres不知道具体要调用哪个 Controller 或哪个方法。 。拥有 ExecutionContext,知道具体的 Class、Handler 方法、参数类型等。
访问元数据 无法直接访问路由装饰器(如 @Roles, @Get)定义的元数据。 可以访问。配合 Reflector 可以轻松读取路由上的自定义元数据。
返回值/控制流 必须调用 next() 才能继续,或者直接 res.end() 结束响应。 返回 boolean (或 Promise/Observable)。true 放行,false 拒绝(抛出异常)。
依赖注入 支持,但配置稍显繁琐(通常通过 forRoot 或模块配置)。 完美支持,像普通 Service 一样注入依赖。
适用场景 记录所有请求日志、解析 JSON/Cookie、设置 CORS、Gzip 压缩。 检查 JWT、验证用户角色、IP 白名单、基于权限的访问控制。
❌
❌