docs(auth-pages, router, main): 补充中文注释
This commit is contained in:
parent
18500c9496
commit
c5d1eb117c
28
src/main.rs
28
src/main.rs
@ -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;
|
||||
|
||||
@ -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(()) })),
|
||||
}
|
||||
}
|
||||
|
||||
@ -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, .. }) => {
|
||||
|
||||
@ -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 });
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user