fbc_starter/
lib.rs

1pub mod config;
2#[cfg(feature = "database")]
3pub mod database;
4pub mod error;
5pub mod handlers;
6pub mod middleware;
7#[cfg(feature = "redis")]
8pub mod redis;
9pub mod routing;
10pub mod server;
11
12pub use config::Config;
13#[cfg(feature = "database")]
14pub use database::{
15    get_database, get_mysql_pool, get_postgres_pool, get_sqlite_pool, init_database, DatabasePool,
16};
17pub use error::{AppError, AppResult};
18pub use handlers::AppState;
19#[cfg(feature = "redis")]
20pub use redis::{get_redis_client, get_redis_connection, init_redis};
21pub use routing::{registry, RouteGroup, RouteRegistry};
22pub use server::Server;
23
24/// 启动 Web 服务器(自动加载配置和初始化日志)
25///
26/// 此函数会:
27/// 1. 从环境变量加载配置(.env 文件)
28/// 2. 初始化日志系统(时区设置为 +8)
29/// 3. 启动 Web 服务器
30pub async fn start() -> Result<(), Box<dyn std::error::Error>> {
31    // 加载配置
32    let config = Config::from_env().unwrap_or_else(|_| {
33        eprintln!("警告: 无法从环境变量加载配置,使用默认配置");
34        Config::default()
35    });
36
37    // 初始化日志
38    init_logging(&config).map_err(|e| -> Box<dyn std::error::Error> { format!("{}", e).into() })?;
39
40    tracing::info!(
41        "启动 {} v{}",
42        env!("CARGO_PKG_NAME"),
43        env!("CARGO_PKG_VERSION")
44    );
45
46    // 初始化数据库(如果配置了且启用了 database 特性)
47    #[cfg(feature = "database")]
48    if let Some(ref db_config) = config.database {
49        database::init_database(
50            &db_config.url,
51            db_config.max_connections,
52            db_config.min_connections,
53        )
54        .await
55        .map_err(|e| -> Box<dyn std::error::Error> {
56            format!("数据库初始化失败: {}", e).into()
57        })?;
58    }
59
60    // 初始化 Redis(如果配置了且启用了 redis 特性)
61    #[cfg(feature = "redis")]
62    if let Some(ref redis_config) = config.redis {
63        redis::init_redis(
64            &redis_config.url,
65            redis_config.password.as_deref(),
66            redis_config.pool_size,
67        )
68        .await
69        .map_err(|e| -> Box<dyn std::error::Error> {
70            format!("Redis 初始化失败: {}", e).into()
71        })?;
72    }
73
74    // 启动服务器
75    let server = Server::new(config)
76        .await
77        .map_err(|e| -> Box<dyn std::error::Error> { format!("{}", e).into() })?;
78    server
79        .start()
80        .await
81        .map_err(|e| -> Box<dyn std::error::Error> { format!("{}", e).into() })
82}
83
84/// 启动 Web 服务器并添加自定义路由
85///
86/// # 参数
87/// - `custom_routes`: 可选的自定义路由 Router,会与基础路由(/health 等)自动合并
88///
89/// # 示例
90/// ```no_run
91/// use fbc_starter::start_with_routes;
92/// use axum::{routing::get, Router};
93///
94/// #[tokio::main]
95/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
96///     // 定义你的 API 路由
97///     let api_routes = Router::new()
98///         .route("/api/users", get(get_users))
99///         .route("/api/posts", get(get_posts));
100///     
101///     // 启动服务器并合并路由
102///     start_with_routes(Some(api_routes)).await?;
103///     Ok(())
104/// }
105///
106/// async fn get_users() -> &'static str { "users" }
107/// async fn get_posts() -> &'static str { "posts" }
108/// ```
109pub async fn start_with_routes(
110    custom_routes: Option<axum::Router>,
111) -> Result<(), Box<dyn std::error::Error>> {
112    // 加载配置
113    let config = Config::from_env().unwrap_or_else(|_| {
114        eprintln!("警告: 无法从环境变量加载配置,使用默认配置");
115        Config::default()
116    });
117
118    // 初始化日志
119    init_logging(&config).map_err(|e| -> Box<dyn std::error::Error> { format!("{}", e).into() })?;
120
121    tracing::info!(
122        "启动 {} v{}",
123        env!("CARGO_PKG_NAME"),
124        env!("CARGO_PKG_VERSION")
125    );
126
127    // 初始化数据库(如果配置了且启用了 database 特性)
128    #[cfg(feature = "database")]
129    if let Some(ref db_config) = config.database {
130        database::init_database(
131            &db_config.url,
132            db_config.max_connections,
133            db_config.min_connections,
134        )
135        .await
136        .map_err(|e| -> Box<dyn std::error::Error> {
137            format!("数据库初始化失败: {}", e).into()
138        })?;
139    }
140
141    // 初始化 Redis(如果配置了且启用了 redis 特性)
142    #[cfg(feature = "redis")]
143    if let Some(ref redis_config) = config.redis {
144        redis::init_redis(
145            &redis_config.url,
146            redis_config.password.as_deref(),
147            redis_config.pool_size,
148        )
149        .await
150        .map_err(|e| -> Box<dyn std::error::Error> {
151            format!("Redis 初始化失败: {}", e).into()
152        })?;
153    }
154
155    // 启动服务器
156    let server = Server::new(config)
157        .await
158        .map_err(|e| -> Box<dyn std::error::Error> { format!("{}", e).into() })?;
159    server
160        .start_with_routes(custom_routes)
161        .await
162        .map_err(|e| -> Box<dyn std::error::Error> { format!("{}", e).into() })
163}
164
165/// 使用已有配置启动 Web 服务器(不自动初始化日志)
166///
167/// 适用于需要自定义日志配置的场景
168pub async fn start_with_config(config: Config) -> Result<(), anyhow::Error> {
169    let server = Server::new(config).await?;
170    server.start().await
171}
172
173/// 初始化日志系统
174///
175/// 设置时区为 +8(东八区,中国时区)
176fn init_logging(config: &Config) -> Result<(), anyhow::Error> {
177    use time::UtcOffset;
178    use tracing_subscriber::{
179        fmt::time::OffsetTime, layer::SubscriberExt, util::SubscriberInitExt,
180    };
181
182    // 设置时区为 +8 (东八区,中国时区)
183    let offset =
184        UtcOffset::from_hms(8, 0, 0).map_err(|e| anyhow::anyhow!("无效的时区偏移: {}", e))?;
185    let timer = OffsetTime::new(
186        offset,
187        time::format_description::parse(
188            "[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]",
189        )
190        .map_err(|e| anyhow::anyhow!("时间格式解析失败: {}", e))?,
191    );
192
193    // 初始化日志
194    let subscriber = tracing_subscriber::registry().with(
195        tracing_subscriber::EnvFilter::try_from_default_env()
196            .unwrap_or_else(|_| format!("{}={}", env!("CARGO_PKG_NAME"), config.log.level).into()),
197    );
198
199    if config.log.json {
200        subscriber
201            .with(tracing_subscriber::fmt::layer().json().with_timer(timer))
202            .init();
203    } else {
204        // 使用紧凑格式,禁用源代码位置显示,避免换行
205        subscriber
206            .with(
207                tracing_subscriber::fmt::layer()
208                    .compact()
209                    .with_timer(timer)
210                    .with_target(true)
211                    .with_file(true)
212                    .with_line_number(true),
213            )
214            .init();
215    }
216
217    Ok(())
218}