//! cube-core: 共享脚手架。所有 cube app 通过这个 crate 拿基础 router、tracing、shutdown。 use std::path::Path; use axum::{routing::get, Router}; use tokio::net::TcpListener; use tokio::signal; use tower_http::services::{ServeDir, ServeFile}; use tower_http::trace::TraceLayer; /// 拼一个带 healthz + 静态前端 SPA fallback 的基础 router。 /// /// `dist_dir` 是前端 vite build 输出目录(容器内一般是 `/dist`)。 pub fn base(dist_dir: impl AsRef) -> Router { let dist = dist_dir.as_ref().to_path_buf(); let index = dist.join("index.html"); let static_svc = ServeDir::new(&dist).fallback(ServeFile::new(index)); Router::new() .route("/healthz", get(healthz)) .fallback_service(static_svc) .layer(TraceLayer::new_for_http()) } async fn healthz() -> &'static str { "ok" } /// 初始化 tracing:JSON to stdout,吃 RUST_LOG env。 pub fn init_tracing() { use tracing_subscriber::{fmt, EnvFilter}; let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); fmt().json().with_env_filter(filter).init(); } /// 起服务,绑定 0.0.0.0:port,挂 SIGTERM/SIGINT 优雅 shutdown。 pub async fn serve(app: Router, port: u16) -> std::io::Result<()> { let addr = format!("0.0.0.0:{port}"); let listener = TcpListener::bind(&addr).await?; tracing::info!(%addr, "cube app listening"); axum::serve(listener, app) .with_graceful_shutdown(shutdown_signal()) .await } async fn shutdown_signal() { let ctrl_c = async { signal::ctrl_c().await.expect("install ctrl_c handler") }; let term = async { signal::unix::signal(signal::unix::SignalKind::terminate()) .expect("install SIGTERM handler") .recv() .await; }; tokio::select! { _ = ctrl_c => tracing::info!("ctrl-c received, shutting down"), _ = term => tracing::info!("SIGTERM received, shutting down"), } }