Nestjs 集成 config module 与 nacos 实现配置化统一

文章目录

  • Nestjs 集成 config module 与 nacos 实现配置化统一
    • 前置简介
    • 配置获取流程
    • 代码实例
      • 结构目录
      • 文件代码
        • main.ts
        • app.module.ts
        • config.module.ts
        • nacos.manage.ts
        • nacos.configuration.ts
        • env.configuration.ts
        • core.configuration.ts
        • config.interface.ts
      • APP中使用

Nestjs 集成 config module 与 nacos 实现配置化统一

一定要看官方文档

nacos: https://nacos.io/zh-cn/docs/what-is-nacos.html

nacos-nodejs: https://github.com/nacos-group/nacos-sdk-nodejs

nestjs config: https://docs.nestjs.com/techniques/configuration#configuration

前置简介

首先我们的项目会分为几个环境: 本地\测试\线上, 之前的项目代码配置都是根据 ts 文件中写死固定的. 但这样会带来新的问题, 配置无法灵活获取, 配置信息暴露不是很安全, 新增配置时需要新增修改代码配置文件.

所以在之后, 项目整体采用了 nestjs 的 config 配置, 统一配置管理, 依赖注入的方式去管理配置.

但后续发现, 在采用环境变量的方式去注入配置, 让nestjs config 管理时, 带来了新的问题. 环境变量在服务器中保存的越来越多, 更加不好管理, 配置.env 文件时, 也会有此问题. 于是将 nacos 集成至了nestjs, 用 nacos 来统一管理配置信息. 

所以这篇文章, 主要记录的就是 nacos 与 config 在 nestjs 中的应用.

配置获取流程

安装等基础的操作, 需要查看 [官方文档][https://docs.nestjs.com/techniques/configuration#configuration]

整个的项目配置流程大概是这样的:
	- 服务器或者项目设置环境变量: NACOS_HOST, NACOS_PORT, PROJECT_ENV
	- 项目启动后, nestjs config 模块会异步连接 nacos, 获取其中的配置信息
    - nacos 上会根据 PROJECT_ENV 来区分不同的环境, 这样就可以多环境配置
    - config 获取配置信息后, 会load config 中
    - 使用 config 时, 需要依赖注入至全局 module 中, ConfigService 即可使用

代码实例

如下代码省略了部分模块, 只记录 config 相关的使用.

结构目录

- server_pro
	- config
		- configurations
			env.configuration.ts
			nacos.configuration.ts
			core.configuration.ts
		config.interface.ts
		config.module.ts
	- src 
		- apps
		- logger
		- packages
			- nacosCli
				nacos.manage.ts
		app.module.ts
		main.ts

文件代码

main.ts

import { ConfigService } from '@nestjs/config'

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule)
  // 在 main 中使用, 直接 app.get() 即可
  const configService = app.get(ConfigService)
  console.log(configService)

  const options = new DocumentBuilder()
    .setTitle('Vmp Server')
    .setDescription('The VMP Server API description')
    .setVersion('0.1')
    .addTag('vmp')
    .build()
  const document = SwaggerModule.createDocument(app, options)
  SwaggerModule.setup('api', app, document)

  await app.listen(configService.get('HttpServerConfig').port)
  if (module.hot) {
    module.hot.accept()
    module.hot.dispose(() => app.close())
  }
}

bootstrap()


app.module.ts

import { Module } from '@nestjs/common'
import { TypeOrmModule } from '@nestjs/typeorm'
import { ConfigService } from '@nestjs/config'

@Module({
  imports: [
    // 这里自定义了 config 模块
    VmpConfigModule,
    // 使用的时候, 需要异步注入 typeorm 中
    TypeOrmModule.forRootAsync({
      useFactory: (config: ConfigService) => config.get('MysqlConfig'),
      inject: [ConfigService]
    }),
  ],
  controllers: [AppController],
  providers: [AppService]
})
export class AppModule {}

config.module.ts

import { Module } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
import { loadNacosConfig } from './configurations/nacos.configuration'

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true,
      cache: true,
      // 由于 nacos 需要异步获取, 这里使用 load 的异步方式.
      load: [async () => loadNacosConfig()]
      // 这里可以用指定 .env 文件的方式
      // load: [loadEnvConfig] 
    })
  ]
})
export class VmpConfigModule {}

nacos.manage.ts

用于连接 nacos, 进行配置信息的获取

import * as nacos from 'nacos'

export class NacosManager {
  private client: any
  private DATA_ID = ''
  private GROUP = ''
  private NAMESPACE = process.env.PROJECT_ENV ? process.env.PROJECT_ENV : 'local'
  private SERVER_ADDR = `${process.env.NACOS_HOST}:${process.env.NACOS_PORT}`

  constructor() {
    if (process.env.NACOS_HOST && process.env.NACOS_PORT) {
      this.getClient()
    }
  }

  private async getClient() {
    this.client = new nacos.NacosConfigClient({
      serverAddr: this.SERVER_ADDR,
      namespace: this.NAMESPACE,
      requestTimeout: 6000
    })
  }

  public async getAllConfig() {
    const content = await this.client.getConfig(this.DATA_ID, this.GROUP)
    return JSON.parse(content)
  }
}

nacos.configuration.ts

异步获取 nacos 的配置信息方法文件

import { envVariatesConfigDTO, VmpProjectConfigDTO } from '../config.interface'
import { NacosManager } from '../../src/packages/nacosCli/nacos.manage'
import { SerializeLoadConfig } from './core.configuration'

export const loadNacosConfig = async (): Promise<VmpProjectConfigDTO> => {
  const configManager = new NacosManager()
  const nacosConfigs: envVariatesConfigDTO = await configManager.getAllConfig()
  return SerializeLoadConfig(nacosConfigs)
}

