Skip to content

Nest.js 通关秘籍

Nest.js CLI 脚手架

  • npx @nestjs/cli new 项目名 创建项目

可以安装到全局,然后执行

sh
npm install -g @nestjs/cli

nest new 项目名
  • nest new 创建项目
  • nest generate 生成Controller、Service、Module等模块代码的
  • nest build 编译项目
  • nest start 启动项目
  • nest info 查看项目信息

五种 HTTP 传输方式

  1. Url Param:直接将传输的参数放在URL中,如/user/1,在Controller中通过@Param('id') id获取
ts
@Controller('api/person')
export class PersonController {
  @Get(':id')
  urlParam(@Param('id') id: string) {
    return `received: id=${id}`;
  }
}
  1. Query:将传输的参数放在URL中,如/user?id=1,在Controller中通过@Query('id') id获取
ts
@Controller('api/person')
export class PersonController {
  @Get('find')
  query(@Param('id') id: string) {
    return `received: id=${id}`;
  }
}
  1. form-urlencoded:form表单传递数据就是这种,将传输的参数放在请求体中,指定Content-Type为application/x-www-form-urlencoded
ts
// form urlencoded 是通过 body 传输数据,其实是把 query 字符串放在了 body 里,所以需要做 url encode

import { CreatePersonDto } from './dto/create-person.dto';

export class CreatePersonDto {
  name: string;
  age: number;
}

@Controller('api/person')
export class PersonController {
  @Post()
  body(@Body() createPersonDto: CreatePersonDto) {
    return `received: ${JSON.stringify(createPersonDto)}`;
  }
}
html
<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://unpkg.com/axios@0.24.0/dist/axios.min.js"></script>
    <script src="https://unpkg.com/qs@6.10.2/dist/qs.js"></script>
  </head>
  <body>
    <script>
      async function formUrlEncoded() {
        const res = await axios.post(
          '/api/person',
          Qs.stringify({
            name: '光',
            age: 20,
          }),
          {
            headers: { 'content-type': 'application/x-www-form-urlencoded' },
          },
        );
        console.log(res);
      }

      formUrlEncode d();
    </script>
  </body>
</html>
  1. form-data:将传输的参数放在请求体中,指定Content-Type为multipart/form-data
  2. JSON:将传输的参数放在请求体中,指定Content-Type为application/json

什么是IOC,Nest.js 的IOC机制是什么样的

IOC(Inversion of Control,控制反转)是一种设计原则,用于降低计算机代码之间的耦合度。在软件工程中,它通过将对象的创建和生命周期管理交给一个专门的容器来实现,从而使得对象的创建和使用分离,提高了代码的模块化和可维护性

Nest.js 的 IOC 机制是基于依赖注入(Dependency Injection,DI)实现的。依赖注入是一种实现控制反转的设计模式,它将对象的依赖关系作为参数传递给对象,而不是在对象内部直接创建依赖对象。这样,对象的创建和依赖关系管理就从对象本身转移到了外部容器,即 IOC 容器

在 Nest.js 中,IOC 机制的工作流程大致如下:

  1. 声明服务:使用 @Injectable() 装饰器来声明一个类,表示该类可以由 Nest 的 IOC 容器管理
ts
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello NestJS';
  }
}
  1. 模块配置:在模块装饰器中注册提供者(providers),这些提供者可以是服务、控制器等
ts
import { Module } from '@nestjs/common';
import { AppService } from './app.service';
import { AppController } from './app.controller';

@Module({
  providers: [AppService], // 注册提供者
  controllers: [AppController], // 注册控制器
})
export class AppModule {}
  1. 依赖注入:在需要使用服务的类(如控制器)的构造函数中注入依赖的服务
ts
import { Controller } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  // 通过构造器注入依赖的服务
  constructor(private readonly appService: AppService) {}

  // 也可以通过属性装饰器注入依赖的服务
  @Inject(AppService)
  private readonly appService: AppService;
}

