Nodejs web框架之Nestjs 使用笔记

关于Nest

1、nest是一个基于nodejs的web框架,支持TS。
2、内置Http服务器框架express(默认),也可以使用Fastify ,社区丰富。
3、注解(装饰器) 语法,很像java的spring boot一套,又有angular的依赖注入(DI)。

安装并初始化项目

$ npm i -g @nestjs/cli
$ nest new project-name

项目入口文件

import {
    
     NestFactory } from '@nestjs/core';
import {
    
     AppModule } from './app.module';
import {
    
     INestApplication } from '@nestjs/common';
async function bootstrap() {
    
    
  // NestFactory工厂类的静态方法create来创建一个Nest应用实例
  const app: INestApplication  = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

创建一个基于fastify的项目

npm i @nestjs/platform-fastify --save 

const app = await NestFactory.create<NestFastifyApplication>(AppModule);


controller

controller负责处理客户端的请求,并作出响应。路由机制决定哪个请求是由哪个controller处理。

@Controller('cats')表示一个定义controller,路径是 /cats, findAll 方法作出响应数据,默认返回状态200,数据会被处理成json数据,也可以只用如express的自己的response对象,作出进一步的控制,比如返回一个网页。

import {
    
    
  Controller,Get,HttpCode,HttpStatus, Param,Post,
  Query,Redirect, Res, Req, Body,Header,
} from '@nestjs/common';
import {
    
     Request } from 'express';
import {
    
     Observable, of } from 'rxjs';

@Controller('cats')
export class CatsController {
    
    

  // 使用request对象
  @Get()
  demo1(@Req() req: Request): string {
    
    
    return 'This action returns all cats';
  }

  // 路由可以使用正则
  @Get('ab*cd')
  demo2() {
    
    
    return 'This route uses a wildcard';
  }

  // params
  @Get('/haha/:id')
  demo3(@Param() params) {
    
    
    console.log(params.id);
    return 'This route uses a wildcard';
  }

  // params
  @Get('/haha2/:id')
  findOne(@Param('id') id: string): string {
    
    
    return `This action returns a #${
    
    id} cat`;
  }

  // 返回不同的状态码
  @Post()
  @HttpCode(204)
  demo4() {
    
    
    return 'This action adds a new cat';
  }

  // 返回自定义的header
  @Post()
  @Header('Cache-Control', 'none')
  demo5() {
    
    
    return 'This action adds a new cat';
  }

  // handler 可以是异步
  @Post('/post')
  async demo6(): Promise<any> {
    
    
    return 'This action adds a new cat';
  }

  // 异步,使用RXjs,返回 RxJS observable streams.
  @Get()
  findAll(): Observable<any[]> {
    
    
    return of([]);
  }

  // 使用DTO,来获取传输数据
  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
    
    
    return 'This action adds a new cat';
  }


  // 重定向
  @Get('docs')
  @Redirect('https://docs.nestjs.com', 302)
  getDocs(@Query('version') version) {
    
    
    if (version && version === '5') {
    
    
      return {
    
     url: 'https://docs.nestjs.com/v5/' };
    }
  }

  // 使用express内置Response返回全信息
  @Post('/code-express1')
  express1(@Res() res: Response) {
    
    
    res.status(HttpStatus.CREATED).send();
  }

  @Get('/code-express2')
  express2(@Res() res: Response) {
    
    
    res.status(HttpStatus.OK).json([]);
  }

  // 返回一个静态文件
  @Get('/html')
  html(@Res() res) {
    
    
    res.sendFile(path.resolve(__dirname, '../../', 'public/demo.html'));
  }

}


providers

provider可以是services, repositories, factories, helpers…
provider可注入其他的依赖。
provider是使用@Injectable()装饰的类。
provider应该作为处理复杂逻辑的东西。

定义

// 文件 interfaces/cat.interface.ts
export interface Cat {
     
     
  name: string;
  age: number;
  breed: string;
}
// 文件 cats.service.ts
import {
     
      Injectable } from '@nestjs/common';
import {
     
      Cat } from './interfaces/cat.interface';

@Injectable()
export class CatsService {
     
     
  private readonly cats: Cat[] = [];

  create(cat: Cat) {
     
     
    this.cats.push(cat);
  }

  findAll(): Cat[] {
     
     
    return this.cats;
  }
}
// 文件 cat.controller.ts
import {
     
      Controller, Get, Post, Body } from '@nestjs/common';
