nodejs企业级开发框架nest学习总结 - 3.NestJS入门guard、interceptor、customDecorator、循环引用、注入作用域、生命钩子等

NestJS入门guard、interceptor、customDecorator、循环引用、注入作用域、生命钩子等

官方API地址(点击跳转)
点击跳转到个人的博客(前面两节)

1.guard-守卫(每个中间件之后执行,但在任何拦截器或管道之前执行。)

1.1简单例子,守卫需要实现CanActivate接口
// guard/auth.guard.ts
@Injectable() // 守卫必须要Injectable装饰器修饰
export class AuthGuard implements CanActivate {
    public canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
        /**
         * ExecutionContext : 继承ArgumentsHost接口,拥有着和过滤器一样的属性,并且多以下两个属性
         * getClass()返回一个(控制器)类型(不是实例)。
         * getHandler()将返回对服务的方法的引用
         */
        const request: Request = context.switchToHttp().getRequest();
        return true; //当返回false的时候,守卫不通过,直接抛出异常
    }
}
1.2守卫的使用,使用UseGuards装饰器,可以是控制器作用域,方法作用域或全局作用域。
控制器作用域
import { Controller, UseGuards } from '@nestjs/common';
import { AuthGuard } from './guard/auth.guard'; // 使用守卫
@Controller('/app')
@UseGuards(new AuthGuard()) 
export class AppController { }
类的方法作用域
import { Controller, UseGuards, Get } from '@nestjs/common';
import { AuthGuard } from './guard/auth.guard'; // 使用守卫
@Controller('/app')
export class AppController {
	@UseGuards(new AuthGuard()) 
	@Get()
	public getHello(): string {
		return 'hello';
	}
}
全局作用域
import { NestFactory } from '@nestjs/core';
import { AuthGuard } from './guard/auth.guard'; // 使用守卫
function async bootstrap(){
	const app = await NestFactory.create(AppModule);
	app.useGlobalGuards(new AuthGuard()); 
	await app.listen(3000);
}
bootstrap();
模块作用域
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from './guard/auth.guard'; // 使用守卫
@Module({
	providers:[
	 	{
           provide: APP_GUARD, // APP_GUARD是一个常量,也可以自定义
           useClass: AuthGuard,
        },
	]
})
export class ChildModule{ }
1.3(假设)权限管理,使用守卫
// guard/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
/**
 * 我们希望根据分配给当前用户的角色与当前正在处理的路径所需的实际角色进行比较,使返回值成为条件。
 * * 1.为了访问路径的角色(自定义元数据),我们将使用Reflector辅助类,它由框架提供并从@nestjs/core包中公开。
 * * 2.在node.js世界中,通常的做法是将授权用户附加到request对象。因此,在上面的示例代码中,我们假设request.user包含用户实例和允许的角色。
 * * 3.在您的应用程序中,您可能会在自定义身份验证防护(或中间件)中建立该关联。
 * 当权限不足的用户请求端点时,Nest会自动返回以下响应:
 * {
 *  "statusCode": 403,
 *  "message": "Forbidden resource"
 * }
 */
@Injectable()
export class RolesGuard implements CanActivate {
    public constructor(private readonly reflector: Reflector) { } // 注入Reflector到整个RolesGuard类中
    public canActivate(context: ExecutionContext): boolean {
        // 使用reflector的get方法,获取角色列表
        // const roles = this.reflector.get('roles', context.getHandler());
        // 我们可以通过提取控制器元数据并使用它来确定当前用户角色来使这个防护更通用。
        // 要提取控制器元数据,我们通过context.getClass()而不是context.getHandler()
        const roles = this.reflector.get<string[]>('roles', context.getClass());
        // 也可以从数据库中获取角色列表,然后判断
        if (!roles) {//当不存在角色列表的情况下,直接通过
            return true;
        }
        const request: Request = context.switchToHttp().getRequest();
        const user = (request as any).user; // 获取请求中的安全认证获取的用户信息 (比如jwt等,此处模拟)
        const hasRole = () => user.roles.some((role) => roles.includes(role)); // 判断用户的角色是否包含和roles相同的角色列表,并返回一个布尔类型
        return user && user.roles && hasRole(); // 当user存在 并且 有roles字段 然后判断roles是否包含在roles角色列表中
    }
}
使用方式和1.2一致,只是上面的角色列表纯属模拟,假的数据

2.interceptor过滤器

2.1拦截器的运用
  • 在方法执行之前/之后绑定额外的逻辑
  • 转换函数返回的结果
  • 转换从函数抛出的异常
  • 扩展基本功能行为
  • 根据特定条件完全覆盖函数(例如,用于缓存目的)
  • 拦截器实现NestInterceptor接口后,重写的intercept方法的参数如下:
1.第一个参数和guard的参数一致,ExecutionContext类型
2.第二个参数是调用句柄
	2.1该CallHandler接口实现的handle()方法,你可以用它在你的拦截某些时候调用路由处理方法。
		  如果在handle()方法实现中没有调用intercept()方法,则根本不会执行路由处理程序方法。
