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