feat: 实现后端多环境配置机制

- 使用 toml crate 替代 config crate 直接读取配置文件
- 创建 config/default.toml、config/development.toml、config/production.toml
- 重写 config.rs 支持多环境配置加载
- 增强 deploy.sh 支持 development/production 环境参数
- 更新 IMPROVEMENTS.md 标记环境区分完成
This commit is contained in:
2026-04-17 11:11:13 +08:00
parent ca7ddcc55e
commit da4e6f557e
8 changed files with 314 additions and 62 deletions

View File

@@ -1,33 +1,105 @@
use std::env;
use dotenvy::dotenv;
use tracing;
//! 应用配置模块
//!
//! 支持多环境配置加载,优先级:环境变量 > 环境配置 > 默认配置
#[derive(Debug, Clone)]
pub struct Config {
use serde::Deserialize;
use std::path::PathBuf;
#[derive(Debug, Deserialize, Clone)]
pub struct AppConfig {
pub database_url: String,
pub jwt_secret: String,
pub wechat_appid: String,
pub wechat_secret: String,
pub jwt_secret: String,
pub ssl_key_path: String,
pub ssl_cert_path: String,
pub rust_log: String,
pub app_version: String,
pub environment: String,
pub free_user_data_limit: i32,
pub server_host: String,
#[serde(rename = "server_ports")]
pub server_ports: Vec<u16>,
}
impl Config {
impl AppConfig {
pub fn load() -> Result<Self, String> {
// 加载.env文件
if let Err(e) = dotenv() {
tracing::warn!("无法加载.env文件 - {}", e);
let env = std::env::var("APP_ENV").unwrap_or_else(|_| "development".into());
let manifest_dir = std::env::var("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| std::env::current_dir().unwrap());
let config_dir = manifest_dir.join("config");
let default_path = config_dir.join("default.toml");
let env_path = config_dir.join(&format!("{}.toml", env));
let mut settings = toml::Table::new();
// 1. 加载 default.toml
if default_path.exists() {
let content = std::fs::read_to_string(&default_path)
.map_err(|e| format!("读取 default.toml 失败: {}", e))?;
let parsed: toml::Table =
toml::from_str(&content).map_err(|e| format!("解析 default.toml 失败: {}", e))?;
settings.extend(parsed);
} else {
return Err(format!("配置文件不存在: {}", default_path.display()));
}
Ok(Self {
database_url: env::var("DATABASE_URL")
.map_err(|_| "环境变量DATABASE_URL未设置".to_string())?,
jwt_secret: env::var("JWT_SECRET")
.map_err(|_| "环境变量JWT_SECRET未设置".to_string())?,
wechat_appid: env::var("WECHAT_APPID")
.map_err(|_| "环境变量WECHAT_APPID未设置".to_string())?,
wechat_secret: env::var("WECHAT_SECRET")
.map_err(|_| "环境变量WECHAT_SECRET未设置".to_string())?,
server_ports: vec![8080, 3000, 8000, 8888],
})
// 2. 合并环境配置(覆盖默认值)
if env_path.exists() {
let content = std::fs::read_to_string(&env_path)
.map_err(|e| format!("读取 {}.toml 失败: {}", env, e))?;
let parsed: toml::Table =
toml::from_str(&content).map_err(|e| format!("解析 {}.toml 失败: {}", env, e))?;
settings.extend(parsed);
}
// 3. 从环境变量加载(最高优先级)
if let Ok(val) = std::env::var("APP_DATABASE_URL") {
settings.insert("database_url".into(), toml::Value::String(val));
}
if let Ok(val) = std::env::var("APP_JWT_SECRET") {
settings.insert("jwt_secret".into(), toml::Value::String(val));
}
if let Ok(val) = std::env::var("APP_WECHAT_APPID") {
settings.insert("wechat_appid".into(), toml::Value::String(val));
}
if let Ok(val) = std::env::var("APP_WECHAT_SECRET") {
settings.insert("wechat_secret".into(), toml::Value::String(val));
}
if let Ok(val) = std::env::var("APP_RUST_LOG") {
settings.insert("rust_log".into(), toml::Value::String(val));
}
if let Ok(val) = std::env::var("APP_FREE_USER_DATA_LIMIT") {
if let Ok(num) = val.parse::<i64>() {
settings.insert("free_user_data_limit".into(), toml::Value::Integer(num));
}
}
let app_config: AppConfig = toml::from_str(&settings.to_string())
.map_err(|e| format!("配置反序列化失败: {}", e))?;
tracing::info!(
"配置加载成功,环境={}app_version={}",
app_config.environment,
app_config.app_version
);
Ok(app_config)
}
}
pub fn database_url(&self) -> &str {
&self.database_url
}
pub fn rust_log(&self) -> &str {
&self.rust_log
}
pub fn is_production(&self) -> bool {
self.environment == "production"
}
}

View File

@@ -8,11 +8,13 @@ use sqlx::postgres::PgPool;
use std::pin::Pin;
mod auth;
mod config;
mod db;
mod handlers;
mod models;
use auth::jwt_middleware;
use config::AppConfig;
use db::create_pool;
use handlers::{
admin_get_user, admin_update_user_payment, delete_weather, generate_temp_token_handler,
@@ -79,8 +81,25 @@ fn create_server_config(
#[actix_web::main]
async fn main() -> std::io::Result<()> {
if let Err(e) = dotenvy::dotenv() {
tracing::warn!("加载.env文件失败使用系统环境变量: {}", e);
// 加载配置文件(支持多环境)
let app_config = match AppConfig::load() {
Ok(cfg) => cfg,
Err(e) => {
eprintln!("配置加载失败: {}", e);
std::process::exit(1);
}
};
// 设置环境变量(向后兼容依赖 env var 的组件)
unsafe {
std::env::set_var("DATABASE_URL", &app_config.database_url);
std::env::set_var("JWT_SECRET", &app_config.jwt_secret);
std::env::set_var("WECHAT_APPID", &app_config.wechat_appid);
std::env::set_var("WECHAT_SECRET", &app_config.wechat_secret);
std::env::set_var("SSL_KEY_PATH", &app_config.ssl_key_path);
std::env::set_var("SSL_CERT_PATH", &app_config.ssl_cert_path);
std::env::set_var("RUST_LOG", &app_config.rust_log);
std::env::set_var("FREE_USER_DATA_LIMIT", app_config.free_user_data_limit.to_string());
}
// 初始化文件日志JSON 格式,带轮转)
@@ -102,9 +121,9 @@ async fn main() -> std::io::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::from_default_env()
.add_directive("rust_backend=info".parse().unwrap())
.add_directive("actix_web=info".parse().unwrap())
.add_directive("sqlx=warn".parse().unwrap()) // SQLx 日志太多,降级
.add_directive(format!("rust_backend={}", app_config.rust_log).parse().unwrap())
.add_directive(format!("actix_web={}", app_config.rust_log).parse().unwrap())
.add_directive("sqlx=warn".parse().unwrap())
)
.with_target(true)
.with_thread_ids(false) // 生产环境可开启

View File

@@ -149,7 +149,11 @@ pub struct WeatherData {
#[sqlx(rename = "averagewindspeed")]
pub average_wind_speed: f64,
#[serde(rename = "calculatedWindSpeed", alias = "calculated_wind_speed", alias = "calculatedwindspeed")]
#[serde(
rename = "calculatedWindSpeed",
alias = "calculated_wind_speed",
alias = "calculatedwindspeed"
)]
#[sqlx(rename = "calculatedwindspeed")]
pub calculated_wind_speed: Option<f64>, // 这个已经是 Option很好
@@ -305,6 +309,7 @@ pub struct AppState {
pub jwt_secret: String,
pub wechat_appid: String,
pub wechat_secret: String,
pub free_user_data_limit: i32,
}
impl AppState {
@@ -316,6 +321,9 @@ impl AppState {
.map_err(|_| "环境变量WECHAT_APPID未设置".to_string())?,
wechat_secret: std::env::var("WECHAT_SECRET")
.map_err(|_| "环境变量WECHAT_SECRET未设置".to_string())?,
free_user_data_limit: std::env::var("FREE_USER_DATA_LIMIT")
.map(|v| v.parse().unwrap_or(20))
.unwrap_or(20),
})
}
}