admin_config/
app_config.rs1use crate::database_config::DatabaseConfig;
26use crate::{
27 AuthConfig, CosConfig, EmailConfig, RedisConfig, SecurityConfig, ServerConfig, SessionConfig, SmsConfig,
28 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 #[serde(default)]
38 pub server: ServerConfig,
39 #[serde(default)]
41 pub database: DatabaseConfig,
42 #[serde(default)]
44 pub redis: RedisConfig,
45 #[serde(default)]
47 pub auth: AuthConfig,
48 #[serde(default)]
50 pub email: EmailConfig,
51 #[serde(default)]
53 pub sms: SmsConfig,
54 #[serde(default)]
56 pub verification_code: VerificationCodeConfig,
57 #[serde(default)]
59 pub cos: CosConfig,
60 #[serde(default)]
62 pub security: SecurityConfig,
63 #[serde(default)]
65 pub session: SessionConfig,
66}
67
68impl AppConfig {
69 pub fn load() -> Result<Self> {
71 Self::load_from_default_path()
72 }
73
74 pub fn load_from_default_path() -> Result<Self> {
76 let path = match Self::find_config_file() {
77 Ok(p) => p,
78 Err(_) => {
79 log::warn!("Config file not found. Using default configuration with environment variables.");
80 return Ok(Self::default_with_env());
81 }
82 };
83
84 Self::load_from_path(&path)
85 }
86
87 fn load_from_path(path: &Path) -> Result<Self> {
89 let config_content = std::fs::read_to_string(path).with_context(|| format!("无法读取配置文件: {:?}", path))?;
90 let mut config: Self = toml::from_str(&config_content).with_context(|| "解析配置文件失败")?;
91 config.apply_env_overrides();
92 Ok(config)
93 }
94
95 fn default_with_env() -> Self {
97 let mut config = Self::default();
98 config.apply_env_overrides();
99 config
100 }
101
102 pub fn from_file(path: &str) -> Result<Self> {
104 Self::load_from_path(Path::new(path))
105 }
106
107 fn find_config_file() -> Result<PathBuf> {
116 if let Ok(config_path) = std::env::var("CONFIG_PATH") {
118 let path = Path::new(&config_path);
119 if path.exists() {
120 return Ok(path.to_path_buf());
121 }
122 }
123
124 let mut possible_paths = Vec::new();
125
126 if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
128 let crate_config = PathBuf::from(&manifest_dir).join("config.toml");
129 possible_paths.push(crate_config.clone());
130 if crate_config.exists() {
131 return Ok(crate_config);
132 }
133 }
134
135 let cwd_config = PathBuf::from("config.toml");
137 possible_paths.push(cwd_config.clone());
138 if cwd_config.exists() {
139 return Ok(cwd_config);
140 }
141
142 if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR")
144 && let Some(workspace_root) = Self::find_workspace_root(&manifest_dir)
145 {
146 let workspace_config = workspace_root.join("config.toml");
147 possible_paths.push(workspace_config.clone());
148 if workspace_config.exists() {
149 return Ok(workspace_config);
150 }
151 }
152
153 if let Some(home_dir) = std::env::var_os("HOME") {
155 let user_config = PathBuf::from(home_dir)
156 .join(".config")
157 .join("admin-config")
158 .join("config.toml");
159 possible_paths.push(user_config.clone());
160 if user_config.exists() {
161 return Ok(user_config);
162 }
163 }
164
165 let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
167 Err(anyhow::anyhow!(
168 "无法找到配置文件 config.toml\n\
169 当前工作目录: {:?}\n\
170 已尝试以下路径:\n{}\n\
171 请确保:\n\
172 1. 设置 CONFIG_PATH 环境变量指向配置文件\n\
173 2. 在当前 crate 目录创建 config.toml\n\
174 3. 在当前工作目录创建 config.toml\n\
175 4. 在 workspace 根目录创建 config.toml\n\
176 5. 在 ~/.config/admin-config/ 目录创建 config.toml",
177 cwd,
178 possible_paths
179 .iter()
180 .map(|p| format!(" - {:?}", p))
181 .collect::<Vec<_>>()
182 .join("\n")
183 ))
184 }
185
186 fn find_workspace_root(start_dir: &str) -> Option<PathBuf> {
190 let mut current = PathBuf::from(start_dir);
191
192 while let Some(parent) = current.parent() {
193 current = parent.to_path_buf();
194 let cargo_toml = current.join("Cargo.toml");
195
196 if cargo_toml.exists()
197 && let Ok(content) = std::fs::read_to_string(&cargo_toml)
198 && content.contains("[workspace]")
199 {
200 return Some(current);
201 }
202
203 if current.parent().is_none() {
204 break;
205 }
206 }
207
208 None
209 }
210
211 fn apply_env_overrides(&mut self) {
213 if let Ok(port) = std::env::var("PORT").or_else(|_| std::env::var("SERVER__PORT"))
215 && let Ok(p) = port.parse()
216 {
217 self.server.port = p;
218 }
219 if let Ok(log_level) = std::env::var("RUST_LOG").or_else(|_| std::env::var("SERVER__LOG_LEVEL")) {
220 self.server.log_level = log_level;
221 }
222
223 if let Ok(host) = std::env::var("REDIS_IP").or_else(|_| std::env::var("REDIS__HOST")) {
225 self.redis.host = host;
226 }
227 if let Ok(port) = std::env::var("REDIS_PORT").or_else(|_| std::env::var("REDIS__PORT"))
228 && let Ok(p) = port.parse()
229 {
230 self.redis.port = p;
231 }
232 if let Ok(password) = std::env::var("REDIS_PASSWORD").or_else(|_| std::env::var("REDIS__PASSWORD")) {
233 self.redis.password = Some(password);
234 }
235
236 if let Ok(host) = std::env::var("MONGODB_IP").or_else(|_| std::env::var("DATABASE__MONGODB__HOST")) {
238 self.database.mongodb.host = host;
239 }
240 if let Ok(port) = std::env::var("MONGODB_PORT").or_else(|_| std::env::var("DATABASE__MONGODB__PORT"))
241 && let Ok(p) = port.parse()
242 {
243 self.database.mongodb.port = p;
244 }
245 if let Ok(user) = std::env::var("MONGODB_USER").or_else(|_| std::env::var("DATABASE__MONGODB__USERNAME")) {
246 self.database.mongodb.username = user;
247 }
248 if let Ok(password) =
249 std::env::var("MONGODB_PASSWORD").or_else(|_| std::env::var("DATABASE__MONGODB__PASSWORD"))
250 {
251 self.database.mongodb.password = password;
252 }
253 if let Ok(database) =
254 std::env::var("MONGODB_DATABASE").or_else(|_| std::env::var("DATABASE__MONGODB__DATABASE"))
255 {
256 self.database.mongodb.database = database;
257 }
258
259 if let Ok(host) = std::env::var("MYSQL_HOST").or_else(|_| std::env::var("DATABASE__MYSQL__HOST")) {
261 self.database.mysql.host = host;
262 }
263 if let Ok(port) = std::env::var("MYSQL_PORT").or_else(|_| std::env::var("DATABASE__MYSQL__PORT"))
264 && let Ok(p) = port.parse()
265 {
266 self.database.mysql.port = p;
267 }
268 if let Ok(user) = std::env::var("MYSQL_USER").or_else(|_| std::env::var("DATABASE__MYSQL__USERNAME")) {
269 self.database.mysql.username = user;
270 }
271 if let Ok(password) = std::env::var("MYSQL_PASSWORD").or_else(|_| std::env::var("DATABASE__MYSQL__PASSWORD")) {
272 self.database.mysql.password = password;
273 }
274 if let Ok(database) = std::env::var("MYSQL_DATABASE").or_else(|_| std::env::var("DATABASE__MYSQL__DATABASE")) {
275 self.database.mysql.database = database;
276 }
277
278 if let Ok(host) = std::env::var("POSTGRES_HOST").or_else(|_| std::env::var("DATABASE__POSTGRESQL__HOST")) {
280 self.database.postgresql.host = host;
281 }
282 if let Ok(port) = std::env::var("POSTGRES_PORT").or_else(|_| std::env::var("DATABASE__POSTGRESQL__PORT"))
283 && let Ok(p) = port.parse()
284 {
285 self.database.postgresql.port = p;
286 }
287 if let Ok(user) = std::env::var("POSTGRES_USER").or_else(|_| std::env::var("DATABASE__POSTGRESQL__USERNAME")) {
288 self.database.postgresql.username = user;
289 }
290 if let Ok(password) =
291 std::env::var("POSTGRES_PASSWORD").or_else(|_| std::env::var("DATABASE__POSTGRESQL__PASSWORD"))
292 {
293 self.database.postgresql.password = password;
294 }
295 if let Ok(database) =
296 std::env::var("POSTGRES_DATABASE").or_else(|_| std::env::var("DATABASE__POSTGRESQL__DATABASE"))
297 {
298 self.database.postgresql.database = database;
299 }
300
301 if let Ok(host) = std::env::var("NEO4J_HOST").or_else(|_| std::env::var("DATABASE__NEO4J__HOST")) {
303 self.database.neo4j.host = host;
304 }
305 if let Ok(port) = std::env::var("NEO4J_PORT").or_else(|_| std::env::var("DATABASE__NEO4J__PORT"))
306 && let Ok(p) = port.parse()
307 {
308 self.database.neo4j.port = p;
309 }
310 if let Ok(user) = std::env::var("NEO4J_USER").or_else(|_| std::env::var("DATABASE__NEO4J__USERNAME")) {
311 self.database.neo4j.username = user;
312 }
313 if let Ok(password) = std::env::var("NEO4J_PASSWORD").or_else(|_| std::env::var("DATABASE__NEO4J__PASSWORD")) {
314 self.database.neo4j.password = password;
315 }
316 if let Ok(database) = std::env::var("NEO4J_DATABASE").or_else(|_| std::env::var("DATABASE__NEO4J__DATABASE")) {
317 self.database.neo4j.database = database;
318 }
319
320 if let Ok(secret) = std::env::var("TOKEN_SECRET").or_else(|_| std::env::var("AUTH__TOKEN_SECRET")) {
322 self.auth.token_secret = secret;
323 }
324
325 if let Ok(host) = std::env::var("SMTP_HOST").or_else(|_| std::env::var("EMAIL__SMTP_HOST")) {
327 self.email.smtp_host = host;
328 }
329 if let Ok(port) = std::env::var("SMTP_PORT").or_else(|_| std::env::var("EMAIL__SMTP_PORT"))
330 && let Ok(p) = port.parse()
331 {
332 self.email.smtp_port = p;
333 }
334 if let Ok(user) = std::env::var("SMTP_USERNAME").or_else(|_| std::env::var("EMAIL__SMTP_USERNAME")) {
335 self.email.smtp_username = user;
336 }
337 if let Ok(password) = std::env::var("SMTP_PASSWORD").or_else(|_| std::env::var("EMAIL__SMTP_PASSWORD")) {
338 self.email.smtp_password = password;
339 }
340
341 if let Ok(provider) = std::env::var("SMS_PROVIDER").or_else(|_| std::env::var("SMS__PROVIDER")) {
343 self.sms.provider = provider;
344 }
345 if let Ok(app_id) = std::env::var("SMS_APP_ID").or_else(|_| std::env::var("SMS__APP_ID")) {
346 self.sms.app_id = app_id;
347 }
348 if let Ok(app_key) = std::env::var("SMS_APP_KEY").or_else(|_| std::env::var("SMS__APP_KEY")) {
349 self.sms.app_key = app_key;
350 }
351
352 if let Ok(provider) = std::env::var("COS_PROVIDER").or_else(|_| std::env::var("COS__PROVIDER")) {
354 self.cos.provider = provider;
355 }
356
357 if let Ok(secret_id) = std::env::var("COS_SECRET_ID").or_else(|_| std::env::var("COS__TENCENT__SECRET_ID")) {
359 self.cos.tencent.secret_id = secret_id;
360 }
361 if let Ok(secret_key) = std::env::var("COS_SECRET_KEY").or_else(|_| std::env::var("COS__TENCENT__SECRET_KEY")) {
362 self.cos.tencent.secret_key = secret_key;
363 }
364 if let Ok(bucket) = std::env::var("COS_BUCKET").or_else(|_| std::env::var("COS__TENCENT__BUCKET")) {
365 self.cos.tencent.bucket = bucket;
366 }
367 if let Ok(region) = std::env::var("COS_REGION").or_else(|_| std::env::var("COS__TENCENT__REGION")) {
368 self.cos.tencent.region = region;
369 }
370
371 if let Ok(access_key_id) =
373 std::env::var("OSS_ACCESS_KEY_ID").or_else(|_| std::env::var("COS__ALIYUN__ACCESS_KEY_ID"))
374 {
375 self.cos.aliyun.access_key_id = access_key_id;
376 }
377 if let Ok(access_key_secret) =
378 std::env::var("OSS_ACCESS_KEY_SECRET").or_else(|_| std::env::var("COS__ALIYUN__ACCESS_KEY_SECRET"))
379 {
380 self.cos.aliyun.access_key_secret = access_key_secret;
381 }
382 if let Ok(bucket) = std::env::var("OSS_BUCKET").or_else(|_| std::env::var("COS__ALIYUN__BUCKET")) {
383 self.cos.aliyun.bucket = bucket;
384 }
385 if let Ok(endpoint) = std::env::var("OSS_ENDPOINT").or_else(|_| std::env::var("COS__ALIYUN__ENDPOINT")) {
386 self.cos.aliyun.endpoint = endpoint;
387 }
388
389 if let Ok(access_key_id) =
391 std::env::var("AWS_ACCESS_KEY_ID").or_else(|_| std::env::var("COS__AWS__ACCESS_KEY_ID"))
392 {
393 self.cos.aws.access_key_id = access_key_id;
394 }
395 if let Ok(secret_access_key) =
396 std::env::var("AWS_SECRET_ACCESS_KEY").or_else(|_| std::env::var("COS__AWS__SECRET_ACCESS_KEY"))
397 {
398 self.cos.aws.secret_access_key = secret_access_key;
399 }
400 if let Ok(bucket) = std::env::var("AWS_S3_BUCKET").or_else(|_| std::env::var("COS__AWS__BUCKET")) {
401 self.cos.aws.bucket = bucket;
402 }
403 if let Ok(region) = std::env::var("AWS_REGION").or_else(|_| std::env::var("COS__AWS__REGION")) {
404 self.cos.aws.region = region;
405 }
406
407 if let Ok(access_key) = std::env::var("MINIO_ACCESS_KEY").or_else(|_| std::env::var("COS__MINIO__ACCESS_KEY")) {
409 self.cos.minio.access_key = access_key;
410 }
411 if let Ok(secret_key) = std::env::var("MINIO_SECRET_KEY").or_else(|_| std::env::var("COS__MINIO__SECRET_KEY")) {
412 self.cos.minio.secret_key = secret_key;
413 }
414 if let Ok(bucket) = std::env::var("MINIO_BUCKET").or_else(|_| std::env::var("COS__MINIO__BUCKET")) {
415 self.cos.minio.bucket = bucket;
416 }
417 if let Ok(endpoint) = std::env::var("MINIO_ENDPOINT").or_else(|_| std::env::var("COS__MINIO__ENDPOINT")) {
418 self.cos.minio.endpoint = endpoint;
419 }
420
421 if let Ok(access_key_id) =
423 std::env::var("HUAWEI_ACCESS_KEY_ID").or_else(|_| std::env::var("COS__HUAWEI__ACCESS_KEY_ID"))
424 {
425 self.cos.huawei.access_key_id = access_key_id;
426 }
427 if let Ok(secret_access_key) =
428 std::env::var("HUAWEI_SECRET_ACCESS_KEY").or_else(|_| std::env::var("COS__HUAWEI__SECRET_ACCESS_KEY"))
429 {
430 self.cos.huawei.secret_access_key = secret_access_key;
431 }
432 if let Ok(bucket) = std::env::var("HUAWEI_BUCKET").or_else(|_| std::env::var("COS__HUAWEI__BUCKET")) {
433 self.cos.huawei.bucket = bucket;
434 }
435 if let Ok(endpoint) = std::env::var("HUAWEI_ENDPOINT").or_else(|_| std::env::var("COS__HUAWEI__ENDPOINT")) {
436 self.cos.huawei.endpoint = endpoint;
437 }
438
439 if let Ok(root_path) = std::env::var("RUSTFS_ROOT_PATH").or_else(|_| std::env::var("COS__RUSTFS__ROOT_PATH")) {
441 self.cos.rustfs.root_path = root_path;
442 }
443 if let Ok(public_url_prefix) =
444 std::env::var("RUSTFS_PUBLIC_URL_PREFIX").or_else(|_| std::env::var("COS__RUSTFS__PUBLIC_URL_PREFIX"))
445 {
446 self.cos.rustfs.public_url_prefix = public_url_prefix;
447 }
448 }
449
450 pub fn generate_to_file(path: &str) -> Result<Self> {
452 let config_path = Path::new(path);
453
454 if config_path.exists() {
455 Self::load_from_path(config_path)
456 } else {
457 let default_config = Self::default();
458
459 let toml_content = toml::to_string_pretty(&default_config).context("序列化配置为 TOML 失败")?;
460
461 if let Some(parent) = config_path.parent() {
462 std::fs::create_dir_all(parent).with_context(|| format!("创建配置文件目录失败: {:?}", parent))?;
463 }
464
465 std::fs::write(config_path, toml_content)
466 .with_context(|| format!("写入配置文件失败: {:?}", config_path))?;
467
468 Self::print_security_warning();
469
470 Ok(default_config)
471 }
472 }
473
474 fn print_security_warning() {
475 log::warn!("⚠️ 安全提示:");
476 log::warn!(" 1. 系统已自动生成安全密钥,请妥善保管配置文件");
477 log::warn!(" 2. 修改配置文件中的其他敏感信息(数据库密码、API密钥等)");
478 log::warn!(" 3. 不要将包含真实密钥的配置文件提交到 Git 仓库");
479 log::warn!(" 4. 生产环境建议使用环境变量或密钥管理服务");
480 }
481}
482
483impl AppConfig {
484 pub fn generate(&self) -> anyhow::Result<AppConfig> {
497 Self::generate_to_file("config.toml")
498 }
499}