1use serde::{Deserialize, Serialize};
2use std::net::SocketAddr;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct Config {
7 pub server: ServerConfig,
9 pub log: LogConfig,
11 pub cors: CorsConfig,
13 #[serde(default)]
15 #[cfg(feature = "database")]
16 pub database: Option<DatabaseConfig>,
17 #[serde(default)]
19 #[cfg(feature = "redis")]
20 pub redis: Option<RedisConfig>,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct ServerConfig {
26 pub addr: String,
28 pub port: u16,
30 pub workers: Option<usize>,
32 #[serde(default)]
34 pub context_path: Option<String>,
35}
36
37impl ServerConfig {
38 pub fn socket_addr(&self) -> Result<SocketAddr, std::net::AddrParseError> {
40 format!("{}:{}", self.addr, self.port).parse()
41 }
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct LogConfig {
47 pub level: String,
49 pub json: bool,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct CorsConfig {
56 pub allowed_origins: Vec<String>,
58 pub allowed_methods: Vec<String>,
60 pub allowed_headers: Vec<String>,
62 pub allow_credentials: bool,
64}
65
66#[cfg(feature = "database")]
68#[derive(Debug, Clone, Serialize, Deserialize)]
69pub struct DatabaseConfig {
70 pub url: String,
72 #[serde(default = "default_max_connections")]
74 pub max_connections: u32,
75 #[serde(default = "default_min_connections")]
77 pub min_connections: u32,
78}
79
80#[cfg(feature = "database")]
81fn default_max_connections() -> u32 {
82 100
83}
84
85#[cfg(feature = "database")]
86fn default_min_connections() -> u32 {
87 10
88}
89
90#[cfg(feature = "redis")]
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct RedisConfig {
94 pub url: String,
96 #[serde(default)]
98 pub password: Option<String>,
99 #[serde(default = "default_pool_size")]
101 pub pool_size: usize,
102}
103
104#[cfg(feature = "redis")]
105fn default_pool_size() -> usize {
106 10
107}
108
109impl Default for Config {
110 fn default() -> Self {
111 Self {
112 server: ServerConfig {
113 addr: "0.0.0.0".to_string(),
114 port: 3000,
115 workers: None,
116 context_path: None,
117 },
118 log: LogConfig {
119 level: "info".to_string(),
120 json: false,
121 },
122 cors: CorsConfig {
123 allowed_origins: vec!["*".to_string()],
124 allowed_methods: vec![
125 "GET".to_string(),
126 "POST".to_string(),
127 "PUT".to_string(),
128 "DELETE".to_string(),
129 "PATCH".to_string(),
130 "OPTIONS".to_string(),
131 ],
132 allowed_headers: vec!["*".to_string()],
133 allow_credentials: false,
135 },
136 #[cfg(feature = "database")]
137 database: None,
138 #[cfg(feature = "redis")]
139 redis: None,
140 }
141 }
142}
143
144impl Config {
145 fn find_project_root() -> Option<std::path::PathBuf> {
165 if let Ok(mut current_dir) = std::env::current_dir() {
167 loop {
168 if current_dir.join("Cargo.toml").exists() {
170 return Some(current_dir);
171 }
172 if current_dir.join(".env").exists() {
174 return Some(current_dir);
175 }
176 match current_dir.parent() {
178 Some(parent) => current_dir = parent.to_path_buf(),
179 None => break,
180 }
181 }
182 }
183
184 if let Ok(exe_path) = std::env::current_exe() {
186 if let Some(exe_dir) = exe_path.parent() {
187 let mut current_dir = exe_dir.to_path_buf();
188 loop {
189 if current_dir.join("Cargo.toml").exists() {
191 return Some(current_dir);
192 }
193 if current_dir.join(".env").exists() {
195 return Some(current_dir);
196 }
197 match current_dir.parent() {
199 Some(parent) => current_dir = parent.to_path_buf(),
200 None => break,
201 }
202 }
203 }
204 }
205
206 None
207 }
208
209 pub fn from_env() -> Result<Self, config::ConfigError> {
210 if let Some(project_root) = Self::find_project_root() {
215 let env_path = project_root.join(".env");
216 if env_path.exists() {
217 if let Err(e) = dotenvy::from_path(&env_path) {
218 tracing::debug!("从项目根目录加载 .env 文件失败: {},尝试其他方法", e);
219 } else {
220 tracing::debug!("成功从项目根目录加载 .env 文件: {}", env_path.display());
221 return Self::load_config_from_env();
223 }
224 }
225 }
226
227 if let Err(e) = dotenvy::dotenv() {
229 tracing::debug!("未找到 .env 文件: {},将使用环境变量和默认配置", e);
230 } else {
231 tracing::debug!("成功加载 .env 文件(从当前工作目录向上查找)");
232 }
233
234 Self::load_config_from_env()
235 }
236
237 fn load_config_from_env() -> Result<Self, config::ConfigError> {
239 let builder = config::Config::builder()
240 .set_default("server.addr", "127.0.0.1")?
241 .set_default("server.port", 3000)?
242 .set_default("log.level", "info")?
243 .set_default("log.json", false)?
244 .set_default("cors.allowed_origins", vec!["*"])?
245 .set_default(
246 "cors.allowed_methods",
247 vec!["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
248 )?
249 .set_default("cors.allowed_headers", vec!["*"])?
250 .set_default("cors.allow_credentials", false)?
252 .add_source(config::Environment::with_prefix("APP").separator("__"));
254
255 builder.build()?.try_deserialize()
256 }
257}