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    #[serde(default)]
511    pub serial: SerialConfig,
512}
513
514#[derive(Debug, Clone, Serialize, Deserialize, Default)]
515pub struct NotificationConfig {
516    /// 是否启用
517    #[serde(default)]
518    pub enabled: bool,
519    /// 邮箱 SMTP 服务器
520    #[serde(default)]
521    pub smtp_host: String,
522    /// SMTP 端口(默认 587)
523    #[serde(default = "default_smtp_port")]
524    pub smtp_port: u16,
525    /// SMTP 登录用户名
526    #[serde(default)]
527    pub smtp_user: String,
528    /// SMTP 登录密码
529    #[serde(default)]
530    pub smtp_pass: String,
531    /// 发件人邮箱地址(与 smtp_user 可能不同,如代理发信场景)
532    #[serde(default)]
533    pub from_email: String,
534    /// 发件人显示名称
535    #[serde(default)]
536    pub from_name: String,
537}
538
539fn default_smtp_port() -> u16 { 587 }
540
541#[derive(Debug, Clone, Serialize, Deserialize, Default)]
542pub struct AsyncTaskConfig {
543    /// 工作线程数(默认 4)
544    #[serde(default = "default_workers")]
545    pub workers: usize,
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize, Default)]
549pub struct SchedulerConfig {
550    /// 调度器工作线程数(默认 4)
551    #[serde(default = "default_workers")]
552    pub workers: usize,
553}
554
555/// 单号生成器配置
556#[derive(Debug, Clone, Serialize, Deserialize)]
557pub struct SerialConfig {
558    /// 后端类型:"memory"(默认)、"redis"、"postgres"
559    #[serde(default = "default_serial_backend")]
560    pub backend: String,
561    /// 静态规则列表
562    #[serde(default)]
563    pub rules: Vec<SerialRuleConfig>,
564}
565
566impl Default for SerialConfig {
567    fn default() -> Self {
568        Self { backend: "memory".into(), rules: Vec::new() }
569    }
570}
571
572fn default_serial_backend() -> String { "memory".into() }
573
574/// 单号规则配置(TOML 可序列化)
575#[derive(Debug, Clone, Serialize, Deserialize)]
576pub struct SerialRuleConfig {
577    /// 规则唯一标识
578    pub key: String,
579    /// 单号格式,如 "ORD{YYYY}{MM}{DD}{SEQ:8}"
580    pub format: String,
581    /// 循环周期:"no_cycle"、"daily"、"monthly"、"yearly"
582    #[serde(default = "default_cycle")]
583    pub cycle: String,
584    /// 计数器初始值
585    #[serde(default = "default_initial")]
586    pub initial_value: u64,
587    /// 增量策略:"sequential"(默认)或 "random:max"
588    #[serde(default = "default_step_strategy")]
589    pub step: String,
590}
591
592fn default_cycle() -> String { "no_cycle".into() }
593fn default_initial() -> u64 { 1 }
594fn default_step_strategy() -> String { "sequential".into() }
595
596// ──── 默认值函数 ────────────────────────────────────
597
598fn default_app_name() -> String { "Alun".into() }
599fn default_profile() -> String { "dev".into() }
600fn default_listen() -> String { "8023".into() }
601fn default_log_level() -> String { "info".into() }
602fn default_log_format() -> String { "text".into() }
603fn default_log_prefix() -> String { "alun".into() }
604fn default_db_type() -> String { "postgres".into() }
605fn default_host() -> String { "localhost".into() }
606fn default_true() -> bool { true }
607fn default_pool_size() -> u32 { 10 }
608fn default_min_idle() -> u32 { 2 }
609fn default_timeout() -> u64 { 10 }
610fn default_workers() -> usize { 4 }
611fn default_redis_url() -> String { "redis://127.0.0.1:6379".into() }
612fn default_cache_type() -> String { "local".into() }
613fn default_cache_capacity() -> u64 { 10000 }
614fn default_ttl() -> u64 { 3600 }
615fn default_access_token_expire() -> u64 { 7200 }
616fn default_refresh_token_expire() -> u64 { 604800 }
617fn default_cors_max_age() -> u64 { 86400 }
618fn default_compress_level() -> u32 { 6 }
619fn default_rate_limit_requests() -> u64 { 100 }
620fn default_rate_limit_window() -> u64 { 60 }
621fn default_hsts_max_age() -> u64 { 31536000 }
622fn default_csp_value() -> String { "default-src 'self'".into() }
623fn default_referrer_policy_value() -> String { "strict-origin-when-cross-origin".into() }
624fn default_permissions_policy_value() -> String {
625    "camera=(), microphone=(), geolocation=()".into()
626}
627fn default_migration_path() -> String { "migrations".into() }
628fn default_upload_path() -> String { "uploads".into() }
629fn default_download_path() -> String { "downloads".into() }
630fn default_template_path() -> String { "templates".into() }
631fn default_static_path() -> String { "static".into() }
632fn default_not_found_msg() -> String { "请求的资源不存在".into() }
633fn default_max_size() -> u64 { 10 }
634
635impl Default for AppConfig {
636    fn default() -> Self {
637        Self {
638            app_name: default_app_name(),
639            profile: default_profile(),
640            server: ServerConfig::default(),
641            log: LogConfig::default(),
642            database: DatabaseConfig::default(),
643            redis: RedisConfig::default(),
644            cache: CacheConfig::default(),
645            middleware: MiddlewareConfig::default(),
646            router: RouterConfig::default(),
647            plugins: PluginsConfig::default(),
648            upload: UploadConfig::default(),
649            download: DownloadConfig::default(),
650            template: TemplateConfig::default(),
651            static_files: StaticConfig::default(),
652            custom: HashMap::new(),
653        }
654    }
655}
656
657impl Default for ServerConfig { fn default() -> Self { Self { listen: default_listen() } } }
658impl Default for LogConfig {
659    fn default() -> Self {
660        Self {
661            level: default_log_level(),
662            format: default_log_format(),
663            dir: None,
664            file_prefix: default_log_prefix(),
665        }
666    }
667}
668impl Default for DatabaseConfig {
669    fn default() -> Self {
670        Self {
671            enabled: false, r#type: default_db_type(), host: default_host(),
672            port: None, name: String::new(), user: String::new(), password: String::new(),
673            password_encrypted: false,
674            max_connections: default_pool_size(), min_connections: default_min_idle(),
675            connect_timeout: default_timeout(), sql_logging: false, slow_query_ms: 0,
676            migration: MigrationConfig::default(),
677        }
678    }
679}
680impl Default for RedisConfig { fn default() -> Self { Self { enabled: false, url: default_redis_url(), max_connections: default_pool_size() } } }
681impl Default for CacheConfig { fn default() -> Self { Self { r#type: default_cache_type(), max_capacity: default_cache_capacity(), default_ttl: default_ttl() } } }
682impl Default for MiddlewareConfig {
683    fn default() -> Self {
684        Self {
685            request_id: false, request_log: false,
686            request_log_config: RequestLogConfig::default(),
687            auth: AuthMiddlewareConfig::default(),
688            cors: CorsConfig::default(),
689            compression: CompressConfig::default(),
690            rate_limit: RateLimitConfig::default(),
691            security_headers: SecurityHeadersConfig::default(),
692            permission: PermissionConfig::default(),
693        }
694    }
695}
696impl Default for RequestLogConfig {
697    fn default() -> Self { Self { exclude_paths: vec![], log_duration: true } }
698}
699impl 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() } } }
700impl 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() } } }
701impl Default for CompressConfig { fn default() -> Self { Self { enabled: false, level: default_compress_level() } } }
702impl Default for RateLimitConfig { fn default() -> Self { Self { enabled: false, requests_per_window: default_rate_limit_requests(), window_secs: default_rate_limit_window() } } }
703impl Default for SecurityHeadersConfig {
704    fn default() -> Self {
705        Self {
706            enabled: true,
707            nosniff: true, frame_options: true,
708            hsts: true, hsts_max_age_secs: default_hsts_max_age(),
709            hsts_include_subdomains: true,
710            csp: true, csp_value: default_csp_value(),
711            referrer_policy: true, referrer_policy_value: default_referrer_policy_value(),
712            permissions_policy: false, permissions_policy_value: default_permissions_policy_value(),
713        }
714    }
715}
716impl Default for PermissionConfig { fn default() -> Self { Self { enabled: false, rules: vec![] } } }
717impl Default for PermissionRule { fn default() -> Self { Self { path: String::new(), methods: vec![], permission: String::new() } } }
718impl Default for RouterConfig { fn default() -> Self { Self { prefix: String::new(), not_found: NotFoundConfig::default() } } }
719impl Default for NotFoundConfig { fn default() -> Self { Self { enabled: true, message: default_not_found_msg() } } }
720impl Default for MigrationConfig { fn default() -> Self { Self { enabled: false, path: default_migration_path(), auto_migrate: false } } }
721impl Default for UploadConfig { fn default() -> Self { Self { path: default_upload_path(), max_size_mb: default_max_size() } } }
722impl Default for DownloadConfig { fn default() -> Self { Self { path: default_download_path() } } }
723impl Default for TemplateConfig { fn default() -> Self { Self { path: default_template_path() } } }
724impl Default for StaticConfig { fn default() -> Self { Self { path: default_static_path(), enabled: false } } }
725impl Default for PluginsConfig { fn default() -> Self { Self { enabled: vec![], notification: NotificationConfig::default(), async_task: AsyncTaskConfig::default(), scheduler: SchedulerConfig::default(), serial: SerialConfig::default() } } }
726
727// ──── 配置管理器 ────────────────────────────────────
728
729/// 配置管理器——持有静态配置 + 允许运行时覆盖
730pub struct ConfigManager {
731    /// 静态配置(启动时加载,不可变)
732    pub static_config: AppConfig,
733    /// 动态配置(运行时可通过插件修改)
734    pub dynamic: RwLock<HashMap<String, serde_json::Value>>,
735}
736
737impl ConfigManager {
738    /// 从 config/config.toml 加载,若不存在则用默认值
739    pub fn load(config_dir: Option<&str>) -> Self {
740        let dir = config_dir.unwrap_or("config");
741        let profile = detect_profile();
742
743        let mut cfg = Self::load_file(dir, &profile);
744
745        // 环境变量覆盖
746        merge_env_overrides(&mut cfg);
747
748        info!("配置加载完成 profile={}, listen={}", cfg.profile, cfg.server.listen);
749
750        Self {
751            static_config: cfg,
752            dynamic: RwLock::new(HashMap::new()),
753        }
754    }
755
756    fn load_file(dir: &str, profile: &str) -> AppConfig {
757        // 1. 尝试 config/config.toml
758        let base_path = Path::new(dir).join("config.toml");
759        let mut cfg = if base_path.exists() {
760            let content = fs::read_to_string(&base_path)
761                .unwrap_or_else(|_| String::new());
762            toml::from_str(&content).unwrap_or_default()
763        } else {
764            AppConfig::default()
765        };
766
767        // 2. 叠加 config.toml 中的 main_env 路径
768        //    或 config/config-{profile}.toml
769        let profile_path = Path::new(dir).join(format!("config-{}.toml", profile));
770        if profile_path.exists() {
771            if let Ok(content) = fs::read_to_string(&profile_path) {
772                if let Ok(profile_cfg) = toml::from_str::<AppConfig>(&content) {
773                    merge_configs(&mut cfg, &profile_cfg);
774                }
775            }
776        }
777
778        cfg.profile = profile.to_string();
779        cfg
780    }
781
782    /// 获取静态配置引用
783    pub fn get(&self) -> &AppConfig {
784        &self.static_config
785    }
786
787    /// 获取动态配置值
788    pub fn get_dynamic(&self, key: &str) -> Option<serde_json::Value> {
789        self.dynamic.read().get(key).cloned()
790    }
791
792    /// 设置动态配置
793    pub fn set_dynamic(&self, key: &str, value: serde_json::Value) {
794        self.dynamic.write().insert(key.to_string(), value);
795    }
796
797    /// 删除动态配置
798    pub fn remove_dynamic(&self, key: &str) {
799        self.dynamic.write().remove(key);
800    }
801
802    /// 生成默认配置文件到 config/config.toml
803    pub fn generate_default(dir: &str) -> std::io::Result<()> {
804        let config_dir = Path::new(dir);
805        fs::create_dir_all(config_dir)?;
806
807        let cfg = AppConfig::default();
808        let toml_str = toml::to_string_pretty(&cfg)
809            .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e.to_string()))?;
810
811        let header = r#"# Alun 默认配置文件
812# 修改后保存即可生效(需重启服务)
813#
814# 使用 --gen-config 参数可重新生成此文件到 config/config.toml
815# 多环境:创建 config/config-dev.toml, config/config-prod.toml
816#         通过环境变量或命令行 --profile=prod 指定
817
818"#;
819
820        fs::write(config_dir.join("config.toml"), format!("{}{}", header, toml_str))?;
821        info!("默认配置文件已生成到 {}/config.toml", dir);
822        Ok(())
823    }
824}
825
826/// 合并两个配置(profile 覆盖 base 中有值的字段)
827fn merge_configs(base: &mut AppConfig, overlay: &AppConfig) {
828    if overlay.server.listen != default_listen() { base.server.listen = overlay.server.listen.clone(); }
829    if overlay.log.level != default_log_level() { base.log.level = overlay.log.level.clone(); }
830    if overlay.log.format != default_log_format() { base.log.format = overlay.log.format.clone(); }
831    if overlay.log.dir.is_some() { base.log.dir = overlay.log.dir.clone(); }
832    if overlay.log.file_prefix != default_log_prefix() { base.log.file_prefix = overlay.log.file_prefix.clone(); }
833    if overlay.database.host != default_host() || !overlay.database.name.is_empty() {
834        base.database = overlay.database.clone();
835    }
836    if overlay.redis.url != default_redis_url() { base.redis = overlay.redis.clone(); }
837    if overlay.cache.r#type != default_cache_type() { base.cache = overlay.cache.clone(); }
838    if overlay.router.prefix != String::new() { base.router.prefix = overlay.router.prefix.clone(); }
839    if overlay.router.not_found.message != default_not_found_msg() {
840        base.router.not_found.message = overlay.router.not_found.message.clone();
841    }
842    if !overlay.router.not_found.enabled {
843        base.router.not_found.enabled = false;
844    }
845    if overlay.upload.path != default_upload_path() { base.upload = overlay.upload.clone(); }
846    if overlay.download.path != default_download_path() { base.download = overlay.download.clone(); }
847    if overlay.template.path != default_template_path() { base.template = overlay.template.clone(); }
848    if overlay.static_files.path != default_static_path() { base.static_files = overlay.static_files.clone(); }
849
850    // 中间件采用字段级合并:仅覆盖 profile 文件中显式配置的项
851    merge_middleware(&mut base.middleware, &overlay.middleware);
852
853    // 插件采用完全替换(数组无法字段级合并)
854    if !overlay.plugins.enabled.is_empty() {
855        base.plugins = overlay.plugins.clone();
856    }
857    for (k, v) in &overlay.custom { base.custom.insert(k.clone(), v.clone()); }
858}
859
860fn merge_middleware(base: &mut MiddlewareConfig, overlay: &MiddlewareConfig) {
861    let default_mw = MiddlewareConfig::default();
862    if overlay.request_id != default_mw.request_id { base.request_id = overlay.request_id; }
863    if overlay.request_log != default_mw.request_log { base.request_log = overlay.request_log; }
864    if overlay.request_log_config.log_duration != default_mw.request_log_config.log_duration {
865        base.request_log_config.log_duration = overlay.request_log_config.log_duration;
866    }
867    if !overlay.request_log_config.exclude_paths.is_empty() {
868        base.request_log_config.exclude_paths = overlay.request_log_config.exclude_paths.clone();
869    }
870    if overlay.auth.enabled != default_mw.auth.enabled { base.auth.enabled = overlay.auth.enabled; }
871    if overlay.auth.jwt_secret != default_mw.auth.jwt_secret { base.auth.jwt_secret = overlay.auth.jwt_secret.clone(); }
872    if overlay.auth.access_token_expire_secs != 0 { base.auth.access_token_expire_secs = overlay.auth.access_token_expire_secs; }
873    if overlay.auth.refresh_token_expire_secs != 0 { base.auth.refresh_token_expire_secs = overlay.auth.refresh_token_expire_secs; }
874    if !overlay.auth.ignore_paths.is_empty() { base.auth.ignore_paths = overlay.auth.ignore_paths.clone(); }
875    if overlay.cors.enabled != default_mw.cors.enabled { base.cors.enabled = overlay.cors.enabled; }
876    if !overlay.cors.allow_origins.is_empty() { base.cors.allow_origins = overlay.cors.allow_origins.clone(); }
877    if overlay.compression.enabled != default_mw.compression.enabled { base.compression.enabled = overlay.compression.enabled; }
878    if overlay.rate_limit.enabled != default_mw.rate_limit.enabled { base.rate_limit.enabled = overlay.rate_limit.enabled; }
879    if overlay.rate_limit.requests_per_window != 0 { base.rate_limit.requests_per_window = overlay.rate_limit.requests_per_window; }
880    if overlay.rate_limit.window_secs != 0 { base.rate_limit.window_secs = overlay.rate_limit.window_secs; }
881}
882
883#[cfg(test)]
884mod tests {
885    use super::*;
886
887    #[test]
888    fn test_default_config_serialization() {
889        let cfg = AppConfig::default();
890        let toml_str = toml::to_string_pretty(&cfg).unwrap();
891        assert!(toml_str.contains("listen = \"8023\""));
892        assert!(toml_str.contains("level = \"info\""));
893    }
894}