接下来,准备接入Log日志系统,NestJS有自带的Log系统但是,我希望自定义一些内容所以会进行改造。在此之前,需要先配置Configration
,因为我希望在开发和生产的时候这套日志系统有不一样的行为。
Configuration 介绍
NestJS的Configuration,官方文档的2种配置方式,一种是用.env
后缀文件,里面的内容是key/value形式,这和前端的环境配置有点像。另一种是yaml
文件形式的,在这我采用yaml文件的方式。
Config 导入
参照官方的例子,尝试导入Configuration。
1. 安装依赖
pnpm i --save @nestjs/config
2. 注册ConfigModule
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
@Module({
/** 重点 */
imports: [ConfigModule.forRoot()],
/** 重点end */
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
一旦注册了ConfigModule
,NestJS就会默认读入.env
文件里面的配置,并且添加到process.env
变量中去。
举个例子.env
里面的内容类似于下面这样
DATABASE_USER=test
DATABASE_PASSWORD=test
3. 自定义文件路径
通过设定envFilePath
可以自定义文件路径
ConfigModule.forRoot({
envFilePath: '.development.env',
});
// 或者 配置成多个数组的形式,当2个文件都有相同配置的时候,则第一个文件的配置优先
ConfigModule.forRoot({
envFilePath: ['.env.development.local', '.env.development'],
});
4. 警用环境变量加载
如果不想自动读入.env
文件,则可以设置ignoreEnvFile
参数为True。
ConfigModule.forRoot({
ignoreEnvFile: true,
});
5. 使模块全局化
当您想在其他模块使用ConfigModule
,则必须导入它。或者,通过设定isGlobal
为true
来使它成为一个全局的模块
ConfigModule.forRoot({
isGlobal: true,
});
6. 自定义配置文件
配置的内容也可以是一个对象,并且对象还可以设定默认值。
// config/configuration.ts
export default () => ({
port: parseInt(process.env.PORT, 10) || 3000,
database: {
host: process.env.DATABASE_HOST,
port: parseInt(process.env.DATABASE_PORT, 10) || 5432
}
});
用load
参数来导入这个对象。
import configuration from './config/configuration';
@Module({
imports: [
ConfigModule.forRoot({
load: [configuration],
}),
],
})
export class AppModule {}
YAML的配置文件类似于下面这种形式
SERVER_VALUE:
host: '0.0.0.0'
port: 8000
LOG_CONFIG:
TIMESTAMP: true
LOG_LEVEL: 'info'
LOG_ON: true
为了能够读入YAML文件,需要下载js-yaml
包
pnpm i js-yaml
pnpm i -D @types/js-yaml
然后通过yaml#load
方法来加载YAML文件
// config/configuration.ts
import { readFileSync } from 'fs';
import * as yaml from 'js-yaml';
import { join } from 'path';
// 获取项目运行环境
export const getEnv = () => {
return process.env.RUNNING_ENV;
};
export const getConfig = (type?: string) => {
const environment = getEnv();
const yamlPath = join(process.cwd(), `./.config/.${environment}.yaml`);
const config = yaml.load(readFileSync(yamlPath, 'utf8')) as Record<
string,
any
>;
if (type) {
return config[type];
}
return config;
};
注意:
在构建过程中,Nest CLI不会自动将您的“assets”(非TS文件)移动到dist文件夹。为了确保您的YAML文件被复制,您必须在nest-cli.json文件的acompilerOptions#assets对象中指定这一点。例如,如果config文件夹与src文件夹处于同一级别,则添加compilerOptions#assets,其值为“assets”:[{”include“:”../config/*.yaml“,”outDir“:“./dist/config”}]。
7. 使用ConfigService
上面的准备工作就绪以后我们就可以通过ConfigService
来获取yaml的内容了。前提是必须要导入ConfigModule
模块(如果在ConfigModule.forRoot()
里面设定了isGlobal
的情况不需要导入)。
@Module({
imports: [ConfigModule],
// ...
})
然后就可以通过构造函数来注入了
constructor(private configService: ConfigService) {}
并且在我们的类中来使用他们
// get an environment variable
const dbUser = this.configService.get<string>('DATABASE_USER');
// get a custom configuration value
const dbHost = this.configService.get<string>('database.host');
如上所示,使用configService.get()
方法通过传递变量名来获得一个简单的环境变量。您可以通过传递类型来进行TypeScript类型提示,如上文所示(例如,get<string>(…))。get()方法还可以遍历嵌套的自定义配置对象(通过自定义配置文件创建),如上文第二个示例所示。
您还可以使用接口作为类型提示来获取整个嵌套的自定义配置对象
interface DatabaseConfig {
host: string;
port: number;
}
const dbConfig = this.configService.get<DatabaseConfig>('database');
// you can now use `dbConfig.port` and `dbConfig.host`
const port = dbConfig.port;
可以通过get()
方法的第二个可选参数,来设定默认值
// use "localhost" when "database.host" is not defined
const dbHost = this.configService.get<string>('database.host', 'localhost');
ConfigService有两个可选的泛型(类型参数)。第一个是帮助防止访问不存在的配置属性。按如下所示使用:
interface EnvironmentVariables {
PORT: number;
TIMEOUT: string;
}
// somewhere in the code
constructor(private configService: ConfigService<EnvironmentVariables>) {
const port = this.configService.get('PORT', { infer: true });
// TypeScript Error: this is invalid as the URL property is not defined in EnvironmentVariables
const url = this.configService.get('URL', { infer: true });
}
将infer
推断属性设置为true
后,ConfigService#get
方法将根据接口自动推断属性类型,例如,typeof port===“number”
(如果您没有使用TypeScript中的strictNullChecks
标志),因为port
在EnvironmentVariables
接口中有一个数字类型。
此外,使用推断功能,您可以推断嵌套的自定义配置对象的属性的类型,即使使用点表示法也是如此,如下所示:
constructor(private configService: ConfigService<{ database: { host: string } }>) {
const dbHost = this.configService.get('database.host', { infer: true })!;
// typeof dbHost === "string" |
// +--> non-null assertion operator
}
第二个泛型依赖于第一个泛型,作为类型断言来消除ConfigService
的方法在strictNullChecks
打开时可以返回的所有undefined
类型。例如:
// ...
constructor(private configService: ConfigService<{ PORT: number }, true>) {
// ^^^^
const port = this.configService.get('PORT', { infer: true });
// ^^^ The type of port will be 'number' thus you don't need TS type assertions anymore
}
命名空间, 缓存,部分登记这些大家可以了解一下这里不多做介绍了。
8. 结构验证
官方的结构验证有2种方式。Joi
和自定义validate()
.
这里我选择用Joi
的方式来验证env。想更多了解自定义validate()
和Joi
的朋友可以去看官网。
首先安装Joi依赖
pnpm i joi --save
configuration.ts
文件里面加入下面,有错误的情况暂时console.log
出来。Joi方法, JoiOption参数
import { readFileSync } from 'fs';
import * as yaml from 'js-yaml';
import { join } from 'path';
import * as Joi from 'joi';
/**
* 对象验证逻辑
*/
const shcema = Joi.object().keys({
SERVER_VALUE: Joi.object({
port: Joi.number().default(8000),
host: Joi.string().required(),
}),
LOG_CONFIG: Joi.object({
TIMESTAMP: Joi.boolean().default(false),
LOG_LEVEL: Joi.string().valid('info', 'error').required(),
LOG_ON: Joi.boolean().default(false),
}),
});
/**
* 去的环境配置内容
* @returns 环境配置
*/
export const getConfig = () => {
// ...
try {
const { value, error } = shcema.validate(config);
if (error) {
console.log(error);
}
return value;
} catch (error) {
console.log(error);
return {};
}
};
9. 在main.ts
中使用
通过app.get()
可以在main.ts
中获取configService.
async function bootstrap() {
// ...
const configService = app.get(ConfigService);
const serverValue = configService.get('SERVER_VALUE');
await app.listen(serverValue.port, serverValue.host);
}
bootstrap();