likes
comments
collection
share

快速入门 Nestjs(二)

作者站长头像
站长
· 阅读数 38

RxJs 与 Nestjs

RxJS(Reactive Extensions for JavaScript)是一个用于异步编程的库,它基于可观察对象(Observables)来处理异步事件和数据流。RxJS 提供了强大的操作符来处理和转换这些数据流,使得代码更加简洁和可维护。RxJS 的核心理念是响应式编程,它强调数据流和变化传播

基本概念

  • Observable: 可观察对象,用于表示一个异步数据流
  • Observer: 观察者,对可观察对象发出的数据进行处理
  • Subscription: 订阅,通过订阅可以开始接收可观察对象发出的数据
  • Operators: 操作符,用于对数据流进行转换、过滤等操作
  • Subject: 特殊类型的可观察对象,可以同时作为数据生产者和消费者

以下是一个简单的 RxJS 示例,用于演示如何创建一个 Observable 并订阅它

import { Observable } from 'rxjs';

const observable = new Observable(subscriber => {
  subscriber.next(1);
  subscriber.next(2);
  subscriber.next(3);
  subscriber.complete();
});

observable.subscribe({
  next(x) { console.log('Received value: ' + x); },
  error(err) { console.error('Error: ' + err); },
  complete() { console.log('Complete'); }
});

// Received value: 1
// Received value: 2
// Received value: 3
// Complete

在 NestJS 中使用 RxJS

  • NestJS 深度集成了 RxJS,使得处理异步操作更加方便
import { Injectable } from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable()
export class ListService {
  findAll(): Observable<string[]> {
    const cats = ['Tom', 'Jerry', 'Luxi'];
    return of(cats);
  }

  findOne(): Observable<string[]> {
    const cats = ['Tom', 'Jerry', 'Jack'];
    return of(cats).pipe(
      map((catsArray) => catsArray.map((cat) => cat.toUpperCase())),
    );
  }
}
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { ListService } from './list.service';
import { Observable } from 'rxjs';

@Controller('list')
export class ListController {
  constructor(private readonly listService: ListService) {}

  @Get()
  findAll(): Observable<string[]> {
    return this.listService.findAll();
  }

  @Post()
  findOne(): Observable<string[]> {
    return this.listService.findOne();
  }
}
  • RxJS 是一个功能强大的库,有很多方法,用于处理异步事件和数据流。

Nestjs 响应拦截器

  • 拦截器具有一系列有用的功能,这些功能受面向切面编程(AOP)技术的启发。它们可以:

    • 在函数执行之前/之后绑定额外的逻辑
    • 转换从函数返回的结果
    • 转换从函数抛出的异常
    • 扩展基本函数行为
    • 根据所选条件完全重写函数 (例如, 缓存目的)
  • 创建拦截器的基本步骤:需要创建一个类,它实现了 nest.js 提供的 NestInterceptor 接口

// 创建一个ts文件,然后在main.ts文件引入
import { Injectable, NestInterceptor, CallHandler } from '@nestjs/common';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';

interface data<T> {
  data: T;
}
@Injectable()
export class Response<T = any> implements NestInterceptor {
  intercept(context, next: CallHandler): Observable<data<T>> {
    // 使用了`rxjs`的`pipe`方法和`map`操作符来转换返回的数据
    return next.handle().pipe(
      map((data) => {
        return {
          data,
          status: 0,
          success: true,
          message: '牛逼',
        };
      }),
    );
  }
}
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Response } from './common/response';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalInterceptors(new Response());
  await app.listen(3000);
}
bootstrap();

快速入门 Nestjs(二)

Nestjs 异常过滤器

异常过滤器是用于捕获和处理抛出的异常,并生成适当的 HTTP 响应的类。通过使用异常过滤器,你可以将异常处理逻辑从控制器或服务中分离出来

import { ExceptionFilter, Catch, ArgumentsHost,HttpException } from '@nestjs/common'
import {Request,Response} from 'express'
 
