Skip to main content

alun_config/
lib.rs

1//! 配置系统:TOML 加载、静态/动态配置、生成默认文件
2//!
3//! 设计要点:
4//! - `profile` → 多环境 Profile 切换
5//! - Settings/Routes/Plugins → 统一 AppConfig struct
6//!
7//! alun 特性:
8//! - TOML 格式(结构清晰,强于 properties)
9//! - 静态配置(启动加载)+ 动态配置(运行时读写)
10//! - `gen-config` 命令行参数一键生成默认配置
11
12use std::path::Path;
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15use std::fs;
16use parking_lot::RwLock;
17use tracing::info;
18
19pub mod env;
20pub use env::{detect_profile, parse_args, merge_env_overrides};
21
22/// 完整应用配置——Settings + Routes + Plugins 三合一
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct AppConfig {
25    /// 应用名称
26    #[serde(default = "default_app_name")]
27    pub app_name: String,
28
29    /// 当前激活的 profile(dev/prod/test)
30    #[serde(default = "default_profile")]
31    pub profile: String,
32
33    /// 服务器配置
34    #[serde(default)]
35    pub server: ServerConfig,
36
37    /// 日志配置
38    #[serde(default)]
39    pub log: LogConfig,
40
41    /// 数据库配置
42    #[serde(default)]
43    pub database: DatabaseConfig,
44
45    /// Redis 配置
46    #[serde(default)]
47    pub redis: RedisConfig,
48
49    /// 缓存配置
50    #[serde(default)]
51    pub cache: CacheConfig,
52
53    /// 中间件配置
54    #[serde(default)]
55    pub middleware: MiddlewareConfig,
56
57    /// 路由配置
58    #[serde(default)]
59    pub router: RouterConfig,
60
61    /// 插件配置
62    #[serde(default)]
63    pub plugins: PluginsConfig,
64
65    /// 上传配置
66    #[serde(default)]
67    pub upload: UploadConfig,
68
69    /// 下载配置
70    #[serde(default)]
71    pub download: DownloadConfig,
72
73    /// 模板配置
74    #[serde(default)]
75    pub template: TemplateConfig,
76
77    /// 静态文件配置
78    #[serde(default)]
79    pub static_files: StaticConfig,
80
81    /// 自定义配置(供插件运行时读写)
82    #[serde(default)]
83    pub custom: HashMap<String, serde_json::Value>,
84}
85
86/// 服务器配置
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct ServerConfig {
89    /// 监听地址
90    #[serde(default = "default_listen")]
91    pub listen: String,
92}
93
94/// 日志配置
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct LogConfig {
97    /// 日志级别:trace/debug/info/warn/error
98    #[serde(default = "default_log_level")]
99    pub level: String,
100
101    /// 输出格式:text/json
102    #[serde(default = "default_log_format")]
103    pub format: String,
104
105    /// 输出目录(同时输出到文件),默认不输出
106    #[serde(default)]
107    pub dir: Option<String>,
108
109    /// 文件名前缀
110    #[serde(default = "default_log_prefix")]
111    pub file_prefix: String,
112}
113
114/// 数据库配置
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct DatabaseConfig {
117    /// 是否启用
118    #[serde(default = "default_true")]
119    pub enabled: bool,
120
121    /// 数据库类型:postgres/mysql/sqlite
122    #[serde(default = "default_db_type")]
123    pub r#type: String,
124
125    /// 主机
126    #[serde(default = "default_host")]
127    pub host: String,
128
129    /// 端口
130    pub port: Option<u16>,
131
132    /// 数据库名
133    #[serde(default)]
134    pub name: String,
135
136    /// 用户名
137    #[serde(default)]
138    pub user: String,
139
140    /// 密码(支持明文或 base64 密文,server.key 解密)
141    #[serde(default)]
142    pub password: String,
143
144    /// 密码是否加密
145    #[serde(default)]
146    pub password_encrypted: bool,
147
148    /// 最大连接数
149    #[serde(default = "default_pool_size")]
150    pub max_connections: u32,
151
152    /// 最小空闲连接
153    #[serde(default = "default_min_idle")]
154    pub min_connections: u32,
155
156    /// 连接超时(秒)
157    #[serde(default = "default_timeout")]
158    pub connect_timeout: u64,
159
160    /// 启用 SQL 日志
161    #[serde(default)]
162    pub sql_logging: bool,
163
164    /// 慢查询阈值(毫秒)
165    #[serde(default)]
166    pub slow_query_ms: u64,
167
168    /// 迁移配置
169    #[serde(default)]
170    pub migration: MigrationConfig,
171}
172
173/// Redis 配置
174#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct RedisConfig {
176    /// 是否启用 Redis
177    #[serde(default)]
178    pub enabled: bool,
179
180    /// Redis 连接 URL(如 `redis://127.0.0.1:6379`)
181    #[serde(default = "default_redis_url")]
182    pub url: String,
183
184    /// 最大连接数
185    #[serde(default = "default_pool_size")]
186    pub max_connections: u32,
187}
188
189/// 缓存配置
190#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct CacheConfig {
192    /// 缓存类型:`local`(内存)或 `redis`(远端)
193    #[serde(default = "default_cache_type")]
194    pub r#type: String,
195
196    /// 本地缓存最大容量(条目数)
197    #[serde(default = "default_cache_capacity")]
198    pub max_capacity: u64,
199
200    /// 默认 TTL(秒),0 表示永不过期
201    #[serde(default = "default_ttl")]
202    pub default_ttl: u64,
203}
204
205/// 中间件配置
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct MiddlewareConfig {
208    /// 请求 ID 中间件
209    #[serde(default)]
210    pub request_id: bool,
211
212    /// 日志中间件
213    #[serde(default)]
214    pub request_log: bool,
215
216    /// 请求日志配置
217    #[serde(default)]
218    pub request_log_config: RequestLogConfig,
219
220    /// 认证中间件
221    #[serde(default)]
222    pub auth: AuthMiddlewareConfig,
223
224    /// CORS 配置
225    #[serde(default)]
226    pub cors: CorsConfig,
227
228    /// 压缩配置
229    #[serde(default)]
230    pub compression: CompressConfig,
231
232    /// IP 限流配置
233    #[serde(default)]
234    pub rate_limit: RateLimitConfig,
235
236    /// 安全头配置
237    #[serde(default)]
238    pub security_headers: SecurityHeadersConfig,
239
240    /// 权限校验配置
241    #[serde(default)]
242    pub permission: PermissionConfig,
243}
244
245/// 请求日志中间件配置
246#[derive(Debug, Clone, Serialize, Deserialize)]
247pub struct RequestLogConfig {
248    /// 不记录日志的路径列表(如 "/api/health") 注意不含prefix前缀
249    #[serde(default)]
250    pub exclude_paths: Vec<String>,
251
252    /// 是否记录请求耗时
253    #[serde(default = "default_true")]
254    pub log_duration: bool,
255}
256
257/// 认证中间件
258#[derive(Debug, Clone, Serialize, Deserialize)]
259pub struct AuthMiddlewareConfig {
260    #[serde(default)]
261    pub enabled: bool,
262
263    /// 白名单路径 注意不含prefix前缀
264    #[serde(default)]
265    pub ignore_paths: Vec<String>,
266
267    /// JWT secret
268    #[serde(default)]
269    pub jwt_secret: String,
270
271    /// Access Token 过期(秒)
272    #[serde(default = "default_access_token_expire")]
273    pub access_token_expire_secs: u64,
274
275    /// Refresh Token 过期(秒)
276    #[serde(default = "default_refresh_token_expire")]
277    pub refresh_token_expire_secs: u64,
278}
279
280/// CORS 配置
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct CorsConfig {
283    #[serde(default)]
284    pub enabled: bool,
285
286    #[serde(default)]
287    pub allow_origins: Vec<String>,
288
289    #[serde(default)]
290    pub allow_methods: Vec<String>,
291
292    #[serde(default)]
293    pub allow_headers: Vec<String>,
294
295    #[serde(default = "default_true")]
296    pub allow_credentials: bool,
297
298    #[serde(default = "default_cors_max_age")]
299    pub max_age_secs: u64,
300}
301
302/// 响应压缩配置
303#[derive(Debug, Clone, Serialize, Deserialize)]
304pub struct CompressConfig {
305    /// 是否启用 gzip 压缩
306    #[serde(default)]
307    pub enabled: bool,
308
309    /// 压缩级别 0-9(0 不压缩,9 最高压缩率)
310    #[serde(default = "default_compress_level")]
311    pub level: u32,
312}
313
314/// IP 限流配置
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct RateLimitConfig {
317    #[serde(default)]
318    pub enabled: bool,
319
320    /// 每个窗口允许的请求数
321    #[serde(default = "default_rate_limit_requests")]
322    pub requests_per_window: u64,
323
324    /// 窗口大小(秒)
325    #[serde(default = "default_rate_limit_window")]
326    pub window_secs: u64,
327}
328
329/// 安全响应头配置
330///
331/// 默认全部开启,通过 `enabled = false` 可关闭整个中间件,
332/// 或按需关闭单个 header。
333#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct SecurityHeadersConfig {
335    /// 是否启用安全头中间件
336    #[serde(default = "default_true")]
337    pub enabled: bool,
338
339    /// X-Content-Type-Options: nosniff
340    #[serde(default = "default_true")]
341    pub nosniff: bool,
342
343    /// X-Frame-Options: DENY
344    #[serde(default = "default_true")]
345    pub frame_options: bool,
346
347    /// Strict-Transport-Security (HSTS)
348    #[serde(default = "default_true")]
349    pub hsts: bool,
350
351    /// HSTS max-age(秒),默认 1 年
352    #[serde(default = "default_hsts_max_age")]
353    pub hsts_max_age_secs: u64,
354
355    /// HSTS 是否包含子域名
356    #[serde(default = "default_true")]
357    pub hsts_include_subdomains: bool,
358
359    /// Content-Security-Policy
360    #[serde(default = "default_true")]
361    pub csp: bool,
362
363    /// CSP 指令值(默认 `default-src 'self'`)
364    #[serde(default = "default_csp_value")]
365    pub csp_value: String,
366
367    /// Referrer-Policy
368    #[serde(default = "default_true")]
369    pub referrer_policy: bool,
370
371    /// Referrer-Policy 值(默认 `strict-origin-when-cross-origin`)
372    #[serde(default = "default_referrer_policy_value")]
373    pub referrer_policy_value: String,
374
375    /// Permissions-Policy(可选,默认关闭)
376    #[serde(default)]
377    pub permissions_policy: bool,
378
379    /// Permissions-Policy 指令值
380    #[serde(default = "default_permissions_policy_value")]
381    pub permissions_policy_value: String,
382}
383
384/// 权限校验中间件配置
385///
386/// 支持两种方式定义权限规则:
387/// 1. 配置文件 `rules`:路径模式匹配,灵活但需要重启
388/// 2. 宏注解 `#[permission]`:编译期绑定,与处理器同在一个文件,直观
389#[derive(Debug, Clone, Serialize, Deserialize)]
390pub struct PermissionConfig {
391    /// 是否启用权限全局开关(关闭后所有权限校验跳过)
392    #[serde(default)]
393    pub enabled: bool,
394
395    /// 路径级权限规则
396    #[serde(default)]
397    pub rules: Vec<PermissionRule>,
398}
399
400/// 单条路径权限规则
401#[derive(Debug, Clone, Serialize, Deserialize)]
402pub struct PermissionRule {
403    /// 匹配路径,支持前缀匹配(如 "/api/admin" 匹配所有 /api/admin/* 请求)
404    pub path: String,
405    /// 限定 HTTP 方法(空表示所有方法)
406    #[serde(default)]
407    pub methods: Vec<String>,
408    /// 所需权限标识(如 "admin:access", "user:write")
409    pub permission: String,
410}
411
412/// 路由配置
413#[derive(Debug, Clone, Serialize, Deserialize)]
414pub struct RouterConfig {
415    /// 全局路由前缀
416    #[serde(default)]
417    pub prefix: String,
418    /// 404 处理配置
419    #[serde(default)]
420    pub not_found: NotFoundConfig,
421}
422
423/// 404 处理配置
424#[derive(Debug, Clone, Serialize, Deserialize)]
425pub struct NotFoundConfig {
426    /// 是否启用自定义 404 响应(返回 JSON 格式的统一错误响应)
427    #[serde(default = "default_true")]
428    pub enabled: bool,
429    /// 自定义 404 提示消息
430    #[serde(default = "default_not_found_msg")]
431    pub message: String,
432}
433
434/// 数据库迁移配置
435#[derive(Debug, Clone, Serialize, Deserialize)]
436pub struct MigrationConfig {
437    /// 是否启用迁移
438    #[serde(default)]
439    pub enabled: bool,
440
441    /// 迁移文件目录
442    #[serde(default = "default_migration_path")]
443    pub path: String,
444
445    /// 启动时自动运行迁移
446    #[serde(default)]
447    pub auto_migrate: bool,
448}
449
450/// 上传配置
451#[derive(Debug, Clone, Serialize, Deserialize)]
452pub struct UploadConfig {
453    /// 上传文件存储目录
454    #[serde(default = "default_upload_path")]
455    pub path: String,
456
457    /// 最大文件大小(MB)
458    #[serde(default = "default_max_size")]
459    pub max_size_mb: u64,
460}
461
462/// 下载配置
463#[derive(Debug, Clone, Serialize, Deserialize)]
464pub struct DownloadConfig {
465    /// 下载文件存储目录
466    #[serde(default = "default_download_path")]
467    pub path: String,
468}
469
470/// 模板配置
471#[derive(Debug, Clone, Serialize, Deserialize)]
472pub struct TemplateConfig {
473    /// 模板文件目录
474    #[serde(default = "default_template_path")]
475    pub path: String,
476}
477
478/// 静态文件配置
479#[derive(Debug, Clone, Serialize, Deserialize)]
480pub struct StaticConfig {
481    /// 静态文件目录
482    #[serde(default = "default_static_path")]
483    pub path: String,
484
485    /// 是否启用静态文件服务
486    #[serde(default)]
487    pub enabled: bool,
488}
489
490/// 插件配置
491#[derive(Debug, Clone, Serialize, Deserialize)]
492pub struct PluginsConfig {
493    /// 启用的插件列表
494    #[serde(default)]
495    pub enabled: Vec<String>,
496
497    /// 通知插件
498    #[serde(default)]
499    pub notification: NotificationConfig,
500
501    /// 异步任务插件
502    #[serde(default)]
503    pub async_task: AsyncTaskConfig,
504
505    /// 定时任务插件
506    #[serde(default)]
507    pub scheduler: SchedulerConfig,
508}
509
510#[derive(Debug, Clone, Serialize, Deserialize, Default)]
511pub struct NotificationConfig {
512    /// 是否启用
513    #[serde(default)]
514    pub enabled: bool,
515    /// 邮箱 SMTP 服务器
516    #[serde(default)]
517    pub smtp_host: String,
518    /// SMTP 端口(默认 587)
519    #[serde(default = "default_smtp_port")]
520    pub smtp_port: u16,
521    /// SMTP 登录用户名
522    #[serde(default)]
523    pub smtp_user: String,
524    /// SMTP 登录密码
525    #[serde(default)]
526    pub smtp_pass: String,
527    /// 发件人邮箱地址(与 smtp_user 可能不同,如代理发信场景)
528    #[serde(default)]
529    pub from_email: String,
530    /// 发件人显示名称
531    #[serde(default)]
532    pub from_name: String,
533}
534
535fn default_smtp_port() -> u16 { 587 }
536
537#[derive(Debug, Clone, Serialize, Deserialize, Default)]
538pub struct AsyncTaskConfig {
539    /// 工作线程数(默认 4)
540    #[serde(default = "default_workers")]
541    pub workers: usize,
542}
543
544#[derive(Debug, Clone, Serialize, Deserialize, Default)]
545pub struct SchedulerConfig {
546    /// 调度器工作线程数(默认 4)
547    #[serde(default = "default_workers")]
548    pub workers: usize,
549}
550
551// ──── 默认值函数 ────────────────────────────────────
552
553fn default_app_name() -> String { "Alun".into() }
554fn default_profile() -> String { "dev".into() }
555fn default_listen() -> String { "8023".into() }
556fn default_log_level() -> String { "info".into() }
557fn default_log_format() -> String { "text".into() }
558fn default_log_prefix() -> String { "alun".into() }
559fn default_db_type() -> String { "postgres".into() }
560fn default_host() -> String { "localhost".into() }
561fn default_true() -> bool { true }
562fn default_pool_size() -> u32 { 10 }
563fn default_min_idle() -> u32 { 2 }
564fn default_timeout() -> u64 { 10 }
565fn default_workers() -> usize { 4 }
566fn default_redis_url() -> String { "redis://127.0.0.1:6379".into() }
567fn default_cache_type() -> String { "local".into() }
568fn default_cache_capacity() -> u64 { 10000 }
569fn default_ttl() -> u64 { 3600 }
570fn default_access_token_expire() -> u64 { 7200 }
571fn default_refresh_token_expire() -> u64 { 604800 }
572fn default_cors_max_age() -> u64 { 86400 }
573fn default_compress_level() -> u32 { 6 }
574fn default_rate_limit_requests() -> u64 { 100 }
575fn default_rate_limit_window() -> u64 { 60 }
576fn default_hsts_max_age() -> u64 { 31536000 }
577fn default_csp_value() -> String { "default-src 'self'".into() }
578fn default_referrer_policy_value() -> String { "strict-origin-when-cross-origin".into() }
579fn default_permissions_policy_value() -> String {
580    "camera=(), microphone=(), geolocation=()".into()
581}
582fn default_migration_path() -> String { "migrations".into() }
583fn default_upload_path() -> String { "uploads".into() }
584fn default_download_path() -> String { "downloads".into() }
585fn default_template_path() -> String { "templates".into() }
586fn default_static_path() -> String { "static".into() }
587fn default_not_found_msg() -> String { "请求的资源不存在".into() }
588fn default_max_size() -> u64 { 10 }
589
590impl Default for AppConfig {
591    fn default() -> Self {
592        Self {
593            app_name: default_app_name(),
594            profile: default_profile(),
595            server: ServerConfig::default(),
596            log: LogConfig::default(),
597            database: DatabaseConfig::default(),
598            redis: RedisConfig::default(),
599            cache: CacheConfig::default(),
600            middleware: MiddlewareConfig::default(),
601            router: RouterConfig::default(),
602            plugins: PluginsConfig::default(),
603            upload: UploadConfig::default(),
604            download: DownloadConfig::default(),
605            template: TemplateConfig::default(),
606            static_files: StaticConfig::default(),
607            custom: HashMap::new(),
608        }
609    }
610}
611
612impl Default for ServerConfig { fn default() -> Self { Self { listen: default_listen() } } }
613impl Default for LogConfig {
614    fn default() -> Self {
615        Self {
616            level: default_log_level(),
617            format: default_log_format(),
618            dir: None,
619            file_prefix: default_log_prefix(),
620        }
621    }
622}
623impl Default for DatabaseConfig {
624    fn default() -> Self {
625        Self {
626            enabled: false, r#type: default_db_type(), host: default_host(),
627            port: None, name: String::new(), user: String::new(), password: String::new(),
628            password_encrypted: false,
629            max_connections: default_pool_size(), min_connections: default_min_idle(),
630            connect_timeout: default_timeout(), sql_logging: false, slow_query_ms: 0,
631            migration: MigrationConfig::default(),
632        }
633    }
634}
635impl Default for RedisConfig { fn default() -> Self { Self { enabled: false, url: default_redis_url(), max_connections: default_pool_size() } } }
636impl Default for CacheConfig { fn default() -> Self { Self { r#type: default_cache_type(), max_capacity: default_cache_capacity(), default_ttl: default_ttl() } } }
637impl Default for MiddlewareConfig {
638    fn default() -> Self {
639        Self {
640            request_id: false, request_log: false,
641            request_log_config: RequestLogConfig::default(),
642            auth: AuthMiddlewareConfig::default(),
643            cors: CorsConfig::default(),
644            compression: CompressConfig::default(),
645            rate_limit: RateLimitConfig::default(),
646            security_headers: SecurityHeadersConfig::default(),
647            permission: PermissionConfig::default(),
648        }
649    }
650}
651impl Default for RequestLogConfig {
652    fn default() -> Self { Self { exclude_paths: vec![], log_duration: true } }
653}
654impl Default for AuthMiddlewareConfig { fn default() -> Self { Self { enabled: false, ignore_paths: vec![], jwt_secret: String::new(), access_token_expire_secs: default_access_token_expire(), refresh_token_expire_secs: default_refresh_token_expire() } } }
655impl Default for CorsConfig { fn default() -> Self { Self { enabled: false, allow_origins: vec![], allow_methods: vec![], allow_headers: vec![], allow_credentials: true, max_age_secs: default_cors_max_age() } } }
656impl Default for CompressConfig { fn default() -> Self { Self { enabled: false, level: default_compress_level() } } }
657impl Default for RateLimitConfig { fn default() -> Self { Self { enabled: false, requests_per_window: default_rate_limit_requests(), window_secs: default_rate_limit_window() } } }
658impl Default for SecurityHeadersConfig {
659    fn default() -> Self {
660        Self {
661            enabled: true,
662            nosniff: true, frame_options: true,
663            hsts: true, hsts_max_age_secs: default_hsts_max_age(),
664            hsts_include_subdomains: true,
665            csp: true, csp_value: default_csp_value(),
666            referrer_policy: true, referrer_policy_value: default_referrer_policy_value(),
667            permissions_policy: false, permissions_policy_value: default_permissions_policy_value(),
668        }
669    }
670}
671impl Default for PermissionConfig { fn default() -> Self { Self { enabled: false, rules: vec![] } } }
672impl Default for PermissionRule { fn default() -> Self { Self { path: String::new(), methods: vec![], permission: String::new() } } }
673impl Default for RouterConfig { fn default() -> Self { Self { prefix: String::new(), not_found: NotFoundConfig::default() } } }
674impl Default for NotFoundConfig { fn default() -> Self { Self { enabled: true, message: default_not_found_msg() } } }
675impl Default for MigrationConfig { fn default() -> Self { Self { enabled: false, path: default_migration_path(), auto_migrate: false } } }
676impl Default for UploadConfig { fn default() -> Self { Self { path: default_upload_path(), max_size_mb: default_max_size() } } }
677impl Default for DownloadConfig { fn default() -> Self { Self { path: default_download_path() } } }
678impl Default for TemplateConfig { fn default() -> Self { Self { path: default_template_path() } } }
679impl Default for StaticConfig { fn default() -> Self { Self { path: default_static_path(), enabled: false } } }
680impl Default for PluginsConfig { fn default() -> Self { Self { enabled: vec![], notification: NotificationConfig::default(), async_task: AsyncTaskConfig::default(), scheduler: SchedulerConfig::default() } } }
681
682// ──── 配置管理器 ────────────────────────────────────
683
684/// 配置管理器——持有静态配置 + 允许运行时覆盖
685pub struct ConfigManager {
686    /// 静态配置(启动时加载,不可变)
687    pub static_config: AppConfig,
688    /// 动态配置(运行时可通过插件修改)
689    pub dynamic: RwLock<HashMap<String, serde_json::Value>>,
690}
691
692impl ConfigManager {
693    /// 从 config/config.toml 加载,若不存在则用默认值
694    pub fn load(config_dir: Option<&str>) -> Self {
695        let dir = config_dir.unwrap_or("config");
696        let profile = detect_profile();
697
698        let mut cfg = Self::load_file(dir, &profile);
699
700        // 环境变量覆盖
701        merge_env_overrides(&mut cfg);
702
703        info!("配置加载完成 profile={}, listen={}", cfg.profile, cfg.server.listen);
704
705        Self {
706            static_config: cfg,
707            dynamic: RwLock::new(HashMap::new()),
708        }
709    }
710
711    fn load_file(dir: &str, profile: &str) -> AppConfig {
712        // 1. 尝试 config/config.toml
713        let base_path = Path::new(dir).join("config.toml");
714        let mut cfg = if base_path.exists() {
715            let content = fs::read_to_string(&base_path)
716                .unwrap_or_else(|_| String::new());
717            toml::from_str(&content).unwrap_or_default()
718        } else {
719            AppConfig::default()
720        };
721
722        // 2. 叠加 config.toml 中的 main_env 路径
723        //    或 config/config-{profile}.toml
724        let profile_path = Path::new(dir).join(format!("config-{}.toml", profile));
725        if profile_path.exists() {
726            if let Ok(content) = fs::read_to_string(&profile_path) {
727                if let Ok(profile_cfg) = toml::from_str::<AppConfig>(&content) {
728                    merge_configs(&mut cfg, &profile_cfg);
729                }
730            }
731        }
732
733        cfg.profile = profile.to_string();
734        cfg
735    }
736
737    /// 获取静态配置引用
738    pub fn get(&self) -> &AppConfig {
739        &self.static_config
740    }
741
742    /// 获取动态配置值
743    pub fn get_dynamic(&self, key: &str) -> Option<serde_json::Value> {
744        self.dynamic.read().get(key).cloned()
745    }
746
747    /// 设置动态配置
748    pub fn set_dynamic(&self, key: &str, value: serde_json::Value) {
749        self.dynamic.write().insert(key.to_string(), value);
750    }
751
752    /// 删除动态配置
753    pub fn remove_dynamic(&self, key: &str) {
754        self.dynamic.write().remove(key);
755    }
756
757    /// 生成默认配置文件到 config/config.toml
758    pub fn generate_default(dir: &str) -> std::io::Result<()> {
759        let config_dir = Path::new(dir);
760        fs::create_dir_all(config_dir)?;
761
762        let cfg = AppConfig::default();
763        let toml_str = toml::to_string_pretty(&cfg)
764            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
765
766        let header = r#"# Alun 默认配置文件
767# 修改后保存即可生效(需重启服务)
768#
769# 使用 --gen-config 参数可重新生成此文件到 config/config.toml
770# 多环境:创建 config/config-dev.toml, config/config-prod.toml
771#         通过环境变量或命令行 --profile=prod 指定
772
773"#;
774
775        fs::write(config_dir.join("config.toml"), format!("{}{}", header, toml_str))?;
776        info!("默认配置文件已生成到 {}/config.toml", dir);
777        Ok(())
778    }
779}
780
781/// 合并两个配置(profile 覆盖 base 中有值的字段)
782fn merge_configs(base: &mut AppConfig, overlay: &AppConfig) {
783    if overlay.server.listen != default_listen() { base.server.listen = overlay.server.listen.clone(); }
784    if overlay.log.level != default_log_level() { base.log.level = overlay.log.level.clone(); }
785    if overlay.log.format != default_log_format() { base.log.format = overlay.log.format.clone(); }
786    if overlay.log.dir.is_some() { base.log.dir = overlay.log.dir.clone(); }
787    if overlay.log.file_prefix != default_log_prefix() { base.log.file_prefix = overlay.log.file_prefix.clone(); }
788    if overlay.database.host != default_host() || !overlay.database.name.is_empty() {
789        base.database = overlay.database.clone();
790    }
791    if overlay.redis.url != default_redis_url() { base.redis = overlay.redis.clone(); }
792    if overlay.cache.r#type != default_cache_type() { base.cache = overlay.cache.clone(); }
793    if overlay.router.prefix != String::new() { base.router.prefix = overlay.router.prefix.clone(); }
794    if overlay.router.not_found.message != default_not_found_msg() {
795        base.router.not_found.message = overlay.router.not_found.message.clone();
796    }
797    if !overlay.router.not_found.enabled {
798        base.router.not_found.enabled = false;
799    }
800    if overlay.upload.path != default_upload_path() { base.upload = overlay.upload.clone(); }
801    if overlay.download.path != default_download_path() { base.download = overlay.download.clone(); }
802    if overlay.template.path != default_template_path() { base.template = overlay.template.clone(); }
803    if overlay.static_files.path != default_static_path() { base.static_files = overlay.static_files.clone(); }
804
805    // 中间件采用字段级合并:仅覆盖 profile 文件中显式配置的项
806    merge_middleware(&mut base.middleware, &overlay.middleware);
807
808    // 插件采用完全替换(数组无法字段级合并)
809    if !overlay.plugins.enabled.is_empty() {
810        base.plugins = overlay.plugins.clone();
811    }
812    for (k, v) in &overlay.custom { base.custom.insert(k.clone(), v.clone()); }
813}
814
815fn merge_middleware(base: &mut MiddlewareConfig, overlay: &MiddlewareConfig) {
816    let default_mw = MiddlewareConfig::default();
817    if overlay.request_id != default_mw.request_id { base.request_id = overlay.request_id; }
818    if overlay.request_log != default_mw.request_log { base.request_log = overlay.request_log; }
819    if overlay.request_log_config.log_duration != default_mw.request_log_config.log_duration {
820        base.request_log_config.log_duration = overlay.request_log_config.log_duration;
821    }
822    if !overlay.request_log_config.exclude_paths.is_empty() {
823        base.request_log_config.exclude_paths = overlay.request_log_config.exclude_paths.clone();
824    }
825    if overlay.auth.enabled != default_mw.auth.enabled { base.auth.enabled = overlay.auth.enabled; }
826    if overlay.auth.jwt_secret != default_mw.auth.jwt_secret { base.auth.jwt_secret = overlay.auth.jwt_secret.clone(); }
827    if overlay.auth.access_token_expire_secs != 0 { base.auth.access_token_expire_secs = overlay.auth.access_token_expire_secs; }
828    if overlay.auth.refresh_token_expire_secs != 0 { base.auth.refresh_token_expire_secs = overlay.auth.refresh_token_expire_secs; }
829    if !overlay.auth.ignore_paths.is_empty() { base.auth.ignore_paths = overlay.auth.ignore_paths.clone(); }
830    if overlay.cors.enabled != default_mw.cors.enabled { base.cors.enabled = overlay.cors.enabled; }
831    if !overlay.cors.allow_origins.is_empty() { base.cors.allow_origins = overlay.cors.allow_origins.clone(); }
832    if overlay.compression.enabled != default_mw.compression.enabled { base.compression.enabled = overlay.compression.enabled; }
833    if overlay.rate_limit.enabled != default_mw.rate_limit.enabled { base.rate_limit.enabled = overlay.rate_limit.enabled; }
834    if overlay.rate_limit.requests_per_window != 0 { base.rate_limit.requests_per_window = overlay.rate_limit.requests_per_window; }
835    if overlay.rate_limit.window_secs != 0 { base.rate_limit.window_secs = overlay.rate_limit.window_secs; }
836}
837
838#[cfg(test)]
839mod tests {
840    use super::*;
841
842    #[test]
843    fn test_default_config_serialization() {
844        let cfg = AppConfig::default();
845        let toml_str = toml::to_string_pretty(&cfg).unwrap();
846        assert!(toml_str.contains("listen = \"8023\""));
847        assert!(toml_str.contains("level = \"info\""));
848    }
849}