返回介绍

管道、守卫、拦截器、过滤器、中间件

发布于 2024-01-18 22:07:39 字数 9935 浏览 0 评论 0 收藏 0

  • 管道:数据处理与转换,数据验证
  • 守卫:验证用户登陆,保护路由
  • 拦截器:对请求响应进行拦截,统一响应内容
  • 过滤器:异常捕获
  • 中间件:日志打印

执行顺序(时机)

从客户端发送一个 post 请求,路径为: /user/login ,请求参数为: {userinfo: ‘xx’,password: ‘xx’} ,到服务器接收请求内容,触发绑定的函数并且执行相关逻辑完毕,然后返回内容给客户端的整个过程大体上要经过如下几个步骤:

全局使用: 管道 - 守卫 - 拦截器 - 过滤器 - 中间件。统一在 main.ts 文件中使用,全局生效

import { NestFactory } from '@nestjs/core';
import { ParseIntPipe } from '@nestjs/common';
import { AppModule } from './app.module';

import { HttpExceptionFilter } from './common/filters/http-exception.filter';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { AuthGuard } from './common/guard/auth.guard';
import { AuthInterceptor } from './common/interceptors/auth.interceptor';


async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  //全局使用管道:这里使用的是内置,也可以使用自定义管道,在下文
  app.useGlobalPipes(new ParseIntPipe());

  //全局使用中间件
  app.use(LoggerMiddleware)
  
  //全局使用过滤器
  //这里使用的是自定义过滤器,先别管,先学会怎么在全局使用
  app.useGlobalFilters(new HttpExceptionFilter());  

  //全局使用守卫
  app.useGlobalGuards(new AuthGuard());
  
  //全局使用拦截器
  app.useGlobalInterceptors(new AuthInterceptor());
  
  await app.listen(3000);
}
bootstrap();

管道

常用内置管道,从 @nestjs/common 导出

  • ParseIntPipe :将字符串数字转数字
  • ValidationPipe :验证管道

局部使用管道

  • 匹配整个路径,使用 UsePipes
  • 只匹配某个接口,使用 UsePipes
  • 在获取参数时匹配,一般使用内置管道
import {
  Controller,
  Get,
  Put,
  Body,
  Param,
  UsePipes,
  ParseIntPipe
} from '@nestjs/common';
import { myPipe } from '../../common/pipes/user.pipe';

@Controller('user')
@UsePipes(new myPipe())  //局部方式 1:匹配整个/user, get 请求和 put 请求都会命中
export class UserController {
  @Get(':id')
  getUserById(@Param('id', new ParseIntPipe()) id) { //局部方式 3:只匹配/user 的 get 请求,使用的是内置管道
    console.log('user', typeof id);
    return id;
  }

  @Put(':id')
  @UsePipes(new myPipe())  //局部方式 2:只匹配/user 的 put 请求
  updateUser(@Body() user, @Param('id') id) {
    return {
      user,
      id,
    };
  }
}

自定义管道

使用快捷命令生成: nest g pi myPipe common/pipes

import {
  ArgumentMetadata,
  Injectable,
  PipeTransform,
  BadRequestException,
} from '@nestjs/common';

//自定义管道必须实现自 PipeTransform,固定写法,该接口有一个 transform 方法
//transform 参数:
//value:使用 myPipe 时所传递的值,可以是 param 传递的的查询路径参数,可以是 body 的请求体
//metadata:元数据,可以用它判断是来自 param 或 body 或 query
@Injectable()
export class myPipe implements PipeTransform<string> {
  transform(value: string, metadata: ArgumentMetadata) {
    if (metadata.type === 'body') {
      console.log('来自请求体', value);
    }
    if (metadata.type === 'param') {
      console.log('来自查询路径', value);

      const val = parseInt(value, 10);
      //如果不是传递一个数字,抛出错误
      if (isNaN(val)) {
        throw new BadRequestException('Validation failed');
      }
      return val;
    }
    return value;
  }
}

守卫

自定义守卫

使用快捷命令生成: nest g gu myGuard common/guards

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core'; //反射器,作用与自定义装饰器桥接,获取数据

//自定义守卫必须 CanActivate,固定写法,该接口只有一个 canActivate 方法
//canActivate 参数:
//context:请求的(Response/Request) 的引用
//通过守卫返回 true,否则返回 false,返回 403 状态码
@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) { }

  // 白名单数组
  private whiteUrlList: string[] = ['/user'];

  // 验证该次请求是否为白名单内的路由
  private isWhiteUrl(urlList: string[], url: string): boolean {
    if (urlList.includes(url)) {
      return true;
    }
    return false;
  }

  canActivate(context: ExecutionContext): boolean {
    // 获取请求对象
    const request = context.switchToHttp().getRequest();
    //console.log('request', request.headers);
    //console.log('request', request.params);
    //console.log('request', request.query);
    //console.log('request', request.url);

    // 用法一:验证是否是白名单内的路由
    if (this.isWhiteUrl(this.whiteUrlList, request.url)) {
      return true;
    } else {
      return false;
    }

    // 用法二:使用反射器,配合装饰器使用,获取装饰器传递过来的数据
    const roles = this.reflector.get<string[]>('roles', context.getHandler());
    //console.log(roles); // [ 'admin' ]
    //http://localhost:3000/user/9?user=admin,如果与装饰器传递过来的值匹配则通过,否则不通过
    //真实开发中可能从 cookie 或 token 中获取值
    const { user } = request.query;
    if (roles.includes(user)) {
      return true;
    } else {
      return false;
    }

    // 其他用法
    // 获取请求头中的 token 字段
    const token = context.switchToRpc().getData().headers.token;
    // console.log('token', token);

    // 获取 session
    const userinfo = context.switchToHttp().getRequest().session;
    // console.log('session', userinfo);

    return true;
  }
}

