admin_config/
app_config.rs

1//! 配置管理模块
2//!
3//! 提供统一的配置加载和管理功能,支持:
4//! - 从 config.toml 文件加载配置
5//! - 从环境变量覆盖配置
6//! - 从命令行参数覆盖配置
7//! - 配置验证和默认值
8//!
9//! # 配置优先级
10//!
11//! 1. 命令行参数(最高优先级)
12//! 2. 环境变量
13//! 3. config.toml 文件
14//! 4. 默认值(最低优先级)
15//!
16//! # 使用示例
17//!
18//! ```rust,ignore
19//! use admin_server_config::AppConfig;
20//!
21//! let config = AppConfig::load()?;
22//! println!("Server port: {}", config.server.port);
23//! ```
24
25use crate::LlmConfig;
26use crate::{
27    AuthConfig, CosConfig, DatabaseConfig, DevelopmentConfig, EmailConfig, RateLimitConfig, RedisConfig,
28    SecurityConfig, ServerConfig, SessionConfig, SmsConfig, UploadConfig, VerificationCodeConfig,
29};
30use anyhow::{Context, Result};
31use serde::{Deserialize, Serialize};
32use std::path::{Path, PathBuf};
33
34#[derive(Debug, Clone, Serialize, Deserialize, Default)]
35pub struct AppConfig {
36    /// 服务器配置
37    #[serde(default)]
38    pub server: ServerConfig,
39    /// 数据库配置
40    #[serde(default)]
41    pub database: DatabaseConfig,
42    /// Redis 配置
43    #[serde(default)]
44    pub redis: RedisConfig,
45    /// 认证配置
46    #[serde(default)]
47    pub auth: AuthConfig,
48    /// 邮件配置
49    #[serde(default)]
50    pub email: EmailConfig,
51    /// 短信配置
52    #[serde(default)]
53    pub sms: SmsConfig,
54    /// 验证码配置
55    #[serde(default)]
56    pub verification_code: VerificationCodeConfig,
57    /// 对象存储配置
58    #[serde(default)]
59    pub cos: CosConfig,
60    /// 安全配置
61    #[serde(default)]
62    pub security: SecurityConfig,
63    /// 会话配置
64    #[serde(default)]
65    pub session: SessionConfig,
66    /// 上传配置
67    #[serde(default)]
68    pub upload: UploadConfig,
69    /// 速率限制配置
70    #[serde(default)]
71    pub rate_limit: RateLimitConfig,
72    /// 开发环境配置
73    #[serde(default)]
74    pub development: DevelopmentConfig,
75    /// LLM 配置
76    #[serde(default)]
77    pub llm: Vec<LlmConfig>,
78}
79
80impl AppConfig {
81    /// 加载配置
82    pub fn load() -> Result<Self> {
83        Self::load_from_default_path()
84    }
85
86    /// 从默认路径加载配置
87    pub fn load_from_default_path() -> Result<Self> {
88        let config_path = std::env::var("CONFIG_PATH").unwrap_or_else(|_| "config.toml".to_string());
89
90        let path = if Path::new(&config_path).exists() {
91            PathBuf::from(config_path)
92        } else {
93            match Self::find_config_file() {
94                Ok(p) => p,
95                Err(_) => {
96                    log::warn!("Config file not found. Using default configuration with environment variables.");
97                    return Ok(Self::default_with_env());
98                }
99            }
100        };
101
102        Self::load_from_path(&path)
103    }
104
105    /// 从指定路径加载配置
106    fn load_from_path(path: &Path) -> Result<Self> {
107        let config_content = std::fs::read_to_string(path).with_context(|| format!("无法读取配置文件: {:?}", path))?;
108        let mut config: Self = toml::from_str(&config_content).with_context(|| "解析配置文件失败")?;
109        config.apply_env_overrides();
110        Ok(config)
111    }
112
113    /// 创建默认配置并应用环境变量
114    fn default_with_env() -> Self {
115        let mut config = Self::default();
116        config.apply_env_overrides();
117        config
118    }
119
120    /// 从文件加载配置
121    pub fn from_file(path: &str) -> Result<Self> {
122        Self::load_from_path(Path::new(path))
123    }
124
125    /// 查找配置文件
126    ///
127    /// 按以下顺序查找配置文件:
128    /// 1. CONFIG_PATH 环境变量指定的路径
129    /// 2. 当前工作目录下的 `config.toml`
130    /// 3. 当前工作目录下的 `apps/admin-server/config.toml`
131    /// 4. 可执行文件所在目录的 `config.toml`
132    fn find_config_file() -> Result<PathBuf> {
133        // 1. 检查环境变量
134        if let Ok(config_path) = std::env::var("CONFIG_PATH") {
135            let path = Path::new(&config_path);
136            if path.exists() {
137                return Ok(path.to_path_buf());
138            }
139        }
140
141        // 2. 尝试查找配置文件的多个可能位置
142        let possible_paths = vec![
143            PathBuf::from("config.toml"),
144            PathBuf::from("apps/admin-server/config.toml"),
145        ];
146
147        for path in &possible_paths {
148            if path.exists() {
149                return Ok(path.clone());
150            }
151        }
152
153        // 3. 检查可执行文件所在目录
154        if let Ok(exe_path) = std::env::current_exe()
155            && let Some(exe_dir) = exe_path.parent()
156        {
157            let config_path = exe_dir.join("config.toml");
158            if config_path.exists() {
159                return Ok(config_path);
160            }
161        }
162
163        // 如果都找不到,返回错误
164        let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
165        Err(anyhow::anyhow!(
166            "无法找到配置文件 config.toml\n\
167            当前工作目录: {:?}\n\
168            已尝试以下路径:\n{}\n\
169            请确保:\n\
170            1. 设置 CONFIG_PATH 环境变量指向配置文件\n\
171            2. 在项目根目录运行程序\n\
172            3. 或将 config.toml 放在可执行文件同目录",
173            cwd,
174            possible_paths
175                .iter()
176                .map(|p| format!("  - {:?}", p))
177                .collect::<Vec<_>>()
178                .join("\n")
179        ))
180    }
181
182    /// 应用环境变量覆盖
183    fn apply_env_overrides(&mut self) {
184        // Server
185        if let Ok(port) = std::env::var("PORT").or_else(|_| std::env::var("SERVER__PORT"))
186            && let Ok(p) = port.parse()
187        {
188            self.server.port = p;
189        }
190        if let Ok(log_level) = std::env::var("RUST_LOG").or_else(|_| std::env::var("SERVER__LOG_LEVEL")) {
191            self.server.log_level = log_level;
192        }
193
194        // Redis
195        if let Ok(host) = std::env::var("REDIS_IP").or_else(|_| std::env::var("REDIS__HOST")) {
196            self.redis.host = host;
197        }
198        if let Ok(port) = std::env::var("REDIS_PORT").or_else(|_| std::env::var("REDIS__PORT"))
199            && let Ok(p) = port.parse()
200        {
201            self.redis.port = p;
202        }
203        if let Ok(password) = std::env::var("REDIS_PASSWORD").or_else(|_| std::env::var("REDIS__PASSWORD")) {
204            self.redis.password = Some(password);
205        }
206
207        // Database (MongoDB)
208        if let Ok(host) = std::env::var("MONGODB_IP").or_else(|_| std::env::var("DATABASE__MONGODB__HOST")) {
209            self.database.mongodb.host = host;
210        }
211        if let Ok(port) = std::env::var("MONGODB_PORT").or_else(|_| std::env::var("DATABASE__MONGODB__PORT"))
212            && let Ok(p) = port.parse()
213        {
214            self.database.mongodb.port = p;
215        }
216        if let Ok(user) = std::env::var("MONGODB_USER").or_else(|_| std::env::var("DATABASE__MONGODB__USERNAME")) {
217            self.database.mongodb.username = user;
218        }
219        if let Ok(password) =
220            std::env::var("MONGODB_PASSWORD").or_else(|_| std::env::var("DATABASE__MONGODB__PASSWORD"))
221        {
222            self.database.mongodb.password = password;
223        }
224        if let Ok(database) =
225            std::env::var("MONGODB_DATABASE").or_else(|_| std::env::var("DATABASE__MONGODB__DATABASE"))
226        {
227            self.database.mongodb.database = database;
228        }
229
230        // Database (MySQL)
231        if let Ok(host) = std::env::var("MYSQL_HOST").or_else(|_| std::env::var("DATABASE__MYSQL__HOST")) {
232            self.database.mysql.host = host;
233        }
234        if let Ok(port) = std::env::var("MYSQL_PORT").or_else(|_| std::env::var("DATABASE__MYSQL__PORT"))
235            && let Ok(p) = port.parse()
236        {
237            self.database.mysql.port = p;
238        }
239        if let Ok(user) = std::env::var("MYSQL_USER").or_else(|_| std::env::var("DATABASE__MYSQL__USERNAME")) {
240            self.database.mysql.username = user;
241        }
242        if let Ok(password) = std::env::var("MYSQL_PASSWORD").or_else(|_| std::env::var("DATABASE__MYSQL__PASSWORD")) {
243            self.database.mysql.password = password;
244        }
245        if let Ok(database) = std::env::var("MYSQL_DATABASE").or_else(|_| std::env::var("DATABASE__MYSQL__DATABASE")) {
246            self.database.mysql.database = database;
247        }
248
249        // Database (PostgreSQL)
250        if let Ok(host) = std::env::var("POSTGRES_HOST").or_else(|_| std::env::var("DATABASE__POSTGRESQL__HOST")) {
251            self.database.postgresql.host = host;
252        }
253        if let Ok(port) = std::env::var("POSTGRES_PORT").or_else(|_| std::env::var("DATABASE__POSTGRESQL__PORT"))
254            && let Ok(p) = port.parse()
255        {
256            self.database.postgresql.port = p;
257        }
258        if let Ok(user) = std::env::var("POSTGRES_USER").or_else(|_| std::env::var("DATABASE__POSTGRESQL__USERNAME")) {
259            self.database.postgresql.username = user;
260        }
261        if let Ok(password) =
262            std::env::var("POSTGRES_PASSWORD").or_else(|_| std::env::var("DATABASE__POSTGRESQL__PASSWORD"))
263        {
264            self.database.postgresql.password = password;
265        }
266        if let Ok(database) =
267            std::env::var("POSTGRES_DATABASE").or_else(|_| std::env::var("DATABASE__POSTGRESQL__DATABASE"))
268        {
269            self.database.postgresql.database = database;
270        }
271
272        // Auth
273        if let Ok(secret) = std::env::var("TOKEN_SECRET").or_else(|_| std::env::var("AUTH__TOKEN_SECRET")) {
274            self.auth.token_secret = secret;
275        }
276
277        // Email
278        if let Ok(host) = std::env::var("SMTP_HOST").or_else(|_| std::env::var("EMAIL__SMTP_HOST")) {
279            self.email.smtp_host = host;
280        }
281        if let Ok(port) = std::env::var("SMTP_PORT").or_else(|_| std::env::var("EMAIL__SMTP_PORT"))
282            && let Ok(p) = port.parse()
283        {
284            self.email.smtp_port = p;
285        }
286        if let Ok(user) = std::env::var("SMTP_USERNAME").or_else(|_| std::env::var("EMAIL__SMTP_USERNAME")) {
287            self.email.smtp_username = user;
288        }
289        if let Ok(password) = std::env::var("SMTP_PASSWORD").or_else(|_| std::env::var("EMAIL__SMTP_PASSWORD")) {
290            self.email.smtp_password = password;
291        }
292
293        // SMS
294        if let Ok(provider) = std::env::var("SMS_PROVIDER").or_else(|_| std::env::var("SMS__PROVIDER")) {
295            self.sms.provider = provider;
296        }
297        if let Ok(app_id) = std::env::var("SMS_APP_ID").or_else(|_| std::env::var("SMS__APP_ID")) {
298            self.sms.app_id = app_id;
299        }
300        if let Ok(app_key) = std::env::var("SMS_APP_KEY").or_else(|_| std::env::var("SMS__APP_KEY")) {
301            self.sms.app_key = app_key;
302        }
303
304        // COS
305        if let Ok(secret_id) = std::env::var("COS_SECRET_ID").or_else(|_| std::env::var("COS__SECRET_ID")) {
306            self.cos.secret_id = secret_id;
307        }
308        if let Ok(secret_key) = std::env::var("COS_SECRET_KEY").or_else(|_| std::env::var("COS__SECRET_KEY")) {
309            self.cos.secret_key = secret_key;
310        }
311        if let Ok(bucket) = std::env::var("COS_BUCKET").or_else(|_| std::env::var("COS__BUCKET")) {
312            self.cos.bucket = bucket;
313        }
314        if let Ok(region) = std::env::var("COS_REGION").or_else(|_| std::env::var("COS__REGION")) {
315            self.cos.region = region;
316        }
317
318        // LLM - 环境变量只覆盖第一个 LLM 配置(如果存在)
319        if let Some(llm) = self.llm.first_mut() {
320            if let Ok(provider) = std::env::var("LLM_PROVIDER").or_else(|_| std::env::var("LLM__PROVIDER")) {
321                llm.provider = provider;
322            }
323            if let Ok(api_key) = std::env::var("LLM_API_KEY").or_else(|_| std::env::var("LLM__API_KEY")) {
324                llm.api_key = api_key;
325            }
326            if let Ok(model) = std::env::var("LLM_MODEL").or_else(|_| std::env::var("LLM__MODEL")) {
327                llm.model = model;
328            }
329            if let Ok(api_base) = std::env::var("LLM_API_BASE").or_else(|_| std::env::var("LLM__API_BASE")) {
330                llm.api_base = Some(api_base);
331            }
332        }
333    }
334}