docs(auth-pages, router, main): 补充中文注释

This commit is contained in:
xfy 2026-06-12 19:28:08 +08:00
parent 18500c9496
commit c5d1eb117c
4 changed files with 85 additions and 0 deletions

View File

@ -1,9 +1,22 @@
//! 服务端入口与启动配置
//!
//! 本文件是 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;
@ -15,10 +28,14 @@ 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()
@ -26,23 +43,28 @@ fn main() {
)
.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};
// 启动后台定时任务IP 信息清理
tokio::spawn(async {
tasks::ip_purge::run_purge().await;
});
// 启动后台定时任务:过期 session 清理
tokio::spawn(async {
tasks::session_cleanup::run_cleanup().await;
});
// 配置增量渲染缓存,默认缓存 3600 秒,可通过 SSR_CACHE_SECS 覆盖
let config = ServeConfig::builder().incremental(
dioxus::server::IncrementalRendererConfig::default().invalidate_after(
std::time::Duration::from_secs(
@ -53,26 +75,32 @@ fn main() {
),
),
);
// 自定义 API 路由:图片上传,禁用默认请求体大小限制以支持大文件
let api_routes = axum::Router::new().route(
"/api/upload",
axum::routing::post(crate::api::upload::upload_image)
.layer(axum::extract::DefaultBodyLimit::disable()),
);
// 静态资源路由:图片文件服务,支持动态裁剪/旋转/格式转换
let static_routes = axum::Router::new().route(
"/uploads/{*path}",
axum::routing::get(crate::api::image::serve_image),
);
// Dioxus 应用路由:自动挂载所有 server function 并渲染前端组件
let dioxus_app =
axum::Router::new().serve_dioxus_application(config, router::AppRouter);
// 合并三条路由:自定义 API、静态资源、Dioxus 主应用
let router = api_routes.merge(static_routes).merge(dioxus_app);
Ok(router)
});
}
// 非 server feature通常为 WASM 前端):启动客户端应用
#[cfg(not(feature = "server"))]
{
use router::AppRouter;

View File

@ -1,3 +1,8 @@
//! 登录页面
//!
//! 提供用户名/密码表单,前端校验通过后调用 `login` server function
//! 登录成功时跳转到管理后台首页。
use dioxus::prelude::*;
use dioxus::router::components::Link;
@ -6,14 +11,19 @@ use crate::components::forms::{AlertBox, FormInput, FormLabel, BUTTON_PRIMARY_CL
use crate::context::UserContext;
use crate::router::Route;
/// 登录页面组件
#[component]
pub fn Login() -> Element {
// 表单输入状态
let mut username = use_signal(|| "".to_string());
let mut password = use_signal(|| "".to_string());
// 错误提示与加载状态
let mut error = use_signal(|| None::<String>);
let mut loading = use_signal(|| false);
// 全局用户上下文,用于触发登录后的状态刷新
let mut ctx: UserContext = use_context();
// 提交登录表单
let on_submit = Callback::new(move |_| {
if loading() {
return;
@ -24,6 +34,7 @@ pub fn Login() -> Element {
let username_val = username();
let password_val = password();
// 在异步任务中调用 server function 登录
spawn(async move {
match login(username_val, password_val).await {
Ok(AuthResponse {
@ -31,6 +42,7 @@ pub fn Login() -> Element {
token: Some(_token),
..
}) => {
// 登录成功:重置上下文检查标记并跳转到后台
ctx.checked.set(false);
let _ = dioxus::router::navigator().push(Route::Admin {});
}
@ -78,6 +90,7 @@ pub fn Login() -> Element {
value: username(),
disabled: is_loading,
oninput: move |v: String| username.set(v),
// 回车键触发提交
onkeydown: Some(EventHandler::new(move |e: KeyboardEvent| if e.key() == Key::Enter { on_submit(()) })),
}
}

View File

@ -1,3 +1,8 @@
//! 注册页面
//!
//! 提供新用户注册表单。首个注册成功的用户将自动成为管理员,
//! 后续注册请求会被服务端拒绝。
use dioxus::prelude::*;
use dioxus::router::components::Link;
@ -5,16 +10,20 @@ use crate::api::auth::{register, AuthResponse};
use crate::components::forms::{AlertBox, FormInput, FormLabel, BUTTON_PRIMARY_CLASS};
use crate::router::Route;
/// 注册页面组件
#[component]
pub fn Register() -> Element {
// 表单输入状态
let mut username = use_signal(|| "".to_string());
let mut email = use_signal(|| "".to_string());
let mut password = use_signal(|| "".to_string());
let mut confirm_password = use_signal(|| "".to_string());
// 错误提示、成功提示与加载状态
let mut error = use_signal(|| None::<String>);
let mut success = use_signal(|| false);
let mut loading = use_signal(|| false);
// 提交注册表单
let on_submit = Callback::new(move |_| {
if loading() {
return;
@ -22,6 +31,7 @@ pub fn Register() -> Element {
error.set(None);
success.set(false);
// 前端基础校验:密码长度与一致性
if password().len() < 8 {
error.set(Some("密码长度至少 8 位".to_string()));
return;
@ -37,6 +47,7 @@ pub fn Register() -> Element {
loading.set(true);
// 在异步任务中调用 server function 注册
spawn(async move {
match register(username_val, email_val, password_val).await {
Ok(AuthResponse { success: true, .. }) => {

View File

@ -1,3 +1,9 @@
//! 全站路由配置
//!
//! 使用 Dioxus Router 定义前端路由层级,包含前台布局、后台管理布局、
//! 独立的登录与注册页面。`Route` 枚举上的 `#[route("/path")]` 属性
//! 既用于生成 URL 匹配规则,也用于组件导航。
use dioxus::prelude::*;
use std::sync::Arc;
@ -18,63 +24,90 @@ use crate::pages::search::Search;
use crate::pages::tags::{TagDetail, Tags};
use crate::theme::{use_theme_provider, Theme, ThemePreload};
/// 全站路由枚举,每个变体对应一个页面路径
#[derive(Clone, Routable, Debug, PartialEq)]
#[rustfmt::skip]
pub enum Route {
// 前台页面共享布局
#[layout(FrontendLayout)]
/// 首页
#[route("/")]
Home {},
/// 首页分页
#[route("/page/:page")]
HomePage { page: i32 },
/// 文章归档页
#[route("/archives")]
Archives {},
/// 标签列表页
#[route("/tags")]
Tags {},
/// 单个标签下的文章列表
#[route("/tags/:tag")]
TagDetail { tag: String },
/// 文章详情页,按 slug 匹配
#[route("/post/:slug")]
PostDetail { slug: String },
/// 搜索页
#[route("/search")]
Search {},
/// 关于页面
#[route("/about")]
About {},
/// 404 兜底路由,匹配所有未命中路径
#[route("/:..segments")]
NotFound { segments: Vec<String> },
#[end_layout]
// 后台管理路由嵌套在 `/admin` 下
#[nest("/admin")]
// 后台页面共享管理布局
#[layout(AdminLayout)]
/// 后台仪表盘
#[route("/")]
Admin {},
/// 写文章页
#[route("/write")]
Write {},
/// 编辑文章页
#[route("/write/:id")]
WriteEdit { id: i32 },
/// 文章管理列表
#[route("/posts")]
Posts {},
/// 文章管理列表分页
#[route("/posts/:page")]
PostsPage { page: i32 },
/// 评论管理
#[route("/comments")]
AdminComments {},
/// 评论管理分页
#[route("/comments/:page")]
AdminCommentsPage { page: i32 },
#[end_layout]
#[end_nest]
/// 登录页面
#[route("/login")]
Login {},
/// 注册页面
#[route("/register")]
Register {},
}
/// 应用路由器组件
///
/// 初始化主题提供者、全局用户上下文,并挂载样式表与 `Router`。
#[component]
pub fn AppRouter() -> Element {
// 获取当前主题以设置顶层 dark 类
let theme = use_theme_provider();
let theme_class = match theme() {
Theme::Dark => "dark",
Theme::Light => "",
};
// 提供全局用户上下文,供登录状态与路由守卫使用
let user = use_signal(|| None::<Arc<crate::models::user::PublicUser>>);
let checked = use_signal(|| false);
use_context_provider(|| UserContext { user, checked });