Nest.js 在应用程序启动时会扫描所有模块、控制器和提供者,收集它们的元数据并建立依赖图。这个依赖图是一种有向无环图(DAG),表示类之间的依赖关系。Nest.js 会根据这个图来解析依赖,并按需实例化对象

多种Provider,灵活的依赖注入内容

Nest 实现了 IoC 容器,会从入口模块开始扫描,分析 Module 之间的引用关系,对象之间的依赖关系,自动把 provider 注入到目标对象

  1. 通用写法
ts
// 在 Module 中
@Module({
  providers: [
    AppService
  ]
})
export const AppModule {}


// 在 Controller 中
@Controller()
export class AppController {
  // 1. 通过构造器注入依赖的服务
  constructor(private readonly appService: AppService) {}

  // 2. 通过属性装饰器注入依赖的服务
  @Inject(AppService)
  private readonly appService: AppService;
}
  1. 命名注入
ts
// 第一种简写,完整的写法是这样的:
@Module({
  providers: [
    {
      provide: 'Custom_Service', // 自定义的提供者名称
      useValue: AppService
    }
  ]
})
export const AppModule {}

// 在 Controller 中
@Controller()
export class AppController {
  // 1. 通过构造器注入依赖的服务
  constructor(@Inject('Custom_Service') private readonly appService: AppService) {}

  // 2. 通过属性装饰器注入依赖的服务
  @Inject('Custom_Service')
  private readonly appService: AppService;
}
  1. 注入其他内容

除了可以注入 Class 外,还可以指定一个值,让 IOC 容器注入

ts
@Module({
  providers: [
    {
      provide: "Custom_Value",
      // 可以注入任何内容,包括字符串、数字、对象等
      useValue: {
        name: "NestJS",
        version: "10.0.0"
      }
    }
  ]
})
export const AppModule {}

// 在 Controller 中
interface CustomValue {
  name: string;
  value: string;
}

@Controller()
export class AppController {
  // 1. 通过构造器注入依赖的服务
  constructor(@Inject('Custom_Value') private readonly customValue: CustomValue) {}

  // 2. 通过属性装饰器注入依赖的服务
  @Inject('Custom_Value')
  private readonly customValue: CustomValue;
}
  1. 动态注入内容

可以使用 useFactory 动态注入内容,同时也支持异步

ts
@Module({
  provider: [
    {
      provide: 'Custom_Factory',
      async useFactory() {
        await new Promise((resolve) => {
          setTimeout(resolve, 3000);
        });
        return {
          name: 'NestJS',
          version: '10.0.0',
        };
      },
    },
  ],
})
export class AppModule {}

@Module({
  provider: [
    AppService,
    {
      provide: 'person',
      useValue: {
        name: 'aaa',
        age: 20,
      },
    },
    {
      provide: 'Custom_Factory',
      inject: ['person', AppService],
      // useFactory 还支持通过参数注入别的 provider 供自己使用
      useFactory(person: { name: string }, appService: AppService) {
        return {
          name: person.name,
          desc: appService.getHello(),
        };
      },
    },
  ],
})
export class AppModule {}

总结:灵活运用这些,就可以利用 Nest 的 IOC 容器中注入任何对象

全局模块和生命周期

全局模块

一个模块exports导出 provider,另一个模块需要 imports 它才能用这些 provider。但如果一个模块被很多模块依赖了,那每次都要 imports 就很麻烦。

这样子就适合将这种设置为全局模块,这样就不需要 imports 了

ts
/**
 * @Global() 装饰器 告诉 NestJS 这个模块是全局的
 * 然后其他模块想使用 AaaService 的话就不需要 imports AaaModule 了
 * 可以在模块直接注入 AaaService
 */
@Global()
@Module({
  controllers: [AaaController],
  providers: [AaaService],
  exports: [AaaService],
})
export class AaaModule {}

生命周期

在 Nest 中,模块、控制器、提供者都有生命周期方法,可以在这些方法中做一些初始化或者销毁的操作

