NestJS学习

NestJS 简介

Nestjs 是一个用于构建高效的可扩展的基于Nodejs服务端 应用程序开发框架

完全支持typescript 并结合的AOP面向切面编程的方式

Spring MVC 风格 其中有依赖注入和IOC控制反转

前置知识

IOC

Inversion of Control 字面意思是控制反转,具体的定义是高层模块不应该依赖底层模块,二者都应该依赖其抽象,抽象不应该依赖细节

DI

依赖注入(Dependency Injection)其实和IoC是同根生,这两个原本就是一个东西,只不过由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系)。 类A依赖类B的常规表现是在A中使用B的instance。

未使用控制反转和依赖注入之前的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class A {
name: string
constructor(name: string) {
this.name = '月晕'
}
}

//强耦合
class B {
a: any
constructor() {
this.a = new A().name
}
}

class C {
a: any
constructor() {
this.a = new A().name
}
}

B中代码的实验是需要依赖A的 两者的代码耦合度非常高。当两者之间的业务逻辑复杂程度增加的情况下 维护成本与代码可读性都会增加 并且会很难在引入额外的模块进行功能扩展

为了解决上面的问题可以使用IOC容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class A {
name: string
constructor(name: string) {
this.name = '月晕'
}
}
class Container {
mo: any
constructor() {
this.mo = {}
}
provide(key: string, mo: any) {
this.mo[key] = mo
}
get(key: string) {
return this.mo[key]
}
}

const container = new Container()
container.provide('a', new A('月晕'))
container.provide('c', new A('月晕52'))
class B {
a: any
c: any
constructor() {
this.a = container.get('a')
this.c = container.get('c')
}
}

其实就是写了一个中间件,来收集依赖,主要是为了解耦,减少维护成本

装饰器

装饰器是一种特殊的类型声明 可以附加在类、方法、属性、参数上面

类装饰器

主要是通过@符号来添加装饰器
会自动把class的构造函数传入到装饰器的第一个参数 target 然后通过prototype可以自定义添加属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
const doc: ClassDecorator = (target: any) => {
console.log(target)
target.prototype.name = 'yueyun'
}

@doc
class yueyun {
constructor() {}
}
// doc(yueyun)
const yueyun1: any = new yueyun()
console.log(yueyun1.name)

属性装饰器

会返回两个参数 原型对象 and 属性的名称

1
2
3
4
5
6
7
8
9
10
11
const doc: PropertyDecorator = (target: any, key: string | symbol) => {
console.log(target, key)
}

class yueyun {
@doc
public name: string
constructor() {
this.name = 'yueyun'
}
}

方法装饰器

会返回三个参数 原型对象 方法的名称 属性描述符[可写对应writable | 可枚举对应enumerable | 可配置对应configurable]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const doc: MethodDecorator = (
target: any,
key: string | symbol,
descriptor: any
) => {
console.log(target, key, descriptor)
}

class yueyun {
public name: string
constructor() {
this.name = 'yueyun'
}
@doc
getName() {
console.log(this.name)
}
}

属性装饰器

会返回三个参数 原型对象 方法的名称 参数的位置(从0开始)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const currency: ParameterDecorator = (
target: any,
key: string | symbol,
index: number
) => {
console.log(target, key, index)
}

class yueyun {
public name: string
constructor() {
this.name = ''
}
getName(name: string, @currency age: number) {
return this.name
}
}

实现一个GET请求

定义装饰器
使用装饰器工厂
定义 descriptor 的类型 通过 descriptor描述符里面的value 把axios的结果返回给当前使用装饰器的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import axios from 'axios'
// 函数柯里化 在包装了一层
const Get = (url: string): MethodDecorator => {
return (target: any, propertyKey: any, descriptor: PropertyDescriptor) => {
// console.log('Get decorator');
const func = descriptor.value
axios
.get(url)
.then((res) => {
func(res.data, {
status: 200,
success: true
})
})
.catch((err) => {
func(err, {
status: 500,
success: false
})
})
}
}

class Controller {
constructor() {
console.log('Controller class')
}
@Get('https://api.apiopen.top/api/getHaoKanVideo?page=0&size=10')
getList(res: any, status: any) {
console.log(res, status)
}
}