2.2日志打印拦截器
// interceptor/logging.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
/**
 * Aspect拦截 使用拦截器来记录用户交互(例如,存储用户调用,异步调度事件或计算时间戳)。
 */
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
    /*
    intercept()方法有效地包装请求/响应流。
    */
    public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
        console.log('Before...');//开始执行到这个过滤器

        const now = Date.now(); // 获取路由执行前的时间
        return next
            .handle() // 由于handle()返回RxJS Observable,我们可以使用多种运算符来操作流。在上面的例子中,我们使用了tap()运算符,它在可观察流的正常或异常终止时调用我们的匿名日志记录函数,但不会干扰响应周期。
            .pipe(
                tap(() => console.log(`After... ${Date.now() - now}ms`)),
            );
    }
}
这NestInterceptor是一个通用接口,其中T指示Observable(支持响应流)R的类型,并且是包含的值的类型Observable。
拦截器在为整个应用程序中出现的需求创建可重用解决方案方面具有重要价值。
  • 例如,假设我们需要将每次出现的null值转换为空字符串’’。
  • 我们可以使用一行代码并全局绑定拦截器,以便每个已注册的处理程序自动使用它。
// interceptor/excluedNull.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class ExcludeNullInterceptor implements NestInterceptor {
 public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
   return next
     .handle()
     .pipe(map(value => value === null ? '' : value ));
 }
}
2.3请求超时拦截器

您想要处理路线请求的超时。当您的端点在一段时间后没有返回任何内容时,您希望以错误响应终止。以下结构可实现此目的

// interceptor/timeout.interceptor.ts
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { timeout } from 'rxjs/operators';
/**
* 您想要处理路线请求的超时。当您的端点在一段时间后没有返回任何内容时,您希望以错误响应终止。以下结构可实现此目的
*/
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
   public intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
       return next.handle().pipe(timeout(5000));
   }
}
2.4 拦截器的注册,和上面的guard差不多,使用UseInterceptors装饰器,可以是控制器作用域,方法作用域或全局作用域以及模块作用域。
控制器作用域
import { Controller, UseInterceptors } from '@nestjs/common';
import { TimeoutInterceptor } from './interceptor/timeout.interceptor'; // 使用超时拦截器
@Controller('/app')
@UseInterceptors(new TimeoutInterceptor()) 
export class AppController { }
类的方法作用域
import { Controller, UseInterceptors } from '@nestjs/common';
import { TimeoutInterceptor } from './interceptor/timeout.interceptor'; // 使用超时拦截器
@Controller('/app')
export class AppController {
	@UseInterceptors(new TimeoutInterceptor()) 
	@Get()
	public getHello(): string {
		return 'hello';
	}
}
全局作用域
import { NestFactory } from '@nestjs/core';
import { TimeoutInterceptor } from './interceptor/timeout.interceptor'; // 使用超时拦截器
function async bootstrap(){
	const app = await NestFactory.create(AppModule);
	app.useGlobalInterceptors(new TimeoutInterceptor()); 
	await app.listen(3000);
}
bootstrap();
模块作用域
import { Module } from '@nestjs/common';
import { APP_INTERCEPTOR } from '@nestjs/core';
import { TimeoutInterceptor } from './interceptor/timeout.interceptor'; // 使用超时拦截器
@Module({
	providers:[
	 	{
           provide: APP_INTERCEPTOR, // APP_INTERCEPTOR是一个常量,也可以自定义
           useClass: TimeoutInterceptor,
        },
	]
})
export class ChildModule{ }

3.自定义参数装饰器

3.1创建装饰器
原本我们是直接从request中获取到安全认证后的user,但是为了代码的可读性和透明性,我们可以创建一个装饰器并在所需要的路由中重复使用它

req.user:为一个jwt安全认证之后存储在request的数据,如果没有,可以模拟

// decorator/user.decorator.ts
import { createParamDecorator } from '@nestjs/common'; // 引入创建参数装饰器
/**
 * 原本我们是直接从request中获取到安全认证后的user,但是为了代码的可读性和透明性,我们可以创建一个装饰器并在所需要的路由中重复使用它
 * data : 即传入的一个参数,正常模仿Body、Param等装饰器来说,data是传递字符串
 */
export const User = createParamDecorator((data: string, req: any) => {
	/* 
	req.user = {
		id: 'ca333ff2e2b2cea33',
		username: 'name',
		password: '123456'
	}
	*/
    const value = data ? req.user[data] : req.user; // 当装饰器传递字符串参数的时候,我们可以获取到user中的一个属性,没有传递则直接返回整个user
    return value;
});
3.2使用装饰器
//无参
interface UserType {
    readonly id: string;
    readonly username: string;
    readonly password: string;
}
@Get() // 不带参数的情况下
async findOne(@User() user: UserType) {
    console.log(`你这个用户的id为 ${id}`);
}
//带参
@Get() // 带参的时候
async findOne(@User('id') id: string) {
    console.log(`你这个用户的id为 ${id}`);
}

