Nest.js从0到1搭建博客系统---中间件、守卫、管道、拦截器、异常过滤器、验证器(3)

1,611 阅读5分钟

中间件、守卫、管道、拦截器、异常过滤器、验证器

前言

Nest.js 作为一个强大的 Node.js 框架,提供了一套丰富的装饰器和帮助函数来支持 AOP 的实现。其中 Nest 的实现主要包括如下五种:

  • 中间件(Middleware)
  • 守卫(Guard)
  • 管道(Pipe)
  • 拦截器(Interceptor)

未命名文件.jpg

  • 安装
npm install --save rxjs xml2js class-validator class-transformer
  • rxjs 提供了一种响应式编程的方式来处理异步数据流
  • xml2js 转换xml内容变成json格式
  • class-validator、class-transformer 管道验证包和转换器

中间件

Middleware是在路由处理程序之前调用的函数。中间件函数可以访问请求响应对象,以及应用程序的请求-响应周期中的next()中间件函数。通常,next中间件函数由一个名为next的变量表示

image.png

中间件函数可以执行以下任务:

  • 执行任何代码

  • 对请求和响应对象进行更改

  • 结束请求-响应周期

  • 调用堆栈中的下一个中间件函数

  • 如果当前中间件函数未结束请求-响应周期,则必须调用next()将控制权传递给下一个中间件函数。否则,请求将被搁置

  • 创建logger.middleware.ts中间件

nest g mi logger --no-spec --flat
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
    use(req: Request, res: Response, next: NextFunction) {
        console.log('Logger 中间件拦截', req.header);
        // 具体内容根据需求去写
        next();
    }
}

1、局部中间件

  • demo.module.ts
import { Module, NestMiddleware, MiddlewareConsumer } from '@nestjs/common';
import { DemoService } from './demo.service';
import { DemoController } from './demo.controller';
import {LoggerMiddleware} from '@/common/middleware/logger.middleware'

@Module({
  controllers: [DemoController],
  providers: [DemoService],
  exports: [DemoService],
})
export class DemoModule implements NestMiddleware {
  configure (consumer:MiddlewareConsumer) {
   // consumer.apply(LoggerMiddleware).forRoutes('demo')
    consumer.apply(LoggerMiddleware).forRoutes({path:'/demo',method:RequestMethod.GET}) //针对 Get
  }
}

执行接口就能监听到结果l image.png

2、全局中间件

  • main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from '@/app.module';
function LoggerMiddleware(req, res, next: () => any) {
  console.log(req.originalUrl, '我是全局中间件LoggerMiddleware')
  next()
}
async function bootstrap() {
     const app = await NestFactory.create(AppModule);
     app.use(middleWareAll)
     await app.listen(3000);
}
bootstrap();

请求demo与user接口都监听到结果了 image.png

守卫

守卫具有单一职责。它们根据运行时的某些条件(如权限、角色、ACL等)确定是否将处理给定请求,这通常称为授权。授权(以及它通常与之协作的身份验证)通常在传统的Express应用程序中由中间件处理。中间件是身份验证的不错选择,因为诸如令牌验证和将属性附加到request对象之类的事情与特定路由上下文(及其元数据)没有紧密关联

image.png

  • 创建auth.guard.ts 守卫
nest g gu auth --no-spec --flat

import { Reflector } from '@nestjs/core'
import { AuthGuard } from '@nestjs/passport'
import { ExecutionContext, ForbiddenException, Inject, Injectable, UnauthorizedException } from '@nestjs/common'
import { UserService } from '@/modules/user/user.service';
import { ALLOW_ANON } from '@/common/decorator/allow-anon.decorator'
@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {
    constructor(private readonly reflector: Reflector, @Inject(UserService)
    private readonly userService: UserService) {
        super()
    }
    async canActivate(ctx: ExecutionContext): Promise<boolean> {
        const allowAnon = this.reflector.getAllAndOverride<boolean>(ALLOW_ANON, [ctx.getHandler(), ctx.getClass()])
        if (allowAnon) return true
        const req = ctx.switchToHttp().getRequest()
        const accessToken = req.get('Authorization')
        if (!accessToken) throw new ForbiddenException('请先登录!')
        const UserId = this.userService.verifyToken(accessToken)
        if (!UserId) throw new UnauthorizedException('当前登录已过期,请重新登录!')
        return this.activate(ctx)

    }
    async activate(ctx: ExecutionContext): Promise<boolean> {
        return super.canActivate(ctx) as Promise<boolean>
    }
}
  • 全局注册守卫
import { Module, ValidationPipe } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './modules/user/user.module';
import { DemoModule } from './modules/demo/demo.module';
import { JwtAuthGuard } from '@/common/guard/auth.guard'

@Module({
  imports: [ConfigModule.forRoot(), UserModule, DemoModule,],
  controllers: [AppController],
  providers: [AppService, {
      // 全局权限认证
      provide: APP_GUARD,
      useClass: JwtAuthGuard,
    }],

})
export class AppModule { }

管道

管道是具有 @Injectable() 装饰器的类。管道应实现 PipeTransform 接口。

image.png

管道有两个典型的应用场景:

  • 转换:管道将输入数据转换为所需的数据输出(例如,将字符串转换为整数)
  • 验证:对输入数据进行验证,如果验证成功继续传递; 验证失败则抛出异常