import {
     
      CreateCatDto } from './dto/create-cat.dto';
import {
     
      CatsService } from './cats.service';
import {
     
      Cat } from './interfaces/cat.interface';

@Controller('cats')
export class CatsController {
     
     
  // 这样是通过构造函数,声明并初始化了catsService
  constructor(private catsService: CatsService) {
     
     }

  @Post()
  async create(@Body() createCatDto: CreateCatDto) {
     
     
    this.catsService.create(createCatDto);
  }

  @Get()
  async findAll(): Promise<Cat[]> {
     
     
    return this.catsService.findAll();
  }
}

scope

provider 通常具有与应用程序生命周期同步的生命周期(“作用域”)。
当应用程序启动时,必须解析每个依赖项,因此每个provider都必须实例化。
同样,当应用程序关闭时,每个provider都将被销毁。
但是,也有一些方法可以使provider的生存期限定在请求范围内(request-scope)

custom providers

Nest有一个内置的控制反转IOC容器,用于解析providers之间的关系
可以使用普通值、类以及异步或同步工厂来创建自定义的provider。

可选的provider

import {
     
      Injectable, Optional, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
     
     
  constructor(@Optional() @Inject('HTTP_OPTIONS') private httpClient: T) {
     
     }
}

基于属性的注入

上述我们使用基于构造方法的注入,如下是几乎属性的注入。

import {
     
      Injectable, Inject } from '@nestjs/common';

@Injectable()
export class HttpService<T> {
     
     
  @Inject('HTTP_OPTIONS')
  private readonly httpClient: T;
}

依赖出入(Dependency injection、 DI)

Nest使用了DI这种设计模式,详情,在nest中使用就很简单,使用了基于Type的DI。
Nest将通过创建并返回CatsService的实例来解析catsService(或者,在单例的正常情况下,如果已在其他地方请求现有实例,则返回现有实例)。此依赖项被解析并传递给控制器的构造函数(或分配给指定的属性):

constructor(private catsService: CatsService) {
     
     }

注册provider

如下的controller是service的消费者,但是只是声明provider是不够的,还要在module中注册,Nest才可以注入依赖。

import {
     
      Module } from '@nestjs/common';
import {
     
      CatsController } from './cats/cats.controller';
import {
     
      CatsService } from './cats/cats.service';

@Module({
     
     
  controllers: [CatsController],
  providers: [CatsService],
})
export class AppModule {
     
     }

providers 深入

DI基础

依赖注入是一种控制反转(IoC)技术,在这种技术中,您将依赖的实例化委托给IoC容器(这里是NestJS运行时系统),而不是在您自己的代码中实例化类。

// 1 使用 @Injectable() 把CatsService定义成一个Provider
// @Injectable() 生命了这个类可以被Nest IOC容器管理, 
import {
     
      Injectable } from '@nestjs/common';
import {
     
      Cat } from './interfaces/cat.interface';

@Injectable()
export class CatsService {
     
     
  private readonly cats: Cat[] = [];

  findAll(): Cat[] {
     
     
    return this.cats;
  }
}

// 2 然后我们通过构造函数注入一个实例到当前的controller
import {
     
      Controller, Get } from '@nestjs/common';
import {
     
      CatsService } from './cats.service';
import {
     
      Cat } from './interfaces/cat.interface';

@Controller('cats')
export class CatsController {
     
     
  constructor(private catsService: CatsService) {
     
     }

  @Get()
  async findAll(): Promise<Cat[]> {
     
     
    return this.catsService.findAll();
  }
}

// 3 最后我们通过 Nest IoC container来注册 provider
// 在 app.module.ts 中,关联或者是注册了 CatsService 和 cats.service.ts 文件的关联 
import {
     
      Module } from '@nestjs/common';
import {
     
      CatsController } from './cats/cats.controller';
import {
     
      CatsService } from './cats/cats.service';

@Module({
     
     
  controllers: [CatsController],
  providers: [CatsService],
})
export class AppModule {
     
     }

