跳转到主内容
Avatar
GrapeWell

nest学习小记(2)

2025-09-08

模块导出与引用 #

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的模块、控制器、服务也是有生命周期的,在不同生命周期中,可以处理不同的业务,例如在销毁阶段可以断开数据库链接

生命周期分为启动和销毁,启动时会依次调用onModuleInitonApplicationBootstrap,销毁时会依次调用onModuleDestroybeforeApplicationShutdownonApplicationShutdowm。调用的次序是,先执行providercontroller中的生命周期函数,在执行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的方式,MiddlewareGuardPipeInterceptorExceptionFilter

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