无题

什么是Nest

Nest (NestJS) 是一个用于构建高效、可扩展 Node.js 服务器端应用程序的框架。它使用渐进式 JavaScript,使用 TypeScript 构建并完全支持(但仍允许开发人员使用纯 JavaScript 进行编码),并结合了 OOP(面向对象编程)、FP(函数式编程)和 FRP(函数式响应式编程)的元素。

Nestjs框架基于Express和Fastify(速度最快的web框架之一)

开始

启动

1
pnpm start

开发的时候需要监视文件的变化,自动重新编译和重新加载服务器

1
pnpm start:dev

文件介绍

1
2
3
4
5
6
7
8
9
10
- node_modules — 放置项目的依赖文件
- src — 放置开发的代码文件
- app.controller.spec.ts — 测试 app.controller.ts 文件的单元测试文件
- app.controller.ts — 根控制器文件,处理路由的请求和定义路由处理器
- app.module.ts — 根模块文件,Nest 应用的主要入口
- app.service.ts — 根服务文件,用于处理根控制器中的业务逻辑
- main.ts — 应用程序的入口文件,用于初始化和启动 Nest 应用
- test — 测试文件,用于编写应用程序的单元测试和集成测试
- app.e2e-spec.ts — 编写和运行 E2E 测试的测试规范文件
- jest-e2e.json —Jest 的 E2E 测试配置文件

项目划分结构

1
2
3
4
5
6
7
8
9
10
11
12
13
- src
- core
- common
- middleware
- interceptors
- guards
- user
- interceptors (scoped interceptors)
- user.controller.ts
- user.model.ts
- store
- store.controller.ts
- store.model.ts

tsconfig.json

1
2
3
4
"experimentalDecorators":true,启动实验性的装饰器特性
"target":"ES2821",指定ECMAScript目标版本
"moduleResolution":"NodeNext",如何查找第三方模块
"module":"NodeNext", 指定生成的模块代码系统

创建新的资源模块

1
nest g res 新的资源的名称

一般会自动在 app.module.ts 文件中引入

IOC控制反转 和 DI 依赖注入

[[spring IOC和DI 理解_springioc和di的一个理解]]

装饰器

使用装饰器前需要确保在 tsconfig,json 中包含以下配置

1
2
3
4
5
{ 
"compilerOptions": {
"experimentalDecorators": true
}
}

[[TypeScript 装饰器什么是装饰器? 装饰器怎么分类的? 什么是普通装饰器和装饰器工厂? 各种类编装饰器]]

Module

@Module是一个装饰器,用于定义模块
模块是组织代码的基本单元,它将相关的组件(控制器、服务器、提供者)组合在一起
Nest的模块系统是受Angular启动

Controller

@Controller也是一个装饰器,用于定义控制器
控制器是处理传入HTTP清求的核心组件,每个控制器负责处理特定的请求路径和对应的HTTP方法在控制器的内部会使用路由装饰器 , 如 @Get , @Post 来定义路径和请求处理的方法

Get

路由装饰器, 用于将控制器的方法 (例如getHello) 映射到 HTTP 的 GET 请求
指定方法处理特定路径上的 GET请求

控制器

装饰器 说明
$\text{@Request()}$ 请求参数
$\text{@Response()}$ 响应参数
$\text{@Query}$ $\text{Get}$ 请求直接获取参数
$\text{@Body}$ $\text{Post}$ 请求直接获取参数
$\text{@Param}$ 动态路由获取参数
$\text{@Headers}$ 获取 $\text{header}$ 信息
$\text{@HttpCode}$ 控制器返回的状态码
$\text{@Next()}$ 继续处理下一个中间件或路由

Get

  • 可以从服务器获取资源。
  • 参数暴露在 URL 中(Query),存在安全隐患。(参数会附加在 URL 的末尾,多个参数之间用 & 符号分隔)
  • 受到 URL 长度的限制,无法传输大量数据。
  • Get 请求具有幂等性,多次请求得到的结果是相同的。
  • 响应结果可以被浏览器缓存。

Post

Post 请求是 HTTP 协议中的一种请求方法,通常用于向服务器提交数据,或者创建新的资源。使用 Post 请求时,数据会放在请求体(body)中传输,而不是暴露在 URL 里。

