AOP(面向切面编程)

  • 所谓面向切面编程,就是对OOP(面向对象编程)的一种补充,我们都知道,在面向对象编程的思想中,我们某一个类却笑功能直接在对应的类中添加即可,但是如果其他类中同样缺少这些功能呢?拿到我们又要复制粘贴到另一个类中去吗? 这里就引出了OOP,在一个项目中类似错误处理,权限校验这些功能一般是集中处理的,在不影响其他业务模块(类)功能的情况下,这里的切面我们可以认为是同等与每一个类(业务功能模块)中的位置,我们在拓展对应的功能时,无需在给模块中修改,直接插入即可,一句话就是: AOP能在不破坏封装功能的前提下,额外新增其他功能。而Nestjs中内置提供了许多AOP功能模块,类似于中间件,管道,错误处理,守卫拦截器等..

IOC(控制反转)以及DI(依赖注入)

  • IOC是一种思想,一种设计模式,用于降低代码之间的耦合度,基本思想是借助第三方来实现具有依赖关系的对象之间的解耦; DIIOC的一种具体实现,它允许字类外创建依赖对象,并通过不同的方式将这些对象提供给类

下面进行代码演示:

// 1. 创建一个类
class IPhone {
playGame(name: string) {
console.log(`${name} play game `);
}
}

/*
2. 在第二个类中使用第一个类
Stduent -> play -> IPhone强依赖关系
IPhone依赖与Student -> 解耦
*/
class Student {
constructor(private name: string) {}
play() {
const iphone = new IPhone();
iphone.playGame(this.name);
}
}

const student = new Student('lam');
student.play(); /// lam play game
/*
此时第一个类与第二个类是具有强依赖关系的
*/


/*
依赖注入
这里我创建一个接口,限定某一个类必须接受一个带有 playGame 方法的参数
*/

// 这个接口就是第三方
interface Phone {
playGame: (name: string) => void;
}

class DIStudent {
constructor(private name: string, private phone: Phone) {
this.phone = phone;
this.name = name;
}
play() {
this.phone.playGame(this.name);
}
// ...
}


class Android implements Phone {
playGame(name: string) {
console.log(`${name} use android play game `);
}
}
const student1 = new DIStudent('lam1', new Android());
student1.play();

const student2 = new DIStudent('lam2', new IPhone());
student2.play();

Nestjs中的生命周期

image

模块化

  • 我们都知道,前端领域有组件化这一概念,而Nestjs中的模块化与组件化也非常的相似,在Nestjs中,所有的功能均由模块来组织(组装)的,在一个Nestjs项目中,需要使用ts中的装饰器来描述模块(@Module),且模块中有4大属性,分别为imports,providers,controllers以及exports
  1. imports以及exports可以认为是ES6中的import以及export用于对应模块的导入以及导出
  2. controllers用于处理请求
  3. providers是用于处理业务逻辑以及联通数据库的(services)

image
image

官方推荐的项目环境配置方法

  1. 安装
npm i --save @nestjs/config
  1. 在根目录下中新建.env以及enum文件夹,以及在enum文件夹下新建config.ts文件,使.env文件与config.ts文件建立映射关系
├── src
│ ├── enum
│ │ └── config.ts
│ └── index.ts
└── .env
// .env 全局配置环境变量
# 全局配置环境(一般用于全局配置)
# 局部配置的化推荐在对应的模块文件夹中新建枚举类型

DB = 'Mysql'
DB_HOST = '127.0.0.1'
// config.ts
export enum ConfigEnum {
DB = 'DB', // 切记 这里是指向 .env 文件中的 DB
DB_HOST = 'DB_HOST', // 切记 这里是指向 .env 文件中的 DB_HOST
}

/*
这样设置的好处是当我们修改 .env 文件中的环境变量时,只需要再次修改
enum中的变量即可(也就是这里),无需改动业务代码
*/
  1. 获取环境变量的两种方法
/*
方法1 在全局的 app.module 中设置
import ...
*/
import { ConfigModule } from '@nestjs/config'; // 引入环境配置模块