nestJS/cli 使用

项目的开发离不开工程化的部分,比如创建项目、编译构建、开发时的HRM等

Nest项目自然也是一样,所以它在@nestjs/cli这个包里提供了 nest 命令

1
2
yarn install -g @nestjs/cli
nest new <your project name>

如果想更新版本则使用

1
npm update -g @nestjs/cli

可以使用nest -h 去查看提供了什么命令

image-20231104152854282

有创建项目的nest new,有创建代码的 nest generate,还有编译构建的nest build,开发模式的nest start

nest new

首先使用nest new --help看看有什么选项

image-20231104153515535

--skip-get || --skip-install: 就是跳过git 初始化和npm install

--package-manager: 包管理器

--language: 语言

等等等

nest generate

生成特定的代码

image-20231104153941334

当然也有一些使用的配置项

image-20231104154145969

nest build

用来构建项目 会在dist目录下生成编译后的代码

image-20231104154337390

--webpack--tsc是指定用什么来编译,默认是tsc编译

tsc不做打包,webpack会做打包,两种方式都可以

这些选项都可以在nest-cli.json配置

总结

nest在@nestjs/cli包里提供了nest命令,可以用来做很多事情

  • 生成项目结构和各种代码
  • 编译代码
  • 监听文件变动自动编译
  • 打印项目依赖信息

也就是这些

  • nest new 创建新项目
  • nest generate 生成各种代码
  • nest build 使用tsc或webpack构建
  • nest start 启动开发服务
  • nest info 打印node 包 nest包等依赖信息

很多配置都可以在nest-cli.json里配置,比如generateOptionscompilerOptions

nestJS控制器

Controller Request (获取传递过来的参数)

装饰器参数
@Request()req
@Response()res
@Next()next
@Session()req.session
@Param(key?: string)req.params/req.params[key] (指的是动态参数/:id)
@Body(key?: string)req.body/req.body[key]
@Query(key?: string)req.query/req.query[key] (传入数据时的params)
@Headers(name?: string)req.headers/req.headers[name]
@HttpCodeCode

获取Get参数

可以使用Request装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import {
Controller,
Get,
Post,
Body,
Patch,
Param,
Delete,
Request,
Query
} from '@nestjs/common'
import { DemoService } from './demo.service'
import { Header, Headers } from '@nestjs/common/decorators'

@Controller('demo')
export class DemoController {
constructor(private readonly demoService: DemoService) {}
@Get()
findAll(@Request() req: any, @Query() query: any) {
console.log(req.query === query)
if (req.query.name === '月晕')
return {
code: 200,
Msg: '月晕'
}
return {
code: 200,
Msg: '你不是本人'
}
}
}

Post获取参数

也能使用Reques装饰器或者Body装饰器也可以直接读取key值

1
2
3
4
5
6
7
8
9
10
11
export class DemoController {
constructor(private readonly demoService: DemoService) {}
@Post()
create(@Body('name') body: string) {
console.log(body)
return {
code: 200,
Msg: '创建成功'
}
}
}

动态路由

可以使用Request装饰器 或者 Param装饰器

1
2
3
4
5
6
7
8
9
10
11
@Controller('demo')
export class DemoController {
constructor(private readonly demoService: DemoService) {}
@Get(':id')
findId (@Param() param) {
console.log(param)
return {
code:200
}
}
}

读取header信息

1
2
3
4
5
6
7
8
9
10
11
@Controller('demo')
export class DemoController {
constructor(private readonly demoService: DemoService) {}
@Get(':id')
findId (@Headers() header) {
console.log(header)
return {
code:200
}
}
}

状态码

1
2
3
4
5
6
7
8
9
10
11
@Controller('demo')
export class DemoController {
constructor(private readonly demoService: DemoService) {}
@Get(':id')
@HttpCode(500)
findId (@Headers() header) {
return {
code:500
}
}
}

nestJS Session

session 是服务器 为每个用户的浏览器创建的一个会话对象 这个session 会记录到 浏览器的 cookie 用来区分用户

我们使用的是nestjs 默认框架express 他也支持express 的插件 所以我们就可以安装express的session

1
2
yarn add express-session -S
yarn add @types/express-session -D

然后在main.ts 引入 通过app.use 注册session

