perf(posts): remove content_md from list/search SQL, read stored word counts
This commit is contained in:
parent
a6f08d5d3f
commit
1eedab8f21
@ -63,7 +63,7 @@ pub(super) async fn row_to_post_list(
|
||||
|
||||
/// 将数据库行转换为轻量列表项 DTO。
|
||||
///
|
||||
/// 不包含 `content_md`/`content_html`,但会临时读取 `content_md` 以计算字数与阅读时长。
|
||||
/// 不包含 `content_md`/`content_html`;字数与阅读时长直接读取已持久化的列。
|
||||
/// 同步函数,不依赖数据库连接。
|
||||
#[cfg(feature = "server")]
|
||||
pub(super) fn row_to_post_list_item(row: &tokio_postgres::Row) -> PostListItem {
|
||||
@ -79,9 +79,8 @@ pub(super) fn row_to_post_list_item(row: &tokio_postgres::Row) -> PostListItem {
|
||||
.filter(|t| !t.is_empty())
|
||||
.collect();
|
||||
|
||||
// 临时读取 content_md 计算字数与阅读时长,不存入 DTO。
|
||||
let content_md: String = row.get("content_md");
|
||||
let word_count = count_words(&content_md);
|
||||
let word_count: i32 = row.get("word_count");
|
||||
let reading_time: i32 = row.get("reading_time");
|
||||
|
||||
PostListItem {
|
||||
id,
|
||||
@ -96,8 +95,8 @@ pub(super) fn row_to_post_list_item(row: &tokio_postgres::Row) -> PostListItem {
|
||||
deleted_at: row.try_get("deleted_at").ok(),
|
||||
tags,
|
||||
cover_image: row.get("cover_image"),
|
||||
reading_time: (word_count / 200).max(1),
|
||||
word_count,
|
||||
reading_time: reading_time.max(1) as u32,
|
||||
word_count: word_count.max(0) as u32,
|
||||
}
|
||||
}
|
||||
|
||||
@ -150,6 +149,17 @@ pub(super) async fn row_to_post_full(
|
||||
None
|
||||
};
|
||||
|
||||
// 读取正文与已持久化的字数/阅读时长;若列尚未回填(旧数据为 0),则现场计算。
|
||||
let content_md: String = row.get("content_md");
|
||||
let stored_word_count: i32 = row.get("word_count");
|
||||
let stored_reading_time: i32 = row.get("reading_time");
|
||||
let (word_count, reading_time) = if stored_word_count > 0 && stored_reading_time > 0 {
|
||||
(stored_word_count as u32, stored_reading_time as u32)
|
||||
} else {
|
||||
let wc = count_words(&content_md);
|
||||
(wc, (wc / 200).max(1))
|
||||
};
|
||||
|
||||
let content_html: Option<String> = row.get("content_html");
|
||||
let toc_html_row: Option<String> = row.get("toc_html");
|
||||
|
||||
@ -157,7 +167,6 @@ pub(super) async fn row_to_post_full(
|
||||
let (content_html, toc_html) = if let Some(html) = content_html {
|
||||
(html, toc_html_row)
|
||||
} else {
|
||||
let content_md: String = row.get("content_md");
|
||||
let rendered = crate::api::markdown::render_markdown_enhanced(&content_md);
|
||||
(
|
||||
rendered.html,
|
||||
@ -169,9 +178,6 @@ pub(super) async fn row_to_post_full(
|
||||
)
|
||||
};
|
||||
|
||||
let content_md: String = row.get("content_md");
|
||||
let word_count = count_words(&content_md);
|
||||
|
||||
Post {
|
||||
id,
|
||||
author_id: row.get("author_id"),
|
||||
@ -187,7 +193,7 @@ pub(super) async fn row_to_post_full(
|
||||
deleted_at: row.try_get("deleted_at").ok(),
|
||||
tags,
|
||||
cover_image: row.get("cover_image"),
|
||||
reading_time: (word_count / 200).max(1),
|
||||
reading_time,
|
||||
word_count,
|
||||
toc_html,
|
||||
prev_post,
|
||||
|
||||
@ -82,8 +82,9 @@ pub async fn list_published_posts(
|
||||
let rows = client
|
||||
.query(
|
||||
"SELECT
|
||||
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md,
|
||||
p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
|
||||
p.id, p.author_id, p.title, p.slug, p.summary, p.status,
|
||||
p.published_at, p.created_at, p.updated_at, p.cover_image,
|
||||
p.word_count, p.reading_time,
|
||||
COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags
|
||||
FROM posts p
|
||||
LEFT JOIN post_tags pt ON p.id = pt.post_id
|
||||
@ -139,8 +140,9 @@ pub async fn list_posts(page: i32, per_page: i32) -> Result<PostListResponse, Se
|
||||
let rows = client
|
||||
.query(
|
||||
"SELECT
|
||||
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md,
|
||||
p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
|
||||
p.id, p.author_id, p.title, p.slug, p.summary, p.status,
|
||||
p.published_at, p.created_at, p.updated_at, p.cover_image,
|
||||
p.word_count, p.reading_time,
|
||||
COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags
|
||||
FROM posts p
|
||||
LEFT JOIN post_tags pt ON p.id = pt.post_id
|
||||
@ -198,8 +200,9 @@ pub async fn list_deleted_posts(
|
||||
let rows = client
|
||||
.query(
|
||||
"SELECT
|
||||
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md,
|
||||
p.status, p.published_at, p.created_at, p.updated_at, p.cover_image, p.deleted_at,
|
||||
p.id, p.author_id, p.title, p.slug, p.summary, p.status,
|
||||
p.published_at, p.created_at, p.updated_at, p.cover_image, p.deleted_at,
|
||||
p.word_count, p.reading_time,
|
||||
COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags
|
||||
FROM posts p
|
||||
LEFT JOIN post_tags pt ON p.id = pt.post_id
|
||||
@ -251,8 +254,9 @@ pub async fn get_posts_by_tag(tag_name: String) -> Result<PostListResponse, Serv
|
||||
let rows = client
|
||||
.query(
|
||||
"SELECT
|
||||
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md,
|
||||
p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
|
||||
p.id, p.author_id, p.title, p.slug, p.summary, p.status,
|
||||
p.published_at, p.created_at, p.updated_at, p.cover_image,
|
||||
p.word_count, p.reading_time,
|
||||
COALESCE(array_agg(t2.name) FILTER (WHERE t2.name IS NOT NULL), '{}') as tags
|
||||
FROM posts p
|
||||
JOIN post_tags pt ON p.id = pt.post_id
|
||||
|
||||
@ -75,6 +75,7 @@ pub async fn get_post_by_slug(slug: String) -> Result<SinglePostResponse, Server
|
||||
"SELECT
|
||||
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md, p.content_html, p.toc_html,
|
||||
p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
|
||||
p.word_count, p.reading_time,
|
||||
COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags,
|
||||
prev.title as prev_title, prev.slug as prev_slug,
|
||||
next.title as next_title, next.slug as next_slug
|
||||
|
||||
@ -75,10 +75,19 @@ pub async fn rebuild_content_html(rebuild_all: bool) -> Result<RebuildResult, Se
|
||||
Some(rendered.toc_html)
|
||||
};
|
||||
|
||||
let word_count = crate::utils::text::count_words(&content_md);
|
||||
let reading_time = (word_count / 200).max(1);
|
||||
|
||||
match client
|
||||
.execute(
|
||||
"UPDATE posts SET content_html = $1, toc_html = $2 WHERE id = $3",
|
||||
&[&rendered.html, &toc_html, &id],
|
||||
"UPDATE posts SET content_html = $1, toc_html = $2, word_count = $3, reading_time = $4 WHERE id = $5",
|
||||
&[
|
||||
&rendered.html,
|
||||
&toc_html,
|
||||
&(word_count as i32),
|
||||
&(reading_time as i32),
|
||||
&id,
|
||||
],
|
||||
)
|
||||
.await
|
||||
{
|
||||
|
||||
@ -57,8 +57,9 @@ pub async fn search_posts(query: String) -> Result<PostListResponse, ServerFnErr
|
||||
let rows = client
|
||||
.query(
|
||||
"SELECT
|
||||
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md,
|
||||
p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
|
||||
p.id, p.author_id, p.title, p.slug, p.summary, p.status,
|
||||
p.published_at, p.created_at, p.updated_at, p.cover_image,
|
||||
p.word_count, p.reading_time,
|
||||
COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags,
|
||||
word_similarity(p.search_text, $2) AS sml
|
||||
FROM posts p
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user