可以向服务器提交数据,或者创建新的资源。
参数在请求体中传输,较为安全和隐蔽。
请求体的大小没有限制,可以传输大量数据。
Post 请求默认不具有幂等性,多次请求可能得到不同结果。
响应结果默认不会被浏览器缓存。

模块

生成新的模块

命令 (生成 controller, module, service)

1
2
3
nest g co 模块名称
nest g mo 模块名称
nest g s 模块名称

生成一个完整的模块

1
nest g resource 模块名称

版本控制

main.ts 里引入

1
import { VersioningType } from '@nestjs/common';

同时添加

1
2
3
app.enableVersioning({
    type: VersioningType.URI,
  });

之后可以在装饰器Controller里书写版本号

1
2
3
4
@Constroller({
path: 'user',
version: '1',
})

或者单个请求控制

1
2
@Get()
@Version('1)

Code码规范

200 OK
304 Not Modified协商缓存了
400 Bad Request参数错误
401 Unauthorized token错误
403 Forbidden referer origin验证失败
404 Not Found接口不存在
500 Internal Server Error服务端错误
502 Bad Gateway上游接口有问题或者服务器问题

上传文件

1.主要会用到两个包

multer   @nestjs/platform-express nestJs自带了

multer   @types/multer 这两个需要安装

在upload  Module 使用MulterModule register注册存放图片的目录

需要用到  multer 的  diskStorage 设置存放目录 extname 用来读取文件后缀 filename给文件重新命名

![[前端了解/Nest/_resources/nest/79208d1db97c771d81a3344d9ec3bd58_MD5.png]]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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,callback) => {
const fileName = `${new Date().getTime() + extname(file.originalname)}`
return callback(null,fileName)
}
})
})],
controllers: [UploadController],
providers: [UploadService]
})
export class UploadModule { }

2.controller 使用

使用 UseInterceptors 装饰器  FileInterceptor是单个 读取字段名称  FilesInterceptor是多个

参数 使用 UploadedFile 装饰器接受file 文件

![[前端了解/Nest/_resources/nest/5bbde31f02f92860ea9be98391cd721a_MD5.png]]

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Controller, Get, Post, Body, Patch, Param, Delete,UseInterceptors,UploadedFile } 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) {
console.log(file)
return true
}
}

3.生成静态目录访问上传之后的图片

useStaticAssets prefix 是虚拟前缀

1
2
3
4
5
6
7
8
9
10
11
12
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:"/xiaoman"
})
await app.listen(3000);
}
bootstrap();

下载文件

1.download 直接下载

这个文件信息应该存数据库 我们这儿演示就写死 了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Controller, Post, UseInterceptors, UploadedFile, Get, Res } from '@nestjs/common';
import { UploadService } from './upload.service';
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express'
import type { Response } from 'express'
import {join} from 'path'
@Controller('upload')
export class UploadController {
constructor(private readonly uploadService: UploadService) { }
@Post('album')
@UseInterceptors(FileInterceptor('file'))
upload(@UploadedFile() file) {
console.log(file, 'file')
return '峰峰35岁憋不住了'
}
@Get('export')
downLoad(@Res() res: Response) {
const url = join(__dirname,'../images/1662894316133.png')
// res
// console.log(url)
res.download(url)
// return true
}
}

2.使用文件流的方式下载

可以使用compressing把他压缩成一个zip包

1
pnpm add compressing

import {zip} from ‘compressing’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Get('stream')
async down (@Res() res:Response) {
const url = join(__dirname,'../images/1662894316133.png')
const tarStream = new zip.Stream()
await tarStream.addEntry(url)

res.setHeader('Content-Type', 'application/octet-stream');

res.setHeader(
'Content-Disposition',
`attachment; filename=xiaoman`,
);

tarStream.pipe(res)

}

前端接受流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const useFetch = async (url: string) => {
const res = await fetch(url).then(res => res.arrayBuffer())
console.log(res)
const a = document.createElement('a')
a.href = URL.createObjectURL(new Blob([res],{
// type:"image/png"
}))
a.download = 'xiaman.zip'
a.click()
}

const download = () => {
useFetch('http://localhost:3000/upload/stream')
}

拦截器

拦截器的核心原理:Observable 流

Nest 的 Interceptor 基于 RxJS,核心是通过 Observable 流来拦截流程。

接口的执行结果会包装成一个“数据流”(Observable),Interceptor 可以拦截这个流,加工后再传递下去

