Compare commits

..

2 Commits

Author SHA1 Message Date
xfy
c60e4e08d7 fix(build): 用 cfg gate 消除 WASM 构建的 27 个假阳性 warning
Some checks failed
CI / check (push) Failing after 5m17s
CI / build (push) Has been skipped
Dioxus fullstack 在 WASM 构建中会剥离 #[server] 函数体,导致仅在
函数体内使用的导入/函数/常量被误报为 unused / dead_code。

- 仅在服务端使用的导入加 #[cfg(feature="server")]:get_conn、
  PostStatus/PostStats/Tag、password/session、User/UserRole、
  slug.rs 的 dioxus::prelude(保留 ServerFnError 给 server-gated
  的 ensure_unique_slug)
- 仅在服务端使用的项加 cfg gate:clamp_pagination 及其常量、
  rebuild 常量、format_relative_time、compute_content_hash、
  CommentStatus::from_str
- CommentCountResponse/PendingCommentsResponse 出现在 #[server]
  签名中(WASM 保留),无法 gate,改用 #[allow(dead_code)],
  与现有 CreateCommentRequest 处理一致

make build 零 warning,cargo test 299 passed。
2026-06-16 11:29:18 +08:00
xfy
b37e33dbf4 fix(build): 修复 Tailwind v4 与 Tiptap 编辑器构建
Tailwind v4 将 CLI 拆为独立包 @tailwindcss/cli,核心包 tailwindcss
不再带 bin,npx tailwindcss 会因找不到可执行入口而失败;即便改用
@tailwindcss/cli,npx 装到全局缓存,@import "tailwindcss" 仍解析不到
./node_modules/tailwindcss。回到直接调用 PATH 上的 tailwindcss 二进制
(brew 安装或 CI 的 npm i -g @tailwindcss/cli),这也是 v4 升级时的
原始设计。

build-editor 中 npm ci 受全局 --omit=dev 影响会跳过 vite,npx vite
退而拉取 latest(v8,不兼容)。改用 npm ci --include=dev 确保装上
lock 钉死的 vite@5.4.21,并以 npm run build 走本地 .bin/vite,避免
npx 版本漂移。
2026-06-16 10:53:37 +08:00
15 changed files with 35 additions and 7 deletions

View File

@ -3,13 +3,13 @@
build: build:
@$(MAKE) build-editor @$(MAKE) build-editor
@$(MAKE) highlight-css @$(MAKE) highlight-css
@npx tailwindcss -i input.css -o public/style.css --minify @tailwindcss -i input.css -o public/style.css --minify
@dx build --release --debug-symbols=false @dx build --release --debug-symbols=false
build-linux: build-linux:
@$(MAKE) build-editor @$(MAKE) build-editor
@$(MAKE) highlight-css @$(MAKE) highlight-css
@npx tailwindcss -i input.css -o public/style.css --minify @tailwindcss -i input.css -o public/style.css --minify
@dx build --release --debug-symbols=false --target x86_64-unknown-linux-musl @dx build --release --debug-symbols=false --target x86_64-unknown-linux-musl
highlight-css: highlight-css:
@ -17,21 +17,21 @@ highlight-css:
build-editor: build-editor:
@echo "Building Tiptap editor..." @echo "Building Tiptap editor..."
@cd libs/tiptap-editor && npm ci && npx vite build @cd libs/tiptap-editor && npm ci --include=dev && npm run build
@echo "Tiptap editor built." @echo "Tiptap editor built."
dev: dev:
@echo "Starting tailwindcss watch and dx serve..." @echo "Starting tailwindcss watch and dx serve..."
@npx tailwindcss -i input.css -o public/style.css --watch & \ @tailwindcss -i input.css -o public/style.css --watch & \
TAILWIND_PID=$$!; \ TAILWIND_PID=$$!; \
trap 'kill $$TAILWIND_PID 2>/dev/null; exit' INT TERM EXIT; \ trap 'kill $$TAILWIND_PID 2>/dev/null; exit' INT TERM EXIT; \
dx serve --addr 0.0.0.0 dx serve --addr 0.0.0.0
css: css:
@npx tailwindcss -i input.css -o public/style.css @tailwindcss -i input.css -o public/style.css
css-watch: css-watch:
@npx tailwindcss -i input.css -o public/style.css --watch @tailwindcss -i input.css -o public/style.css --watch
test: test:
@cargo test @cargo test

View File