4.循环引用的问题解决

4.1服务的循环引用
forwardRef: 传入一个回调函数,返回一个服务类/模块
import { Injectable, Inject, forwardRef } from '@nestjs/common';
// CommonService依赖注入了CatsService,而CatsService也依赖注入了CommonService
@Injectable()
export class CatsService {
    public constructor(
        @Inject(forwardRef(() => CommonService))
        private readonly commonService: CommonService,
    ) { }
}
@Injectable()
export class CommonService {
    public constructor(
        @Inject(forwardRef(() => CatsService))
        private readonly catsService: CatsService,
    ) { }
}
4.2模块的循环引用
@Module({
   imports: [forwardRef(() => CommonModule)],
})
export class CatsModule {}

@Module({
   imports: [forwardRef(() => CatsModule)],
})
export class CommonModule {}
4.3解决循环引用的其它方式:Nest提供了ModuleRef可以简单地注入任何组件的类。
import { Injectable, OnModuleInit } from '@nestjs/common';
import { CatsService } from './cats.service';
import { ModuleRef } from '@nestjs/core';
/**
 * 3.Nest提供了ModuleRef可以简单地注入任何组件的类。也是解决循环依赖的方式
 */
@Injectable()
export class ModuleService implements OnModuleInit {
    private service: CatsService;
    public constructor(private readonly moduleRef: ModuleRef) { }
    
    public onModuleInit() {
        // ModuleRef有一个get()方法,允许检索当前模块中可用的提供程序。此外,您可以切换到非严格模式,这样您就可以在整个应用程序中选择任何现有的提供程序。
        this.service = this.moduleRef.get(CatsService, { strict: false });
    }
}
onModuleInit为实现OnModuleInit接口所重写的方法,也是一个生命钩子函数

5.injectable范围

基本上,每个提供程序都可以充当单例,请求范围,并切换到瞬态模式。请参阅下表以熟悉它们之间的差异。
  • SINGLETON 每个提供程序可以跨多个类共享。提供者生命周期严格依赖于应用程序生命周期。应用程序引导后,所有提供程序都已实例化。默认情况下使用单例范围。
  • REQUEST 将在请求处理完成后为每个传入请求和垃圾专门创建提供程序的新实例。
  • TRANSIENT 瞬态提供者不能在提供者之间共享。每次当另一个提供程序向Nest容器请求特定的瞬态提供程序时,容器将创建一个新的专用实例。
import { Injectable, Scope, Inject } from '@nestjs/common';
import { REQUEST } from '@nestjs/core';
import { Request } from 'express';
@Injectable({ scope: Scope.REQUEST })
export class CatsService {
    constructor(@Inject(REQUEST) private readonly request: Request) { }
}
对于自定义提供程序,您必须设置一个额外的scope属性:
{
  provide: 'CACHE_MANAGER',
  useClass: CacheManager,
  scope: Scope.TRANSIENT, // 加上这个属性
}
对于控制器来说,就是设置成这样的属性
@Controller({
  path: 'cats',
  scope: Scope.REQUEST,
})
export class CatsController {}
scope作用域可以设置在服务类,自定义提供程序,控制器上,看情况而定

6.生命周期事件

每个应用程序元素都有一个由Nest管理的生命周期。Nest提供了生命周期钩子,可以提供对关键生命时刻的可见性以及在它们发生时采取行动的能力。
通过调用其构造函数创建注入/控制器后,(Nest在特定时刻按以下顺序调用生命周期钩子方法):
  • OnModuleInit 主机模块初始化后调用
  • OnApplicationBootstrap 应用程序完全启动并进行自举后调用
  • OnModuleDestroy 就在Nest破坏主机模块之前进行清理(app.close()方法已被评估)
  • OnApplicationShutdown 响应系统信号(当应用程序被关闭时SIGTERM)

用法:每个生命周期钩子都由接口表示。接口在技术上是可选的,因为它们在TypeScript编译后无论如何都不存在。

例如:OnModuleInit,在初始化成功之后调用
import { Injectable, OnModuleInit } from '@nestjs/common';
@Injectable()
export class UsersService implements OnModuleInit {
 onModuleInit() {
   console.log(`The module has been initialized.`);
 }
}
调用之后打印了一句话 ‘The module has been initialized.’ 当然需要把UsersService注册到模块的providers中

其它三个钩子函数也是一样,只需要实现对应的生命周期接口,重写函数即可
但是使用OnModuleDestroy挂钩,需要在main.ts的app下面加入app.enableShutdownHooks();才能使用

下节学习Authentication认证,TypeORM数据库连接
nodejs企业级开发框架nest学习总结 - 1.NestJS入门controller、DTO、providers、module
nodejs企业级开发框架nest学习总结 - 2.NestJS入门middleware、exceptionFilter、Pipe。

本博客主地址(单击跳转)

你可能感兴趣的:(Nest,TypeScript,Nodejs)