网络异常,图片无法展示
|
前言
在写后端的时候,我们一般提倡配置文件分离.
所以.env
就可以很方面来维护我们的环境变量,
封装对应的工厂函数也能组合更复杂的配置!
比如我们用镜像(Docker),就可以外部映射配置文件目录;
达到不同环境使用差异化配置的需求!(运行时加载是允许的!)
其他不多说,往下可以看看我的配置分离思路~~
实战
安装
- @nestjs/config : 基于dotenv 封装的Nest配置中心
- joi : 一个很灵活的schema校验工具
- @types/hapi__joi : joi的typescript声明
# @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就可以看到了
网络异常,图片无法展示
|