NestJS 7.x 折腾记: (2) 环境变量及配置维护

简介: 在写后端的时候,我们一般提倡配置文件分离.所以.env就可以很方面来维护我们的环境变量,封装对应的工厂函数也能组合更复杂的配置!比如我们用镜像(Docker),就可以外部映射配置文件目录;达到不同环境使用差异化配置的需求!(运行时加载是允许的!)其他不多说,往下可以看看我的配置分离思路~~

网络异常,图片无法展示
|


前言


在写后端的时候,我们一般提倡配置文件分离.


所以.env就可以很方面来维护我们的环境变量,


封装对应的工厂函数也能组合更复杂的配置!


比如我们用镜像(Docker),就可以外部映射配置文件目录;


达到不同环境使用差异化配置的需求!(运行时加载是允许的!)


其他不多说,往下可以看看我的配置分离思路~~


实战


安装



# @nestjs/config 内置了dotenv
yarn add @nestjs/config joi
yarn add -D @types/hapi__joi


基础配置介绍


@nestjs/config 选项解释


直接拿它的typescript来注解


export interface ConfigModuleOptions {
    isGlobal?: boolean;  // 启用这个会作用于整个大系统(全局module),而非仅你当前注入的module!
    ignoreEnvFile?: boolean; // 若是开启这个就会忽略.env文件的读取!!
    ignoreEnvVars?: boolean; // 忽略系统级变量注入,默认是关闭(会读取系统变量)
    envFilePath?: string | string[];// .env文件的去,基于运行时根路径找(process.cwd)
    encoding?: string; // 文件编码,推荐utf-8,容错率高!
    validationSchema?: any; // 可以校验所有传入自定义环境变量(没关闭系统变量也会追加进来)
    validationOptions?: Record<string, any>;
    load?: Array<ConfigFactory>; // 加载环境变量的工厂函数,可以用于组合复杂的配置
    expandVariables?: boolean; // 支持环境变量嵌套变量,
}


{
  "validationOptions": {
    "allowUnknown": false, // 控制是否允许环境变量中未知的键。默认为true。
    "abortEarly": true, // 如果为true,在遇到第一个错误时就停止验证;如果为false,返回所有错误。默认为false。
  }
}


{
  # 比如环境变量  APP_NAME=HHH
  # 拓展变量就是这样写法,跟字符串模板类似  APP_VERSION=${APP_NAME}-V1
  # 基于 https://github.com/motdotla/dotenv-expand 实现的
  "expandVariables":true
}


项目中应用


我倾向于把所有环境变量配置放到根目录config目录,


这样有什么好处呢?配置集中化,映射也很方便(比如用了Docker)


指定volume就可以了..不同环境互不干涉(开发,测试,生产!)


├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── README.md
├── config # 这里
│   └── env
│       ├── dev.local.env
│       ├── http.env
│       └── report.env
├── nest-cli.json
├── package.json
├── src
│   ├── app.controller.spec.ts
│   ├── app.controller.ts
│   ├── app.dto.ts
│   ├── app.module.ts
│   ├── app.service.ts
│   ├── config
│   │   ├── env
│   │   ├── http-status-code.msg.ts
│   │   └── module
│   ├── main.ts
│   └── utils
│       ├── apm-init.ts
│       ├── get-dir-all-file-name-arr.ts
│       └── terminal-help-text-console.ts
├── test
│   ├── app.e2e-spec.ts
│   └── jest-e2e.json
├── tsconfig.build.json
├── tsconfig.json
├── yarn-error.log
└── yarn.lock


比如我把swagger,axios这些都抽出来了


网络异常,图片无法展示
|


初始化配置(app.module)