当Nest IoC容器实例化CatsController时,它首先查找任何依赖项*。
当它找到CatsService依赖项时,它对CatsService令牌执行查找,
该令牌按照上面的注册步骤(app.module.ts中)返回CatsService类。
假设是单例作用域(默认行为),Nest将创建CatsService的一个实例,缓存它并返回它,
或者如果一个实例已经缓存了,返回现有的实例

  providers: [CatsService], 写法是以下的简化
  {
     
     
    provide: CatsService, // 这里叫做token
    useClass: CatsService,
  },
  
  // useValue 是使用一个类的实例,而不是一个类,例如mock一个service的返回
  const mockCatsService = {
     
     
    /* mock implementation
    ...
    */
  };
  {
     
     
    provide: CatsService,
    useValue: mockCatsService,
   }
      


不是class的token

import {
     
      connection } from './connection';

@Module({
     
     
  providers: [
    {
     
     
      provide: 'CONNECTION', // 字符串的token 
      useValue: connection,
    },
  ],
})
export class AppModule {
     
     }

// 那么再次注入就这么注入 
@Injectable()
export class CatsRepository {
     
     
  constructor(@Inject('CONNECTION') connection: Connection) {
     
     }
}

useClass

useClass语法允许动态地确定token应该解析到的类

// 比如 ConfigService 是一个抽象类,需要根据环境确定这个token解析哪个类 
const configServiceProvider = {
     
     
  provide: ConfigService,
  useClass:
    process.env.NODE_ENV === 'development'
      ? DevelopmentConfigService
      : ProductionConfigService,
};

@Module({
     
     
  providers: [configServiceProvider],
})
export class AppModule {
     
     }

useFactory

useFactory语法允许动态创建provider,实际的provider是工厂函数返回的值,
一个factory可能依赖于其他的provider,且需要在inject参数声明.

const connectionFactory = {
     
     
  provide: 'CONNECTION',
  useFactory: (optionsProvider: OptionsProvider) => {
     
     
    const options = optionsProvider.get();
    return new DatabaseConnection(options);
  },
  inject: [OptionsProvider],
};

@Module({
     
     
  providers: [connectionFactory],
})
export class AppModule {
     
     }


useExisting

使用现有的provider

const loggerAliasProvider = {
     
     
  provide: 'AliasedLoggerService',
  useExisting: LoggerService,
};

provider 并不只是提供service


const configFactory = {
     
     
  provide: 'CONFIG',
  useFactory: () => {
     
     
    return process.env.NODE_ENV === 'development' ? devConfig : prodConfig;
  },
};

@Module({
     
     
  providers: [configFactory],
})
export class AppModule {
     
     }

export自定义provider

在exports字段,导出provider的token


const connectionFactory = {
     
     
  provide: 'CONNECTION',
  useFactory: (optionsProvider: OptionsProvider) => {
     
     
    const options = optionsProvider.get();
    return new DatabaseConnection(options);
  },
  inject: [OptionsProvider],
};

@Module({
     
     
  providers: [connectionFactory],
  exports: ['CONNECTION'],
})
export class AppModule {
     
     }

异步provider

// 定义一个异步 Provider
{
     
     
  provide: 'ASYNC_CONNECTION',
  useFactory: async () => {
     
     
    const connection = await createConnection(options);
    return connection;
  },
}

// 使用
@Inject('ASYNC_CONNECTION')

Injection scopes 注入作用域

nodejs不是多线程,不遵循请求/响应多线程无状态模型,所以nest使用单例模式是安全的。

Provider scope


Modules

使用@Module()来定义一个module,module是nest组织代码的推荐的方式。
每个Nest应用,至少含有一个 root module,root module是Nest用来建立application graph(解析module、Provider关系和依赖)的入口,
每个module封装了自己这一快的功能。

import {
       
        Module } from '@nestjs/common';
import {
       
        CatsController } from './cats.controller';
import {
       
        CatsService } from './cats.service';

// 此模块封装了,所有cat相关的功能,也叫做一个Feature module
@Module({
       
                  
  imports: []                       // 其他模块,暴露出的exports的provider给本模块使用
  controllers: [CatsController],    // 此模块负责实例化的controllers
  providers: [CatsService],         // nest injector 负责实例化,并在模块中使用
  exports: []                       // 把自己模块的providers暴露出去,给其他模块使用
})
export class CatsModule {
       
       }

module默认是共享的

每个module默认是单例的、共享的。就是把exports的service导出去

import {
       
        Module } from '@nestjs/common';
import {
       
        CatsController } from './cats.controller';
import {
       
        CatsService } from './cats.service';

@Module({
       
       
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService]
})
export class CatsModule {
       
       }

module同样可以依赖注入provider

