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。
|
/// 将数据库行转换为轻量列表项 DTO。
|
||||||
///
|
///
|
||||||
/// 不包含 `content_md`/`content_html`,但会临时读取 `content_md` 以计算字数与阅读时长。
|
/// 不包含 `content_md`/`content_html`;字数与阅读时长直接读取已持久化的列。
|
||||||
/// 同步函数,不依赖数据库连接。
|
/// 同步函数,不依赖数据库连接。
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub(super) fn row_to_post_list_item(row: &tokio_postgres::Row) -> PostListItem {
|
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())
|
.filter(|t| !t.is_empty())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// 临时读取 content_md 计算字数与阅读时长,不存入 DTO。
|
let word_count: i32 = row.get("word_count");
|
||||||
let content_md: String = row.get("content_md");
|
let reading_time: i32 = row.get("reading_time");
|
||||||
let word_count = count_words(&content_md);
|
|
||||||
|
|
||||||
PostListItem {
|
PostListItem {
|
||||||
id,
|
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(),
|
deleted_at: row.try_get("deleted_at").ok(),
|
||||||
tags,
|
tags,
|
||||||
cover_image: row.get("cover_image"),
|
cover_image: row.get("cover_image"),
|
||||||
reading_time: (word_count / 200).max(1),
|
reading_time: reading_time.max(1) as u32,
|
||||||
word_count,
|
word_count: word_count.max(0) as u32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,6 +149,17 @@ pub(super) async fn row_to_post_full(
|
|||||||
None
|
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 content_html: Option<String> = row.get("content_html");
|
||||||
let toc_html_row: Option<String> = row.get("toc_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 {
|
let (content_html, toc_html) = if let Some(html) = content_html {
|
||||||
(html, toc_html_row)
|
(html, toc_html_row)
|
||||||
} else {
|
} else {
|
||||||
let content_md: String = row.get("content_md");
|
|
||||||
let rendered = crate::api::markdown::render_markdown_enhanced(&content_md);
|
let rendered = crate::api::markdown::render_markdown_enhanced(&content_md);
|
||||||
(
|
(
|
||||||
rendered.html,
|
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 {
|
Post {
|
||||||
id,
|
id,
|
||||||
author_id: row.get("author_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(),
|
deleted_at: row.try_get("deleted_at").ok(),
|
||||||
tags,
|
tags,
|
||||||
cover_image: row.get("cover_image"),
|
cover_image: row.get("cover_image"),
|
||||||
reading_time: (word_count / 200).max(1),
|
reading_time,
|
||||||
word_count,
|
word_count,
|
||||||
toc_html,
|
toc_html,
|
||||||
prev_post,
|
prev_post,
|
||||||
|
|||||||
@ -82,8 +82,9 @@ pub async fn list_published_posts(
|
|||||||
let rows = client
|
let rows = client
|
||||||
.query(
|
.query(
|
||||||
"SELECT
|
"SELECT
|
||||||
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md,
|
p.id, p.author_id, p.title, p.slug, p.summary, p.status,
|
||||||
p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
|
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
|
COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags
|
||||||
FROM posts p
|
FROM posts p
|
||||||
LEFT JOIN post_tags pt ON p.id = pt.post_id
|
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
|
let rows = client
|
||||||
.query(
|
.query(
|
||||||
"SELECT
|
"SELECT
|
||||||
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md,
|
p.id, p.author_id, p.title, p.slug, p.summary, p.status,
|
||||||
p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
|
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
|
COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags
|
||||||
FROM posts p
|
FROM posts p
|
||||||
LEFT JOIN post_tags pt ON p.id = pt.post_id
|
LEFT JOIN post_tags pt ON p.id = pt.post_id
|
||||||
@ -198,8 +200,9 @@ pub async fn list_deleted_posts(
|
|||||||
let rows = client
|
let rows = client
|
||||||
.query(
|
.query(
|
||||||
"SELECT
|
"SELECT
|
||||||
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md,
|
p.id, p.author_id, p.title, p.slug, p.summary, p.status,
|
||||||
p.status, p.published_at, p.created_at, p.updated_at, p.cover_image, p.deleted_at,
|
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
|
COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags
|
||||||
FROM posts p
|
FROM posts p
|
||||||
LEFT JOIN post_tags pt ON p.id = pt.post_id
|
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
|
let rows = client
|
||||||
.query(
|
.query(
|
||||||
"SELECT
|
"SELECT
|
||||||
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md,
|
p.id, p.author_id, p.title, p.slug, p.summary, p.status,
|
||||||
p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
|
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
|
COALESCE(array_agg(t2.name) FILTER (WHERE t2.name IS NOT NULL), '{}') as tags
|
||||||
FROM posts p
|
FROM posts p
|
||||||
JOIN post_tags pt ON p.id = pt.post_id
|
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
|
"SELECT
|
||||||
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md, p.content_html, p.toc_html,
|
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.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,
|
COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags,
|
||||||
prev.title as prev_title, prev.slug as prev_slug,
|
prev.title as prev_title, prev.slug as prev_slug,
|
||||||
next.title as next_title, next.slug as next_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)
|
Some(rendered.toc_html)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let word_count = crate::utils::text::count_words(&content_md);
|
||||||
|
let reading_time = (word_count / 200).max(1);
|
||||||
|
|
||||||
match client
|
match client
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE posts SET content_html = $1, toc_html = $2 WHERE id = $3",
|
"UPDATE posts SET content_html = $1, toc_html = $2, word_count = $3, reading_time = $4 WHERE id = $5",
|
||||||
&[&rendered.html, &toc_html, &id],
|
&[
|
||||||
|
&rendered.html,
|
||||||
|
&toc_html,
|
||||||
|
&(word_count as i32),
|
||||||
|
&(reading_time as i32),
|
||||||
|
&id,
|
||||||
|
],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
|
|||||||
@ -57,8 +57,9 @@ pub async fn search_posts(query: String) -> Result<PostListResponse, ServerFnErr
|
|||||||
let rows = client
|
let rows = client
|
||||||
.query(
|
.query(
|
||||||
"SELECT
|
"SELECT
|
||||||
p.id, p.author_id, p.title, p.slug, p.summary, p.content_md,
|
p.id, p.author_id, p.title, p.slug, p.summary, p.status,
|
||||||
p.status, p.published_at, p.created_at, p.updated_at, p.cover_image,
|
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,
|
COALESCE(array_agg(t.name) FILTER (WHERE t.name IS NOT NULL), '{}') as tags,
|
||||||
word_similarity(p.search_text, $2) AS sml
|
word_similarity(p.search_text, $2) AS sml
|
||||||
FROM posts p
|
FROM posts p
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user