Skip to content

拦截器

拦截器和中间件的不同处

  • 中间件的执行时机要早于拦截器

中间件的执行时机要早于拦截器

  • 中间件是可以中断不合法的请求的,拦截器不行

拦截器是对控制器方法的增强或者削弱,它必须依赖于控制器的方法

  • 中间件无法知道处理当前请求的控制器和处理请求的控制器方法

拦截器可以知道.拦截器能够利用反射获取到控制器或方法上的一些元数据,从而判断token,鉴权之类的

拦截器种类和位置

种类

  1. 全局拦截器

  2. 控制器拦截器

  3. 路由拦截器

位置

  1. 前置拦截器

  2. 后置拦截器

拦截器使用

按照位置(来说)

  • 先创建一个拦截器起名 : xxxx.interceptor.ts
ts
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
  Inject,
  HttpException,
} from "@nestjs/common";
import { Observable, map, of } from "rxjs";
import { Request } from "express";
import { WINSTON_MODULE_PROVIDER } from "nest-winston";
import { Logger } from "winston";
import { getReqMainInfo } from "../utils/utils";

@Injectable()
export class GlobalInterceptor implements NestInterceptor {
  constructor(
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger
  ) {}

  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    /* 前置拦截器 */
    /* 判断有没有token,没有就不要进入路由 直接返回,有则放行 */
    /* 这里返回用of,你抛出异常也可以会记录到日志,所以一般用of */
    const ctx = context.switchToHttp();
    const req = ctx.getRequest<Request>();
    // 获取路径
    const url = req.url;
    const token = req.headers["access-token"];
    if (token) {
      return of({
        code: 403,
        data:""
        message: "token不存在",
      });

      // 或者直接抛出异常
      // throw new HttpException('token不存在', 401);
    }
    /* 前置拦截器结束 */
    /* 后置拦截器 */
    /* 统一回复管理 */
    /* 控制器 返回格式: {code:200,data:{}} */
    return next.handle().pipe(
      map((data) => {
        console.log("After全局...");
        let { code, message, custom, ...result } = data;
        // 局部返回什么就是什么
        if (custom) {
          let { custom, ...result } = data;
          return result;
        }
        // 剩下的是全局返回
        let resultmessage = "";
        let resultcode = code || 200;
        if (resultcode == 200) {
          resultmessage = "操作成功";
        } else {
          resultmessage = "操作失败";
        }
        return {
          code: resultcode,
          message: resultmessage,
          data: result.data,
        };
      })
    );
  }
}

拦截器功能

  • 全局拦截器

找到 app.module.ts 文件,在 providers 中添加

ts
// 拦截器
// 引入类型 方便挂载全局
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
// 引入 你自己的拦截器
import { GlobalInterceptor } from './global/interceptor/global.interceptor';
@Module({
  imports: [],
  controllers: [],
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: GlobalInterceptor,
    },
  ],
})

这样全局都可以使用

  • 控制器使用

在你自己的控制器 xxxxx.controller.ts 中添加

这样这个路由下面的所有路由都会使用这个拦截器

ts
// 引用
import { UseInterceptors } from "@nestjs/common";
import { GlobalInterceptor } from "src/global/interceptor/global.interceptor";

@Controller("logins")
@UseInterceptors(GlobalInterceptor)
export class LoginController {
  // .......
}
  • 针对单个方法
ts
// 引用
import { UseInterceptors } from "@nestjs/common";
import { GlobalInterceptor } from "src/global/interceptor/global.interceptor";

@Controller("logins")
export class LoginController {
  @Get("test")
  @UseInterceptors(GlobalInterceptor)
  test() {
    return "test";
  }
}

拦截器局部和全局共存

NEST 拦截器执行顺序

-> 全局拦截器 -> 局部拦截器 -> 处理数据-> -> 局部拦截器 -> 全局拦截器 -> 返回响应

NEST 拦截器 仅执行局部

流程

简单来说 局部拦截器给全局拦截器传递一个参数 custom ,要是 custom 为 true,则全局拦截器放行,否则就拦截

创建局部拦截器

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

@Injectable()
export class TestInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log("Beforetest...");
    return next.handle().pipe(
      map((data) => {
        console.log("Aftertest...");
        let { code, message, custom, ...result } = data;
        return {
          custom: true,
          msg: "test自己的拦截器",
          data: result.data,
          code: 200,
        };
      })
    );
  }
}

创建全局拦截器

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

@Injectable()
export class GlobalInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log("Before...");
    return next.handle().pipe(
      map((data) => {
        console.log("After全局...");
        let { code, message, custom, ...result } = data;
        // 局部返回什么就是什么
        if (custom) {
          let { custom, ...result } = data;
          // 取消掉custom属性
          return result;
        }
        // 剩下的是全局返回
        // 控制器里面就可以返回 {code:200,data:xxx}
        let resultmessage = "";
        let resultcode = code || 200;
        if (resultcode == 200) {
          resultmessage = "操作成功";
        } else {
          resultmessage = "操作失败";
        }
        return {
          code: resultcode,
          message: resultmessage,
          data: result.data,
        };
      })
    );
  }
}

全局拦截器绑定到全局

  • app.module.ts
ts
import { Module } from "@nestjs/common";
import { TestModule } from "./test/test.module";
import { APP_INTERCEPTOR } from "@nestjs/core";
import { GlobalInterceptor } from "./global.interceptor";
@Module({
  imports: [TestModule],
  controllers: [],
  providers: [
    {
      provide: APP_INTERCEPTOR,
      useClass: GlobalInterceptor,
    },
  ],
})
export class AppModule {}

Rxjs 拦截器补充

  • Tap

就像“偷窥”“烟斗”。数据保持不变,你可以用它做些什么。一些数据进入,你看,同样的数据出来。

  • Map

用于转换/映射“管道”中的数据。一些数据输入,不同的/转换的数据出来。

抛出异常

timeout 操作符 会在 3S 没收到消息的时候 抛出一个 TimeoutError

然后 catchError 操作符处理下,如果是 TimeoutError 就抛出 RequestTimeoutException

这个有内置的 exception filter 会处理成对应的响应格式

其余错误就直接 throwError 抛出去

ts
import {
  CallHandler,
  ExecutionContext,
  Injectable,
  NestInterceptor,
  RequestTimeoutException,
} from "@nestjs/common";

import {
  catchError,
  Observable,
  throwError,
  timeout,
  TimeoutError,
} from "rxjs";

@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    return next.handle().pipe(
      // 时间延迟3S
      timeout(3000),
      catchError((err) => {
        if (err instanceof TimeoutError) {
          console.log(err);
          return throwError(() => new RequestTimeoutException());
        }
        return throwError(() => err);
      })
    );
  }
}