拦截器

拦截器_第1张图片
拦截器.png
  1. 拦截器的功能受面向切面编程AOP技术的启发,它可以:
    1. 在函数执行之前/之后绑定额外的逻辑
    2. 转换从函数返回的结果
    3. 转换从函数抛出的异常
    4. 扩展基本函数行为
    5. 根据所选条件完全重写函数 (例如, 缓存目的)
  2. 创建拦截器
    nest g interceptor xxx
    
  3. logging.interceptor.ts
    import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
    import { Observable } from 'rxjs';
    import { tap } from 'rxjs/operators';
    
    @Injectable()
    export class LoggingInterceptor implements NestInterceptor {
        intercept(context: ExecutionContext, next: CallHandler): Observable {
            //拦截器之前
            console.log('Before...');
    
            const now = Date.now();
            return next.handle()
                    .pipe(
                        tap(() => {
                            //拦截器之后
                            console.log(`After... ${Date.now() - now}ms`)
                        }),
                    );
        }
    }
    
  4. 拦截器必须实现NestInterceptor接口,并且被 @Injectable() 所装饰;
    1. T 表示已处理的 Observable 的类型(在流后面)
    2. R 表示包含在返回的 Observable 中的值的返回类型;
  5. ExecutionContext:执行上下文对象,与守卫的对象完全相同;
  6. CallHandler:一个包装执行流的对象,必须手动调用它的 handler() 方法,否则主程序不会到达控制器,这是因为Nest订阅了返回的流,并使用此流生成的值来为最终用户创建单个/多个响应;

绑定方式

  1. 与守卫一样,拦截器可以作用域控制器范围内,控制器方法范围内,全局范围内;
  2. 控制器上
    @Controller('cats')
    @UseInterceptors(LoggingInterceptor)
    export class CatsController { }
    
  3. 全局范围
    1. 方式一:main.ts,模块中不可以依赖注入实例
        app.useGlobalInterceptors(new LoggingInterceptor());
    
    1. 方式二:在模块上注册,从而可以实现依赖注入实例
    import { Module } from '@nestjs/common';
    import { APP_INTERCEPTOR } from '@nestjs/core';
    
    @Module({
        providers: [{
            provide: APP_INTERCEPTOR,
            useClass: LoggingInterceptor,
        }],
    })
    export class ApplicationModule { }
    
  4. 访问与响应过程:请求到达 --> 拦截器之前 --> 控制器 --> 拦截器之后 --> 响应数据

响应映射

  1. handler()返回一个Observable,此流中包含控制器返回的响应数据,使用它的 map() 运算符可以修改返回数据;
    import { Observable } from 'rxjs';
    import { map } from 'rxjs/operators';
    
    export interface Response {  // 是否存在并不影响响应结果
        data: T;
    }
    
    @Injectable()
    export class TransInterceptor implements NestInterceptor> {
        intercept(context: ExecutionContext, next: CallHandler): Observable> {
            return next.handle()
                    .pipe(map(data => ({ data })));
        }
    }
    
  2. 运算符map()对控制器的返回值做了二次封装,最终响应的数据为 { "data": 控制器的返回值 }
  3. 还可以对控制器的返回值做判断,对空数据做统一处理
    @Injectable()
    export class ExcludeNullInterceptor implements NestInterceptor {
        intercept(context: ExecutionContext, next: CallHandler): Observable {
            return next.handle()
                .pipe(map(value => value === null ? '' : value ));
        }
    }
    

超时处理

  1. Observable的运算符timeout():当端点在一段时间内没有任何返回内容时,则抛出异常;
    import { timeout } from 'rxjs/operators';
    
    @Injectable()
    export class TimeoutInterceptor implements NestInterceptor {
        intercept(context: ExecutionContext, next: CallHandler): Observable {
            return next.handle().pipe(timeout(5000));
        }
    }
    
  2. 如果请求的控制器(方法)上装饰了相应的异常过滤器,则在超过 5s 还没有获取到控制器的返回值时,进入异常过滤器,由过滤器处理响应。
    1. 捕获一切的异常过滤器
        @Catch()
        export class AllExceptionsFilter implements ExceptionFilter {
            catch(exception: unknown, host: ArgumentsHost) {
                let resp = host.switchToHttp().getResponse();
                // 发送响应
                resp.json({ message: "服务器内部错误!" });
            }
        }
    
    1. 控制器
        @Controller('cats')
        @UseFilters(AllExceptionsFilter)
        export class CatsController {
            @Get('list')
            @UseInterceptors(TimeoutInterceptor)
            async getList() {
                //延迟6s, 发生了超时
                let res = await new Promise(resolve=>{
                    setTimeout(()=>{
                        resolve({ Message: 'Success' })
                    }, 6000);
                })
                return res;
            }
        }
    
    1. 访问:http://localhost:3000/cats/list --> {"message": "服务器内部错误!"}

异常映射

Observable的操作符 catchError 可以覆盖抛出的异常,然后自己处理;

    import { Observable, throwError } from 'rxjs';
    import { catchError } from 'rxjs/operators';

    @Injectable()
    export class ErrorsInterceptor implements NestInterceptor {
        intercept(context: ExecutionContext, next: CallHandler): Observable {
            return next.handle()
                    .pipe(catchError(err => {
                        // 抛出自己的异常
                        throwError(new BadGatewayException());
                    }),);
        }
    }

重写Stream

  1. 有时希望完全阻止请求达到处理程序(控制器),并响应不同的值,比如由于性能问题而从缓存中获取;
  2. Observable的运算符 of
    import { Observable, of } from 'rxjs';

    @Injectable()
    export class CacheInterceptor implements NestInterceptor {
        intercept(context: ExecutionContext, next: CallHandler): Observable {
            // 缓存是否存在
            const isCached = true;
            if (isCached) {
                return of({message: '缓存数据'});   //直接响应缓存数据
            }
            return next.handle();
        }
    }
  1. 为了创建一个通用的解决方案,可以结合反射器Relector使用,判别哪些请求做了缓存处理。

请求与响应过程

客户端请求 --> 中间件 --> 守卫 --> 拦截器之前 --> 管道 --> 控制器 --> 拦截器之后 --> 过滤器
  1. 中间件做请求处理,如```helmet,csrf,rate limiting,compression``等常用的中间件;
  2. 守卫常用于验证该用户的身份,如果未通过验证,则抛出异常;最适合做权限管理;
  3. 拦截器之前不能修改请求信息,只能获取请求信息;
  4. 管道做请求的数据验证和转化,如果验证失败,则抛出异常;
  5. 控制器负责处理请求和服务桥梁,直接响应服务处理结果;
  6. 拦截器之后只能修改响应body数据;
  7. 过滤器:如果前面任何位置发生抛出异常操作,都会直接进入过滤器,否则不经过。

你可能感兴趣的:(拦截器)