模块导出与引用 #
nest模块之间的相互导出和引用有两种方式 第一种是利用module的exports进行导出,imports进行导入
// 导出
@Module({
controllers: [AaaController],
providers: [AaaService],
exports: [AaaService],
})
export class AaaModule {}
// 引入
@Module({
imports: [AaaModule],
controllers: [BbbController],
providers: [BbbService],
})
export class BbbModule {}
// 使用
export class BbbService {
constructor(private aaaService: AaaService) {}
create(createBbbDto: CreateBbbDto) {
return 'This action adds a new bbb';
}
findAll() {
return `This action returns all bbb` + this.aaaService.findAll();
}
}第二种是全局模块,能够让module的导出在全局生效
@Global() // 添加Global装饰器
@Module({
controllers: [AaaController],
providers: [AaaService],
exports: [],
})
export class AaaModule {}模块与应用生命周期 #
nest的模块、控制器、服务也是有生命周期的,在不同生命周期中,可以处理不同的业务,例如在销毁阶段可以断开数据库链接
生命周期分为启动和销毁,启动时会依次调用onModuleInit,onApplicationBootstrap,销毁时会依次调用onModuleDestroy,beforeApplicationShutdown,onApplicationShutdowm。调用的次序是,先执行provider和controller中的生命周期函数,在执行module中的生命周期函数。
@Controller('ccc')
export class CccController
implements
OnModuleInit,
OnApplicationBootstrap,
OnModuleDestroy,
BeforeApplicationShutdown,
OnApplicationShutdown
{
constructor(private readonly cccService: CccService) {}
onModuleDestroy() {
console.log('CccController OnModuleDestroy');
}
beforeApplicationShutdown(signal?: string) {
console.log(`CccController BeforeApplicationShutdown: ${signal}`);
}
onApplicationShutdown(signal?: string) {
console.log(`CccController OnApplicationShutdown: ${signal}`);
}
onModuleInit() {
console.log('CccController OnModuleInit');
}
onApplicationBootstrap() {
console.log('CccController OnApplicationBootstrap');
}
}AOP 概念与在 Nest 中的实现 #
aop表示面向切面编程,大概的意思就是,在执行流程的前后,插入一段通用的代码逻辑,像是切了一刀一样。这种做法不会直接入侵业务代码,而是通过透明的方式,保持业务代码的纯粹性。例如权限校验,参数校验等。
nest中有五种实现aop的方式,Middleware、Guard、Pipe、Interceptor、ExceptionFilter。
Middleware 中间件 #
中间件分为全局中间件和路由中间件
全局中间件就是在main.ts里面增加执行handler的前后逻辑
import { LoginGuard } from './login.guard';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(function (req: Request, res: Response, next: NextFunction) {
console.log('before', req.url);
next(); // 表示执行对应的handler,具体来说就是路由对应的方法
console.log('after');
});
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();路由中间件的话是写在module里面,可以更具体的指定生效的路由
// 中间件
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class LogMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
console.log('before2', req.url);
next();
console.log('after2');
}
}
// 使用
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LogMiddleware).forRoutes('aaa'); //对路由/aaa才会有LogMiddleware
}
}Guard 路由守卫 #
Guard就是路由守卫的意思,主要运用于调用某个controller之前鉴权
// 声明
import {
CanActivate,
ExecutionContext,
Inject,
Injectable,
} from '@nestjs/common';
import { Observable } from 'rxjs';
import { AppService } from './app.service';
@Injectable()
export class LoginGuard implements CanActivate {
// 路由守卫也能注入一些service调用一些方法
@Inject(AppService)
private appService: AppService;
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
console.log('login check', this.appService.getHello());
return false;
}
}
// 启用
{
provide: APP_GUARD,
useClass: LoginGuard,
},
// 使用
@Get('aaa')
@UseGuards(LoginGuard)
aaa(): string {
console.log('aaa...');
return 'aaa';
}
// 全局启用
app.useGlobalGuards(new LoginGuard());对于全局启用方式和写在provide里面的方式的不同点在于,全局启用相当于是手动创建的对象,不在ioc容器里,而provider声明的guard在ioc容器里面,能注入其他provider
Interceptor 拦截器 #
Interceptor是拦截器的意思,可以对请求和响应做一定的处理,比如计算执行时间
// 声明
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Observable, tap } from 'rxjs';
@Injectable()
export class TimeInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
// context中能取出执行的controller和对应的handler
// context.getClass()
// context.getHandler()
const startTime = Date.now();
return next.handle().pipe(
tap(() => {
console.log('time: ', Date.now() - startTime);
}),
);
}
}
// 使用
@Get('bbb')
@UseInterceptors(TimeInterceptor)
bbb(): string {
console.log('bbb...');
return 'bbb';
}
// 全局启用
app.useGlobalInterceptors(new TimeInterceptor());
// module启用
{
provide: APP_INTERCEPTOR,
useClass: TimeInterceptor,
}Pipe 管道 #
Pipe是管道的意思,主要是对参数做校验和数据转换
// 声明
import {
ArgumentMetadata,
BadRequestException,
Injectable,
PipeTransform,
} from '@nestjs/common';
@Injectable()
export class ValidatePipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata) {
if (Number.isNaN(parseInt(value))) {
throw new BadRequestException(`参数${metadata.data}只能是字符串或者数字`);
}
return typeof value === 'number' ? value * 10 : parseInt(value) * 10;
}
}
// 使用
@Get('ccc')
ccc(@Query('num', ValidatePipe) num: number) {
return num + 1;
}
// 全局启用
app.useGlobalPipes(new ValidatePipe());ExceptionFilter 异常过滤器 #
ExceptionFilter可以对抛出的异常做处理,返回对应的响应
// 声明
import { ArgumentsHost, BadRequestException, Catch, ExceptionFilter } from '@nestjs/common';
import { Response } from 'express';
@Catch(BadRequestException)
export class TestFilter implements ExceptionFilter {
catch(exception: BadRequestException, host: ArgumentsHost) {
const response: Response = host.switchToHttp().getResponse();
response.status(400).json({
statusCode: 400,
message: 'test: ' + exception.message
})
}
}
// 使用
@Get('ccc')
@UseFilters(TestFilter)
ccc(@Query('num', ValidatePipe) num: number) {
return num + 1;
}总结一下就是这几个aop除了middleware都能够单独建立文件,实现上来说都是继承了nest自己的类,实现了具体的方法,达到自定义的效果,启用的方式分为单路由,整个controller和全局。
aop的调用顺序为 middleware -> guard -> intercept -> pipe -> handler -> intercept -> exceptionfilters