# nest-crash **Repository Path**: yhding/nest-crash ## Basic Information - **Project Name**: nest-crash - **Description**: 测试nest的filter,middleware,guard,interceptor,pipe的优先级。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-11-06 - **Last Updated**: 2022-11-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 基础知识 - [基础知识](#基础知识) - [注意全局范围的异常,会被用在整个应用和每一个控制器和路由函数](#注意全局范围的异常会被用在整个应用和每一个控制器和路由函数) - [导航守卫](#导航守卫) - [对比](#对比) - [filter过滤器](#filter过滤器) - [middleware 中间件](#middleware-中间件) - [Guard 守卫](#guard-守卫) - [interceptor 拦截器](#interceptor-拦截器) - [pipe 管道](#pipe-管道) ## 注意全局范围的异常,会被用在整个应用和每一个控制器和路由函数 Global-scoped filters are used across the whole application, for every controller and every route handler. 这种情况下,出现如下情况时module层通过token声明的方式注入filter不能使用。 ```ts // 前提 // main.ts ... app.useGlobalFilters(new GlobalExceptionFilter()); // 全局上用 useGlobalFilters 绑定 GlobalExceptionFilter 的情况下 // 1. 等价于在所有的 controller 上绑定 GlobalExceptionFilter, // 2. 但路由层不会绑定该过滤器。 // 3. 需要注意:如果 controller 层同样定义了 AppExceptionFilter 那么局部覆盖全局。 // 4. 但是token方式注入的表现有些异常,如下:不能覆盖全局的异常过滤器。 ... // app.module.ts @Module({ providers: [ { provide: APP_FILTER, useClass: CoffeesExceptionFilter, // 这里将无效 }, ], }) export class AppModule {} ``` 总结:global范围的filters会应用在控制器中绑定了过滤器,没有对route层进行绑定过滤器。 - 危险警告 需要注意:如下这种方式是向全局注册filter, ```ts // main.ts // app没有使用filter // app.module.ts ... @Module({ imports: [CoffeesModule, TasksModule], ... }) export class AppModule {} ... // coffees.module.ts @Module({ providers: [ { provide: APP_FILTER, useClass: CoffeesExceptionFilter, // 这里将无效 }, ], }) export class CoffeesModule {} // tasks.controller.ts ... @Get('tasks') findA() { throw new HttpException(...) } ... ``` 请求 /tasks 时 报错拦截走的是 CoffeesExceptionFilter。 ## 导航守卫 Guards are executed after all middleware, but before any interceptor or pipe. 在所有的中间件之后,所有的拦截器和pipe之前。 ## 对比 ### filter过滤器 ```ts @Catch(HttpException) export class AppExceptionFilter implements ExceptionFilter { catch(exception: HttpException, host: ArgumentsHost) { const ctx = host.switchToHttp(); const res = ctx.getResponse(); const req = ctx.getRequest(); const status = exception.getStatus(); console.group('appExceptionFilter in'); console.log('in req', req.url); console.log('in res', JSON.stringify(res.getHeaders())); console.groupEnd(); console.log('her'); res.status(status).json({ from: 'app-exception', statusCode: status, timestamp: new Date().toISOString(), path: req.url, }); } } ``` 注意这个状态已经是服务端响应数据的路上了 - exception 中存放了堆栈信息,异常状态码等 - host 中能够获取此次请求和响应的信息。 ### middleware 中间件 ```ts @Injectable() export class AppMiddleware implements NestMiddleware { use(req: Request, res: Response, next: () => void) { console.group('AppMiddleware in'); console.log('in req', req.baseUrl); console.log('in res', JSON.stringify(res.getHeaders())); console.groupEnd(); const start = performance.now(); res.on('finish', () => console.log('AppMiddleware run time,', performance.now() - start), ); next(); } } ``` 发生的阶段是客户端向服务端发消息,只能看到经过流转,获取请求信息,设置响应信息,但是没办法修改数据。 - req 请求状态信息,可以拿到请求头里的信息 - res 响应状态信息,可以绑定事件拿到响应结束事件或者直接终止响应,直接返回。 - next 请求响应生命周期函数,需要调用才能往下执行。 ### Guard 守卫 ```ts @Injectable() export class AppGuard implements CanActivate { constructor(private reflect: Reflector) {} canActivate( context: ExecutionContext, ): boolean | Promise | Observable { const rolesContorller = this.reflect.get( 'admin-controller', context.getClass(), ); const testController = this.reflect.get( 'test-controller', context.getClass(), ); const rolesMethods = this.reflect.get( 'roles', context.getHandler(), ); const testMethods = this.reflect.get('test', context.getHandler()); console.group(`appGuard in`); console.log('rolesController', rolesContorller); console.log('rolesMethods', rolesMethods); console.log('testController', testController); console.log('testMethods', testMethods); console.groupEnd(); return true; } } ``` - context 类型是ExecutionContext,而这个类型继承自ArgumentsHost。不仅提供获取request, response的句柄,还额外提供 getHandler(), getClass()工具函数,供reflect对象获取装饰器meta data数据的获取。 - reflect 可以用来反射获取meta data的注册的值。常用来做角色,登录验证。 ### interceptor 拦截器 ```ts @Injectable() export class AppInterceptor implements NestInterceptor { constructor(private from: string) {} intercept(context: ExecutionContext, next: CallHandler): Observable { console.log(`${this.from} before ...`); const now = Date.now(); return next .handle() .pipe(tap(() => console.log(`After... ${Date.now() - now}ms`))); } } ``` 发生在请求处理参数前,请求处理前的执行某段代码,绑定好请求处理后的数据处理方式 - context 同 guards 守卫,不过这里的作用是获取 request, response。 - next 这里的next需要跟 中间件里的[req, res, next]区别开来,这里只是用来改数据的。相当于 return之前的都是请求处理前执行,return之后的借助rxjs的能改改值。做到了即在处理请求前执行了,也在处理完之后改数据。 ### pipe 管道 ```ts @Injectable() export class AppPipe implements PipeTransform { constructor(private readonly from: string) {} transform(value: any, metadata: ArgumentMetadata) { console.log( `AppPipe in ${this.from}`, JSON.stringify(metadata), JSON.stringify(value || ''), ); return value; } } ``` 发生在请求处理前出参数进行处理,没有操作请求处理后的能力。 - value 是绑定的参数值 - metadata 是绑定的值的类型,body, query, param, or custom parameter,常常用来做参数校验,和数据格式化。 ```ts @UsePipes(new AppPipe('controller')) // 1* @Controller() export class AppController { constructor(private readonly appService: AppService) {} @UsePipes(new AppPipe('methods')) // 2* getHelloPi( @Query(new AppPipe('Query')) query: any, // 3* @Query('page') page: string, @Query('size') size: string, // @Query('size', new AppPipe('size param')) size: string, ): string { console.log(query); console.log(page, size); return this.appService.getHello(); } } ``` 假设绑定如上:getHelloPi 有三个参数,那么管道的执行结果如下: - `1*` 执行 3次,分别处理的参数是 size, page, query - `2*` 执行 3次,分别处理的参数是 size,page, query - `3*` 执行 1次,分别处理的参数是 query 相当于对每个参数都走了一遍所有的管道。