@Module({
imports: [
/*
连接数据库
....
*/
// 引入全局环境配置模块
ConfigModule.forRoot({
isGlobal: true, // 设置为全局环境引入
}),
BoysModule,
],
controllers: [],
providers: [],
})

/*
其他的controller中
import ...
*/
// 引入 ConfigService
import { ConfigService } from '@nestjs/config/dist';
import { ConfigEnum } from '../enum/config';
@Controller('boys')
export class BoysController {
constructor(
// 配置 ConfigService
private ConfigService: ConfigService,
) {
/*
BoysService: BoysService 等价于 this.BoysService = new BoysService()
*/
}

@Get()
getBoys(): any {
// 直接获取 .env 中的环境变量
console.log('config', this.ConfigService.get('DB'));
// 通过 config.ts 中的映射关系获取 .env中的环境变量
console.log('config', this.ConfigService.get(ConfigEnum.DB_HOST));
}
}
/*
方法2 :局部引入,在对应的module中引入并使用
import ...
*/
// 局部引入
import { ConfigModule } from '@nestjs/config'; // 引入环境配置模块
import { ConfigEnum } from '../enum/config';

// boys 的 Module
@Module({
imports: [ConfigModule.forRoot()], // 使用方法操作实体
controllers: [BoysController],
providers: [],
})

// 使用 controller
import { ConfigService } from '@nestjs/config/dist';
@Controller('boys')
export class BoysController {
constructor(
// 配置 ConfigService
private ConfigService: ConfigService,
) {
/*
BoysService: BoysService 等价于 this.BoysService = new BoysService()
*/
}

@Get()
getBoys(): any {
// 直接获取 .env 中的环境变量
console.log('config', this.ConfigService.get('DB'));
// 通过 config.ts 中的映射关系获取 .env中的环境变量
console.log('config', this.ConfigService.get(ConfigEnum.DB_HOST));
}
}

下面再来讲讲nestjs/config的进阶使用

  1. 根据生产环境来自动获取环境数据(envFilePath)
// 环境参数 
/* 1. .env.development(开发环境) */
DB = 'Mysql-dev'
DB_HOST = '127.0.0.1-dev'

/* 2. .env.production(生产环境) */
DB = 'Mysql-prod'
DB_HOST = '127.0.0.1-prod'
/*
app.module.ts

在官方的 ConfigMoudle 中,除了提供 isGlobal 属性外,还有 envFilePath属性
他能够使得醒目加载对应的 .env 文件,这样就方便我们根据对应的环境来加载对应的
环境数据,如开发环境就加载 .env.development , 生产环境就加载 .env.production

import ...
*/

import { ConfigModule } from '@nestjs/config'; // 引入环境配置模块

// 在 package.json 中配置运行脚本时通过第三方库 cross-env配置当前环境(生产,开发)
const envFilePath = `.env.${process.env.NODE_ENV || `development`}`;

@Module({
imports: [
// 配置环境变量
ConfigModule.forRoot({
isGlobal: true, // 设置为全局环境引入
envFilePath,
}),
BoysModule,
],
controllers: [],
providers: [],
})
// ...
/*
package.json
配置脚本时同时修改环境变量 cross-env
*/
"scripts": {
// ...
"start:dev": "cross-env NODE_ENV=development nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "cross-env NODE_ENV=production node dist/main",
// ...
},
// 在其他模块中使用
import { ConfigService } from '@nestjs/config/dist';
import { ConfigEnum } from '../enum/config';
@Controller('boys')
export class BoysController {
constructor(
// 配置 ConfigService
private ConfigService: ConfigService,
) {
/*
BoysService: BoysService 等价于 this.BoysService = new BoysService()
*/
}

@Get()
getBoys(): any {
// 直接获取 .env 中的环境变量
console.log('config', this.ConfigService.get('DB'));
// 通过 config.ts 中的映射关系获取 .env中的环境变量
console.log('config', this.ConfigService.get(ConfigEnum.DB_HOST));
}
}

结果展示

image
image

  1. 使用 .env 来配置公共变量,在上面提到的生产环境以及开发环境中使用的.env.development/production等,那么当这个项目中有非常多的公共环境变量是时,我们就需要在两个.env(开发环境,生产环境)文件中重复定义,这样就会显得非常的冗余,此时我们就可以使用到共享配置文件(.env)

