首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >NestJS 7.x 折腾记: (2) 环境变量及配置维护

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

作者头像
CRPER
发布2022-03-08 14:55:11
发布2022-03-08 14:55:11
4.5K10
代码可运行
举报
文章被收录于专栏:CRPER折腾记CRPER折腾记
运行总次数:0
代码可运行

前言

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

实战

安装

代码语言:javascript
代码运行次数:0
运行
复制
# @nestjs/config 内置了dotenv
yarn add @nestjs/config joi
yarn add -D @types/hapi__joi

基础配置介绍

@nestjs/config 选项解释
代码语言:javascript
代码运行次数:0
运行
复制
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; // 支持环境变量嵌套变量,
}
代码语言:javascript
代码运行次数:0
运行
复制
代码语言:javascript
代码运行次数:0
运行
复制
{
比如环境变量  APP_NAME=HHH
拓展变量就是这样写法,跟字符串模板类似  APP_VERSION=${APP_NAME}-V1
基于 https://github.com/motdotla/dotenv-expand 实现的
"expandVariables":true
}

项目中应用

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

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

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

代码语言:javascript
代码运行次数:0
运行
复制
├── .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)

代码语言:javascript
代码运行次数:0
运行
复制
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中, 然后需要组装成一个对象传入,方便使用! 具体项目中的例子,先定义!

代码语言:javascript
代码运行次数:0
运行
复制
// 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,
  }),
);

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

代码语言:javascript
代码运行次数:0
运行
复制
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();
});
代码语言:javascript
代码运行次数:0
运行
复制
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

代码语言:javascript
代码运行次数:0
运行
复制
// 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就可以看到了

总结

至此,一个维护性还不错的姿势已经落实; 有不对之处请留言,会及时修正,谢谢阅读!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/11/07 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 实战
    • 安装
    • 基础配置介绍
      • @nestjs/config 选项解释
    • 项目中应用
    • 初始化配置(app.module)
    • Joi
    • ConfigModule 之 load
    • ConfigModule 之 envPath
    • 环境变量输出
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档