配置模块案例工程代码讲解——基于 NestJS 7.x

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 配置模块案例工程代码讲解——基于 NestJS 7.x

项目结构

$ tree -a   # 已删除 .git 文件夹
nestjs-config
    ├── docker-compose.yml
    ├── Dockerfile
    ├── .dockerignore
    ├── .env.development
    ├── .eslintrc.js
    ├── .gitignore
    ├── nest-cli.json
    ├── .nvmrc
    ├── package.json
    ├── .prettierrc
    ├── README.md
    ├── src
    │   ├── app.module.ts
    │   ├── main.ts
    │   └── modules
    │       └── config
    │           ├── config.module.ts
    │           ├── constants
    │           │   └── config.constant.ts
    │           ├── registers
    │           │   ├── common.register.ts
    │           │   ├── database.register.ts
    │           │   └── redis.register.ts
    │           ├── services
    │           │   └── config.service.ts
    │           └── validations
    │               └── config.validation.ts
    ├── test
    │   ├── app.e2e-spec.ts
    │   └── jest-e2e.json
    ├── tsconfig.build.json
    ├── tsconfig.build.prod.json
    ├── tsconfig.json
    ├── .vscode
    │   ├── launch.json
    │   └── settings.json
    ├── yarn.lock
    └── .yarnrc
9 directories, 29 files点击复制复制失败已复制


按照文档,先生成本地配置文件

$ cp .env.development .env点击复制复制失败已复制


主要讲解 src 文件夹下的文件,其他文件参考nestjs工程配置

$ tree -a 
src
├── app.module.ts   # 主模块
├── main.ts     # 项目入口文件
└── modules 
    └── config
        ├── config.module.ts    # 自定义Config模块声明文件
        ├── constants       
        │   └── config.constant.ts  # config 模块常量文件
        ├── registers
        │   ├── common.register.ts  # 公共配置注册文件
        │   ├── database.register.ts    # 数据库(Postgresql)配置注册文件
        │   └── redis.register.ts   # Redis配置注册文件
        ├── services
        │   └── config.service.ts   # 自定义配置服务
        └── validations
            └── config.validation.ts    # 配置校验
6 directories, 9 files点击复制复制失败已复制


功能实现

配置校验功能

项目一般都会依赖一些必要的配置才能正常运行,比如项目需要连接数据库,然而配置文件中没有数据库的连接地址,那么项目就不应该启动,而是在启动前直接报错,退出。


承接上一篇笔记,采用 joi 这个库进行配置校验,代码实现在 src/modules/config/validations/config.validation.ts 这个文件中,内容如下:

展开查看源码

import * as Joi from 'joi';
/** .env文件校验 */
export const ConfigValidation = Joi.object({
  NODE_ENV: Joi.string().valid('development', 'production', 'test', 'staging').default('development'),
  // Common
  COMMON_JWT_EXPIRES_IN: Joi.number().default(720000),
  COMMON_PRINT_USER_ACTIVITY_LOG: Joi.boolean().default(true),
  COMMON_PRINT_SYSTEM_LOG: Joi.boolean().default(true),
  COMMON_ENABLE_SWAGGER: Joi.boolean().default(true),
  COMMON_PASSWORD_SALT: Joi.string().required(),
  COMMON_PORT: Joi.number().default(3000),
  // Postgresql数据库配置
  DATABASE_TYPE: Joi.string().default('postgres'),
  DATABASE_HOST: Joi.string().required(),
  DATABASE_PORT: Joi.number().default(5432),
  DATABASE_USERNAME: Joi.string().required(),
  DATABASE_PASSWORD: Joi.string().required(),
  DATABASE_DATABASE: Joi.string().required(),
  DATABASE_SYNCHRONIZE: Joi.boolean().default(false),
  DATABASE_LOGGING: Joi.boolean().default(false),
  // Redis Token数据库配置
  REDIS_TOKEN_NAME: Joi.string().required(),
  REDIS_TOKEN_DB: Joi.number().default(1),
  REDIS_TOKEN_HOST: Joi.string().required(),
  REDIS_TOKEN_PORT: Joi.number().default(6379),
  REDIS_TOKEN_PASSWORD: Joi.string().allow('').default(''),
  REDIS_TOKEN_KEY_PREFIX: Joi.string().required(),
  // Redis 分布式锁数据库配置
  REDIS_LOCK_NAME: Joi.string().required(),
  REDIS_LOCK_DB: Joi.number().default(2),
  REDIS_LOCK_HOST: Joi.string().required(),
  REDIS_LOCK_PORT: Joi.number().default(6379),
  REDIS_LOCK_PASSWORD: Joi.string().allow('').default(''),
  REDIS_LOCK_KEY_PREFIX: Joi.string().required()
});