env.configuration.ts

指定 .env 文件时, 可以用它来获取

import { VmpProjectConfigDTO } from '../config.interface'
import { SerializeLoadConfig } from './core.configuration'

export const loadEnvConfig = (): VmpProjectConfigDTO => {
  return SerializeLoadConfig(process.env)
}

core.configuration.ts

这里将处理环境变量的方法抽出来了, 统一管理

import { envVariatesConfigDTO, VmpProjectConfigDTO } from '../config.interface'
import { DbLogger } from '../../src/logger/log4js.core'

export const SerializeLoadConfig = (configs: envVariatesConfigDTO | any): VmpProjectConfigDTO => {
  return {
    ProjectName: configs.PROJECT_NAME,
    ProjectENV: configs.PROJECT_ENV,
    HttpServerConfig: {
      port: Number(configs.SERVER_PORT),
      baseUrl: configs.SERVER_BASE_URL
    },
    MysqlConfig: {
      host: configs.VMP_MYSQL_HOST,
      port: Number(configs.VMP_MYSQL_PORT),
      username: configs.VMP_MYSQL_USERNAME,
      password: configs.VMP_MYSQL_PASSWORD,
      database: configs.VMP_MYSQL_DATABASE,
      type: 'mysql',
      timezone: '+08:00',
      logger: new DbLogger(),
      maxQueryExecutionTime: 1000,
      entities: ['dist/**/*.entity{.ts,.js}']
    },
    AliyunOssConfig: {
      options: {
        accessKeyId: configs.OSS_ALIYUN_ACCESS_KEY,
        accessKeySecret: configs.OSS_ALIYUN_ACCESS_SECRET,
        bucket: configs.OSS_ALIYUN_BUCKET,
        region: configs.OSS_ALIYUN_REGION,
        secure: Boolean(configs.OSS_ALIYUN_SECURE)
      },
      pathPrefix: configs.OSS_ALIYUN_PATH_PREFIX
    },
    permissionLimit: configs.PERMISSION_LIMIT,
    ApiServers: {
      vmp_api_upload: configs.VMP_API_UPLOAD,
      vmp_api_pychart: configs.VMP_API_PYCHART,
      vmp_api_schart: configs.VMP_API_SCHART,
      vmp_api_pdf2svg: configs.VMP_API_PDF2SVG,
      vmp_api_nls: configs.VMP_API_NLS
    },
    RedisConfig: {
      host: configs.VMP_REDIS_HOST,
      port: Number(configs.VMP_REDIS_PORT),
      password: configs.VMP_REDIS_PASSWORD,
      exp: Number(configs.VMP_REDIS_EXP)
    },
    ServiceHost: configs.SERVER_HOST_URL
  }
}

config.interface.ts

参数的类型定义可能不是很优雅, 后续再修改…

import { ConnectionOptions } from 'typeorm'

interface HttpServerConfig {
  port: number
  baseUrl: string
}

export interface AliyunOssOptions {
  accessKeyId: string
  accessKeySecret: string
  bucket: string
  region: string
  secure: boolean
}

export interface AliyunOssConfig {
  options: AliyunOssOptions
  pathPrefix: string
}

export interface ApiServersConfig {
  vmp_api_upload: string
  vmp_api_pychart: string
  vmp_api_schart: string
  vmp_api_pdf2svg: string
  vmp_api_nls: string
}

interface RedisConfig {
  host: string
  port: number
  password: string
  exp: number
}

// vmp 项目中使用的 config 结构体
export interface VmpProjectConfigDTO {
  ProjectName: string
  ProjectENV: string
  HttpServerConfig: HttpServerConfig
  MysqlConfig: ConnectionOptions
  AliyunOssConfig: AliyunOssConfig
  permissionLimit: string
  ApiServers: ApiServersConfig
  RedisConfig: RedisConfig
  ServiceHost: string
}

// 环境变量的结构体
export interface envVariatesConfigDTO {
  PROJECT_NAME: string
  PROJECT_ENV: string

  SERVER_PORT: number
  SERVER_BASE_URL: string

  VMP_MYSQL_HOST: string
  VMP_MYSQL_PORT: number
  VMP_MYSQL_USERNAME: string
  VMP_MYSQL_PASSWORD: string
  VMP_MYSQL_DATABASE: string

  OSS_ALIYUN_ACCESS_KEY: string
  OSS_ALIYUN_ACCESS_SECRET: string
  OSS_ALIYUN_BUCKET: string
  OSS_ALIYUN_REGION: string
  OSS_ALIYUN_SECURE: boolean
  OSS_ALIYUN_PATH_PREFIX: string

  PERMISSION_LIMIT: 'CRM' | 'ALL'

  VMP_API_UPLOAD: string
  VMP_API_PYCHART: string
  VMP_API_SCHART: string
  VMP_API_PDF2SVG: string
  VMP_API_NLS: string

  VMP_REDIS_HOST: string
  VMP_REDIS_PORT: number
  VMP_REDIS_PASSWORD: string
  VMP_REDIS_EXP: number

  SERVER_HOST_URL: string
}

APP中使用

如果想要在项目模块中使用, 参考官方文档即可

@Injectable()
export class ApiConfigService {
  constructor(private configService: ConfigService) {}

  get isAuthEnabled(): boolean {
    return this.configService.get('AUTH_ENABLED') === 'true';
  }
}
// 因为是全局模块, 所以不需要再 import 了, 但官方好像不是很推荐全局

你可能感兴趣的:(nestjs,java,spring,cloud,spring,boot)