Nest Dynamic modules 笔记
Nest Dynamic modules 文档地址👈
记录Dynamic modules是因为确实抽象,文档并没有很详细的指出不同方式创建动态模块的区别
两种不同的动态模块创建方式
- 静态模块
- 传统动态模块方式实现
- 三种不同的方法命名
- 使用ConfigurableModuleBuilder
- 异步动态模块
- 如果想要把动态模块全局化
- 延缓执行动态模块
静态模块
静态模块非常好理解,就是使用imports
关键字导入其他的模块,并在@Module
的imports
属性中加上,代码如下:
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';@Module({imports: [UsersModule],providers: [AuthService],exports: [AuthService],
})
export class AuthModule {}
导入之后便可以使用UsersModule
内的Server
,或者说注入,代码如下:
import { Injectable } from '@nestjs/common';
import { UsersService } from '../users/users.service';@Injectable()
export class AuthService {constructor(private usersService: UsersService) {}/*Implementation that makes use of this.usersService*/
}
注:在Nest
中,如果只是在AuthService
文件内使用import
通过路径导入UsersService
,没有在模块内导入UsersModule
模块,那么在构造函数中加入UsersService
也是报错的,因为Nest
的机制是以模块为单位的
换句话说,想在某个服务下引用其他模块的服务,必须先导入该服务所在的模块
传统动态模块方式实现
动态模块的使用场景,或者说为什么要有动态模块的出现,原因在于开发场景的不同,例如一个系统可能会在不同周期使用不同的数据库
?就好比开发也有生产环境、开发环境、测试环境,不同环境下需要导入的.env
或许有些不同
所以动态模块就是可以根据不同的条件去导入不同的Module
,进而,使用不同的Service
在文档中,讲述的方式是一种倒叙的形式,Let's see how this works. It's helpful if we start from the end-goal of how this might look from the consuming module's perspective, and then work backwards.
- 假设需要导入一个用于配置的模块
静态模式下的代码如下:
@Module({imports: [ConfigModule],controllers: [AppController],providers: [AppService],
})
export class AppModule {}
- 因为是动态的,所以这个
ConfigModule
就应该有一个接口方便开发人员去替换不同的配置,例如可以传入一个配置文件的路径,然后通过fs
便可以读取里面的内容,代码如下:
@Module({imports: [ConfigModule.register({ folder: './config' })],controllers: [AppController],providers: [AppService],
})
export class AppModule {}
- 想象一下,一个模块包含哪些内容?有
imports
、controllers
、providers
和exports
,所以调用ConfigModule.register({ folder: './config' })
返回的就是一个包含上述内容的东西。而ConfigModule
内部包含有一个静态方法register
,代码如下:
import { DynamicModule, Module } from '@nestjs/common';
import { ConfigService } from './config.service';@Module({})
export class ConfigModule {static register(): DynamicModule {return {module: ConfigModule,providers: [ConfigService],exports: [ConfigService],};}
}
- 传递的
{ folder: './config' }
如何进行消费?代码如下:
import { DynamicModule, Module } from '@nestjs/common';
import { ConfigService } from './config.service';@Module({})
export class ConfigModule {static register(options: Record<string, any>): DynamicModule {return {module: ConfigModule,providers: [{provide: 'CONFIG_OPTIONS',useValue: options,},ConfigService,],exports: [ConfigService],};}
}
注:在TypeScript
中,Record<K, T>
是一个内置的工具类型,它用于构建一个对象类型,该对象的所有属性键都属于类型K
,而对应的属性值则属于类型T
注:在Nest
文档中推荐将'CONFIG_OPTIONS'
定义为单独文件中的常量,也就是唯一值
静态方法register
接收一个配置选项,然后作为提供者'CONFIG_OPTIONS'
的值,被注入到了ConfigService
中,代码如下:
import * as dotenv from 'dotenv';
import * as fs from 'fs'; // 前面提到的fs
import * as path from 'path';
import { Injectable, Inject } from '@nestjs/common';
import { EnvConfig } from './interfaces';@Injectable()
export class ConfigService {private readonly envConfig: EnvConfig;// 注入'CONFIG_OPTIONS',拿到{ folder: './config' }constructor(@Inject('CONFIG_OPTIONS') private options: Record<string, any>) {// 根据运行环境找到.env文件const filePath = `${process.env.NODE_ENV || 'development'}.env`;// 构建绝对路径const envFile = path.resolve(__dirname, '../../', options.folder, filePath);// 读取内容,并解析this.envConfig = dotenv.parse(fs.readFileSync(envFile));}// 读取解析内容的方法get(key: string): string {return this.envConfig[key];}
}
这里有一个细节就是为什么要用 dotenv.parse()
,很简单,想想.env
文件是怎么存储内容的,KEY=value
那么至此,就完成了可以通过传入不同的options.folder
,去形成不同的ConfigService
,其他的模块使用的便是动态的ConfigService
三种不同的方法命名
在上面的静态方法中,方法的命名为register
,这是一个社区规范,或者说默认情况下就是这个名字,此外还有两种,分别是forRoot
和forFeature
forRoot
很好理解,Root
是根的意思,该方法是用于全局或者基础配置,是用在AppModule
中的,一般只会调用一次
forFeature
则用于特定功能或者子模块的配置
总的来说,就是通过命名向其他的开发者传达当前这个动态模块是用于什么场景的
使用ConfigurableModuleBuilder
Nest
的官方推荐新手使用ConfigurableModuleBuilder
去搭建动态模块
- 定义接收参数的类型,例如
{ folder: './config' }
,就需要定义folder:string
,代码如下:
export interface ConfigModuleOptions {folder: string;
}
- 调用
ConfigurableModuleBuilder
,构造动态模块,代码如下:
// config.module-definition
import { ConfigurableModuleBuilder } from '@nestjs/common';
import { ConfigModuleOptions } from './interfaces/config-module-options.interface';export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =new ConfigurableModuleBuilder<ConfigModuleOptions>().build();
- 在
config.module.ts
里利用生成的动态模块,代码如下:
import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';
import { ConfigurableModuleClass } from './config.module-definition';@Module({providers: [ConfigService],exports: [ConfigService],
})
export class ConfigModule extends ConfigurableModuleClass {}
这里非常抽象,但我们先看其他的步骤
- 在根模块内,或者说导入动态模块的模块内,代码如下:
@Module({imports: [ConfigModule.register({ folder: './config' }),// or alternatively:// ConfigModule.registerAsync({// useFactory: () => {// return {// folder: './config',// }// },// inject: [...any extra dependencies...]// }),],
})
export class AppModule {} // 这里是根模块
- 在
configService
内,需要将'CONFIG_OPTIONS'
替换为在文件config.module-definition
导出的'MODULE_OPTIONS_TOKEN'
,代码如下:
@Injectable()
export class ConfigService {constructor(@Inject(MODULE_OPTIONS_TOKEN) private options: ConfigModuleOptions) { ... }
}
对比在哪?第一个是在config.module.ts
,从
import { DynamicModule, Module } from '@nestjs/common';
import { ConfigService } from './config.service';@Module({})
export class ConfigModule {static register(options: Record<string, any>): DynamicModule {return {module: ConfigModule,providers: [{provide: 'CONFIG_OPTIONS',useValue: options,},ConfigService,],exports: [ConfigService],};}
}
变成了👇
import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';
import { ConfigurableModuleClass } from './config.module-definition';@Module({providers: [ConfigService],exports: [ConfigService],
})
export class ConfigModule extends ConfigurableModuleClass {}
减去了return
里面的代码和静态方法return
,笔者不觉得这样更适合新手,非常抽象
第二个变化就是'CONFIG_OPTIONS'
变成了'MODULE_OPTIONS_TOKEN'
所以ConfigurableModuleBuilder
的作用就是,直接帮开发者省去了构造register
方法和return
的流程,但抽象的是在ConfigModule
里的register
哪来的?所以这就是抽象,在阅读官方文档时真的绕
从这里也可以看出,使用ConfigurableModuleBuilder
后,在AppModule
使用的方法是register
,所以说明这是默认的命名,如果要改名怎么办?比方说使用forRoot
,需要调用.setClassMethodName()
,代码如下:
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =new ConfigurableModuleBuilder<ConfigModuleOptions>().setClassMethodName('forRoot').build();
异步动态模块
在上面的例子中,是主动的传入一个配置项,例如路径,但如果配置需要异步获取呢?需要等待Promise
解析后才能得到呢?
Nest
提供了registerAsync
(或者forRootAsync
)方法,代码如下:
@Module({imports: [ConfigModule.registerAsync({useClass: ConfigModuleOptionsFactory,}),],
})
export class AppModule {}
这里抽象的是,ConfigModuleOptionsFactory
必须要有一个create
方法,如果没有,那么会在解析的过程中出现问题,并且文档并没有给出ConfigModuleOptionsFactory
究竟是怎么样的,但可以模拟一下,代码如下:
import { Injectable } from '@nestjs/common';
import { ConfigModuleOptions } from './config.module-definition';@Injectable()
export class ConfigModuleOptionsFactory {async create(): Promise<ConfigModuleOptions> {// 这里可以进行异步操作,例如从数据库或文件系统加载配置return {folder: 'path/to/config/folder',};}
}
这个create
方法可以自定义取名,例如改成createConfigOptions
,代码如下:
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } =new ConfigurableModuleBuilder<ConfigModuleOptions>().setFactoryMethodName('createConfigOptions').build();
一切的目的,都是让开发者通过命名清楚这个动态模块是干什么的,见名知意
如果想要把动态模块全局化
代码如下:
export const { ConfigurableModuleClass, MODULE_OPTIONS_TOKEN } = new ConfigurableModuleBuilder<ConfigModuleOptions>().setExtras({isGlobal: true,},(definition, extras) => ({...definition,global: extras.isGlobal,}),).build();
.setExtras()
的第一个形参名字是extras
,所以第二个函数参数里的extras.isGlobal
就是true
,这样设置之后,就可以在AppModule
内加多一个isGlobal
属性,代码如下:
@Module({imports: [ConfigModule.register({isGlobal: true,folder: './config',}),],
})
export class AppModule {}
延缓执行动态模块
在Dynamic modules
的最后一个章节中,提供了延缓执行动态模块的方法,代码如下:
import { Module } from '@nestjs/common';
import { ConfigService } from './config.service';
import { ConfigurableModuleClass, ASYNC_OPTIONS_TYPE, OPTIONS_TYPE } from './config.module-definition';@Module({providers: [ConfigService],exports: [ConfigService],
})
export class ConfigModule extends ConfigurableModuleClass {static register(options: typeof OPTIONS_TYPE): DynamicModule {return {// your custom logic here...super.register(options),};}static registerAsync(options: typeof ASYNC_OPTIONS_TYPE): DynamicModule {return {// your custom logic here...super.registerAsync(options),};}
}
在options
传进来之后,在返回模块之前,可以执行想要的逻辑,例如日志记录,调用了哪个动态模块,就可以在这里加上了
相关文章:
Nest Dynamic modules 笔记
Nest Dynamic modules 文档地址👈 记录Dynamic modules是因为确实抽象,文档并没有很详细的指出不同方式创建动态模块的区别 两种不同的动态模块创建方式 静态模块传统动态模块方式实现三种不同的方法命名使用ConfigurableModuleBuilder异步动态模块如果…...

生成式AI、大模型、多模态技术开发与应用学习清单
学习目的: 了解AIGC发展现状与核心技术。 掌握Transformer核心开发技术。掌握向量数据库的工作原理、检索算法、主要开源数据库。掌握大模型调用、微调方法。掌握以GPT大语言模型为基础的工作原理。 掌握AIGC技术在跨模态领域的应用技术。了解GPT提示工程和AIGC的安…...

STM32 CubeMx HAL库 独立看门狗IWDG配置使用
看门狗这里我就不多介绍了,能搜到这篇文章说明你了解 总之就是一个单片机重启程序,设定好超时时间,在超时时间内没有喂狗,单片机就会复位 主要应用在单片机异常重启方面,比如程序跑飞(注意程序跑飞时你就…...
网络安全渗透测试概论
渗透测试,也称为渗透攻击测试是一种通过模拟恶意攻击者的手段来评估计算机系统、网络或应用程序安全性的方法。 目的 旨在主动发现系统中可能存在的安全漏洞、脆弱点以及潜在风险,以便在被真正的恶意攻击者利用之前,及时进行修复和加固&…...

【大数据技术基础】【记录Ubuntu 16.04升级到18.04】Ubuntu的一个版本升级到另一个版本
在 Ubuntu 操作系统中进行软件更新和系统升级 Ubuntu Kylin 16.04 LTS 系统进行系统升级到 Ubuntu 18.04.6 LTS 版本 升级提示:系统弹出提示框,告知用户有新版本的 Ubuntu 可用,询问用户是否想要升级。 认证窗口:显示了一个认证…...

知识库系统,集成neo4j,集成activiti工作流,集成es全文检索,知识图谱血缘关系,nlp知识库
一、项目介绍 一款全源码,可二开,可基于云部署、私有部署的企业级知识库云平台,一款让企业知识变为实打实的数字财富的系统,应用在需要进行文档整理、分类、归集、检索、分析的场景。 为什么建立知识库平台? 助力企业…...

批量合并多个Excel到一个文件
工作中,我们经常需要将多个Excel的数据进行合并,很多插件都可以做这个功能。但是今天我们将介绍一个完全免费的独立软件【非插件】,来更加方便的实现这个功能。 准备Excel 这里我们准备了两张待合并的Excel文件 的卢易表 打开的卢易表软件…...

CNCF云原生生态版图-项目和产品综合分析
CNCF云原生生态版图-项目和产品综合分析 CNCF云原生生态版图-项目和产品综合分析整体统计分析中国研发人员贡献项目和产品其中,纳入 CNCF 管理的开源项目 链接 CNCF云原生生态版图-项目和产品综合分析 整体统计分析 在对云原生技术选型时,优先选择经过 …...

MySQL生产环境备份脚本
全量备份脚本,其中BakDir,ZlbakDir,LogFile需要自己创建 #!/bin/bash export LANGen_US.UTF-8# 指定备份目录 BakDir/root/beifen/data/mysqlbak/data/allbak # 指定增量备份目录 ZlbakDir/root/beifen/data/mysqlbak/data/zlbak # 备份日志…...
leetcode 3224. 使差值相等的最少数组改动次数
题目链接:3224. 使差值相等的最少数组改动次数 题目: 给你一个长度为 n 的整数数组 nums ,n 是偶数 ,同时给你一个整数 k 。 你可以对数组进行一些操作。每次操作中,你可以将数组中任一元素替换为 0 到 k 之间的任一…...

多线程动态库里面调用静态库分配内存函数导致的崩溃cltp汇编指令导致
1、概述 有这样的一个场景,我有一个动态库myso.so里面有函数start_crash(),用到静态库的内存分配函数,其实静态库里面的static.a 里面就封装了一个函数叫system_malloc(),函数返回的是分配的内存地址,然后发现,我在测试demo里面创…...
力扣刷题TOP101: 31.BM38 在二叉树中找到两个节点的最近公共祖先
目录: 目的 思路 复杂度 记忆秘诀 python代码 目的: 给定一棵二叉树(保证非空)以及这棵树上的两个节点对应的val值 o1 和 o2,请找o1 和 o2 的最近公共祖先节点。 思路 这个任务目和上一题在二叉搜索树中找到两个节点的最近公共祖先有点类…...
前端项目打包部署
打包和部署前端项目是将开发环境中的代码转化为生产环境可直接运行的静态文件,并将其部署到服务器上的过程。 # 项目打包 pnpm run build# 上传文件至远程服务器 将本地打包生成的 dist 目录下的所有文件拷贝至服务器的 /usr/share/nginx/html 目录。# nginx.cofig…...

《CSS 知识点》大屏卡片布局思路:弹性布局 flex-grow
思路 大屏左右两侧高宽一致,内部卡片可按比例设置! 使用弹性布局和属性 flex-grow 设置比例;间隔使用 margin-bottom 设置,最后一个卡片不设置; 效果如图 代码说明 CSS代码 26 - 30,左右两侧设置弹性布…...

nVisual 登录页页面配置说明
一、概述 nVisual登录页面可根据具体客户需要通过public\config\access.js文件进行自定义配置。页面可以大致分为4个部分,头部、底部、可移动区域以及页面中间的信息填写区域。其中头部和底部又包含头部左侧、头部中间、头部右侧、底部左侧、底部中间、底部右侧六个…...

后端接受前端传递数组进行批量删除
问题描述:当我们需要做批量删除功能的时候,我们循环单次删除的接口也能进行批量删除,但要删除100条数据就要调用100次接口,或者执行100次sql,这样系统开销是比较大的,那么我们直接采用接收的数组格式数据sq…...

拍频实例 - 一组恒力矩电流采样数据
这是一组功率电机的感应电流波形。加载了重载恒力矩设备。你能看到什么? 首先,时间轴的坐标是对的,9.9~10.0秒,单位是秒,100ms有5个波形,所以是20ms一个波形。这是50Hz的信号。频差就体现为幅度的周期起伏…...
Jvm之NativeMemoryTracking 使用
开启 Native Memory Tracking 通过 -XX:NativeMemoryTracking 开启: -XX:NativeMemoryTrackingoff:这是默认值,即关闭 Native Memory Tracking -XX:NativeMemoryTrackingsummary: 开启 Native Memory Tracking,但是仅仅按照各个 JVM 子系统…...
PKCS#7、Bit padding(位填充)、Byte padding(字节填充)、Zero padding(零填充)
PKCS#7、Bit padding(位填充)、Byte padding(字节填充)、Zero padding(零填充)是密码学常见的填充方式。 Bit padding(位填充): 位填充可以应用于任意长度的消息。在消息…...

R语言学习笔记-1
1. 基础操作和函数 清空环境:rm(list ls()) 用于清空当前的R环境。 打印输出:print("Hello, world") 用于输出文本到控制台。 查看已安装包和加载包: search():查看当前加载的包。install.packages("package_na…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...

门静脉高压——表现
一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构:由肠系膜上静脉和脾静脉汇合构成,是肝脏血液供应的主要来源。淤血后果:门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血,引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …...

HTTPS证书一年多少钱?
HTTPS证书作为保障网站数据传输安全的重要工具,成为众多网站运营者的必备选择。然而,面对市场上种类繁多的HTTPS证书,其一年费用究竟是多少,又受哪些因素影响呢? 首先,HTTPS证书通常在PinTrust这样的专业平…...

【大模型】RankRAG:基于大模型的上下文排序与检索增强生成的统一框架
文章目录 A 论文出处B 背景B.1 背景介绍B.2 问题提出B.3 创新点 C 模型结构C.1 指令微调阶段C.2 排名与生成的总和指令微调阶段C.3 RankRAG推理:检索-重排-生成 D 实验设计E 个人总结 A 论文出处 论文题目:RankRAG:Unifying Context Ranking…...
【Ftrace 专栏】Ftrace 参考博文
ftrace、perf、bcc、bpftrace、ply、simple_perf的使用Ftrace 基本用法Linux 利用 ftrace 分析内核调用如何利用ftrace精确跟踪特定进程调度信息使用 ftrace 进行追踪延迟Linux-培训笔记-ftracehttps://www.kernel.org/doc/html/v4.18/trace/events.htmlhttps://blog.csdn.net/…...
CppCon 2015 学习:Simple, Extensible Pattern Matching in C++14
什么是 Pattern Matching(模式匹配) ❝ 模式匹配就是一种“描述式”的写法,不需要你手动判断、提取数据,而是直接描述你希望的数据结构是什么样子,系统自动判断并提取。❞ 你给的定义拆解: ✴ Instead of …...