@Catch(HttpException)
export class HttpFilter implements ExceptionFilter {
    catch(exception:HttpException, host: ArgumentsHost) {
        const ctx = host.switchToHttp()
        const request = ctx.getRequest<Request>()
        const response = ctx.getResponse<Response>()
 
        const status = exception.getStatus()
 
        response.status(status).json({
           data:exception.message,
           time:new Date().getTime(),
           success:false,
           path:request.url,
           status
        })
    }
}
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Response } from './common/response';
import { HttpFilter } from './common/filter';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalInterceptors(new Response());
  app.useGlobalFilters(new HttpFilter());
  await app.listen(3000);
}
bootstrap();
  • 随便访问一个错误的路由

快速入门 Nestjs(二)

Nestjs 管道

在 NestJS 中,管道(Pipes)是一种用于处理传入请求数据的机制。管道主要有两个用途:转换验证。转换指的是将输入数据转换为所需的类型,验证指的是检查输入数据是否符合预期的格式或规则

内置管道

NestJS 提供了几个内置的管道:

  • ValidationPipe:用于验证输入数据
  • ParseIntPipe:将输入数据解析为整数
  • ParseBoolPipe:将输入数据解析为布尔值
  • ParseArrayPipe:将输入数据解析为数组
  • ParseUUIDPipe:将输入数据解析为 UUID
  • ...

使用内置管道进行转换

  • 有一个路由需要接收一个 ID 参数,并且希望将其转换为整数
import { Controller, Get, Param, ParseIntPipe } from '@nestjs/common';

@Controller('p')
export class ItemsController {
  @Get(':id')
  findOne(@Param('id', ParseIntPipe) id: number) {
    return `Item #${id}`;
  }
}
  • ParseIntPipe 将把传入的 id 参数从字符串转换为整数。如果传入的 id 不能转换为整数,管道会抛出一个 BadRequestException
  • 比如请求 http://localhost:3000/p/123a

快速入门 Nestjs(二)

使用内置管道进行验证

  • 使用 ValidationPipe 验证请求体的数据
  • 首先需要定义一个 DTO(数据传输对象),并使用 class-validator 提供的装饰器来定义验证规则
//安装验证器
npm i --save class-validator
  • 创建一个dto文件

在Nestjs项目中,DTO(Data Transfer Object)文件是用于数据传输的对象。DTO是一个对象,它定义了如何通过网络将数据传输到方法。简单来说,它是用来定义接口数据类型或传输数据的载体

import { IsInt, IsString, Min, MaxLength } from 'class-validator';

export class CreatePDto {
  @IsString()
  @MaxLength(50)
  name: string;

  @IsInt()
  @Min(0)
  quantity: number;
}
  • 然后在控制器中使用 ValidationPipe
import { Body, Controller, Post, UsePipes, ValidationPipe } from '@nestjs/common';
import { CreatePDto } from './dto/create-p.dto';

@Controller('p')
export class PController {
  @Post()
  @UsePipes(new ValidationPipe())
  create(@Body() createPDto: CreatePDto) {
    return `Item created: ${createPDto.name} (${createPDto.quantity})`;
  }
}
  • ValidationPipe 将验证传入的请求体是否符合 CreateItemDto 中定义的规则。如果验证失败,管道会抛出一个 BadRequestException

快速入门 Nestjs(二)

全局使用管道

  • 在整个应用程序范围内使用管道,以便所有请求都经过该管道处理
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe());
  await app.listen(3000);
}
bootstrap();

自定义管道

自定义管道需要实现 PipeTransform 接口,并实现其 transform 方法

  • 在对应模块下执行 nest g pi p 生成 .pipe.ts 文件
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';

@Injectable()
export class PPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    return value;
  }
}
  • 将输入的字符串转换为大写
import { ArgumentMetadata, Injectable, PipeTransform, BadRequestException } from '@nestjs/common';