1
2
import * as session from 'express-session'
app.use(session())

参数配置详解

参数用法
secret生成服务端session 签名 可以理解为加盐
name生成客户端cookie 的名字 默认 connect.sid
cookie设置返回到前端 key 的属性,默认值为{ path: ‘/’, httpOnly: true, secure: false, maxAge: null }。
rolling在每次请求时强行设置 cookie,这将重置 cookie 过期时间(默认:false)

nestJS配置

1
2
3
4
5
6
7
8
9
10
11
12
13
import { NestFactory } from '@nestjs/core';
import { VersioningType } from '@nestjs/common';
import { AppModule } from './app.module';
import * as session from 'express-session'
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableVersioning({
type: VersioningType.URI
})
app.use(session({ secret: "YueYun", name: "yueyun.session", rolling: true, cookie: { maxAge: null } }))
await app.listen(3000);
}
bootstrap();

验证码案例

前端: Vue3 ts element-plus fetch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<template>
<div class="wraps">
<el-form :label-position="labelPosition" label-width="100px" :model="formLabelAlign" style="max-width: 460px">
<el-form-item label="账号">
<el-input v-model="formLabelAlign.name" />
</el-form-item>
<el-form-item label="密码">
<el-input type="password" v-model="formLabelAlign.password" />
</el-form-item>
<el-form-item label="验证码">
<div style="display:flex">
<el-input v-model="formLabelAlign.code" />
<img @click="resetCode" :src="codeUrl" alt="">
</div>
</el-form-item>
<el-form-item>
<el-button @click="submit">登录</el-button>
</el-form-item>
</el-form>
</div>
</template>

<script setup lang='ts'>
import { onMounted, reactive, ref } from 'vue';

const codeUrl = ref<string>('/api/user/code')

const resetCode = () => codeUrl.value = codeUrl.value + '?' + Math.random()

const labelPosition = ref<string>('right')

const formLabelAlign = reactive({
name: "",
password: "",
code: ""
})

const submit = async () => {
await fetch('/api/user/create', {
method: "POST",
body: JSON.stringify(formLabelAlign),
headers: {
'content-type': 'application/json'
}
}).then(res => res.json())
}



</script>

<style>
* {
padding: 0;
margin: 0;
}

.wraps {
display: flex;
justify-content: center;
align-items: center;
height: inherit;
}

html,
body,
#app {
height: 100%;
}
</style>

后端nestjs 验证码插件 svgCaptcha

1
yarn add svg-captcha -S
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import { Controller, Get, Post, Body, Param, Request, Query, Headers, HttpCode, Res, Req } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import * as svgCaptcha from 'svg-captcha';
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) { }
@Get('code')
createCaptcha(@Req() req, @Res() res) {
const captcha = svgCaptcha.create({
size: 4,//生成几个验证码
fontSize: 50, //文字大小
width: 100, //宽度
height: 34, //高度
background: '#cc9966', //背景颜色
})
req.session.code = captcha.text //存储验证码记录到session
res.type('image/svg+xml')
res.send(captcha.data)
}

@Post('create')
createUser(@Req() req, @Body() body) {
console.log(req.session.code, body)
if (req.session.code.toLocaleLowerCase() === body?.code?.toLocaleLowerCase()) {
return {
message: "验证码正确"
}
} else {
return {
message: "验证码错误"
}
}

}
}

nestJS 提供者

Proveders

Provides 是 Nest 的一个基本概念 许多基本的Nest类可能被视为 provider-service、repository、factory、helper 等,都可以通过constructor 注入依赖关系,这就意味着对象可以彼此创建各种关系 并且连接对象实例的功能在很大程度上可以委托给nest的运行时系统。Provider只是一个用@Injectable() 装饰器注释的类

基本用法

module 引入 service 在 providers 注入

1
2
3
4
5
6
7
8
import {Module} from '@nestjs/common'
import {UserSerice} from './user.service'
import {UserController} from './user.controller'
@Module({
controllers: [UserService]
providers: [UserService]
})
export class UserModule {}

在Controller 层就能使用注入好的service

1
2
3
4
5
6
import {Controller,Get} from '@nestjs/common'
import {UserService} from './user.service'
@Controller('user')
export class UserController {
constructor(private readonly userService:UserSerivece) {}
}