关于 Joi 这个库的具体用法可查看。虽然校验写完了,但是如何将这个校验逻辑放到代码中让其执行仍需其他配置,在下面会介绍配置的地方。


与NestJS提供的包 @nestjs/config 整合

NestJS 本身提供了 config 功能,官方文档,这篇文档写的算是官方文档中比较全的了,不得不吐槽官方文档的简陋,做了很多功能,却不写出来。。。


通过官方文档,会发现 @nestjs/config 这个包提供一个 ConfigService 的可注入类,并且会将配置文件中的内容注册成属性(当然,需要写 register 配置文件,可阅读 src/modules/config/registers 目录下的文件来查看实现细节,在此不做展开)。我们在此基础上进行魔改,魔改的原因是因为我们通过其默认的

ConfigService 拿到的字段类型未知,导致类型推断不友好,需要指定泛型才行,同时还需要输入字符串来获取。默认方式如下所示:

this.configService.get<number>('common.jwtExpiresIn');点击复制复制失败已复制


所以我在官方的基础上有封装了一层,文件为: src/modules/config/services/config.service.ts ,内容如下

展开查看源码

import { Injectable } from '@nestjs/common';
import { ConfigService as NestConfigService } from '@nestjs/config';
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { RedisModuleOptions } from 'nestjs-redis';
@Injectable()
export class ConfigService {
  constructor(private readonly nestConfigService: NestConfigService) {}
  /** 公共配置 */
  get common() {
    return {
      /** jwt token有效期,单位:毫秒 */
      jwtExpiresIn: this.nestConfigService.get<number>('common.jwtExpiresIn'),
      /** 是否打印用户活动日志 */
      printUserActivityLog: this.nestConfigService.get<number>('common.printUserActivityLog'),
      /** 是否打印系统日志 */
      printSystemLog: this.nestConfigService.get<number>('common.printSystemLog'),
      /** 密码盐 */
      passwordSalt: this.nestConfigService.get<number>('common.passwordSalt'),
      /** 是否启用Swagger */
      enableSwagger: this.nestConfigService.get<boolean>('common.enableSwagger'),
      /** 程序占用端口号 */
      port: this.nestConfigService.get<number>('common.port')
    };
  }
  /** postgresql数据库配置 */
  get database(): TypeOrmModuleOptions {
    return {
      /** 数据库类型 */
      type: this.nestConfigService.get<'postgres'>('database.type'),
      /** 数据库连接Host */
      host: this.nestConfigService.get<string>('database.host'),
      /** 数据库连接端口 */
      port: this.nestConfigService.get<number>('database.port'),
      /** 数据库连接用户名 */
      username: this.nestConfigService.get<string>('database.username'),
      /** 数据库连接密码 */
      password: this.nestConfigService.get<string>('database.password'),
      /** 要连接的数据库名称 */
      database: this.nestConfigService.get<string>('database.database'),
      /** 代码实体目录 */
      entities: [__dirname + '../../../**/*.entity{.ts,.js}'],
      /** 是否同步数据库 */
      synchronize: this.nestConfigService.get<boolean>('database.synchronize'),
      /** 是否打印orm */
      logging: this.nestConfigService.get<boolean>('database.logging')
    };
  }
  /** redis数据库配置 */
  get redis() {
    return {
      /** token存储数据库配置 */
      token: this.redisTokenConfig(),
      /** 分布式锁数据库配置 */
      lock: this.redisLockConfig()
    };
  }
  private redisTokenConfig(): RedisModuleOptions {
    return {
      /** 自定义服务名称 */
      name: this.nestConfigService.get<string>('redis.token.name'),
      /** 数据库Host */
      host: this.nestConfigService.get<string>('redis.token.host'),
      /** 数据库端口 */
      port: this.nestConfigService.get<number>('redis.token.port'),
      /** 数据库编号0-15 */
      db: this.nestConfigService.get<number>('redis.token.db'),
      /** 登录密码 */
      password: this.nestConfigService.get<string>('redis.token.password'),
      /** Key前缀 */
      keyPrefix: this.nestConfigService.get<string>('redis.token.keyPrefix')
    };
  }
  private redisLockConfig(): RedisModuleOptions {
    return {
      /** 自定义服务名称 */
      name: this.nestConfigService.get<string>('redis.lock.name'),
      /** 数据库Host */
      host: this.nestConfigService.get<string>('redis.lock.host'),
      /** 数据库端口 */
      port: this.nestConfigService.get<number>('redis.lock.port'),
      /** 数据库编号0-15 */
      db: this.nestConfigService.get<number>('redis.lock.db'),
      /** 登录密码 */
      password: this.nestConfigService.get<string>('redis.lock.password'),
      /** Key前缀 */
      keyPrefix: this.nestConfigService.get<string>('redis.lock.keyPrefix')
    };
  }
}