启动时

Nest 在启动的时候,会递归解析 Module 依赖,扫描其中的 provider、controller,注入它的依赖。全部解析完后,会监听网络端口,开始处理请求。

这个过程中,Nest 暴露了一些生命周期方法:

首先,递归初始化模块的时候,会依次调用模块内部的 controllerprovideronModuleInit 方法,然后调用模块的 onModuleInit 方法

初始化完成后,又会依次调用它们的 onApplicationBootstrap 方法

然后监听网络端口,之后 Nest 应用就正常运行了。

销毁时

依次调用 onModuleDestroy 方法,然后调用 beforeApplicationShutdown 方法,

然后停止监听网络端口

之后再依次调用 onApplicationShutdown

最后结束进程

总结

  • OnModuleInit() 初始化主模块依赖处理后调用一次
  • OnApplicationBootstrap() 在应用程序完全启动并监听连接后调用一次
  • OnModuleDestroy 收到终止信号(例如SIGTERM)后调用
  • beforeApplicationShutdownonModuleDestroy()完成,一旦完成,将关闭所有连接(调用app.close() 方法)
  • OnApplicationShutdown 连接关闭处理时调用(app.close())

什么是 AOP

MVC 是 Model View Controller 的简写。MVC 架构下,请求会先发送给 Controller,由它调度 Model 层的 Service 来完成业务逻辑,然后返回对应的 View

在这个过程中,Nest 提供了 AOP 的能力,可以在某些方法执行前后,插入一些自定义逻辑,比如日志、权限校验、异常处理等

  • 中间件 Middleware (Nest 底层是 Express,所以使用了中间件)
  • 守卫 Guard(可以在调用某个 Controller 前判断权限,来决定是否放行)
  • 拦截器 Interceptor
  • 管道 Pipe(用来对接口参数做校验和转换)
  • 异常过滤器 ExceptionFilter

总结:

Nest 基于 Express 这种 HTTP 平台做了一层封装,应用了 MVC、IOC、AOP 等架构思想。

  • MVC 就是 Model、View Controller 的划分,请求先经过 Controller,然后调用 Model 层的 Service、Repository 完成业务逻辑,最后返回对应的 View。
  • IOC 是指 Nest 会自动扫描带有 @Controller、@Injectable 装饰器的类,创建它们的对象,并根据依赖关系自动注入它依赖的对象,免去了手动创建和组装对象的麻烦。
  • AOP 则是把通用逻辑抽离出来,通过切面的方式添加到某个地方,可以复用和动态增删切面逻辑。

NestJS 里怎么打印&输出日志

  1. NestJS 提供了 Logger 日志类来打印日志信息
  2. 通过日志框架 Winston 来生成日志文件

NestJS 如何通过 Docker 部署

编写 .dockerignore 文件,忽略不需要打包进镜像的文件

sh
*.md
!README.md
node_modules/
[a-c].txt
.git/
.DS_Store
.vscode/
.dockerignore
.eslintignore
.eslintrc
.prettierrc
.prettierignore

创建 Dockerfile 文件来构建 NestJS 镜像,

项目打包启动后实际只需要一个 dist 和 node_modules 文件夹的内容,其它的文件可以不需要,所以采用多阶段构建

dockerfile
# 构建打包阶段
FROM node:18-alpine3.14 as build-stage

WORKDIR /app

COPY package.json .

RUN npm install

COPY . .

RUN npm run build

# 部署阶段(运行脚本)
FROM node:18-alpine3.14 as production-stage

# 从 build-stage 阶段拷贝构建产物到当前镜像的 /app 下
COPY --from=build-stage /app/dist /app
COPY --from=build-stage /app/package.json /app/package.json

WORKDIR /app

RUN npm install --production

EXPOSE 3000

CMD ["node", "/app/main.js"]

NestJS 通过 PM2 来管理

PM2 是一个进程管理工具,可以用来管理 Node.js 应用程序,提供日志管理,负载均衡、自动重启和性能监控等功能

  • 安装:npm install -g pm2
  • 运行:pm2 start ./dist/main.js

