Nest 中的知识点
谢谢你!ChatGPT!你是我的老师!.jpg
IOC(控制反转)
学习自 IOC 控制反转 和 Nest.js 入门 —— 控制反转与依赖注入(一)TODO:通过这个加深理解 部分技巧来自于:Nestjs 全家桶系列
后端系统中,有很多对象。例如我们要使用 controller 对象
而 Controller 依赖了 Service 实现业务逻辑,Service 依赖了 Repository 来做增删改查,Repository 依赖 DataSource 来建立连接,DataSource 又需要从 Config 对象拿到用户名密码等信息
我们需要这么写:
const config = new Config({ username: 'xxx', password: 'xxx'});
const dataSource = new DataSource(config);
const repository = new Repository(dataSource);
const service = new Service(repository);
const controller = new Controller(service);
要经过一系列的初始化之后才可以使用 Controller 对象。
而且像 config、dataSource、repository、service、controller 等这些对象不需要每次都 new 一个新的,一直用一个就可以,也就是保持单例。
在应用初始化的时候,需要理清依赖的先后关系,创建一大堆对象组合起来,还要保证不要多次 new,是不是很麻烦
所以我们可以使用 IOC 来解决这个问题
我在 class 上直接声明它依赖啥不就行了嘛,然后工具会去分析我声明的依赖关系,根据先后顺序自动把对象创建好了,然后组装起来
IOC 有一个容器用来放对象,初始化时扫描 class 上声明的依赖关系,并 new 一个放入容器里,创建对象时自动注入依赖的对象
这种方式叫 DI(依赖注入)
本来是手动 new 依赖对象,现在是声明依赖,等待被注入,即控制反转
声明依赖的方式,大家都选择了装饰器方式(java 叫注解)
Nestjs的生命周期
模块可以通过 @Global 声明为全局的,这样它 exports 的 provider 就可以在各处使用了,不需要 imports。
provider、controller、module 都支持启动和销毁的生命周期函数,这些生命周期函数都支持 async 的方式。
可以在其中做一些初始化、销毁的逻辑,比如 onApplicationShutwon 里通过 moduleRef.get 取出一些 provider,执行关闭连接等销毁逻辑。
全局模块、生命周期、moduleRef 都是 Nest 很常用的功能。
Nest 在启动时,会递归解析 Moudules 依赖,扫描其中的 provider、controller,注入它的依赖
全部解析完成后,会开始监听网络端口,开始处理请求
这个过程中,Nest 暴露了一些生命周期方法:
首先,递归初始化模块,会依次调用模块内的 controller、provider 的 onModuleInit 方法,然后再调用 module 的 onModuleInit 方法。
全部初始化完之后,再依次调用模块内的 controller、provider 的 onApplicationBootstrap 方法,然后调用 module 的 onApplicationBootstrap 方法
然后监听网络端口。
之后 Nest 应用就正常运行了。
形如:
销毁时候也是这样
先调用每个模块的 controller、provider 的 onModuleDestroy 方法,然后调用 Module 的 onModuleDestroy 方法。
之后再调用每个模块的 controller、provider 的 beforeApplicationShutdown 方法,然后调用 Module 的 beforeApplicationShutdown 方法。
然后停止监听网络端口。
之后调用每个模块的 controller、provider 的 onApplicationShutdown 方法,然后调用 Module 的 onApplicationShutdown 方法。
之后停止进程。
beforeApplicationShutdown 和别的不一样,可以(signal?: string)接受一个string
beforeApplicationShutdown 是可以拿到 signal 系统信号的,比如 SIGTERM。
这些终止信号是别的进程传过来的,让它做一些销毁的事情,比如用 k8s 管理容器的时候,可以通过这个信号来通知它。
形如:
AOP 面向切面(结合 MVC)
MVC 是 Model View Controller 的简写。MVC 架构下,请求会先发送给 Controller,由它调度 Model 层的 Service 来完成业务逻辑,然后返回对应的 View。
如果想要加日志记录、权限控制、异常处理等通用逻辑
例如可以在 control 层前后切一刀
这样的横向扩展点就叫做切面,这种透明的加入一些切面逻辑的编程方式就叫做 AOP (面向切面编程)
而 Nest 实现 AOP 的方式更多,一共有五种,包括 Middleware、Guard、Pipe、Interceptor、ExceptionFilter
Middleware(中间件)
......
Provider(提供者)
左边是 Service,中间是IOC,右边是 Controller ,通过key-value映射
如果这么写的话
就需要注入的时候使用@Inject() 注解
支持类似如下的形式 app.module.ts
@Module({
imports: [],
controllers: [AppController], // 注入到providers中
providers: [
AppService2,
{
provide: 'ABC',
useClass: AppService,
},
{
provide: 'Test',
useValue: ['TB', 'PDD', 'JD'],
},
{
// 工厂模式
provide: 'Test2', // 可以通过inject注入其他服务
inject: [AppService2],
async useFactory(AppService2) {
console.log(AppService2.getHello());
return await new Promise((r) =>
setTimeout(() => r(AppService2.getHello()), 2000),
);
},
},
],
})
export class AppModule {}
开启版本号
有时候接口形如/v1/user,怎么开启呢
在 main.ts 处开启
在模块.controller.ts 处使用
或者,只给一个加
共享模块
第一种:导出、导入
需要在 user.module.ts 进行导出
@Module({
controllers: [UserController],
providers: [UserService],
exports: [UserService],
})
export class UserModule {}
在 app.module.ts 使用 imports:[UserModule]
导入
并且在要使用的地方,app.controller.ts 使用
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { UserService } from './user/user.service';
@Controller()
export class AppController {
constructor(
private readonly appService: AppService,
private readonly userService: UserService,
) {}
@Get()
getHello(): string {
return this.userService.findAll();
}
}
全局(优势在于不用在 module 里导入)
例如说我们有一个 config/config.module.ts 模块,我们需要在头上加上 @Global() 注释,并导出它
import { Global, Module } from '@nestjs/common';
@Global()
@Module({
providers: [
{
provide: 'CONFIG_OPTIONS',
useValue: {
baseUrl: {
baseUrl: '/api',
},
},
},
],
// 这里也要导出
exports: [
{
provide: 'CONFIG_OPTIONS',
useValue: {
baseUrl: {
baseUrl: '/api',
},
},
},
],
})
export class ConfigModule {}
这样,所有的模块都可以使用 ConfigModule 了
同时,在 list.controller.ts 使用
import {
Controller,
Get,
Inject,
} from '@nestjs/common';
import { ListService } from './list.service';
@Controller('list')
export class ListController {
constructor(
private readonly listService: ListService,
@Inject('Config') private readonly base: any,
) {}
@Get()
findAll() {
return this.base;
}
}
动态模块
如何使用动态模块
import { DynamicModule, Global, Module } from '@nestjs/common';
import { ConfigService } from './config.service';
import { ConfigController } from './config.controller';
interface Options {
path: string;
}
@Global()
@Module({})
export class ConfigModule {
static forRoot(options: Options): DynamicModule {
return {
module: ConfigModule,
controllers: [ConfigController],
providers: [
ConfigService,
{ provide: 'Config', useValue: 'Config' + options.path },
],
exports: [
ConfigService,
{ provide: 'Config', useValue: 'Config' + options.path },
],
};
}
}
然后就可以在要用的地方使用了,比如说 app.module.ts, warning:哪怕是 @Globel 声明了,也要在 app.module 引入才能用
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserModule } from './user/user.module';
import { ListModule } from './list/list.module';
import { ConfigModule } from './config/config.module';
@Module({
imports: [
UserModule,
ListModule,
ConfigModule.forRoot({ path: '/starMars' }),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
中间件
依赖中间件
执行 nest g middleware logger --no-spec --flat
创建一个中间件
如下所示(记得标注类型):
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
console.log('brefore');
next();
console.log('after');
}
}
然后在 module 里这么使用:
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
import { Logger } from 'src/middleware';
@Module({
controllers: [UserController],
providers: [UserService],
})
export class UserModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
// 这样是控制所有 UserController 的接口
consumer.apply(Logger).forRoutes(UserController);
// 这样是只控制 UserController 里的为user的POST接口
//.forRoutes({ path: 'user', method: RequestMethod.POST });
}
}
全局中间件
其实照我看来,就是中间件的另一种写法(函数式),不是全局中间件也可以这么写
main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
const whiteList = ['/list'];
const middlewareAll = (req, res, next) => {
console.log(req.originalUrl);
if (whiteList.includes(req.originalUrl)) {
next();
} else {
res.send({ code: 0, msg: '没有权限' });
}
};
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(middlewareAll);
await app.listen(3000);
}
bootstrap();
跨域相关
- 安装 cors 和 cors 的类型文件
npm i @types/cors
npm i @types/cors -D
- 使用
import { NestFactory } from '@nestjs/core';
import * as cors from 'cors';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.use(cors());
await app.listen(3000);
}
bootstrap();
上传文件与下载文件
上传文件
使用 multer 实现上传文件
npm i multer -S
npm i @types/multer -D
然后新建一个模块,在其中的 upload.module.ts 中引入multer
import { Module } from '@nestjs/common';
import { UploadService } from './upload.service';
import { UploadController } from './upload.controller';
import { MulterModule } from '@nestjs/platform-express';
import { diskStorage } from 'multer';
import { extname, join } from 'path';
@Module({
imports: [
MulterModule.register({
storage: diskStorage({
// 设置上传后的目录
destination: join(__dirname, '../images'),
filename: (_, file, cb) => {
// 重新命名文件
// extname 取后缀
const fileName = `${
new Date().getTime() + extname(file.originalname)
}`;
return cb(null, fileName);
},
}),
}),
],
controllers: [UploadController],
providers: [UploadService],
})
export class UploadModule {}
然后开始写接口,在 upload.control.ts 中 说实话,写在 upload.service.ts 里比较好,他偷懒了,我也偷懒了
import {
Controller,
Post,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { UploadService } from './upload.service';
import { FileInterceptor } from '@nestjs/platform-express';
@Controller('upload')
export class UploadController {
constructor(private readonly uploadService: UploadService) {}
@Post('album')
@UseInterceptors(FileInterceptor('file'))
upload(@UploadedFile() file: Express.Multer.File) {
console.log(file);
return 'Get it!';
}
}
附赠一个开启静态文件访问的代码
在 main.ts 中
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { NestExpressApplication } from '@nestjs/platform-express';
import { join } from 'path';
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// 开启静态访问
app.useStaticAssets(join(__dirname, 'images'), {
prefix: '/images',
});
await app.listen(3000);
}
bootstrap();
下载文件
先开启静态访问
加入以下方法
import {
Controller,
Get,
Post,
Res,
UploadedFile,
UseInterceptors,
} from '@nestjs/common';
import { Response } from 'express';
import { UploadService } from './upload.service';
import { FileInterceptor } from '@nestjs/platform-express';
import { zip } from 'compressing';
import { join } from 'path';
@Controller('upload')
export class UploadController {
constructor(private readonly uploadService: UploadService) {}
@Post('album')
@UseInterceptors(FileInterceptor('file'))
upload(@UploadedFile() file: Express.Multer.File) {
console.log(file);
return '??????';
}
@Get('export')
downLoad(@Res() res: Response) {
res.download(join(__dirname, '../images/1.jpg'));
}
@Get('stream')
async down(@Res() res: Response) {
const url = join(__dirname, '../images/1.jpg');
// 新建一个zip
const stream = new zip.Stream();
// 把文件加进去
await stream.addEntry(url);
// 设置一大堆有用的东西(header)
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', 'attachment; filename=export.zip');
stream.pipe(res);
}
}
Rxjs 相关
RxJS is a library for composing asynchronous and event-based programs by using observable sequences.
安装
pnpm i rxjs
使用
import { of, interval, take, Observable, fromEvent } from "rxjs";
import { map, filter, scan } from "rxjs/operators";
const observable = new Observable((subscriber) => {
subscriber.next(1);
subscriber.next(2);
subscriber.next(3);
setTimeout(() => {
subscriber.next(4);
subscriber.complete();
}, 3000);
});
observable.subscribe({
next: (x) => console.log(x),
error: (err) => console.log(err),
complete: () => console.log("done"),
});
// interval(500) 输出 0 1 2 3 4 5 6 7 8 9 10 ... 500
const subs = interval(500)
.pipe(
map((v) => ({ num: v })),
filter((v) => v.num % 2 === 0)
)
.subscribe((e) => {
console.log(e);
if (e.num === 10) {
subs.unsubscribe();
}
});
// of(5) 自定义数据
const source = of(1, 2, 3, 4, 5);
// retry(2) 重试2次
// formEvent(document, 'click') 事件监听
const click$ = fromEvent(document, "click").pipe(map((e) => e.target));
click$.subscribe((e) => console.log(e));
// take(3) 只取前3个
响应拦截器
在 src 下新建 common 文件夹,新建 response.ts
import { CallHandler, Injectable, NestInterceptor } from '@nestjs/common';
import { Observable, map } from 'rxjs';
interface Data<T> {
data: T;
}
@Injectable()
export class Response<T> implements NestInterceptor {
intercept(context, next: CallHandler): Observable<Data<T>> {
return next.handle().pipe(
map((data) => {
return {
status: 0,
data,
message: '牛逼',
success: true,
};
}),
);
}
}
在 main.ts 中引入,使用
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Response } from './common/response';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(new Response());
await app.listen(3000);
}
bootstrap();
异常拦截器
在 src 下新建common 文件夹,新建filter.ts
import {
ArgumentsHost,
Catch,
ExceptionFilter,
HttpException,
} from '@nestjs/common';
@Catch()
export class HttpFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const request = ctx.getRequest();
const response = ctx.getResponse();
const status = exception.getStatus();
response.status(status).json({
status,
data: exception.message,
time: new Date(),
success: false,
path: request.url,
});
}
}
在 main.ts 中使用
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpFilter } from './common/filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpFilter());
await app.listen(3000);
}
bootstrap();
管道
管道转换
可以用来检查参数的 type
在 p,controller.ts 中
import {
Controller,
Get,
Param,
ParseUUIDPipe,
} from '@nestjs/common';
import { PService } from './p.service';
@Controller('p')
export class PController {
constructor(private readonly pService: PService) {}
@Get(':id')
findOne(@Param('id', ParseUUIDPipe) id: string) {
// ParseIntPipe
// 这样就变成了number类型
// ParseUUIDPipe
// 使用
// import * as uuid from 'uuid';
// console.log(uuid.v4());
// 管道转换
// ValidationPipe
// ParseIntPipe
// ParseFloatPipe
// ParseBoolPipe
// ParseArrayPipe
// ParseUUIDPipe
// ParseEnumPipe
// DefaultValuePipe
/**
* {
"message": "Validation failed (uuid is expected)",
"error": "Bad Request",
"statusCode": 400
}
*/
console.log(typeof id);
return this.pService.findOne(+id);
}
}
管道验证 DTO
使用nest g pi login
生成 login 文件夹,其中包括 login.pipe.ts
使用 CreateLoginDTO
import { IsNotEmpty, IsString, Length, IsNumber } from 'class-validator';
export class CreateLoginDto {
@IsNotEmpty()
@IsString()
@Length(4, 20, { message: '用户名长度不对' })
name: string;
@IsNumber()
age: number;
}
import {
ArgumentMetadata,
HttpException,
HttpStatus,
Injectable,
PipeTransform,
} from '@nestjs/common';
import { plainToInstance } from 'class-transformer';
import { validate } from 'class-validator';
@Injectable()
export class LoginPipe implements PipeTransform {
async transform(value: any, metadata: ArgumentMetadata) {
// LoginPipe { name: '1111', age: 12 } { metatype: [class CreateLoginDto], type: 'body', data: undefined }
// console.log('LoginPipe', value, metadata);
// 实例化 CreateLoginDTO
const DTO = plainToInstance(metadata.metatype, value);
// 验证
const errors = await validate(DTO);
console.log('errors', errors);
if (errors.length) {
throw new HttpException(errors, HttpStatus.BAD_REQUEST);
}
console.log('DTO', DTO);
return value;
}
}
其实更推荐以下的全局验证管道
在 login.controller.ts 中
import {
Controller,
Post,
Body,
} from '@nestjs/common';
import { LoginService } from './login.service';
import { CreateLoginDto } from './dto/create-login.dto';
// import { LoginPipe } from './login/login.pipe';
@Controller('login')
export class LoginController {
constructor(private readonly loginService: LoginService) {}
/**
* @Post()
create(@Body(LoginPipe) createLoginDto: CreateLoginDto) {
return this.loginService.create(createLoginDto);
}
*/
@Post()
create(@Body() createLoginDto: CreateLoginDto) {
return this.loginService.create(createLoginDto);
}
}
直接在 main.ts 中使用
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpFilter } from './common/filter';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpFilter());
// 全局验证管道
app.useGlobalPipes(new ValidationPipe());
await app.listen(3000);
}
bootstrap();
爬虫
写不了一点,网站被爬死了
守卫
使用nest g res guard
创建模块
使用nest g gu role
创建守卫
部分守卫
在 guard.controller.ts 中使用
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
UseGuards,
SetMetadata,
} from '@nestjs/common';
import { GuardService } from './guard.service';
import { CreateGuardDto } from './dto/create-guard.dto';
import { UpdateGuardDto } from './dto/update-guard.dto';
import { RoleGuard } from './role/role.guard';
@Controller('guard')
// 添加
@UseGuards(RoleGuard)
export class GuardController {
constructor(private readonly guardService: GuardService) {}
@Get()
// 设置啥可以用这个接口
@SetMetadata('role', ['admin'])
findAll() {
return this.guardService.findAll();
}
}
全局守卫
在 main.js 中使用
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { RoleGuard } from './guard/role/role.guard';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 全局守卫
app.useGlobalGuards(new RoleGuard());
await app.listen(3000);
}
bootstrap();
修改守卫读取
在 role.guard.ts 中
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Reflector } from '@nestjs/core';
import type { Request } from 'express';
@Injectable()
export class RoleGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
// 获取路由处理器上的元数据
const roles = this.reflector.get<string[]>('role', context.getHandler());
const req = context.switchToHttp().getRequest<Request>();
console.log(req.query.role);
if (roles.includes(req.query.role as string)) {
return true;
} else {
return false;
}
}
}