.env 文件管理
为了项目开发方便, 一般会将一些重要的配置放到 .env 文件中, 方便随时修改 比如: 链接数据库服务的用户名/密码/主机/端口等 在 Rust 中, 可以使用: dotenvy 来加载和解析 .env 文件
项目初始化
sh
cargo new env_demo && cd env_demo安装依赖
注意安装版本API是否兼容笔记中的
sh
cargo add dotenvytoml
[dependencies]
dotenvy = "0.15.7"创建 .env 文件
sh
touch .env文件内容如下:
env
DEBUG_MODE=false
DB_HOST="127.0.0.1"
DB_PORT="3306"
DB_NAME="projdb"
DB_USERNAME="root"
DB_PASSWORD="73xF3f7%9r2436z"加载配置文件并解析
rust
use dotenvy;
use std::env;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
// 加载当前目录或父目录下的 .env 文件, 返回一个 Result
// 只要这个函数执行, 就会解析env文件内容并设置环境变量(std::env::set_var)
// 然后就可以使用标准库的 env::var 来获取
dotenvy::dotenv()?;
// 获取单个环境变量, 返回 Result
let db_host = env::var("DB_HOST")?;
println!("db_host = {db_host:?}"); // 127.0.0.1
// 获取所有环境变量, 返回 std::env::Vars
let env_vars = env::vars();
for (k, v) in env_vars {
println!("{k} = {v}");
}
Ok(())
}通用配置文件管理
一般为了更直观的管理配置文件, 不会选择使用 .env 来管理配置文件, 因为 .env 一般是用于 docker-compose 部署时用的, 程序用的配置文件 一般会选择使用 toml/json/yaml 等更结构化的配置文件格式, 而需要使用这些 格式的配置文件, 可以选择使用 config
初始化项目
sh
cargo new config_demo && cd config_demo添加依赖
注意版本API是否兼容
sh
cargo add configtoml
[dependencies]
config = "0.15.25"创建配置文件
笔记这里以 toml 为例, 其他格式的配置文件也是一样的
在项目根目录创建 config.toml
toml
debug_mode=false
db_host="127.0.0.1"
db_port="3306"
db_name="projdb"
db_username="root"
db_password="73xf3f7%9r2436z"加载配置文件
rust
use std::collections::HashMap;
use std::error::Error;
use config::Config;
fn main() -> Result<(), Box<dyn Error>> {
let cfg_builder = Config::builder()
.add_source(config::File::with_name("config.toml"))
.build()?;
// 直接返回一个 HashMap
let config = cfg_builder.try_deserialize::<HashMap<String, String>>()?;
for (k, v) in config.iter() {
println!("{k} = {v}");
}
// 我准备的配置文件中有这个, 所以可以直接 unwrap
let is_debug_mode = config.get("debug_mode");
println!("is_debug_mode = {is_debug_mode:?}");
Ok(())
}将配置文件并解析为 struct
rust
use std::error::Error;
use config::Config;
// 注意需要安装依赖
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct AppConfig {
pub debug_mode: bool,
pub db_host: Option<String>, // 可选配置, 默认 127.0.0.1
pub db_port: u32,
pub db_name: String,
pub db_username: String,
pub db_password: String,
}
fn main() -> Result<(), Box<dyn Error>> {
let cfg_builder = Config::builder()
.add_source(config::File::with_name("config.toml"))
.build()?;
// 将配置文件解析为 AppConfig 实例
let config = cfg_builder.try_deserialize::<AppConfig>()?;
// 我准备的配置文件中有这个, 所以可以直接 unwrap
let is_debug_mode = config.debug_mode;
// 此时: debug_mode 是一个 bool 值, 而不是一个 String
println!("is_debug_mode = {is_debug_mode:?}");
// 关于 db_host 字段 Option<String> 类型, 可以这样测试
// 1. 将 config 直接输出
// 2. 注释掉 config.toml 中的 db_host 字段, 然后再次测试
println!("{config:?}");
Ok(())
}解析配置时设置默认值
rust
use std::error::Error;
use config::Config;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct AppConfig {
pub debug_mode: bool,
pub db_host: Option<String>, // 可选配置, 默认 127.0.0.1
pub db_port: u32,
pub db_name: String,
pub db_username: String,
pub db_password: String,
}
fn main() -> Result<(), Box<dyn Error>> {
let cfg_builder = Config::builder()
// 设置默认值, 即使配置文件中没有这个字段也不会报错, 而是默认值
.set_default("debug_mode", false)?
.add_source(config::File::with_name("config.toml"))
.build()?;
let config = cfg_builder.try_deserialize::<AppConfig>()?;
let is_debug_mode = config.debug_mode;
println!("is_debug_mode = {is_debug_mode:?}");
println!("{config:?}");
Ok(())
}解析层级嵌套的配置文件
所谓层级嵌套, 就是说所有的配置字段并不都是在顶层的, 而是有层级结构的, 如:
json
{
"debug": false,
"db": {
"host": "127.0.0.1",
"port": 3306,
"database": "projdb",
"username": "root",
"password": "123456"
}
}toml
# top level
debug_mode=true
# db field
[db]
host="127.0.0.1"
port="3306"
name="projdb"
username="root"
password="73xf3f7%9r2436z"像这样的配置就需要嵌套的结构体来解析
rust
use std::error::Error;
use config::Config;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct AppDbConfig {
pub host: String,
pub port: u32,
pub database: String,
pub username: String,
pub password: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct AppConfig {
pub debug_mode: bool,
pub db: AppDbConfig,
}
fn main() -> Result<(), Box<dyn Error>> {
let cfg_builder = Config::builder()
.set_default("debug_mode", false)?
// 如果要给非顶层的字段设置默认值, 可以这样设置
.set_default("db.port", 3306)?
.add_source(config::File::with_name("config.toml"))
.build()?;
let config = cfg_builder.try_deserialize::<AppConfig>()?;
println!("{config:?}");
// 控制台输出如下:
// AppConfig {
// debug_mode: true,
// db: AppDbConfig {
// host: "127.0.0.1",
// port: 3306,
// database: "projdb",
// username: "root",
// password: "73xf3f7%9r2436z"
// }
// }
Ok(())
}配置文件分层
所有的配置文件分层就是:
- 按照不同的命令行参数, 加载不同的配置文件
- 比如: 在开发环境就用开发环境配置文件(合并默认配置文件)
在项目根目录下创建如下配置文件
txt
.
├── Cargo.lock
├── Cargo.toml
├── config
│ ├── default.toml # 默认配置文件,所有环境都加载
│ ├── dev.toml # 开发环境配置文件
│ ├── prod.toml # 生产环境配置文件
│ └── test.toml # 测试环境配置文件
└── src
└── main.rstoml
env = "dev" # default is: dev
debug_mode = false
# db field
[db]
host = "127.0.0.1"
port = 3306
database = "projdb_dev"
username = "root"
password = "123456"toml
[db]
port = 33060 # override default.tomltoml
env = "prod"
debug_mode = false
[db]
database = "projdb_prod"
username = "root"toml
env = "test"
debug_mode = false
[db]
database = "projdb_test"
username = "test"代码实现:
rust
use std::error::Error;
use std::process;
use config::{Config, File, FileFormat};
use serde::{Deserialize, Serialize};
// 1. 处理命令行参数, 注意安装依赖 clap
use clap::Parser;
#[derive(Parser, Debug)]
#[command(author, version, about)]
pub struct Cli {
/// 运行环境: dev, test, prod
#[arg(short, long, default_value = "dev")]
pub env: String,
}
// 用户解析配置文件 struct
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
pub debug_mode: bool,
pub db: AppDbConfig,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppDbConfig {
pub env: String,
pub host: String,
pub port: u32,
pub database: String,
pub username: String,
pub password: String,
}
// env 参数允许的值
pub fn get_allow_envs() -> Vec<&'static str> {
vec!["dev", "prod", "test"]
}
// 分层加载配置文件
pub fn load_configs(default_cfg_path: &str, env_cfg_path: &str) -> Result<Config, Box<dyn Error>> {
// 1. 构建配置构建器
let mut config_builder = Config::builder();
// 2. 第一层: 加载基础配置 config.base.toml
// format(FileFormat::Toml): 表示是 toml 格式的配置文件
// required(true): 配置文件必须存在
let base_cfg_file = File::with_name(default_cfg_path)
.format(FileFormat::Toml)
.required(true);
config_builder = config_builder.add_source(base_cfg_file);
// 3. 第二层: 根据环境加载对应的配置文件
// required(false) 表示特定环境配置文件可以不存在(test.toml)
// 以允许优雅降级, 使用 defalt.toml 中的内容, 如果需要环境配置
// 文件也必须存在, 可以设置为 true
let env_cfg_file = File::with_name(env_cfg_path)
.format(FileFormat::Toml)
.required(false);
config_builder = config_builder.add_source(env_cfg_file);
// 4. 构建 & 返回结果
let cfg = config_builder.build()?;
Ok(cfg)
}
fn main() -> Result<(), Box<dyn Error>> {
// 1. 解析命令行参数获取 env 并验证, 只允许值为 dev/test/prod
let cli = Cli::parse();
let app_env = cli.env;
let allowd_envs = get_allow_envs();
if !allowd_envs.iter().any(|v| v.eq(&app_env)) {
eprintln!("env arguments only allowed to be {}", allowd_envs.join(","));
process::exit(1);
}
// 2. 分层加载配置文件, 后者覆盖前者
println!("{app_env} config file loading...");
let base_cfg_path = "config/default.toml";
let env_cfg_path = format!("config/{}.toml", app_env);
let cfg = load_configs(base_cfg_path, &env_cfg_path)?;
// 3. 解析配置文件
let config = cfg.try_deserialize::<AppConfig>()?;
println!("{config:?}");
// cargo run -- --env dev 输出结果如下:
// AppConfig {
// env: "dev",
// debug_mode: false,
// db: AppDbConfig {
// host: "127.0.0.1",
// port: 33060, // dev.toml 存在所以被覆盖了
// database: "projdb_dev",
// username: "root",
// password: "123456"
// }
// }
// cargo run -- --env test 输出结果如下:
// AppConfig {
// env: "test",
// debug_mode: true,
// db: AppDbConfig {
// host: "127.0.0.1",
// port: 3306,
// database: "projdb_test",
// username: "test",
// password: "123456"
// }
// }
// cargo run -- --env prod 输出结果如下:
// AppConfig {
// env: "prod",
// debug_mode: false,
// db: AppDbConfig {
// host: "127.0.0.1",
// port: 3306,
// database: "projdb_prod",
// username: "root",
// password: "123456"
// }
// }
Ok(())
}