Nest.js 通关秘籍
Nest.js CLI 脚手架
npx @nestjs/cli new 项目名
创建项目
可以安装到全局,然后执行
npm install -g @nestjs/cli
nest new 项目名
nest new
创建项目nest generate
生成Controller、Service、Module等模块代码的nest build
编译项目nest start
启动项目nest info
查看项目信息
五种 HTTP 传输方式
- Url Param:直接将传输的参数放在URL中,如
/user/1
,在Controller中通过@Param('id') id
获取
@Controller('api/person')
export class PersonController {
@Get(':id')
urlParam(@Param('id') id: string) {
return `received: id=${id}`;
}
}
- Query:将传输的参数放在URL中,如
/user?id=1
,在Controller中通过@Query('id') id
获取
@Controller('api/person')
export class PersonController {
@Get('find')
query(@Param('id') id: string) {
return `received: id=${id}`;
}
}
- form-urlencoded:form表单传递数据就是这种,将传输的参数放在请求体中,指定Content-Type为
application/x-www-form-urlencoded
// 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)}`;
}
}
<!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>
- form-data:将传输的参数放在请求体中,指定Content-Type为
multipart/form-data
- JSON:将传输的参数放在请求体中,指定Content-Type为
application/json
什么是IOC,Nest.js 的IOC机制是什么样的
IOC(Inversion of Control,控制反转)是一种设计原则,用于降低计算机代码之间的耦合度。在软件工程中,它通过将对象的创建和生命周期管理交给一个专门的容器来实现,从而使得对象的创建和使用分离,提高了代码的模块化和可维护性
Nest.js 的 IOC 机制是基于依赖注入(Dependency Injection,DI)实现的。依赖注入是一种实现控制反转的设计模式,它将对象的依赖关系作为参数传递给对象,而不是在对象内部直接创建依赖对象。这样,对象的创建和依赖关系管理就从对象本身转移到了外部容器,即 IOC 容器
在 Nest.js 中,IOC 机制的工作流程大致如下:
- 声明服务:使用
@Injectable()
装饰器来声明一个类,表示该类可以由 Nest 的 IOC 容器管理
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello NestJS';
}
}
- 模块配置:在模块装饰器中注册提供者(providers),这些提供者可以是服务、控制器等
import { Module } from '@nestjs/common';
import { AppService } from './app.service';
import { AppController } from './app.controller';
@Module({
providers: [AppService], // 注册提供者
controllers: [AppController], // 注册控制器
})
export class AppModule {}
- 依赖注入:在需要使用服务的类(如控制器)的构造函数中注入依赖的服务
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 注入到目标对象
- 通用写法
// 在 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;
}
- 命名注入
// 第一种简写,完整的写法是这样的:
@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;
}
- 注入其他内容
除了可以注入 Class 外,还可以指定一个值,让 IOC 容器注入
@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;
}
- 动态注入内容
可以使用 useFactory
动态注入内容,同时也支持异步
@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 了
/**
* @Global() 装饰器 告诉 NestJS 这个模块是全局的
* 然后其他模块想使用 AaaService 的话就不需要 imports AaaModule 了
* 可以在模块直接注入 AaaService
*/
@Global()
@Module({
controllers: [AaaController],
providers: [AaaService],
exports: [AaaService],
})
export class AaaModule {}
生命周期
在 Nest 中,模块、控制器、提供者都有生命周期方法,可以在这些方法中做一些初始化或者销毁的操作
启动时
Nest 在启动的时候,会递归解析 Module 依赖,扫描其中的 provider、controller,注入它的依赖。全部解析完后,会监听网络端口,开始处理请求。
这个过程中,Nest 暴露了一些生命周期方法:
首先,递归初始化模块的时候,会依次调用模块内部的 controller
、 provider
的 onModuleInit
方法,然后调用模块的 onModuleInit
方法
初始化完成后,又会依次调用它们的 onApplicationBootstrap
方法
然后监听网络端口,之后 Nest 应用就正常运行了。
销毁时
依次调用 onModuleDestroy
方法,然后调用 beforeApplicationShutdown
方法,
然后停止监听网络端口
之后再依次调用 onApplicationShutdown
最后结束进程
总结
OnModuleInit()
初始化主模块依赖处理后调用一次OnApplicationBootstrap()
在应用程序完全启动并监听连接后调用一次OnModuleDestroy
收到终止信号(例如SIGTERM)后调用beforeApplicationShutdown
在onModuleDestroy()
完成,一旦完成,将关闭所有连接(调用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 里怎么打印&输出日志
- NestJS 提供了
Logger
日志类来打印日志信息 - 通过日志框架
Winston
来生成日志文件
NestJS 如何通过 Docker 部署
编写 .dockerignore
文件,忽略不需要打包进镜像的文件
*.md
!README.md
node_modules/
[a-c].txt
.git/
.DS_Store
.vscode/
.dockerignore
.eslintignore
.eslintrc
.prettierrc
.prettierignore
创建 Dockerfile 文件来构建 NestJS 镜像,
项目打包启动后实际只需要一个 dist 和 node_modules 文件夹的内容,其它的文件可以不需要,所以采用多阶段构建
# 构建打包阶段
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
,可以通过配置文件来管理应用
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 项目文档的工具。Nest
和 Angular
看起来非常相似,所以也支持 Nest
应用程序
当项目复杂之后,模块之间的关系就会变得非常混乱,这时候就需要一个文档来记录各个模块之间的关系和功能
pnpm add @compodoc/compodoc -D
然后执行命令,生成文档
npx @compodoc/compodoc -p tsconfig.json -s -o
-p 是指定 tsconfig.json 配置文件
-s 是启动讲台服务器
-o 是打开浏览器