返回文章列表 →

Note

拦截器

nestjs的拦截器介绍

发布于 更新于

拦截器

包裹在控制器方法外面的一层处理程序

作用

它不仅能在控制器执行前做事情,还能在控制器执行后继续处理返回值、异常和整个请求流程

定义

使用 @Injectable() 修饰,并实现 NestInterceptor 接口,那么这个类就是拦截器,拦截器内部必须实现 intercept方法。拦截器文件通常命名为 xxx.interceptor.ts

import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from "@nestjs/common";
import { Observable } from "rxjs";

@Injectable()
export class TestInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<unknown> {
    console.log("控制器执行前-前置代码");

    return next.handle().pipe(
      // data 响应数据
      map((data) => {
        console.log("控制器执行后-后置代码");

        return data;
      })
    );
  }
}

// 执行顺序:
// 前置代码
//     ↓
// 控制器执行
//     ↓
// 后置代码

参数CallHandler

它有一个非常重要的方法handle,这个方法表示继续执行后续流程(包括管道、控制器方法以及控制器内部调用的服务),如果拦截器不调用 next.handle() ,控制器方法就不会执行。并且 next.handle() 会返回 Observalbe ,这属于 rxjs 概念。

Rxjs常用操作符

  1. map():转换控制器返回结果,将每个原始值转换成新值
控制器原本的返回值
[
  {
    "id": 1,
    "name": "张三"
  }
]
map修改返回值
return next.handle().pipe(
  map((data) => {
    return {
      code: 200,
      data,
    };
  })
);
最终返回
{
  "code": 200,
  "data": [
    {
      "id": 1,
      "name": "张三"
    }
  ]
}
  1. tap():用于观察数据,但不修改数据。因为它主要起副作用,例如:日志、性能统计、埋点、事件记录等等
return next.handle().pipe(
  tap((data) => {
    console.log("控制器返回了:", data);
  })
);
  1. finalize():在请求结束时执行,不管是成功还是异常。常用于计算总耗时、释放资源、结束日志追踪、关闭临时状态等等
return next.handle().pipe(
  finalize(() => {
    console.log("请求处理结束");
  })
);
  1. catchError():用于捕获异常,接收到控制器、服务或者管道中产生的异常。如果还想让异常继续交给异常过滤器处理,需要重新抛出
return next.handle().pipe(
  catchError((error) => {
    console.error("请求出错:", error);

    // 让异常继续交给异常过滤器处理
    return throwError(() => error);
  })
);
  1. timeout():设置超时,可以设置在请求超时后抛出异常
return next.handle().pipe(
  // 设置超时时间
  timeout(5000),

  catchError((error) => {
    console.error("请求超时:", error);

    return throwError(() => error);
  })
);
  1. of():创建一个新的Observable。用于直接返回结果,常用于缓存。
@Injectable()
export class SimpleCacheInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler) {
    const cacheData = {
      id: 1,
      name: "缓存用户",
    };

    const hasCache = true;

    if (hasCache) {
      return of(cacheData);
    }

    return next.handle();
  }
}
执行逻辑
检查缓存
    ↓
有缓存?
 ┌──┴──┐
 是     否
 ↓      ↓
of()   next.handle()
 ↓      ↓
直接   执行控制器
返回

注册/作用域

方法级

只作用于对应方法

@Controller("users")
export class UsersController {
  @Get()
  @UseInterceptors(LoggingInterceptor)
  findAll() {
    return [];
  }
}

控制器级

只作用于对应控制器

@Controller("users")
@UseInterceptors(LoggingInterceptor)
export class UsersController {
  @Get()
  findAll() {
    return [];
  }

  @Post()
  create() {
    return {};
  }
}

全局

作用于整个应用中的所有控制器和路由。也可通过在任意模块中使用 APP_INTERCEPTOR + useClass 进行全局注册。

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // 注册全局拦截器
  app.useGlobalInterceptors(new LoggingInterceptor());

  await app.listen(3000);
}

多个拦截器执行顺序

拦截器进入阶段依次按照全局、控制器、路由运行,返回阶段则按照路由、控制器、全局的顺序反向解析,即先进后出

假设有三个拦截器:
GlobalInterceptor
ControllerInterceptor
RouteInterceptor

执行顺序为:
Global 前置
    ↓
Controller 前置
    ↓
Route 前置
    ↓
Controller 方法

Controller 方法返回
    ↓
Route 后置
    ↓
Controller 后置
    ↓
Global 后置