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() {} }
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) => { 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
去查看提供了什么命令
有创建项目的nest new
,有创建代码的 nest generate
,还有编译构建的nest build
,开发模式的nest start
等
nest new
首先使用nest new --help
看看有什么选项
--skip-get || --skip-install
: 就是跳过git 初始化和npm install
--package-manager
: 包管理器
--language
: 语言
等等等
nest generate
生成特定的代码
当然也有一些使用的配置项
nest build
用来构建项目 会在dist目录下生成编译后的代码
--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里配置,比如generateOptions、compilerOptions 等
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] |
@HttpCode | Code |
获取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 } } }
|
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 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 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 调试