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:
116
src/config.rs
116
src/config.rs
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
29
src/main.rs
29
src/main.rs
@@ -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) // 生产环境可开启
|
||||
|
||||
@@ -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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user