比如接口返回{ id: 1, name: "张三" },这个结果会编程一个 Observable 流,Interceptor 可以把流里的数据改成{ success: true, data: { id: 1, name: "张三" }},再传给客户端

核心方法是intercept,它接受两个参数:

  1. context:执行上下文(和 Guard、Filter 里的类似,能拿到请求/响应对象)

  2. next: “下一个”步骤的引用,调用next.handle()就能触发接口执行,返回的是 Observable 流

  3. 返回值:加工后的 Observable 流,必须返回流,否则客户端拿不到响应

响应拦截器

实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import type {
ExecutionContext,
NestInterceptor,
CallHandler,
} from '@nestjs/common';
import type { Observable } from 'rxjs';
import { map } from 'rxjs';
import { Injectable } from '@nestjs/common';

export class ResponseIntercept implements NestInterceptor {
//调用 next 处理器, 执行里面的handle()方法, 使用过 pipe 操作符进行管道处理, map操作符映射数据
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map((data) => {
return {
code: 0,
data,
};
}),
);
}
}

异常拦截器

  • 用于全局处理HTTP异常的中间件,提供了一种统一的方式来处理和修改从后端返回给前端的错误
  • 例如统一错误格式、向客户端发送适当的错误响应
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import type { ExceptionFilter, ArgumentsHost } from '@nestjs/common';
import { Catch, HttpException } from '@nestjs/common';

@Catch(HttpException)
export class AbnormalFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
//获取请求上下文
const ctx = host.switchToHttp();
//上下文的response对象
const response = ctx.getResponse();
//获取异常的状态码
const status = exception.getStatus();
//异常的消息
const message = exception.message ? exception.message : 'service error';
response.status(status).json({
code: -1,
data: null,
msg: message,
});
}
}

启用

1
2
3
4
5
6
7
8
9
10
import { ResponseIntercept } from './common/responseintercept';
import { AbnormalFilter } from './common/abnormalFilter';

async function bootstrap() {

app.useGlobalInterceptors(new ResponseIntercept());
app.useGlobalFilters(new AbnormalFilter());

}

Pipe

使用
main.ts

1
2
3
4
5
6
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);

app.useGlobalPipes(new ValidationPipe());

constroller.ts

1
2
3
4
5
@Post()
register(@Body() req: RegisterDto) {
console.log(req);
return req;
}

定义管道

1
2
3
4
5
6
7
8
9
10
11
12
import { IsString, Length } from 'class-validator';

export class RegisterDto {
@IsString()
name: string;
@IsString()
@Length(11)
phone: string;
@IsString()
password: string;
}

链接数据库

1
pnpm i typeorm mysql2 @nestjs/typeorm

在根目录下面创建 config.ts文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Config.ts / ...
import type { TypeOrmModuleOptions } from '@nestjs/typeorm';

export const typeOrmConfig: TypeOrmModuleOptions = {
type: 'mysql',
host: '127.0.0.1',
port: 3306,
username: 'root',
password: '密码',
database: 'nest-mysql',
entities: ['dist/**/*.entity.{ts,js}'], // 注意:此处可能略有模糊,但格式一般如此
autoloadEntities: true,
synchronize: true,
};

app.module.ts 中添加

1
2
3
4
5
6
7
8
import { typeOrmConfig } from './config';

import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [
TypeOrmModule.forRoot(typeOrmConfig),
],
})

创建变量
在项目的 entities下添加文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number = 0;

@Column()
name: string = '';

@Column()
phone: string = '';

@Column()
password: string = '';
}

在 相关模块的module.ts下面引入

1
2
3
4
5
6
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';

@Module({
imports: [TypeOrmModule.forFeature([User])],
})

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { User } from './entities/user.entity';
import { Repository } from 'typeorm';

@Controller('user')
export class UserController {
constructor(
@InjectRepository(User) private readonly userRespository: Repository<User>,
) {}

@Post()
register(@Body() req: RegisterDto) {
//删除
this.userRespository.delete({ name: 'k', phone: '01299', password: '99' });
//增加
this.userRespository.save({ name: 'k', phone: '01299', password: '99' });
//更新
this.userRespository.update(
{ name: 'k' },
{ name: 'sj', phone: '01299', password: '99' },
);
return req;
}
}

配置跨域

1
2
3
4
5
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors();
await app.listen(process.env.PORT ?? 3000);
}