NestJS 核心概念
主要讲下 NestJS 中 IOC 和 五种 AOP 的相关知识点,即 Middleware, Guard, Pipe, Interceptor, ExceptionFilter
的使用和概念
Provider
可以先创建下项目
nest new xxx
providers 是可以注入的对象,我们可以把带有 @Injectable()
的 class 放到 Module 的 providers 里声明,因为 Nest 实现了 IOC,这样就会被它给识别到,从而实现依赖注入。
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
当然这是一种简写,原本是这样:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: AppService,
useClass: AppService,
},
],
})
export class AppModule {}
这样就实现了依赖注入,我们就可以通过 @Inject()
在其他地方使用了,比如在 Controller 里使用
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(@Inject(AppService) private readonly appService: AppService) {}
//或者直接写,不用构造器
// @Inject(AppService) private readonly appService: AppService
@Get()
getHello(): string {
return this.appService.getHello();
}
}
但因为我们的 provide 和 useClass 是相同的,所以可以省略,所以可以简写成如下:
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
我们可以实验一下,把 provide 的值改为一个字符串
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: 'suemor',
useClass: AppService,
},
],
})
export class AppModule {}
这样就没办法省略了,就得老老实实写 @Inject()
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(@Inject('suemor') private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}
除了在 providers 里指定 class,我们也可以指定值或动态的对象,分别对应 useValue
和 useFactory
,如下为 useFactory
写法
import { Inject, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: 'suemor',
useClass: AppService,
},
{
provide: 'suemor2',
useFactory(appService: AppService) {
return {
age: 19,
gender: 'male',
say: appService.getHello(),
};
},
inject: ['suemor'],
},
],
})
export class AppModule {}
修改 controller
import { Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(
@Inject('suemor') private readonly appService: AppService,
@Inject('suemor2')
private readonly user: { age: number; gender: string; say: string },
) {}
@Get()
getHello() {
return this.user;
}
}
五种 AOP
因为 NestJS 使用的 MVC 架构,所以有 AOP 的能力,其中 Nest 的实现主要包括如下五种(按执行顺序排列)
- Middleware
- Guard
- Interceptor
- Pipe
- ExceptionFilte
Middleware
即中间件,但这并不是 Nest 独有的,你用 Express 或者 Fastify 当请求的库他们本身也都拥有 middleware,概念基本相同,我们一般会用它处理些通用逻辑(如日志)。
nest g middleware logger --no-spec --flat
应该会生成如下代码,这里的 req 和 res 都是 any 是因为 Nest 并不知道你用的是 Express 还是 Fastify 当请求库,所以类型得自行补全。
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
next();
}
}
修改类型,并添加下 console.log,然后我们得去注册这个 middleware
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: () => void) {
console.log('start');
next();
}
}
去 app.module.ts
里全局注册
import { MiddlewareConsumer, Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { NestModule } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
@Module({
imports: [],
controllers: [AppController],
providers: [
{
provide: 'suemor',
useClass: AppService,
},
{
provide: 'suemor2',
useFactory(appService: AppService) {
return {
age: 19,
gender: 'male',
say: appService.getHello(),
};
},
inject: ['suemor'],
},
],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes('*');
}
}
然后去浏览器访问,我们可以看到控制台正确输出了
Guard
即路由守卫的意思,一般在 Controller 之前鉴权,返回 true 和 false
nest g guard roles --no-spec --flat
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}
修改 Controller,UseGuards 和 SetMetadata。
import {
Controller,
Get,
Inject,
SetMetadata,
UseGuards,
} from '@nestjs/common';
import { AppService } from './app.service';
import { RolesGuard } from './roles.guard';
@Controller()
export class AppController {
constructor(
@Inject('suemor') private readonly appService: AppService,
@Inject('suemor2')
private readonly user: { age: number; gender: string; say: string },
) {}
@Get()
@UseGuards(RolesGuard)
@SetMetadata('roles', ['admin'])
getHello() {
return this.appService.getHello();
}
}
修改 RolesGuard,这里我们会引 Reflector 的 Metadata 概念,这目前还没有被 ES 标准化,还处于草案阶段,Nest 应该是使用 reflect-metadata 这个 polyfill 包,这可以说是 Nest 的核心了,它的 IOC 基本都是靠这个实现的,这里大意就是从 ExecutationContext 取到 handle,然后注入 reflector,通过 reflector.get 取出 handler 上的 metadata,这样就可以获取到当前路由的权限了。
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
console.log(this.reflector.get<string[]>('roles', context.getHandler())); // [ 'admin' ]
return true;
}
}
我们现在可以获得当前路由的权限了,那接下来就要用这个与请求的权限进行比较了,关于解析请求的权限要用到 @nestjs/passport
库,就是通过 token 获取到当前的 user,但这不是本文重点,我们假设已经在 req 赋上 user 字段了,就可以通过 context.switchToHttp().getRequest().user
获取到当前的请求的 user 了,然后进行 if 判断下就可以了。
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const routerRole = this.reflector.get<string[]>(
'roles',
context.getHandler(),
);
const ReqUser = context.switchToHttp().getRequest().user;
console.log(routerRole, ReqUser);
if (routerRole[0] == ReqUser.role) {
return true;
}
return false;
}
}
Interceptor
即拦截器,它可以在 Controller 方法前后加入一些逻辑,其实和 Middleware 挺像的,也可以用来记录日志等,不过它也可以在 Controller 之后进行拦截,比如统一返回结果之类的。
nest g interceptor logging --flat --no-spec
它还有些优势就是可以使用 rxjs 的各种 operator,且也可以使用 reflector 和 ExecutionContext。
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Observable, tap } from 'rxjs';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
console.log('before');
return next.handle().pipe(tap(() => console.log('after')));
}
}
然后去 app 注册
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { LoggingInterceptor } from './logging.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new LoggingInterceptor());
await app.listen(3000);
}
bootstrap();
Pipe
即管道,可以对参数进行校验,比如我们说 age 不能是字符串,就可以这样写。
import { Controller, Get, Inject, ParseIntPipe, Query } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(@Inject('suemor') private readonly appService: AppService) {}
@Get()
getHello2(@Query('age', ParseIntPipe) age: number) {
return age;
}
}
但实际业务我们一般会用 class-validator
这个库,它就是利用的 Pipe。
ExceptionFilter
即异常过滤器,它是很强大的,除了 middleware 以外,只要出现异常,都可以被它给捕获到。
import { ArgumentsHost, ExceptionFilter } from '@nestjs/common';
import { Catch, HttpException } from '@nestjs/common';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const request = ctx.getRequest();
const status = exception.getStatus();
response.status(status).json({
statusCode: status,
timestamp: new Date().toISOString(),
path: request.url,
});
}
}
去 app 注册
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './any-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(3000);
}
bootstrap();
我们 throw 个异常
import { BadRequestException, Controller, Get, Inject } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(@Inject('suemor') private readonly appService: AppService) {}
@Get()
getHello2() {
throw new BadRequestException();
return this.appService.getHello();
}
}
被正确捕获