import {
       
        Module } from '@nestjs/common';
import {
       
        CatsController } from './cats.controller';
import {
       
        CatsService } from './cats.service';

@Module({
       
       
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {
       
       
  constructor(private catsService: CatsService) {
       
       }
}

Global Module

使用@Global()装饰一个module,这个module就成了一个全局module,其他地方不用引入,直接使用。全局module只能注册一次。

import {
        
         Module, Global } from '@nestjs/common';
import {
        
         CatsController } from './cats.controller';
import {
        
         CatsService } from './cats.service';

@Global()
@Module({
        
        
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService],
})
export class CatsModule {
        
        }

Dynamic module

Dynamic modules可以动态配置和注册provider

import {
         
          Module, DynamicModule } from '@nestjs/common';
import {
         
          createDatabaseProviders } from './database.providers';
import {
         
          Connection } from './connection.provider';

动态模块扩展了模块,而不是覆盖了基础的module配置,并没有覆盖下边的connection
@Module({
         
         
  providers: [Connection],
})
export class DatabaseModule {
         
         
  // forRoot 返回一个可能是同步或者异步的动态模块
  static forRoot(entities = [], options?): DynamicModule {
         
         
    const providers = createDatabaseProviders(options, entities);
          return {
         
         
            global: true,
            module: DatabaseModule,
            providers: providers,
            exports: providers,
          };
    }
}

// 使用动态模块
import {
         
          Module } from '@nestjs/common';
import {
         
          DatabaseModule } from './database/database.module';
import {
         
          User } from './users/entities/user.entity';

@Module({
         
         
  imports: [DatabaseModule.forRoot([User])],
})
export class AppModule {
         
         }

如下是一个简单的模块定义和引用


// 1 定义UsersModule 
import {
         
          Module } from '@nestjs/common';
import {
         
          UsersService } from './users.service';

@Module({
         
         
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {
         
         }

// 2 定义 AuthModule 
import {
         
          Module } from '@nestjs/common';
import {
         
          AuthService } from './auth.service';
import {
         
          UsersModule } from '../users/users.module';

@Module({
         
         
  imports: [UsersModule],
  providers: [AuthService],
  exports: [AuthService],
})
export class AuthModule {
         
         }

// 3 使用
import {
         
          Injectable } from '@nestjs/common';
import {
         
          UsersService } from '../users/users.service';

@Injectable()
export class AuthService {
         
         
  constructor(private usersService: UsersService) {
         
         }
  /*
    Implementation that makes use of this.usersService
  */
}

这种叫做静态模块绑定,以上代码发生的过程是,
1 实例化 UsersModule,传递的解析它的依赖模块和其他依赖、
2 实例化 AuthModule,使UsersModule的导出providers在 AuthModule 中可用
3 在 AuthService 注入一个 UsersService 的实例。

Dynamic Module 使用

比如,@nestjs/config 包ConfigModule 可以动态加载.env文件来加载环境变量

使用静态模块绑定,消费者模块不能影响来自宿主模块的providers配置,有时候我们需要宿主模块在不同的状态下有不同的行为。有点像是插件,在消费者使用之前要进行配置。

比如配置模块,使用配置模块来让配置细节外部化很有用,这样在不同的环境可以动态改变应用的设置。通过将配置参数的功能管理委托给配置模块,应用程序源代码保持独立于配置参数。

通过将配置参数的管理委托给配置模块,应用程序源代码保持独立于配置参数。
因为配置模块是通用的,需要由消费模块来定制,这就是动态模块的作用了。
可以让消费模块通过API控制消费模块的行为。

动态模块提供了一个API,用于将一个模块导入到另一个模块中的时候自定义该模块的属性和行为,而不是使用我们到目前为止看到的静态绑定。

指定module的imports属性的参数,不仅可以是module类名,也可以是返回module类的方法
Dynamic Module同样可以 import 别的 module。

// 一般的模块是这样的
import {
         
          Module } from '@nestjs/common';
import {
         
          AppController } from './app.controller';
import {
         
          AppService } from './app.service';
import {
         
          ConfigModule } from './config/config.module';

@Module({
         
         
  imports: [ConfigModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {
         
         }

// 我们推断,配置模块应该这样
// 一般动态模块,按照惯例都有应该叫forRoot()或Register()的静态方法,
// 方法应该接受一个配置对象,返回一个module,实际上是一个Dynamic Module,
// Dynamic Module是一个有module作为必须参数的module哈
import {
         
          Module } from '@nestjs/common';
import {
         
          AppController } from './app.controller';
import {
         
          AppService } from './app.service';
import {
         
          ConfigModule } from './config/config.module';

@Module({
         
         
  imports: [ConfigModule.register({
         
          folder: './config' })],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {
         
         }

// 一个配置模块是这么写的
import {
         
          DynamicModule, Module } from '@nestjs/common';
import {
         
          ConfigService } from './config.service';

@Module({
         
         })
export class ConfigModule {
         
         
  static register(): DynamicModule {
         
         
    return {
         
         
      module: ConfigModule,
      providers: [ConfigService],
      exports: [ConfigService],
    };
  }
}

// 然后我们假设这个动态模块的service是这样的
import {
         
          Injectable } from '@nestjs/common';
import * as dotenv from 'dotenv';
import * as fs from 'fs';
import {
         
          EnvConfig } from './interfaces';

@Injectable()
export class ConfigService {
         
         
  private readonly envConfig: EnvConfig;

  constructor() {
         
         
    const options = {
         
          folder: './config' };

    const filePath = `${
         
         process.env.NODE_ENV || 'development'}.env`;
    const envFile = path.resolve(__dirname, '../../', options.folder, filePath);
    this.envConfig = dotenv.parse(fs.readFileSync(envFile));
  }

  get(key: string): string {
         
         
    return this.envConfig[key];
  }
}

// ConfigModule提供ConfigService。
// ConfigService又依赖于仅在运行时提供的Options对象。
// 因此在运行时,我们需要首先将Options对象绑定到Nest IOC容器,
// 然后让Nest将其注入到ConfigService中

import {
         
          DynamicModule, Module } from '@nestjs/common';
import {
         
          ConfigService } from './config.service';

@Module({
         
         })
export class ConfigModule {
         
         
  static register(options): DynamicModule {
         
         
    return {
         
         
      module: ConfigModule,
      providers: [
        {
         
         
          provide: 'CONFIG_OPTIONS',
          useValue: options,
        },
        ConfigService,
      ],
      exports: [ConfigService],
    };
  }
}

// 我们把传入的options作为一个provider,然后在service中依赖注入
import * as dotenv from 'dotenv';
import * as fs from 'fs';
import {
         
          Injectable, Inject } from '@nestjs/common';
import {
         
          EnvConfig } from './interfaces';

@Injectable()
export class ConfigService {
         
         
  private readonly envConfig: EnvConfig;

  constructor(@Inject('CONFIG_OPTIONS') private options) {
         
         
    const filePath = `${
         
         process.env.NODE_ENV || 'development'}.env`;
    const envFile = path.resolve(__dirname, '../../', options.folder, filePath);
    this.envConfig = dotenv.parse(fs.readFileSync(envFile));
  }

  get(key: string): string {
         
         
    return this.envConfig[key];
  }
}

Nest 中间件

中间件是在路由处理方法之前调用的方法,中间件函数可以访问请求和响应对象,以及使用Next方法。

Nest的中间件默认等价于Express的中间件,中间件有以下功能,执行任意代码、修改请求和响应对象、结束响应、调用Next方法(必须调用,否则请求无法处理)。


// 文件 logger.middleware.ts 声明中间件的3个方式
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
         
         
  // eslint-disable-next-line @typescript-eslint/ban-types
  use(req: Request, res: Response, next: Function) {
         
         
    console.log('Request...');
    next();
  }
}

@Injectable()
export class LoggerMiddleware2 {
         
         
  use(req, res, next) {
         
         
    console.log(req._parsedUrl.pathname)
    console.log(req.query)
    console.log('\n');
    next();
  }
}

export function LoggerMiddleware3(req: Request, res: Response, next: Function) {
         
         
  console.log(`logger3...`);
  next();
};

// 使用 
export class AppModule {
         
         
  // module的configure来使用中间件,此方法可以async
  configure(consumer: MiddlewareConsumer) {
         
         
    consumer
      .apply(LoggerMiddleware2) //  apply可以使用多个中间件 .apply(LoggerMiddleware1, logger2)
      .exclude(
          {
         
          path: 'cats', method: RequestMethod.GET },
          {
         
          path: 'cats', method: RequestMethod.POST },
          'cats/(.*)',
       )
      .forRoutes({
         
          path: 'ca*', method: RequestMethod.ALL });
    // 可使用控制器      forRoutes(CatsController)
    // 简单使用         forRoutes('cats')
    // 正则匹配路由      forRoutes('cat*') forRoutes('cat*')
    // 指定http请求方法  forRoutes({ path: 'cats', method: RequestMethod.GET });
    // exclude排除哪些路由
  }
}

// 全局过滤器
app.use(logger)


Exception filters 异常过滤器

Nest内置了一个异常层处理所有未处理的异常,一个全局的异常过滤器来处理HttpException类型(及其子类)的异常,如果不是就会返回如下json数据


{
         
         
  "statusCode": 500,
  "message": "Internal server error"
}

主动抛出一个 HttpException

import {
         
         HttpException, HttpStatus} from '@nestjs/common';
@Get('/admin')
async gundan(): Promise<Cat[]> {
         
         
    throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}

自定义异常

import {
         
         HttpException, HttpStatus} from '@nestjs/common';
export class ForbiddenException extends HttpException {
         
         
  constructor() {
         
         
    super('Forbidden', HttpStatus.FORBIDDEN);
  }
}

内置很多继承于HttpException的类

NotFoundException、
ForbiddenException、
UnauthorizedException

异常过滤器

完全控制抛出异常之后返回的信息


// 创建一个 HttpExceptionFilter

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

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
          
          
  catch(exception: HttpException, host: ArgumentsHost) {
          
          
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();

    response.status(status).json({
          
          
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}

// 在controller中使用
import {
          
          UseFilters} from '@nestjs/common';
@Controller('cats')
@UseFilters(HttpExceptionFilter)
export class CatsController {
          
          
  ...
}

// 全局使用
 app.useGlobalFilters(new HttpExceptionFilter());

捕获处理所有的异常

不仅可以处理HttpException,把@Catch置空即可


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

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
           
           
  catch(exception: unknown, host: ArgumentsHost) {
           
           
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();

    const status =
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;

    response.status(status).json({
           
           
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
    });
  }
}

只是简单覆盖一下默认的全局过滤器

// 定义方法
import {
           
            Catch, ArgumentsHost } from '@nestjs/common';
import {
           
            BaseExceptionFilter } from '@nestjs/core';

@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
           
           
  catch(exception: unknown, host: ArgumentsHost) {
           
           
    super.catch(exception, host);
  }
}

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

  const {
           
            httpAdapter } = app.get(HttpAdapterHost);
  app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));

  await app.listen(3000);
}
bootstrap();


Pipes 管道

一个pine是有@Injectable()的类,实现了PipeTransform接口。通常有2个用法,数据转换和数据校验。管道在exceptions zone内运行,如果出现一个异常,控制器就不会往下运行。Nest在controller的route handler方法调用之前插入一个管道。

import {
           
            Controller, Get, Param, ParseIntPipe } from '@nestjs/common';

@Controller('demo2')
export class Demo2Controller {
           
           
  @Get('/num/:id')
  get1(@Param('id', ParseIntPipe) id: number): number {
           
           
    return id;
  }
}

// 如果访问了/demo2/num/1 没问题,如果是/demo2/num/a 就是抛异常,默认是下边的
{
           
           
  "statusCode": 400,
  "message": "Validation failed (numeric string is expected)",
  "error": "Bad Request"
}

自定义pipes

必须实现PipeTransform接口,并实现transform方法,然后在controller中直接引入使用

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

@Injectable()
export class ValidationPipe implements PipeTransform {
           
           
  transform(value: any, metadata: ArgumentMetadata) {
           
           
    return typeof value;
  }
}

基于Schema的验证

// ***** 开始是这样的 ****** 
// controller 
@Post()
async create(@Body() createCatDto: CreateCatDto) {
           
           
  this.catsService.create(createCatDto);
}

// create-cat.dto.ts
export class CreateCatDto {
           
           
  name: string;
  age: number;
  breed: string;
}

// ******** 使用 Object schema validation 验证
// 需要下边两个库
$ npm install --save joi
$ npm install --save-dev @types/joi

// 创建一个验证的schema 规则 cat.schema.ts 
import Joi = require('joi');
export default Joi.object({
           
           
  name: Joi.string()
    .alphanum()
    .min(3)
    .max(10)
    .required(),
  age: Joi.number()
    .integer()
    .min(1900)
    .max(2020)
    .required(),
  breed: Joi.string().required()
});

// 新建自定义的管道 JoiValidationPipe.ts 
import {
           
            PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import {
           
            ObjectSchema } from '@hapi/joi';

@Injectable()
export class JoiValidationPipe implements PipeTransform {
           
           
  constructor(private schema: ObjectSchema) {
           
           }

  transform(value: any, metadata: ArgumentMetadata) {
           
           
    const {
           
            error } = this.schema.validate(value);
    if (error) {
           
           
      throw new BadRequestException('Validation failed');
    }
    return value;
  }
}

// 在controller中直接使用 
import {
           
            JoiValidationPipe } from '../pipes/JoiValidationPipe';
import CatSchema from '../schema/cat.schema';

@Controller('cats')
export class CatsController {
           
           
  constructor(private catsService: CatsService) {
           
           }
  @Post('/add')
  @UsePipes(new JoiValidationPipe(CatSchema))
  async create(@Body() createCatDto: CreateCatDto) {
           
           
    console.log(createCatDto);
    this.catsService.create(createCatDto);
  }
}

Class validator,基于TS的类验证

// 安装需要的包
npm i class-validator class-transformer --save 

//修改DTO,增加需要校验的注解 create-cat.dto.ts
import {
           
            IsString, IsInt } from 'class-validator';
export class CreateCatDto {
           
           
  @IsString()
  name: string;

  @IsInt()
  age: number;

  @IsString()
  breed: string;
}

// 新建一个检验pipe
import {
           
            PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
import {
           
            validate } from 'class-validator';
import {
           
            plainToClass } from 'class-transformer';

@Injectable()
export class ValidationPipe implements PipeTransform<any> {
           
           
  // 因为校验可能是异步的
  async transform(value: any, {
           
            metatype }: ArgumentMetadata) {
           
           
    if (!metatype || !this.toValidate(metatype)) {
           
           
      return value;
    }
    const object = plainToClass(metatype, value);
    const errors = await validate(object);
    if (errors.length > 0) {
           
           
      throw new BadRequestException('Validation failed');
    }
    return value;
  }

  private toValidate(metatype: Function): boolean {
           
           
    const types: Function[] = [String, Boolean, Number, Array, Object];
    return !types.includes(metatype);
  }
}







Guard守卫

守卫使用@Injectable(),继承CanActivate接口。守卫就干一件事,决定某个请求要不要处理,在特定的情况下。通常用处是authorization。一般中间件干这事。
守卫在每个中间件之后执行,但在任何拦截器或管道之前执行。
同样是全局、controller、handler3个作用域。

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();
    return this.validateRequest(request);
  }

  validateRequest(request){
           
           
    return true;
  }
}

// 使用全局守卫
app.useGlobalGuards(new RolesGuard());

Interceptors 拦截器

拦截使用@Injectable(),继承NestInterceptor接口
拦截器具有一些受AOP技术启发的有用功能。

  • 方法调用前后增加额外逻辑。
  • transform 方法的返回值
  • transform 方法抛出的异常
  • 继承简单的方法行为
  • 根据特定条件完全覆盖函数(例如,用于缓存目的)
// LoggingInterceptor.ts 
import {
           
            Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import {
           
            Observable } from 'rxjs';
import {
           
            tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
           
           


  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
           
           
    console.log('Before...');

    const now = Date.now();
    return next
      .handle() // returns an RxJS Observable 
      .pipe(
        tap(() => console.log(`After... ${
           
           Date.now() - now}ms`)),
      );
  }
}

// controller使用
@UseInterceptors(new LoggingInterceptor())
export class CatsController {
           
           }

// 全局使用
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());

Custom route decorator

ES2016修饰符是一个返回函数的表达式,可以将目标、名称和属性描述符作为参数。您可以通过在装饰符前面加上@字符,并将其放在您想要装饰的最顶端来应用它。可以为类、方法或属性定义修饰符。

session认证和jwt认证

session认证

1.客户端向服务器请求
2.服务器创建一个session,并把sessionid的cookie给客户端
3.客户端每次请求的cookie都带有这个,服务器认证,完成请求。

jwt认证 json web token

1.客户端向服务器请求
2.服务器给客户端一个token,客户端存起来
3.每次客户端请求,都把这个token放到请求头里,完成认证。

相关资料

猜你喜欢

转载自blog.csdn.net/qq_29334605/article/details/109670133