局部使用守卫

import {
  Controller,
  Get,
  Delete,
  Param,
  UsePipes,
  UseGuards,
  ParseIntPipe,
} from '@nestjs/common';
import { AuthGuard } from '../../common/guard/auth.guard';
import { Role } from '../../common/decorator/role.decorator'; //自定义装饰器

@UseGuards(AuthGuard) //局部使用守卫,守卫整个 user 路径
@Controller('user')
export class UserController {
  @Get(':id')
  getUserById(@Param('id', new ParseIntPipe()) id) {
    console.log('user', typeof id);
    return id;
  }

  @Delete(':id')
  @Role('admin')  //使用自定义装饰器,传入角色,必须是 admin 才能删除
  removeUser(@Param('id') id) {
    return id;
  }
}

装饰器

自定义守卫中使用到了自定义装饰器

nest g d role common/decorator
//这是快捷生成的代码

import { SetMetadata } from '@nestjs/common';

//SetMetadata 作用:将获取到的值,设置到元数据中,然后守卫通过反射器才能获取到值
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

拦截器

使用快捷命令生成: nest g in auth common/intercepters

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

//自定义拦截器必须实现自 NestInterceptor,固定写法,该接口只有一个 intercept 方法
//intercept 参数:
//context:请求上下文,可以拿到的 Response 和 Request
@Injectable()
export class AuthInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    console.log('拦截器', request.url);
    return next.handle().pipe(
      map((data) => {
        console.log('全局响应拦截器方法返回内容后...');
        return {
          status: 200,
          timestamp: new Date().toISOString(),
          path: request.url,
          message: '请求成功',
          data: data,
        };
      }),
    );
  }
}

过滤器

局部使用过滤器

import {
  Controller,
  Get,
  UseFilters,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
import { HttpExceptionFilter } from '../../common/filters/http-exception.filter';

//局部使用过滤器
@UseFilters(new HttpExceptionFilter())
@Controller('/user')
export class ExceptionController {
  @Get()
  getUserById(@Query() { id }): string {
    if (!id) {
      throw new HttpException(
        {
          status: HttpStatus.BAD_REQUEST,
          message: '请求参数 id 必传',
          error: 'id is required',
        },
        HttpStatus.BAD_REQUEST,
      );
    }
    return 'hello error';
  }
}

自定义过滤器

使用快捷命令生成: nest g f myFilter common/filters

import {
  ArgumentsHost,
  Catch,
  ExceptionFilter,
  HttpException,
} from '@nestjs/common';

//必须实现至 ExceptionFilter,固定写法,该接口只有一个 catch 方法
//catch 方法参数:
//exception:当前正在处理的异常对象
//host:传递给原始处理程序的参数的一个包装(Response/Request) 的引用
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter<HttpException> {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status = exception.getStatus(); //获取状态码
    const exceptionRes: any = exception.getResponse(); //获取响应对象
    const { error, message } = exceptionRes;

    //自定义的异常响应内容
    const msgLog = {
      status,
      timestamp: new Date().toISOString(),
      path: request.url,
      error,
      message,
    };

    response.status(status).json(msgLog);
  }
}

中间件

局部使用中间件

import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middlerware';
import { UserModule } from './modules/user/user.module';

@Module({
imports:[ UserModule ]
})
export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware) //应用中间件
      .exclude({ path: 'user', method: RequestMethod.POST })  //排除 user 的 post 方法
      .forRoutes('user'); //监听路径  参数:路径名或*,*是匹配所以的路由
      // .forRoutes({ path: 'user', method: RequestMethod.POST }, { path: 'album', method: RequestMethod.ALL }); //多个
     // .apply(UserMiddleware) //支持多个中间件
     // .forRoutes('user')
  }
}

自定义中间件

nest g mi logger common/middleware
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  //req:请求参数
  //res:响应参数
  //next:执行下一个中件间
  use(req: Request, res: Response, next: () => void) {
    const { method, path } = req;
    console.log(`${method} ${path}`);
    next();
  }
}

函数式中间件

// 函数式中间件-应用于全局
export function logger(req, res, next) {
  next();
}

// main.ts
async function bootstrap() {
  // 创建实例
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  
  // 设置全局日志函数中间件
  app.use(logger);
}
bootstrap();

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文