Nest 自带九个开箱即用的管道,即

  • ValidationPipe
  • ParseIntPipe
  • ParseFloatPipe
  • ParseBoolPipe
  • ParseArrayPipe
  • ParseUUIDPipe
  • ParseEnumPipe
  • DefaultValuePipe
  • ParseFilePipe

他们从 @nestjs/common 包中导出。

  • 创建demo.pipe.ts管道
nest g pipe demo --no-spec --flat
import { ArgumentMetadata, Injectable, PipeTransform } from '@nestjs/common';
@Injectable()
export class DemoPipe implements PipeTransform {
    transform(value: any, metadata: ArgumentMetadata) {
        //这个里面可以修改传入的值以及验证转入值的合法性
        console.log("我是处理Demo参数管道");
        return value;
    }
}
  • 全局管道注册
import { DemoPipe } from '@/common/pipe/demo.pipe';
async function bootstrap() { 
    const app = await NestFactory.create(AppModule);
    app.useGlobalPipes(new DemoPipe()); a
    wait app.listen(3000); 
} 
bootstrap();

  • 或者
import { Module } from '@nestjs/common'; 
import { APP_PIPE } from '@nestjs/core'; 
import { DemoPipe } from '@/common/pipe/demo.pipe';
@Module({ 
    providers: [ { provide: APP_PIPE, useClass: DemoPipe } ] 
})
export class AppModule {}
  • Demo控制中使用
  @Post()
  @SwaggerDoc("创建demo", "demo描述", "创建demo请求", String)
  create(@Body(new DemoPipe()) createDemoDto: CreateDemoDto) {
    return this.demoService.create(createDemoDto);
  }

image.png

拦截器

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

  • 在函数执行之前/之后绑定额外的逻辑
  • 转换从函数返回的结果
  • 转换从函数抛出的异常
  • 扩展基本函数行为
  • 根据所选条件完全重写函数 (例如, 缓存目的)

image.png

  • 创建transform.interceptor.ts 拦截器
nest g itc interceptor/transform --no-spec --flat
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from "@nestjs/common"
import { Observable } from "rxjs"
import { map } from 'rxjs/operators';

interface Response<T> {
    data: T;
}

@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
    intercept(context: ExecutionContext, next: CallHandler<T>): Observable<Response<T>> | Promise<Observable<Response<T>>> {
        return next.handle().pipe(
            map((data) => ({
                code: 0, //状态码
                data,//返回数据
                pagination: { //分页
                    total: 100,
                    pageSize: 10,
                    pages: 10,
                    page: 1,
                },
                extra: {}, // 拓展
                message: 'success',// 返回结果
            })),
        );
    }
}
  • 在app.module.ts全局注册
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './modules/user/user.module';
import { DemoModule } from './modules/demo/demo.module';
import { TransformInterceptor } from '@/common/interceptor/transform.interceptor';

@Module({
  imports: [ConfigModule.forRoot(), UserModule, DemoModule,],
  controllers: [AppController],
  providers: [AppService, {
    // 全局拦截器
    provide: APP_INTERCEPTOR,
    useClass: TransformInterceptor,
  }],

})
export class AppModule { }

  • 或者main.ts注册
import { TransformInterceptor } from '@/common/interceptor/transform.interceptor';
app.useGlobalInterceptors(new TransformInterceptor())

image.png

异常过滤器

Nest框架内置了一个异常处理层,负责处理应用程序中的所有未处理异常。当一个异常没有被应用程序代码处理时,它会被这个异常处理层捕获,然后自动发送一个适当的用户友好响应。

image.png

  • 创建http.exception.filter.ts异常过滤器
nest g f http.exception --no-spec --flat
import {
    ArgumentsHost,
    Catch,
    ExceptionFilter,
    HttpException,
    HttpStatus,
} from '@nestjs/common';
import { Request, Response } from 'express';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
    catch(exception: HttpException, host: ArgumentsHost) {
        const request = host.switchToHttp().getRequest<Request>();
        const response = host.switchToHttp().getResponse<Response>();
        // http异常处理
        response.status(HttpStatus.NOT_FOUND).send({
            statusCode: HttpStatus.NOT_FOUND,
            timestamp: new Date().toISOString(),
            path: request.url,
            message: exception.getResponse(),
        });
    }
}
  • main.ts注册
import { HttpExceptionFilter } from '@/common/filters/exceptions/http.exception.filter';
app.useGlobalFilters(new HttpExceptionFilter())
  • 或app.module.ts注册
import { Module } from '@nestjs/common';
import {  APP_FILTER } from '@nestjs/core';
import { ConfigModule } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './modules/user/user.module';
import { DemoModule } from './modules/demo/demo.module';
import { HttpExceptionFilter } from '@/common/filters/exceptions/http.exception.filter';

@Module({
  imports: [ConfigModule.forRoot(), UserModule, DemoModule,],
  controllers: [AppController],
  providers: [AppService, {
      // Http异常
      provide: APP_FILTER,
      useClass: HttpExceptionFilter,
    },],

})
export class AppModule { }

image.png

验证器

 
import { IsNotEmpty, IsString, Length, IsIn,IsNumber } from 'class-validator'
export class CreateDemoDto {

   @IsNotEmpty()//验证是否为空
   @IsString() //是否为字符串
   @Length(1, 10)
   username: string;
   @IsNotEmpty()
   @IsNumber()
   age: number
   @IsIn([0, 1])
   status: number;
}
  • 全局注册Nest内置ValidationPipe验证管道 image.png

image.png

github

项目地址:nest-vhen-blog