diff --git a/src/api/posts/create.rs b/src/api/posts/create.rs index 7e10a6c..58e7ac5 100644 --- a/src/api/posts/create.rs +++ b/src/api/posts/create.rs @@ -93,7 +93,7 @@ pub async fn create_post( // 计算字数与阅读时长,随文章一并持久化,供列表查询直接使用。 let word_count = crate::utils::text::count_words(&content_md); - let reading_time = (word_count / 200).max(1); + let reading_time = crate::utils::text::reading_time(word_count); // 发布状态的文章设置当前发布时间;草稿则为 None。 let published_at = if post_status == PostStatus::Published { diff --git a/src/api/posts/helpers.rs b/src/api/posts/helpers.rs index bdb0cb8..117ea9d 100644 --- a/src/api/posts/helpers.rs +++ b/src/api/posts/helpers.rs @@ -8,7 +8,7 @@ use crate::api::error::AppError; #[cfg(feature = "server")] use crate::models::post::{Post, PostListItem, PostStatus}; #[cfg(feature = "server")] -use crate::utils::text::count_words; +use crate::utils::text::{count_words, reading_time}; /// 复用认证模块的当前 admin 用户获取逻辑。 #[cfg(feature = "server")] @@ -110,7 +110,7 @@ pub(super) async fn row_to_post_full( (stored_word_count as u32, stored_reading_time as u32) } else { let wc = count_words(&content_md); - (wc, (wc / 200).max(1)) + (wc, reading_time(wc)) }; let content_html: Option = row.get("content_html"); diff --git a/src/api/posts/rebuild.rs b/src/api/posts/rebuild.rs index 83076f4..2142bde 100644 --- a/src/api/posts/rebuild.rs +++ b/src/api/posts/rebuild.rs @@ -76,7 +76,7 @@ pub async fn rebuild_content_html(rebuild_all: bool) -> Result Result { let _user = get_current_admin_user().await?; diff --git a/src/api/posts/update.rs b/src/api/posts/update.rs index f28069d..3962473 100644 --- a/src/api/posts/update.rs +++ b/src/api/posts/update.rs @@ -57,7 +57,7 @@ pub async fn update_post( // 重新计算字数与阅读时长,保持与正文同步。 let word_count = crate::utils::text::count_words(&content_md); - let reading_time = (word_count / 200).max(1); + let reading_time = crate::utils::text::reading_time(word_count); let tx = client.transaction().await.map_err(AppError::tx)?; diff --git a/src/utils/text.rs b/src/utils/text.rs index bed63fc..8edbc8f 100644 --- a/src/utils/text.rs +++ b/src/utils/text.rs @@ -72,6 +72,13 @@ pub fn count_words(md: &str) -> u32 { count.max(1) } +/// 由字数估算阅读时长(分钟)。 +/// +/// 按每分钟 200 字计算,至少返回 1 分钟。 +pub fn reading_time(word_count: u32) -> u32 { + (word_count / 200).max(1) +} + /// 自动生成文本摘要,取去除 Markdown 后的前 200 个字符。 pub fn auto_summary(md: &str) -> String { let plain = strip_markdown(md); @@ -158,6 +165,21 @@ mod tests { assert_eq!(count_words(""), 1); } + #[test] + fn reading_time_defaults_to_one() { + assert_eq!(reading_time(0), 1); + assert_eq!(reading_time(1), 1); + assert_eq!(reading_time(199), 1); + } + + #[test] + fn reading_time_scales_by_two_hundred() { + assert_eq!(reading_time(200), 1); + assert_eq!(reading_time(201), 1); + assert_eq!(reading_time(400), 2); + assert_eq!(reading_time(1000), 5); + } + #[test] fn auto_summary_truncates_at_200_chars() { let long_md: String = "a ".repeat(200);