@Injectable()
export class PPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    if (typeof value !== 'string') {
      throw new BadRequestException('Validation failed');
    }
    return value.toUpperCase();
  }
}
import {Get, Body, Controller, Post, UsePipes, ValidationPipe, Query } from '@nestjs/common';
import { PPipe } from './p/p.pipe';

@Controller('p')
export class PController {

  @Get()
  @UsePipes(new PPipe())
  findAll(@Query('search') search: string) {
    return `Searching for items with keyword: ${search}`;
  }
}

Nestjs 守卫

守卫(Guards)是一种用于实现权限控制和身份验证的机制。它们可以在请求被处理之前拦截并执行一些逻辑,以决定是否允许请求继续进行。守卫类似于中间件,但它们专注于授权逻辑

守卫是一种可以用于控制请求流的类。它实现了 CanActivate 接口,该接口定义了一个 canActivate 方法。这个方法接受 ExecutionContext 作为参数,并返回一个布尔值或 Promise/Observable,如果返回 true 则允许请求继续,否则拒绝请求

  • 可以在模块下执行 nest g gu [name]创建
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class GGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    return true;
  }
}

基本使用

  • 创建一个简单的身份验证守卫
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    const user = request.user;
    
    // 示例:简单的身份验证逻辑
    return !!user;
  }
}

模块内全路由使用

import { Module } from '@nestjs/common';
import { GuardService } from './guard.service';
import { GuardController } from './guard.controller';
import { GGuard } from './g/g.guard';
import { APP_GUARD } from '@nestjs/core';

@Module({
  controllers: [GuardController],
  providers: [
    GuardService,
    {
      provide: APP_GUARD,
      useClass: GGuard,
    },
  ],
})
export class GuardModule {}

控制器级别使用

  • 在控制器上使用 @UseGuards() 装饰器
import { Controller, Get, UseGuards } from '@nestjs/common';
import { GGuard } from './g/g.guard';

@Controller('guard')
@UseGuards(GGuard)
export class CatsController {
  @Get()
  findAll() {
    return 'This action returns all cats';
  }
}

方法级别的使用

  • 在控制器的方法上使用 @UseGuards() 装饰器
import { Controller, Get, UseGuards } from '@nestjs/common';
import { GGuard } from './g/g.guard';

@Controller('guard')
export class CatsController {
  @Get()
  @UseGuards(GGuard)
  findAll() {
    return 'This action returns all cats';
  }
}

全局使用

app.useGlobalGuards(new RoleGuard())

自定义装饰器

  • 自定义参数装饰器:用于提取和处理参数
  • 自定义方法装饰器:用于在方法执行前后添加逻辑
  • 自定义类装饰器:用于在类级别添加元数据或逻辑

中间件、守卫、拦截器、管道、dto、自定义参数装饰器、控制器

执行顺序

  • 中间件:最先执行,用于处理请求和响应的预处理
  • 守卫:中间件之后执行,用于实现权限控制和身份验证,主要作用是决定请求是否被允许继续执行
  • 拦截器:守卫之后执行,用于在请求和响应的处理过程中进行额外的逻辑处理。拦截器分请求前和响应后两个阶段
  • 管道:用于验证和转换请求数据,会在路由处理程序处理请求之前运行
    • DTO验证:如果使用了验证管道(如:ValidationPipe),则管道会对传入的 DTO 数据进行验证
  • 自定义参数装饰器
  • 控制器方法:在所有的预处理之后,实际的控制器方法被执行,处理业务逻辑

在 NestJS 中,虽然中间件、守卫、拦截器、管道、DTO、装饰器和控制器有各自的职责和最佳实践用法,但在某些情况下,它们的功能是可以重叠和兼容的。例如,数据转换和验证可以在多个地方实现,但每个组件在架构中的位置和职责是不同的

转载自:https://juejin.cn/post/7372078061897154597
评论
请登录