这里需要下载第三方库dotenv

npm i dotenv
/*
app.module.ts
使用 load 来加载 生产环境以及开发环境之间的公共环境变量
import ...
*/
import { ConfigModule } from '@nestjs/config'; // 引入环境配置模块
import * as dotenv from 'dotenv'; // 引入 dotenv 操作环境变量文件

// 在 package.json 中配置运行脚本时通过第三方库 cross-env配置当前环境(生产,开发)
const envFilePath = `.env.${process.env.NODE_ENV || `development`}`;

@Module({
imports: [
// ...
ConfigModule.forRoot({
isGlobal: true, // 设置为全局环境引入
envFilePath,
load: [() => dotenv.config({ path: '.env' })],// 使用load来加载公共环境变量(自定义环境变量)
}),
BoysModule,
],
controllers: [],
providers: [],
})
# 环境变量
# .env 公共环境属性
DB = 'Mysql'
DB_HOST = '127.0.0.1'

DB_USER = 'lam' # 公共环境属性

# .env.development 开发环境变量
DB = 'Mysql-dev'
DB_HOST = '127.0.0.1-dev'

# DB_USER = 'lam222' # 这里如果单独配置公共环境变量会覆盖掉 .env 中的公共环境变量
// 在其他 controller 中使用
import { ConfigService } from '@nestjs/config/dist';
import { ConfigEnum } from '../enum/config';

@Controller('boys')
export class BoysController {
// 创建一个构造函数 (注入依赖)
constructor(
private ConfigService: ConfigService,
) {
/*
BoysService: BoysService 等价于 this.BoysService = new BoysService()
*/
}

@Get()
getBoys(): any {
console.log(
'config',
this.ConfigService.get(ConfigEnum.DB), // 开发环境简历映射数据
this.ConfigService.get(ConfigEnum.DB_USER),// 公共环境简历映射数据获取
);
return {
message: '跨域请求成功!',
};
}
}
// config.ts 与 .env 中的映射关系
export enum ConfigEnum {
DB = 'DB', // 切记 这里是指向 .env 文件中的 DB
DB_HOST = 'DB_HOST', // 切记 这里是指向 .env 文件中的 DB_HOST
DB_USER = 'DB_USER', // 公共环境变量建立映射
}

/*
这样设置的好处是当我们修改 .env 文件中的环境变量时,只需要再次修改
enum中的变量即可(也就是这里),无需改动业务代码
*/

结果展示:

image

环境变量的参数校验(后面进行数据库的灵活调用)

  • 我们在配置项目的环境变量时,通常是需要进行参数校验的,因为在一些场景下,我们进行环境变量的配置例如数据库的一些参数,端口是数值型,而密码是字符串类型,如果数据类型填错就会在数据入库或者联通数据库时报错,因此我们就需要在配置环境变量时加上参数的校验,Nestjs官方推荐使用Joi这个第三方库结合Nestjs/Config来进行环境变量的参数校验

  • Joi官方文档 Nestjs官方文档

安装

npm install --save joi

app.module.ts中引入

// import ...
import * as Joi from 'joi';

// 在 package.json 中配置运行脚本时通过第三方库 cross-env配置当前环境(生产,开发)
const envFilePath = `.env.${process.env.NODE_ENV || `development`}`;

@Module({
imports: [
// ConfigModule 配置环境变量
ConfigModule.forRoot({
isGlobal: true, // 设置为全局环境引入
envFilePath, // 环境参数文件路径
load: [() => dotenv.config({ path: '.env' })], // 使用load来加载公共环境变量(自定义环境变量)

// 设置规则校验(数据库的连接 - 环境变量)
validationSchema: Joi.object({
NODE_ENV: Joi.string()
.valid('development', 'production')
.default('development'), // 可以设置选项中的一项
DB_PORT: Joi.number().default(3306), // 设置数据库端口号必须为数值型且默认值为3306
DB_HOST: Joi.string().ip(), //还可以判断是不是ip类型地址
}),
}),
BoysModule,
],
controllers: [],
providers: [],
})
export class AppModule {}

在官方文档中还提供自定义的校验规则,需要自己去查看….