拦截器
拦截器和中间件的不同处
- 中间件的执行时机要早于拦截器
中间件的执行时机要早于拦截器
- 中间件是可以中断不合法的请求的,拦截器不行
拦截器是对控制器方法的增强或者削弱,它必须依赖于控制器的方法
- 中间件无法知道处理当前请求的控制器和处理请求的控制器方法
拦截器可以知道.拦截器能够利用反射获取到控制器或方法上的一些元数据,从而判断token,鉴权之类的
拦截器种类和位置
种类
全局拦截器
控制器拦截器
路由拦截器
位置
前置拦截器
后置拦截器
拦截器使用
按照位置(来说)
- 先创建一个拦截器起名 : 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);
})
);
}
}