141 lines
5.3 KiB
Rust
141 lines
5.3 KiB
Rust
//! 服务端入口与启动配置
|
||
//!
|
||
//! 本文件是 Dioxus fullstack 应用的启动入口。
|
||
//! 当启用 `server` feature 时,启动 Axum 服务器并挂载:
|
||
//! - Dioxus server function(由 `serve_dioxus_application` 自动注册);
|
||
//! - 自定义 Axum 路由:图片上传 `/api/upload`、图片服务 `/uploads/{*path}`;
|
||
//! - 增量渲染(Incremental Rendering)缓存配置。
|
||
//!
|
||
//! 当未启用 `server` feature(例如编译为 WASM 前端)时,
|
||
//! 仅调用 `dioxus::launch` 启动客户端应用。
|
||
|
||
// 业务模块
|
||
mod api;
|
||
mod auth;
|
||
mod cache;
|
||
mod components;
|
||
mod context;
|
||
mod db;
|
||
// highlight 模块仅在服务端构建时编译
|
||
#[cfg(feature = "server")]
|
||
mod highlight;
|
||
mod hooks;
|
||
mod models;
|
||
mod pages;
|
||
mod router;
|
||
mod tasks;
|
||
mod theme;
|
||
mod utils;
|
||
mod webp;
|
||
|
||
/// 程序入口
|
||
fn main() {
|
||
// server feature:启动服务端
|
||
#[cfg(feature = "server")]
|
||
{
|
||
// 加载 .env 环境变量
|
||
dotenvy::dotenv().ok();
|
||
// 初始化 tracing 日志,默认级别为 info
|
||
tracing_subscriber::fmt()
|
||
.with_env_filter(
|
||
tracing_subscriber::EnvFilter::try_from_default_env()
|
||
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
|
||
)
|
||
.init();
|
||
|
||
// 校验数据库连接串,未设置则直接退出
|
||
if std::env::var("DATABASE_URL").is_err() {
|
||
tracing::error!("DATABASE_URL environment variable not set. Make sure .env exists or the variable is exported.");
|
||
eprintln!("ERROR: DATABASE_URL environment variable not set");
|
||
std::process::exit(1);
|
||
}
|
||
|
||
// 启动 Dioxus 服务端,返回构建好的 Axum Router
|
||
dioxus::server::serve(|| async move {
|
||
use dioxus::server::{axum, DioxusRouterExt, ServeConfig};
|
||
use std::time::Duration;
|
||
use tower_http::compression::CompressionLayer;
|
||
use tower_http::timeout::TimeoutLayer;
|
||
use axum::http::StatusCode;
|
||
|
||
// 启动后台定时任务:IP 信息清理
|
||
tokio::spawn(async {
|
||
tasks::ip_purge::run_purge().await;
|
||
});
|
||
|
||
// 启动后台定时任务:过期 session 清理
|
||
tokio::spawn(async {
|
||
tasks::session_cleanup::run_cleanup().await;
|
||
});
|
||
|
||
// 启动后台定时任务:回收站自动清理
|
||
tokio::spawn(async {
|
||
tasks::post_purge::run_purge().await;
|
||
});
|
||
|
||
// 配置增量渲染缓存,默认缓存 3600 秒,可通过 SSR_CACHE_SECS 覆盖
|
||
let config = ServeConfig::builder().incremental(
|
||
dioxus::server::IncrementalRendererConfig::default().invalidate_after(
|
||
std::time::Duration::from_secs(
|
||
std::env::var("SSR_CACHE_SECS")
|
||
.ok()
|
||
.and_then(|s| s.parse().ok())
|
||
.unwrap_or(3600),
|
||
),
|
||
),
|
||
);
|
||
|
||
// 自定义 API 路由:图片上传(大文件,需要更长超时)
|
||
let upload_route = axum::Router::new()
|
||
.route(
|
||
"/api/upload",
|
||
axum::routing::post(crate::api::upload::upload_image),
|
||
)
|
||
.layer(axum::extract::DefaultBodyLimit::max(10 * 1024 * 1024))
|
||
.layer(TimeoutLayer::with_status_code(
|
||
StatusCode::REQUEST_TIMEOUT,
|
||
Duration::from_secs(300),
|
||
));
|
||
|
||
// Dioxus 应用路由:自动挂载所有 server function 并渲染前端组件
|
||
let dioxus_app =
|
||
axum::Router::new().serve_dioxus_application(config, router::AppRouter);
|
||
|
||
// 合并 Dioxus + 压缩/30s 超时中间件
|
||
let app_routes = dioxus_app
|
||
.layer(CompressionLayer::new())
|
||
.layer(TimeoutLayer::with_status_code(
|
||
StatusCode::REQUEST_TIMEOUT,
|
||
Duration::from_secs(30),
|
||
));
|
||
|
||
// 静态资源路由:图片文件服务。
|
||
// 注意:axum 0.8 没有 ConnectInfoLayer,且 dioxus::server::serve 不会把
|
||
// ConnectInfo 扩展传播到手动 merge 的路由,所以 serve_image 使用
|
||
// Option<Extension<ConnectInfo<SocketAddr>>> 优雅降级。生产环境应在反向代理后
|
||
// 部署并配置 TRUSTED_PROXY_COUNT,使限流能拿到真实客户端 IP。
|
||
let static_routes = axum::Router::new()
|
||
.route(
|
||
"/uploads/{*path}",
|
||
axum::routing::get(crate::api::image::serve_image),
|
||
)
|
||
.route(
|
||
"/uploads",
|
||
axum::routing::get(|| async { StatusCode::NOT_FOUND }),
|
||
);
|
||
|
||
// 合并:upload 路由保持自己独立的 300s 超时;app routes 加压缩/30s;static routes 无任何中间件
|
||
let router = upload_route.merge(app_routes).merge(static_routes);
|
||
|
||
Ok(router)
|
||
});
|
||
}
|
||
|
||
// 非 server feature(通常为 WASM 前端):启动客户端应用
|
||
#[cfg(not(feature = "server"))]
|
||
{
|
||
use router::AppRouter;
|
||
dioxus::launch(AppRouter);
|
||
}
|
||
}
|