import * as Joi from '@hapi/joi';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import envReportConfig from './config/env/report.config';
import envSwaggerConfig from './config/env/swagger.config';
import { getDirAllFileNameArr } from './utils/get-dir-all-file-name-arr';
@Module({
  imports: [
    ConfigModule.forRoot({
      encoding: 'utf-8',
      envFilePath: [...getDirAllFileNameArr()],
      expandVariables: true, // 开启嵌套变量
      ignoreEnvVars: true,
      load: [envReportConfig, envSwaggerConfig],
      validationSchema: Joi.object({
        H3_APM_SERVER_URL: Joi.string().default(''),
        H3_LATEINOS_REPORT_URL: Joi.string().default(''),
        SERVE_LISTENER_PORT: Joi.number().default(3000),
        SWAGGER_SETUP_PATH: Joi.string().default('api-docs'),
        SWAGGER_ENDPOINT_PREFIX: Joi.string().default('api/v1'),
        SWAGGER_UI_TITLE: Joi.string().default('Swagger文档标题'),
        SWAGGER_UI_TITLE_DESC: Joi.string().default('赶紧改相关配置啊~~'),
        SWAGGER_API_VERSION: Joi.string().default('1.0'),
        HTTP_TIMEOUT: Joi.number().default(5000),
        HTTP_MAX_REDIRECTS: Joi.number().default(5),
        NODE_ENV: Joi.string()
          .valid('development', 'production', 'test', 'provision')
          .default('development'),
      }),
      validationOptions: {
        allowUnknown: false, // 控制是否允许环境变量中未知的键。默认为true。
        abortEarly: true, // 如果为true,在遇到第一个错误时就停止验证;如果为false,返回所有错误。默认为false。
      },
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}


这里面有四处可以说一下

Joi

这里只用到了joi的基础语法,比如默认转换格式,添加默认值.


匹配数组有效值,不匹配默认用默认值~


若是环境变量使用异常,或者转换异常就会抛出类似的错误


网络异常,图片无法展示
|


ConfigModule 之 load


这个可以用来加载组合的配置函数,


比如你一些配置分散在多个.env中,


然后需要组装成一个对象传入,方便使用!


具体项目中的例子,先定义!


// env configuration
import { registerAs } from '@nestjs/config';
export interface EnvSwaggerOptions {
  title: string;
  setupUrl: string;
  desc?: string;
  prefix: string;
  version: string;
}
export default registerAs(
  'EnvSwaggerOptions',
  (): EnvSwaggerOptions => ({
    title: process.env.SWAGGER_UI_TITLE,
    desc: process.env.SWAGGER_UI_TITLE_DESC,
    version: process.env.SWAGGER_API_VERSION,
    setupUrl: process.env.SWAGGER_SETUP_PATH,
    prefix: process.env.SWAGGER_ENDPOINT_PREFIX,
  }),
);


然后使用,比如我们在项目主入口用!


import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config';
import { EnvSwaggerOptions } from './config/env/swagger.config';
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from './common/pipes/validataion.pipe';
import { terminalHelpTextConsole } from './utils/terminal-help-text-console';
async function bootstrap() {
  const app = await NestFactory.create(AppModule, {
    cors: false,
    logger: false,
  });
  // app.get 可以获取到对应初始化成功的实例!
  const configService = app.get(ConfigService);
  // configService.get可以获取到我们封装的配置对象或者系统变量!
  const swaggerOptions = configService.get<EnvSwaggerOptions>(
    'EnvSwaggerOptions',
  );
  const options = new DocumentBuilder()
    .setTitle(swaggerOptions.title)
    .setDescription(swaggerOptions.desc)
    .setVersion(swaggerOptions.version)
    .addBearerAuth()
    .build();
  const document = SwaggerModule.createDocument(app, options);
  SwaggerModule.setup(swaggerOptions.setupUrl, app, document, {
    customSiteTitle: swaggerOptions.title,
    swaggerOptions: {
      docExpansion: 'list',
      filter: true,
      showRequestDuration: true,
    },
  });
  await app.listen(configService.get('SERVE_LISTENER_PORT'));
}
bootstrap().then(() => {
  // 这个东西就是自己拼接的东东,启动成功后在终端输出点东东实现及效果图如下
  terminalHelpTextConsole();
});


import * as chalk from 'chalk';
type paramType = {
  Port: string | number;
  DocUrl: string;
  ApiPrefix: string;
};
const defaultParam: paramType = {
  Port: process.env.SERVE_LISTENER_PORT,
  DocUrl: process.env.SWAGGER_SETUP_PATH,
  ApiPrefix: process.env.SWAGGER_ENDPOINT_PREFIX,
};
/**
 * 打印相关的帮助信息到终端
 * @param params
 */
export function terminalHelpTextConsole(params = defaultParam): void {
  const Host = `http://localhost`;
  console.log(
    chalk.red.bold('Swagger文档链接:'.padStart(16)),
    chalk.green.underline(`${Host}:${params.Port}/${params.DocUrl}`),
  );
  console.log(
    chalk.red.bold('Restful接口链接:'.padStart(16)),
    chalk.green.underline(`${Host}:${params.Port}/${params.ApiPrefix}`),
  );
}


网络异常,图片无法展示
|


ConfigModule 之 envPath


我不喜欢手动去维护可能越来越多的配置文件,


所以我写了个函数来一次性拿到第一级所有文件名拼接成数组;


判定是否为文件且后缀为.env


// get-dir-all-file-name-arr.ts
import * as fs from 'fs';
import * as path from 'path';
// 默认存放env文件的文件夹路径
const directory = path.resolve(process.cwd(), 'config/env');
type optionsType = {
  dirPath?: string;
  prefix?: string;
};
/**
 * 返回目录下所有文件的文件名(字符串数组形式)
 * @typedef {Object} options  参数选项
 * @param {string} options.dirPath  目录路径
 * @param {string} options.prefix  给每一个匹配项增加前缀文本
 * @return {string[]} 不传参数默认返回/config/env下所有文件拼接的数组
 */
export function getDirAllFileNameArr(options?: optionsType): string[] {
  const params = { dirPath: directory, prefix: 'config/env/', ...options };
  const results = [];
  try {
    for (const dirContent of fs.readdirSync(params.dirPath)) {
      const dirContentPath = path.resolve(directory, dirContent);
      console.log(dirContentPath);
      if (fs.statSync(dirContentPath).isFile()) {
        if (dirContent.endsWith('.env')) {
          if (params.prefix) {
            results.push(`${params.prefix}${dirContent}`);
          } else {
            results.push(dirContent);
          }
        }
      }
    }
    return results;
  } catch (error) {
    return results;
  }
}
// output
/**
* [
*   'config/env/dev.local.env',
*   'config/env/http.env',
*   'config/env/report.env'
* ]
*/


环境变量输出


在main.js的bootstrap内输出process.env就可以看到了


网络异常,图片无法展示
|


目录
相关文章
|
jenkins 持续交付
Manage Jenkins管理界面提示“依赖错误: 部分插件由于缺少依赖无法加载...“问题解决办法
Manage Jenkins管理界面提示“依赖错误: 部分插件由于缺少依赖无法加载...“问题解决办法
1158 0
Manage Jenkins管理界面提示“依赖错误: 部分插件由于缺少依赖无法加载...“问题解决办法
|
6月前
|
缓存 资源调度 JavaScript
从零到一nvm、npm、cnpm、yarn、vue全套安装和环境配置以及创建新项目和如何运行人家的项目大全,最详细,保姆级
从零到一nvm、npm、cnpm、yarn、vue全套安装和环境配置以及创建新项目和如何运行人家的项目大全,最详细,保姆级
230 0
|
5月前
|
前端开发 JavaScript 开发者
探索npm的高级特性:自定义脚本与包的发布与维护
探索npm的高级特性:自定义脚本与包的发布与维护
|
10月前
|
JavaScript 前端开发 Java
SpringBoot+VUE实现文件导入并将其保存到Liunx系统
需求: 必须支持PDF、docx、xlsx、xls、doc等格式, 文件上传后保存在本地文件夹, 需要进行在线预览
116 0
|
10月前
|
NoSQL Java 关系型数据库
bat脚本一键配置java开发环境
在新电脑配置或者新人入职时需要对java开发相关环境进行配置安装,但时常会因为安装配置不到位或者操作错误导致时间的浪费,所以在空余时间收集了一系列软件的免安装版本,并且编写了相关配置脚本,让环境安装变得标准化
129 0
bat脚本一键配置java开发环境
|
移动开发 前端开发 小程序
为了偷懒,我用google/zx一键自动打包编译了前后端项目并发布到指定环境
由于正在负责的一个项目,就说前端涉及到PC端、公众号端、APP端的H5、小程序端、可视化大屏端,而PC和APP又通过qiankun引入了微前端的理念。整体一圈下来可能光前端编译打包就要build差不多二十次。而有时候经常性的bug改动,这个时候便只需要进行测试后需要进行小范围的测试。
178 0
|
监控 前端开发 测试技术
还在手动维护Yapi?
因前后端人员通过接口定义字段,返回值等对接时非常苦恼,没有一个很好的平台维护,后端每次迭代都要写开发文档,需求变化,多系统联调等,给前后端联调造成阻塞。
283 0
|
安全 IDE Java
Linxu搭建maven环境,实现服务器修改代码
Linxu搭建maven环境,实现服务器修改代码
202 0
Linxu搭建maven环境,实现服务器修改代码
|
Oracle IDE Java
最详细的Android开发环境配置经验分享(包含配置过程中可能出现的问题及解决办法。繁琐的配置步骤是否是你头疼呢,详细配置步骤你值得拥有!)
最详细的Android开发环境配置经验分享(包含配置过程中可能出现的问题及解决办法。繁琐的配置步骤是否是你头疼呢,详细配置步骤你值得拥有!)
279 0
最详细的Android开发环境配置经验分享(包含配置过程中可能出现的问题及解决办法。繁琐的配置步骤是否是你头疼呢,详细配置步骤你值得拥有!)
【Django | 开发】分离上线环境与开发环境(多settings配置)
【Django | 开发】分离上线环境与开发环境(多settings配置)
【Django | 开发】分离上线环境与开发环境(多settings配置)