这样我们在调用的时候就可以通过如下方式了:

const port = this.configService.common.port;    点击复制复制失败已复制


以上写法可以自动推断出 port 的类型为 number ,并且不用手写字符串输入,注意:这里的 configService 是我们封装的,不是 @nestjs/config 这个包提供的 ConfigService


配置整合

虽然大体思路已经写完了,但是如何将这些文件组装,以及应用仍需一定的修改。先看一下 src/modules/config/config.module.ts 这个文件,内容如下:

import { Global, Module } from '@nestjs/common';
import { ConfigModule as NestConfigModule } from '@nestjs/config';
import { ConfigValidation } from './validations/config.validation';
import { CommonConfigRegister } from './registers/common.register';
import { DatabaseConfigRegister } from './registers/database.register';
import { RedisConfigRegister } from './registers/redis.register';
import { ConfigProvider } from './constants/config.constant';
import { ConfigService } from './services/config.service';
@Global()
@Module({
  imports: [
    NestConfigModule.forRoot({
      validationSchema: ConfigValidation,
      validationOptions: {
        allowUnknown: true,
        abortEarly: true
      },
      load: [CommonConfigRegister, DatabaseConfigRegister, RedisConfigRegister]
    })
  ],
  providers: [
    {
      provide: ConfigProvider,
      useClass: ConfigService
    }
  ],
  exports: [ConfigProvider]
})
export class ConfigModule {}点击复制复制失败已复制


这里面因为名称冲突的原因,将 @nestjs/config 中的 ConfigModule 类重命名为 NestConfigModule ,其中 forRoot() 方法里面参数属性 validationSchema 是用来校验配置的逻辑,参数属性 load 是用来注册到 @nestjs/config 中的 ConfigModule 类属性的声明。


接下来看一下如何动态配置其他模块,这里拿 typeormnestjs-redis 来举例。查看 src/app.module.ts 文件,内容如下:

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { RedisModule } from 'nestjs-redis';
import { ConfigModule } from './modules/config/config.module';
import { ConfigProvider } from './modules/config/constants/config.constant';
import { ConfigService } from './modules/config/services/config.service';
@Module({
  imports: [
    TypeOrmModule.forRootAsync({
      useFactory: (configService: ConfigService) => configService.database,
      inject: [ConfigProvider]
    }),
    RedisModule.forRootAsync({
      useFactory: (configService: ConfigService) => [configService.redis.token, configService.redis.lock],
      inject: [ConfigProvider]
    }),
    ConfigModule
  ]
})
export class AppModule {}点击复制复制失败已复制


大功告成。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
JavaScript
若依代码生成自带导入功能
若依代码生成自带导入功能
551 0
|
3月前
|
前端开发 Java C++
超简单使用Vite+Vue3构建共享开发和分模块打包的前端项目
使用Vite和Vue3构建支持共享组件和分模块独立打包的前端项目的方法。
433 0
超简单使用Vite+Vue3构建共享开发和分模块打包的前端项目
|
3月前
|
Java Maven 微服务
Java 项目工程搭建 --创建子模块(依赖父工程)
Java 项目工程搭建 --创建子模块(依赖父工程)
63 1
|
5月前
|
前端开发 测试技术 API
vite项目怎么build打包成不同环境的代码?从而适配不同环境api接口
vite项目怎么build打包成不同环境的代码?从而适配不同环境api接口
325 0
|
6月前
|
JavaScript 前端开发
若依 自定义实现导入功能
若依 自定义实现导入功能
147 1
|
JavaScript 前端开发 Java
项目引入文件的常见报错
项目引入文件的常见报错
83 5
|
JSON 小程序 JavaScript
小程序项目结构与组件基础
小程序项目结构与组件基础
97 0
小程序项目结构与组件基础
|
Java 数据库
项目的模块以及每一个模块的作用
项目的模块以及每一个模块的作用
项目的模块以及每一个模块的作用
|
JavaScript 前端开发 API
前端工程自动化——动态导入
介绍webpack的require.context用法和实例
|
开发者 Python
导入模块 | 学习笔记
快速学习导入模块
导入模块 | 学习笔记