查看日志

pm2 logs 会把通过PM2启动的程序都日志打印出来,通过前面的 "进程ID|进程名称" 来区分

并且,它会把日志写到 ~/.pm2/logs 下,以 进程名-out.log进程名-error.log 的形式保存

也可以通过 pm2 logs 进程名pm2 logs 进程ID 查看单个进程的日志

进程管理

只需要在 pm2 start 的时候后面添加对应的选项就好了

例如:

pm2 start xxx --max-memory-restart 200M 表示当内存占用超过 200M 时,自动重启进程

pm2 start xxx --watch 表示当文件发生变化时,自动重启进程

pm2 start xxx --no-autorestart 表示不自动重启进程

负载均衡

node 应用是单进程的,为了充分利用多核CPU,可以使用多进程来提高性能

node 提供的 cluster 模块就是做这个的,pm2 就是基于这个实现了负载均衡

我们只要启动进程的时候加上 -i num 就是启动 num 个进程做负载均衡的意思

  • pm2 start app.js -i max|num

跑起来之后,还可以动态调整进程数,通过 pm2 scale 命令

  • pm2 scale 进程名 3 表示调整为 3 个进程
  • pm2 scale 进程名 +3 表示添加 3 个进程

性能监控

执行 pm2 monit 命令,可以看到不同进程的 CPU 和内存占用情况

配置文件

执行 pm2 ecosystem 会创建一个配置文件 ecosystem.config.js,可以通过配置文件来管理应用

js
module.exports = {
  apps: [
    {
      name: '进程名',
      script: './dist/main.js',
      instances: 1,
      autorestart: true,
      watch: false,
      max_memory_restart: '1G',
      env: {
        NODE_ENV: 'development',
      },
      env_production: {
        NODE_ENV: 'production',
      },
      // 日志文件配置
      output: '/home/log/console_out.log', // 标准输出日志
      error: '/home/log/console_error.log', // 错误输出日志
      merge_logs: true, // 将所有实例的日志合并到同一个日志文件中
      log_date_format: 'YYYY-MM-DD HH:mm:ss', // 日志日期格式
    },
  ],
};

MySQL 基本操作和概念

一对一、一对多、多对多关系

  • 一对一:一个用户对应一个身份证号,一个身份证号对应一个用户
  • 一对多:一个班级对应多个学生,一个学生对应一个班级
  • 多对多:一个学生可以选多门课程,一门课程可以被多个学生选

外键

外键是数据库中的一种约束,用于建立表与表之间的关系

外键约束可以保证数据的完整性和一致性,例如,如果一个表中的某个字段是另一个表的主键,那么这个字段就是外键

级联方式

外键的级联方式有三种:CASCADE、SET NULL、NO ACTION

  • CASCADE:当父表中的数据发生变化时,子表中的对应数据也会自动更新或删除
  • SET NULL:当父表中的数据发生变化时,子表中的对应数据会设置为 NULL
  • NO ACTION:当父表中的数据发生变化时,子表中的对应数据不会发生变化

连接查询

  • 内连接INNER JOIN:只返回两个表中能关联上的数据
  • 左连接LEFT JOIN:返回左表中的所有数据,以及右表中能关联上的数据
  • 右连接RIGHT JOIN:返回右表中的所有数据,以及左表中能关联上的数据

Compodoc 文档

Compodoc 是一个用于生成 Angular 项目文档的工具。NestAngular 看起来非常相似,所以也支持 Nest 应用程序

当项目复杂之后,模块之间的关系就会变得非常混乱,这时候就需要一个文档来记录各个模块之间的关系和功能

  • pnpm add @compodoc/compodoc -D

然后执行命令,生成文档

  • npx @compodoc/compodoc -p tsconfig.json -s -o

  • -p 是指定 tsconfig.json 配置文件

  • -s 是启动讲台服务器

  • -o 是打开浏览器