自定义名称

基本用法其实上是一种语法糖

全称是下面这样

1
2
3
4
5
6
7
8
9
10
11
12
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';

@Module({
controllers: [UserController],
providers: [{
provide: "Yueyun",
useClass: UserService
}]
})
export class UserModule { }

自定义名称之后 需要用对应的Inject

1
2
3
4
5
6
import {Controller,Get} from '@nestjs/common'
import {UserService} from './user.service'
@Controller('user')
export class UserController {
constructor(@Inject('Yueyun') private readonly userService:UserSerivece) {}
}

自定义注入值

通过useValue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';

@Module({
controllers: [UserController],
providers: [{
provide: "Yueyun",
useClass: UserService
}, {
provide: "JD",
useValue: ['TB', 'PDD', 'JD']
}]
})
export class UserModule { }
1
2
3
4
5
6
7
8
9
10
11
12
13
import {Controller,Get} from '@nestjs/common'
import {UserService} from './user.service'
@Controller('user')
export class UserController {
constructor(
@Inject('Yueyun') private readonly userService:UserSerivece,
@Inject('JD') private shopList:string[]
) {}
@Get()
findAll() {
return this.userService.findAll() + this.shopList
}
}

工厂模式

如果服务 之间有相互依赖 或者逻辑处理 可以使用useFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserService2 } from './user.service2';
import { UserService3 } from './user.service3';
import { UserController } from './user.controller';

@Module({
controllers: [UserController],
providers: [{
provide: "Xiaoman",
useClass: UserService
}, {
provide: "JD",
useValue: ['TB', 'PDD', 'JD']
},
UserService2,
{
provide: "Test",
inject: [UserService2],
useFactory(UserService2: UserService2) {
return new UserService3(UserService2)
}
}
]
})
export class UserModule { }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import {Controller,Get} from '@nestjs/common'
import {UserService} from './user.service'
@Controller('user')
export class UserController {
constructor(
@Inject('Yueyun') private readonly userService:UserSerivece,
@Inject('JD') private shopList:string[],
@Inject('Testt') private readonly Test:any,
) {}
@Get()
findAll() {
return this.userService.findAll() + this.shopList
}
@Get()
findAll(){
return this.userService.findAll() + this.Test.get()
}
}

异步模式

useFactory 返回一个promise 或者其他异步操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserService2 } from './user.service2';
import { UserService3 } from './user.service3';
import { UserController } from './user.controller';

@Module({
controllers: [UserController],
providers: [{
provide: "Xiaoman",
useClass: UserService
}, {
provide: "JD",
useValue: ['TB', 'PDD', 'JD']
},
UserService2,
{
provide: "Test",
inject: [UserService2],
useFactory(UserService2: UserService2) {
return new UserService3(UserService2)
}
},
{
provide: "sync",
async useFactory() {
return await new Promise((r) => {
setTimeout(() => {
r('sync')
}, 3000)
})
}
}
]
})
export class UserModule { }

nestJS 模块

模块@Module

每个Nest应用程序至少有一个模块,即根模块 ,根模块是Nest开始安排应用程序树的地方 事实上,根模块可能是应用程序中唯一的模块 特别是当应用程序很小的时候,但是对于大型程序来说这是没有意义的。在大多数情况下,您将拥有多个模块,每个模块都有一组紧密相关的功能

基本用法

当使用nest g res user创建一个CURD模板的时候 nestjs会自动帮我们引入模块

1
2
3
4
5
6
7
8
9
10
11
12
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { DemoModule } from './demo/demo.module'
import { UserModule } from './user/user.module'

@Module({
imports: [DemoModule, UserModule],
controllers: [AppController],
providers: [AppService]
})
export class AppModule {}

共享模块

例如user的Service想暴露给其他模块使用就可以使用exports 导出该服务

1
2
3
4
5
6
7
8
9
10
11
import { Module } from '@nestjs/common'
import { UserService } from './user.service'
import { UserController } from './user.controller'

@Module({
controllers: [UserController],
providers: [UserService],
// 导出
exports: [UserService]
})
export class UserModule {}

由于App.modules 已经引入该模块 就可以直接使用user模块的Service

NestJS 调试