@ -15,9 +15,13 @@ use http::header::{HeaderValue, SET_COOKIE};
use crate::api::error::AppError; use crate::api::error::AppError;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use crate::auth::session::get_session_from_ctx; use crate::auth::session::get_session_from_ctx;
#[cfg(feature = "server")]
use crate::auth::{password, session}; use crate::auth::{password, session};
#[cfg(feature = "server")]
use crate::db::pool::get_conn; use crate::db::pool::get_conn;
use crate::models::user::{PublicUser, User, UserRole}; #[cfg(feature = "server")]
use crate::models::user::{User, UserRole};
use crate::models::user::PublicUser;
#[allow(dead_code)] #[allow(dead_code)]
fn validate_username(username: &str) -> Result<(), String> { fn validate_username(username: &str) -> Result<(), String> {

View File

@ -68,6 +68,7 @@ pub fn row_to_admin_comment(row: &tokio_postgres::Row) -> AdminComment {
} }
/// 将 UTC 时间格式化为相对时间(刚刚 / N 分钟前 / N 小时前 / N 天前 / 日期)。 /// 将 UTC 时间格式化为相对时间(刚刚 / N 分钟前 / N 小时前 / N 天前 / 日期)。
#[cfg(feature = "server")]
pub fn format_relative_time(dt: chrono::DateTime<chrono::Utc>) -> String { pub fn format_relative_time(dt: chrono::DateTime<chrono::Utc>) -> String {
let now = chrono::Utc::now(); let now = chrono::Utc::now();
let diff = now.signed_duration_since(dt); let diff = now.signed_duration_since(dt);
@ -139,6 +140,7 @@ pub fn validate_comment_content(content: &str) -> Result<(), String> {
} }
/// 计算评论内容哈希,用于检测短时间内的重复提交。 /// 计算评论内容哈希,用于检测短时间内的重复提交。
#[cfg(feature = "server")]
pub fn compute_content_hash( pub fn compute_content_hash(
post_id: i32, post_id: i32,
parent_id: Option<i64>, parent_id: Option<i64>,

View File

@ -52,6 +52,7 @@ pub struct CommentTreeResponse {
/// 评论计数响应。 /// 评论计数响应。
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(dead_code)]
pub struct CommentCountResponse { pub struct CommentCountResponse {
/// 评论数量。 /// 评论数量。
pub count: i64, pub count: i64,
@ -59,6 +60,7 @@ pub struct CommentCountResponse {
/// 待审核评论列表响应。 /// 待审核评论列表响应。
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
#[allow(dead_code)]
pub struct PendingCommentsResponse { pub struct PendingCommentsResponse {
/// 待审核评论列表。 /// 待审核评论列表。
pub comments: Vec<AdminComment>, pub comments: Vec<AdminComment>,

View File

@ -14,7 +14,9 @@ use super::helpers::{clean_tags, get_current_admin_user, sync_tags};
use super::types::CreatePostResponse; use super::types::CreatePostResponse;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use crate::api::error::AppError; use crate::api::error::AppError;
#[cfg(feature = "server")]
use crate::db::pool::get_conn; use crate::db::pool::get_conn;
#[cfg(feature = "server")]
use crate::models::post::PostStatus; use crate::models::post::PostStatus;
/// 创建一篇新文章。 /// 创建一篇新文章。

View File

@ -12,6 +12,7 @@ use super::helpers::get_current_admin_user;
use super::types::CreatePostResponse; use super::types::CreatePostResponse;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use crate::api::error::AppError; use crate::api::error::AppError;
#[cfg(feature = "server")]
use crate::db::pool::get_conn; use crate::db::pool::get_conn;
/// 删除指定文章。 /// 删除指定文章。

View File

@ -12,12 +12,14 @@ use super::helpers::{get_current_admin_user, row_to_post_list};
use super::types::PostListResponse; use super::types::PostListResponse;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use crate::api::error::AppError; use crate::api::error::AppError;
#[cfg(feature = "server")]
use crate::db::pool::get_conn; use crate::db::pool::get_conn;
/// 单页允许的最大文章数。 /// 单页允许的最大文章数。
/// ///
/// 公开的 `list_published_posts` 接口无需认证,若不对 `per_page` 设上限, /// 公开的 `list_published_posts` 接口无需认证,若不对 `per_page` 设上限,
/// 攻击者可传入巨大值迫使数据库扫描并实例化超大 Vec造成内存放大与拒绝服务。 /// 攻击者可传入巨大值迫使数据库扫描并实例化超大 Vec造成内存放大与拒绝服务。
#[cfg(feature = "server")]
const MAX_PER_PAGE: i32 = 50; const MAX_PER_PAGE: i32 = 50;
/// 允许的最大页码。 /// 允许的最大页码。
@ -25,11 +27,13 @@ const MAX_PER_PAGE: i32 = 50;
/// `page` 无上限时,攻击者可用海量不同 `page` 值撑大缓存键空间(缓存污染), /// `page` 无上限时,攻击者可用海量不同 `page` 值撑大缓存键空间(缓存污染),
/// 并触发无意义的超大 `OFFSET` 扫描。10_000 对任何实际博客都足够宽裕 /// 并触发无意义的超大 `OFFSET` 扫描。10_000 对任何实际博客都足够宽裕
/// (配合 `MAX_PER_PAGE` 最多覆盖 50 万篇文章),同时把缓存键空间限制在有限范围。 /// (配合 `MAX_PER_PAGE` 最多覆盖 50 万篇文章),同时把缓存键空间限制在有限范围。
#[cfg(feature = "server")]
const MAX_PAGE: i32 = 10_000; const MAX_PAGE: i32 = 10_000;
/// 将分页参数钳制到安全范围:页码 1`MAX_PAGE`,每页 1`MAX_PER_PAGE`。 /// 将分页参数钳制到安全范围:页码 1`MAX_PAGE`,每页 1`MAX_PER_PAGE`。
/// ///
/// 注意:返回值必须同时用于缓存键与 SQL 查询,避免同一逻辑页落入不同缓存条目。 /// 注意:返回值必须同时用于缓存键与 SQL 查询,避免同一逻辑页落入不同缓存条目。
#[cfg(feature = "server")]
fn clamp_pagination(page: i32, per_page: i32) -> (i32, i32) { fn clamp_pagination(page: i32, per_page: i32) -> (i32, i32) {
(page.clamp(1, MAX_PAGE), per_page.clamp(1, MAX_PER_PAGE)) (page.clamp(1, MAX_PAGE), per_page.clamp(1, MAX_PER_PAGE))
} }

View File

@ -12,6 +12,7 @@ use super::helpers::{get_current_admin_user, row_to_post_full, row_to_post_list}
use super::types::SinglePostResponse; use super::types::SinglePostResponse;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use crate::api::error::AppError; use crate::api::error::AppError;
#[cfg(feature = "server")]
use crate::db::pool::get_conn; use crate::db::pool::get_conn;
/// 根据文章 id 获取详情。 /// 根据文章 id 获取详情。

View File

@ -12,11 +12,14 @@ use super::helpers::get_current_admin_user;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use crate::api::error::AppError; use crate::api::error::AppError;
use crate::api::posts::RebuildResult; use crate::api::posts::RebuildResult;
#[cfg(feature = "server")]
use crate::db::pool::get_conn; use crate::db::pool::get_conn;
/// 单次重建批处理数量上限。 /// 单次重建批处理数量上限。
#[cfg(feature = "server")]
const REBUILD_BATCH_LIMIT: i64 = 500; const REBUILD_BATCH_LIMIT: i64 = 500;
/// 返回给前端展示的最大错误条数。 /// 返回给前端展示的最大错误条数。
#[cfg(feature = "server")]
const MAX_DISPLAY_ERRORS: usize = 5; const MAX_DISPLAY_ERRORS: usize = 5;
/// 批量重建文章 content_html 与 toc_html。 /// 批量重建文章 content_html 与 toc_html。

View File

@ -12,6 +12,7 @@ use super::helpers::row_to_post_list;
use super::types::PostListResponse; use super::types::PostListResponse;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use crate::api::error::AppError; use crate::api::error::AppError;
#[cfg(feature = "server")]
use crate::db::pool::get_conn; use crate::db::pool::get_conn;
/// 搜索已发布文章。 /// 搜索已发布文章。

View File

@ -11,7 +11,9 @@ use super::helpers::get_current_admin_user;
use super::types::PostStatsResponse; use super::types::PostStatsResponse;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use crate::api::error::AppError; use crate::api::error::AppError;
#[cfg(feature = "server")]
use crate::db::pool::get_conn; use crate::db::pool::get_conn;
#[cfg(feature = "server")]
use crate::models::post::PostStats; use crate::models::post::PostStats;
/// 获取文章统计信息。 /// 获取文章统计信息。

View File

@ -9,7 +9,9 @@ use dioxus::prelude::*;
use super::types::TagListResponse; use super::types::TagListResponse;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use crate::api::error::AppError; use crate::api::error::AppError;
#[cfg(feature = "server")]
use crate::db::pool::get_conn; use crate::db::pool::get_conn;
#[cfg(feature = "server")]
use crate::models::post::Tag; use crate::models::post::Tag;
/// 获取全部标签列表。 /// 获取全部标签列表。

View File

@ -14,7 +14,9 @@ use super::helpers::{clean_tags, get_current_admin_user, sync_tags};
use super::types::CreatePostResponse; use super::types::CreatePostResponse;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use crate::api::error::AppError; use crate::api::error::AppError;
#[cfg(feature = "server")]
use crate::db::pool::get_conn; use crate::db::pool::get_conn;
#[cfg(feature = "server")]
use crate::models::post::PostStatus; use crate::models::post::PostStatus;
/// 更新指定文章。 /// 更新指定文章。

View File

@ -6,6 +6,7 @@
#![allow(clippy::unused_unit, deprecated)] #![allow(clippy::unused_unit, deprecated)]
#[cfg(feature = "server")]
use dioxus::prelude::*; use dioxus::prelude::*;
#[cfg(feature = "server")] #[cfg(feature = "server")]

View File

@ -22,6 +22,7 @@ pub enum CommentStatus {
impl CommentStatus { impl CommentStatus {
/// 将数据库或 API 中的状态字符串解析为 CommentStatus未知值默认回退到 Pending。 /// 将数据库或 API 中的状态字符串解析为 CommentStatus未知值默认回退到 Pending。
#[cfg(feature = "server")]
pub fn from_str(s: &str) -> Self { pub fn from_str(s: &str) -> Self {
match s { match s {
"approved" => Self::Approved, "approved" => Self::Approved,