1use std::path::PathBuf;
23use std::sync::Arc;
24
25use axum::Router;
26use sz_rust_addons_admin::AdminAddonPlugin;
27use sz_rust_addons_loader::capability_hook::CapabilityHook;
28use sz_rust_capability::CapabilityRegistry;
29use sz_rust_core::config::AppConfig;
30use sz_rust_core::container::App;
31use sz_rust_orm_facade::{Connection, ConnectionFactory, DbError, Pool, PoolConfig};
32
33use crate::error::CliError;
34
35mod access_log;
36mod runtime;
37mod signal;
38mod watcher;
39
40pub use runtime::{build_runtime, resolve_workers, validate_workers};
41
42#[derive(Debug, Clone)]
46pub struct ServeArgs {
47 pub with_admin: bool,
49 pub with_tenant: bool,
51 pub with_data_scope: bool,
53 pub addr: String,
55 pub watch_config: bool,
57 pub workers: Option<u16>,
59 pub grace_timeout: Option<u16>,
61 pub tls_cert: Option<PathBuf>,
63 pub tls_key: Option<PathBuf>,
65 pub access_log: bool,
67 pub health: bool,
69}
70
71impl ServeArgs {
72 pub fn validate(&self) -> Result<(), CliError> {
74 if let Some(w) = self.workers {
75 if w == 0 {
76 return Err(CliError::Generic("worker 数量必须 >= 1".to_string()));
77 }
78 if w > 1024 {
79 return Err(CliError::Generic("worker 数量超过上限 1024".to_string()));
80 }
81 }
82 if let Some(t) = self.grace_timeout {
83 if t > 300 {
84 return Err(CliError::Generic("优雅关闭超时超过上限 300 秒".to_string()));
85 }
86 }
87 if self.tls_cert.is_some() != self.tls_key.is_some() {
88 return Err(CliError::Generic(
89 "--tls-cert 和 --tls-key 必须同时提供或同时缺失".to_string(),
90 ));
91 }
92 Ok(())
93 }
94}
95
96struct AnyPoolConnectionFactory(sz_orm_sqlx::any_driver::AnyPool);
101
102#[async_trait::async_trait]
103impl ConnectionFactory for AnyPoolConnectionFactory {
104 async fn create(&self) -> Result<Box<dyn Connection>, DbError> {
105 let conn = self
106 .0
107 .create()
108 .await
109 .map_err(|e| DbError::ConnectionError(format!("AnyPool create failed: {e}")))?;
110 Ok(Box::new(conn))
111 }
112}
113
114pub fn build_router_with_tenant(router: Router, with_tenant: bool) -> Router {
119 if with_tenant {
120 router.layer(axum::middleware::from_fn(
121 sz_rust_core::multi_tenant::tenant_middleware,
122 ))
123 } else {
124 router
125 }
126}
127
128pub fn build_router_with_data_scope(router: Router, with_data_scope: bool) -> Router {
133 if with_data_scope {
134 let state = sz_rust_middleware_facade::data_scope::DataScopeMiddlewareState {
135 field_scope_registry: Arc::new(
136 sz_rust_orm_facade::data_scope::field_scope::registry::FieldScopePolicyRegistry::new(),
137 ),
138 };
139 router.layer(axum::middleware::from_fn_with_state(
140 state,
141 sz_rust_middleware_facade::data_scope::data_scope_middleware,
142 ))
143 } else {
144 router
145 }
146}
147
148pub fn build_router_with_admin(pool: Arc<Pool>, admin_roles: Vec<String>) -> (Router, usize) {
153 let plugin = AdminAddonPlugin::new(pool, admin_roles);
154 let admin_router = plugin.router();
155 let base_router = Router::new().route("/", axum::routing::get(|| async { "SZ-Rust" }));
156 let merged_router = base_router.merge(admin_router);
157
158 let hook = plugin.capability_hook();
159 let registry = CapabilityRegistry::new();
160 let registered = hook.register_capabilities(®istry).unwrap_or_default();
161
162 (merged_router, registered.len())
163}
164
165async fn acquire_pool(config: &AppConfig) -> Result<Arc<Pool>, CliError> {
167 let db_name = &config.database.default;
168 let conn_config = config
169 .database
170 .connections
171 .get(db_name)
172 .ok_or_else(|| CliError::Generic(format!("数据库连接 '{db_name}' 未配置")))?;
173
174 let db_url = build_db_url(conn_config);
175 let any_pool = sz_orm_sqlx::any_driver::AnyPool::connect(&db_url)
176 .await
177 .map_err(|e| CliError::Generic(format!("数据库连接失败: {e}")))?;
178
179 let factory: Arc<dyn ConnectionFactory> = Arc::new(AnyPoolConnectionFactory(any_pool));
180 let pool = Pool::new(PoolConfig::default(), factory)
181 .map_err(|e| CliError::Generic(format!("连接池创建失败: {e}")))?;
182 Ok(Arc::new(pool))
183}
184
185fn build_db_url(conn: &sz_rust_core::config::DatabaseConnection) -> String {
187 let driver = match conn.r#type.as_str() {
188 "mysql" => "mysql",
189 "postgres" | "pgsql" => "postgres",
190 "sqlite" => "sqlite",
191 other => other,
192 };
193 format!(
194 "{driver}://{}:{}@{}:{}/{}",
195 conn.username, conn.password, conn.hostname, conn.hostport, conn.database
196 )
197}
198
199fn acquire_admin_roles() -> Vec<String> {
203 std::env::var("SZ_RUST_ADMIN_ROLES")
204 .ok()
205 .and_then(|s| {
206 let roles: Vec<String> = s.split(',').map(|r| r.trim().to_string()).collect();
207 if roles.is_empty() {
208 None
209 } else {
210 Some(roles)
211 }
212 })
213 .unwrap_or_else(|| {
214 tracing::warn!("SZ_RUST_ADMIN_ROLES 未设置,使用默认角色 [super_admin]");
215 vec!["super_admin".to_string()]
216 })
217}
218
219pub fn execute(args: ServeArgs) -> Result<i32, CliError> {
224 args.validate()?;
225
226 let config_dir = std::env::var("SZ_RUST_CONFIG_DIR")
227 .map(std::path::PathBuf::from)
228 .unwrap_or_else(|_| std::path::PathBuf::from("config"));
229
230 let config = {
231 let tmp_rt = tokio::runtime::Builder::new_current_thread()
232 .enable_all()
233 .build()
234 .map_err(|e| CliError::Generic(format!("临时 runtime 构建失败: {e}")))?;
235 tmp_rt.block_on(async {
236 AppConfig::load_from_dir(&config_dir)
237 .await
238 .unwrap_or_else(|e| {
239 tracing::warn!("加载配置失败(使用默认配置): {e}");
240 AppConfig::default()
241 })
242 })
243 };
244
245 let workers = resolve_workers(args.workers, config.server.workers);
246 tracing::info!("使用 {workers} 个 worker 线程");
247
248 let runtime = build_runtime(workers)?;
249 runtime.block_on(execute_async(args, config, config_dir))
250}
251
252async fn execute_async(
254 args: ServeArgs,
255 config: AppConfig,
256 config_dir: std::path::PathBuf,
257) -> Result<i32, CliError> {
258 if args.watch_config {
259 let (reload_tx, reload_rx) = tokio::sync::mpsc::channel::<std::path::PathBuf>(16);
260 match watcher::ConfigWatcher::start(&config_dir, reload_tx) {
261 Ok(_) => {
262 tracing::info!("配置热重载已启用,监听目录: {}", config_dir.display());
263 watcher::spawn_reload_coordinator(reload_rx, config_dir.clone());
264 }
265 Err(e) => {
266 tracing::warn!("配置热重载启动失败,降级为不启用: {e}");
267 }
268 }
269 }
270
271 let (reload_signal_tx, mut reload_signal_rx) = tokio::sync::mpsc::channel::<()>(1);
272 let (loglevel_tx, mut loglevel_rx) = tokio::sync::mpsc::channel::<()>(1);
273 signal::install_runtime_signals(reload_signal_tx, loglevel_tx);
274 let signal_config_dir = config_dir.clone();
275 tokio::spawn(async move {
276 while reload_signal_rx.recv().await.is_some() {
277 match watcher::reload_config(&signal_config_dir).await {
278 Ok(_) => tracing::info!("信号触发配置重载成功(数据库/路由变更需重启生效)"),
279 Err(e) => tracing::error!("信号触发配置重载失败,保留旧配置: {e}"),
280 }
281 }
282 });
283 tokio::spawn(async move {
284 let mut current_level = tracing::Level::INFO;
285 while loglevel_rx.recv().await.is_some() {
286 current_level = signal::log_level_cycle(current_level);
287 tracing::info!("日志级别切换为 {current_level}");
288 }
289 });
290
291 let _app = App::init(config.clone());
292
293 let router = if args.with_admin {
294 let pool = acquire_pool(&config).await?;
295 let admin_roles = acquire_admin_roles();
296 let (router, cap_count) = build_router_with_admin(pool, admin_roles);
297 tracing::info!("Admin 插件已加载:{cap_count} 个 Capability 已注册");
298 router
299 } else {
300 Router::new().route("/", axum::routing::get(|| async { "SZ-Rust" }))
301 };
302
303 let router = if args.health {
304 tracing::info!(
305 "健康检查端点已启用:GET /health/ (liveness) + GET /health/ready (readiness)"
306 );
307 router.merge(sz_rust_core::health::default_health_router())
308 } else {
309 router
310 };
311
312 let router = build_router_with_tenant(router, args.with_tenant);
313 if args.with_tenant {
314 tracing::info!("tenant_middleware 已启用(X-Tenant-Id Header 提取)");
315 }
316
317 let router = build_router_with_data_scope(router, args.with_data_scope);
318 if args.with_data_scope {
319 tracing::info!("data_scope_middleware 已启用(数据权限上下文注入)");
320 }
321
322 let router = if args.access_log {
323 tracing::info!("访问日志中间件已启用");
324 router.layer(axum::middleware::from_fn(access_log::access_log_handler))
325 } else {
326 router
327 };
328
329 let grace_timeout = args.grace_timeout.unwrap_or(config.server.grace_timeout);
330 let timeout = std::time::Duration::from_secs(grace_timeout as u64);
331
332 if let (Some(cert), Some(key)) = (&args.tls_cert, &args.tls_key) {
333 tracing::info!(
334 "HTTPS 服务启动于 {}(TLS 证书: {},优雅关闭超时 {}s)",
335 args.addr,
336 cert.display(),
337 grace_timeout
338 );
339 let serve_tls =
340 sz_rust_core::h2::serve_h2_with_graceful_shutdown(router, &args.addr, cert, key);
341 match tokio::time::timeout(timeout, serve_tls).await {
342 Ok(result) => {
343 result.map_err(|e| CliError::Generic(format!("TLS 服务错误: {e}")))?;
344 }
345 Err(_) => {
346 tracing::warn!("TLS 优雅关闭超时,强制中断剩余连接");
347 }
348 }
349 } else {
350 tracing::info!(
351 "HTTP 服务启动于 {}(优雅关闭超时 {}s)",
352 args.addr,
353 grace_timeout
354 );
355 sz_rust_core::server::serve_with_graceful_shutdown_timeout(router, &args.addr, timeout)
356 .await
357 .map_err(CliError::from)?;
358 }
359 Ok(0)
360}
361#[cfg(test)]
362mod tests {
363 use super::*;
364
365 fn default_args() -> ServeArgs {
366 ServeArgs {
367 with_admin: false,
368 with_tenant: false,
369 with_data_scope: false,
370 addr: "0.0.0.0:8080".to_string(),
371 watch_config: false,
372 workers: None,
373 grace_timeout: None,
374 tls_cert: None,
375 tls_key: None,
376 access_log: false,
377 health: true,
378 }
379 }
380
381 #[test]
382 fn test_validate_ok() {
383 assert!(default_args().validate().is_ok());
384 }
385
386 #[test]
387 fn test_validate_workers_zero() {
388 let mut args = default_args();
389 args.workers = Some(0);
390 assert!(args.validate().is_err());
391 }
392
393 #[test]
394 fn test_validate_workers_too_many() {
395 let mut args = default_args();
396 args.workers = Some(1025);
397 assert!(args.validate().is_err());
398 }
399
400 #[test]
401 fn test_validate_workers_max_ok() {
402 let mut args = default_args();
403 args.workers = Some(1024);
404 assert!(args.validate().is_ok());
405 }
406
407 #[test]
408 fn test_validate_grace_timeout_too_large() {
409 let mut args = default_args();
410 args.grace_timeout = Some(301);
411 assert!(args.validate().is_err());
412 }
413
414 #[test]
415 fn test_validate_grace_timeout_max_ok() {
416 let mut args = default_args();
417 args.grace_timeout = Some(300);
418 assert!(args.validate().is_ok());
419 }
420
421 #[test]
422 fn test_validate_tls_cert_only() {
423 let mut args = default_args();
424 args.tls_cert = Some(PathBuf::from("/tmp/cert.pem"));
425 assert!(args.validate().is_err());
426 }
427
428 #[test]
429 fn test_validate_tls_key_only() {
430 let mut args = default_args();
431 args.tls_key = Some(PathBuf::from("/tmp/key.pem"));
432 assert!(args.validate().is_err());
433 }
434
435 #[test]
436 fn test_validate_tls_both_ok() {
437 let mut args = default_args();
438 args.tls_cert = Some(PathBuf::from("/tmp/cert.pem"));
439 args.tls_key = Some(PathBuf::from("/tmp/key.pem"));
440 assert!(args.validate().is_ok());
441 }
442
443 #[tokio::test]
444 async fn test_build_router_with_tenant_disabled() {
445 use tower::ServiceExt;
446 let router = build_router_with_tenant(
447 Router::new().route("/", axum::routing::get(|| async { "ok" })),
448 false,
449 );
450 let resp = router
453 .oneshot(
454 axum::http::Request::builder()
455 .method("GET")
456 .uri("/")
457 .body(axum::body::Body::empty())
458 .unwrap(),
459 )
460 .await
461 .unwrap();
462 assert_eq!(resp.status(), axum::http::StatusCode::OK);
463 }
464
465 #[tokio::test]
466 async fn test_build_router_with_tenant_enabled() {
467 use tower::ServiceExt;
468 let router = build_router_with_tenant(
469 Router::new().route("/", axum::routing::get(|| async { "ok" })),
470 true,
471 );
472 let resp = router
475 .oneshot(
476 axum::http::Request::builder()
477 .method("GET")
478 .uri("/")
479 .body(axum::body::Body::empty())
480 .unwrap(),
481 )
482 .await
483 .unwrap();
484 assert_eq!(resp.status(), axum::http::StatusCode::BAD_REQUEST);
485 }
486
487 #[tokio::test]
488 async fn test_build_router_with_data_scope_disabled() {
489 use tower::ServiceExt;
490 let router = build_router_with_data_scope(
491 Router::new().route("/", axum::routing::get(|| async { "ok" })),
492 false,
493 );
494 let resp = router
495 .oneshot(
496 axum::http::Request::builder()
497 .method("GET")
498 .uri("/")
499 .body(axum::body::Body::empty())
500 .unwrap(),
501 )
502 .await
503 .unwrap();
504 assert_eq!(resp.status(), axum::http::StatusCode::OK);
505 }
506
507 #[tokio::test]
508 async fn test_build_router_with_data_scope_enabled() {
509 use tower::ServiceExt;
510 let router = build_router_with_data_scope(
511 Router::new().route("/", axum::routing::get(|| async { "ok" })),
512 true,
513 );
514 let mut req = axum::http::Request::builder()
517 .method("GET")
518 .uri("/")
519 .body(axum::body::Body::empty())
520 .unwrap();
521 req.extensions_mut().insert(
522 sz_rust_middleware_facade::data_scope::DataScopeUserContext::new(10).with_dept(5),
523 );
524 let resp = router.oneshot(req).await.unwrap();
525 assert_eq!(resp.status(), axum::http::StatusCode::OK);
526 }
527
528 fn make_conn(
529 r#type: &str,
530 hostname: &str,
531 port: u16,
532 database: &str,
533 username: &str,
534 password: &str,
535 ) -> sz_rust_core::config::DatabaseConnection {
536 sz_rust_core::config::DatabaseConnection {
537 r#type: r#type.to_string(),
538 hostname: hostname.to_string(),
539 database: database.to_string(),
540 username: username.to_string(),
541 password: password.to_string(),
542 hostport: port,
543 charset: "utf8mb4".to_string(),
544 prefix: String::new(),
545 deploy: 0,
546 rw_separate: false,
547 fields_strict: true,
548 break_reconnect: true,
549 }
550 }
551
552 #[test]
553 fn test_build_db_url_mysql() {
554 let conn = make_conn("mysql", "localhost", 3306, "testdb", "root", "pass");
555 let url = build_db_url(&conn);
556 assert_eq!(url, "mysql://root:pass@localhost:3306/testdb");
557 }
558
559 #[test]
560 fn test_build_db_url_postgres() {
561 let conn = make_conn("postgres", "localhost", 5432, "testdb", "user", "pass");
562 let url = build_db_url(&conn);
563 assert_eq!(url, "postgres://user:pass@localhost:5432/testdb");
564 }
565
566 #[test]
567 fn test_build_db_url_pgsql_alias() {
568 let conn = make_conn("pgsql", "localhost", 5432, "testdb", "user", "pass");
569 let url = build_db_url(&conn);
570 assert_eq!(url, "postgres://user:pass@localhost:5432/testdb");
571 }
572
573 #[test]
574 fn test_build_db_url_sqlite() {
575 let conn = make_conn("sqlite", "localhost", 0, "test.db", "", "");
576 let url = build_db_url(&conn);
577 assert_eq!(url, "sqlite://:@localhost:0/test.db");
578 }
579
580 #[test]
581 fn test_build_db_url_unknown_driver() {
582 let conn = make_conn("custom_driver", "host", 1234, "db", "u", "p");
583 let url = build_db_url(&conn);
584 assert_eq!(url, "custom_driver://u:p@host:1234/db");
585 }
586
587 #[test]
588 fn test_acquire_admin_roles_default() {
589 let _lock = super::super::test_support::acquire_global_lock();
590 std::env::remove_var("SZ_RUST_ADMIN_ROLES");
591 let roles = acquire_admin_roles();
592 assert_eq!(roles, vec!["super_admin".to_string()]);
593 }
594
595 #[test]
596 fn test_acquire_admin_roles_from_env() {
597 let _lock = super::super::test_support::acquire_global_lock();
598 std::env::set_var("SZ_RUST_ADMIN_ROLES", "admin,super_admin,guest");
599 let roles = acquire_admin_roles();
600 assert_eq!(roles, vec!["admin", "super_admin", "guest"]);
601 std::env::remove_var("SZ_RUST_ADMIN_ROLES");
602 }
603
604 #[test]
605 fn test_acquire_admin_roles_single() {
606 let _lock = super::super::test_support::acquire_global_lock();
607 std::env::set_var("SZ_RUST_ADMIN_ROLES", "only_one");
608 let roles = acquire_admin_roles();
609 assert_eq!(roles, vec!["only_one".to_string()]);
610 std::env::remove_var("SZ_RUST_ADMIN_ROLES");
611 }
612}