fix(posts): lock rows and read metadata in transaction for delete/purge/restore
This commit is contained in:
parent
1092fbb3ce
commit
ca212a2aab
@ -25,12 +25,13 @@ pub async fn delete_post(post_id: i32) -> Result<CreatePostResponse, ServerFnErr
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
{
|
{
|
||||||
let client = get_conn().await.map_err(AppError::db_conn)?;
|
let mut client = get_conn().await.map_err(AppError::db_conn)?;
|
||||||
|
let tx = client.transaction().await.map_err(AppError::tx)?;
|
||||||
|
|
||||||
// 在软删除前查询 slug 与标签,用于后续精准失效缓存。
|
// 在事务内锁定行并读取 slug,避免并发更新导致缓存失效目标过期。
|
||||||
let slug_row = client
|
let slug_row = tx
|
||||||
.query_opt(
|
.query_opt(
|
||||||
"SELECT slug FROM posts WHERE id = $1 AND deleted_at IS NULL",
|
"SELECT slug FROM posts WHERE id = $1 AND deleted_at IS NULL FOR UPDATE",
|
||||||
&[&post_id],
|
&[&post_id],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@ -46,7 +47,7 @@ pub async fn delete_post(post_id: i32) -> Result<CreatePostResponse, ServerFnErr
|
|||||||
};
|
};
|
||||||
let slug: String = slug_row.get(0);
|
let slug: String = slug_row.get(0);
|
||||||
|
|
||||||
let tag_rows = client
|
let tag_rows = tx
|
||||||
.query(
|
.query(
|
||||||
"SELECT t.name FROM tags t JOIN post_tags pt ON t.id = pt.tag_id WHERE pt.post_id = $1",
|
"SELECT t.name FROM tags t JOIN post_tags pt ON t.id = pt.tag_id WHERE pt.post_id = $1",
|
||||||
&[&post_id],
|
&[&post_id],
|
||||||
@ -56,13 +57,13 @@ pub async fn delete_post(post_id: i32) -> Result<CreatePostResponse, ServerFnErr
|
|||||||
let tags: Vec<String> = tag_rows.iter().map(|r| r.get(0)).collect();
|
let tags: Vec<String> = tag_rows.iter().map(|r| r.get(0)).collect();
|
||||||
|
|
||||||
// 软删除:仅影响未被删除的文章。
|
// 软删除:仅影响未被删除的文章。
|
||||||
let result = client
|
let result = tx
|
||||||
.execute(
|
.execute(
|
||||||
"UPDATE posts SET deleted_at = NOW() WHERE id = $1 AND deleted_at IS NULL",
|
"UPDATE posts SET deleted_at = NOW() WHERE id = $1 AND deleted_at IS NULL",
|
||||||
&[&post_id],
|
&[&post_id],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(AppError::query)?;
|
.map_err(AppError::tx)?;
|
||||||
|
|
||||||
if result == 0 {
|
if result == 0 {
|
||||||
return Ok(CreatePostResponse {
|
return Ok(CreatePostResponse {
|
||||||
@ -73,6 +74,8 @@ pub async fn delete_post(post_id: i32) -> Result<CreatePostResponse, ServerFnErr
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tx.commit().await.map_err(AppError::tx)?;
|
||||||
|
|
||||||
// 删除后按影响范围精准失效缓存。
|
// 删除后按影响范围精准失效缓存。
|
||||||
crate::cache::invalidate_post_lists();
|
crate::cache::invalidate_post_lists();
|
||||||
crate::cache::invalidate_all_tags();
|
crate::cache::invalidate_all_tags();
|
||||||
|
|||||||
@ -34,10 +34,10 @@ pub async fn restore_post(post_id: i32) -> Result<CreatePostResponse, ServerFnEr
|
|||||||
let mut client = get_conn().await.map_err(AppError::db_conn)?;
|
let mut client = get_conn().await.map_err(AppError::db_conn)?;
|
||||||
let tx = client.transaction().await.map_err(AppError::tx)?;
|
let tx = client.transaction().await.map_err(AppError::tx)?;
|
||||||
|
|
||||||
// 读取待恢复文章的当前 slug、标签与是否确已删除。
|
// 在事务内锁定行并读取当前 slug、标签与是否确已删除。
|
||||||
let row = tx
|
let row = tx
|
||||||
.query_opt(
|
.query_opt(
|
||||||
"SELECT slug FROM posts WHERE id = $1 AND deleted_at IS NOT NULL",
|
"SELECT slug FROM posts WHERE id = $1 AND deleted_at IS NOT NULL FOR UPDATE",
|
||||||
&[&post_id],
|
&[&post_id],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@ -123,12 +123,13 @@ pub async fn purge_post(post_id: i32) -> Result<CreatePostResponse, ServerFnErro
|
|||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
{
|
{
|
||||||
let client = get_conn().await.map_err(AppError::db_conn)?;
|
let mut client = get_conn().await.map_err(AppError::db_conn)?;
|
||||||
|
let tx = client.transaction().await.map_err(AppError::tx)?;
|
||||||
|
|
||||||
// 物理删除前查询 slug 与标签,用于精准失效缓存。
|
// 在事务内锁定行并读取 slug 与标签,避免并发更新导致缓存失效目标过期。
|
||||||
let slug_row = client
|
let slug_row = tx
|
||||||
.query_opt(
|
.query_opt(
|
||||||
"SELECT slug FROM posts WHERE id = $1 AND deleted_at IS NOT NULL",
|
"SELECT slug FROM posts WHERE id = $1 AND deleted_at IS NOT NULL FOR UPDATE",
|
||||||
&[&post_id],
|
&[&post_id],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
@ -144,7 +145,7 @@ pub async fn purge_post(post_id: i32) -> Result<CreatePostResponse, ServerFnErro
|
|||||||
};
|
};
|
||||||
let slug: String = slug_row.get(0);
|
let slug: String = slug_row.get(0);
|
||||||
|
|
||||||
let tag_rows = client
|
let tag_rows = tx
|
||||||
.query(
|
.query(
|
||||||
"SELECT t.name FROM tags t JOIN post_tags pt ON t.id = pt.tag_id WHERE pt.post_id = $1",
|
"SELECT t.name FROM tags t JOIN post_tags pt ON t.id = pt.tag_id WHERE pt.post_id = $1",
|
||||||
&[&post_id],
|
&[&post_id],
|
||||||
@ -153,13 +154,13 @@ pub async fn purge_post(post_id: i32) -> Result<CreatePostResponse, ServerFnErro
|
|||||||
.map_err(AppError::query)?;
|
.map_err(AppError::query)?;
|
||||||
let tags: Vec<String> = tag_rows.iter().map(|r| r.get(0)).collect();
|
let tags: Vec<String> = tag_rows.iter().map(|r| r.get(0)).collect();
|
||||||
|
|
||||||
let result = client
|
let result = tx
|
||||||
.execute(
|
.execute(
|
||||||
"DELETE FROM posts WHERE id = $1 AND deleted_at IS NOT NULL",
|
"DELETE FROM posts WHERE id = $1 AND deleted_at IS NOT NULL",
|
||||||
&[&post_id],
|
&[&post_id],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(AppError::query)?;
|
.map_err(AppError::tx)?;
|
||||||
|
|
||||||
if result == 0 {
|
if result == 0 {
|
||||||
return Ok(CreatePostResponse {
|
return Ok(CreatePostResponse {
|
||||||
@ -170,6 +171,8 @@ pub async fn purge_post(post_id: i32) -> Result<CreatePostResponse, ServerFnErro
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tx.commit().await.map_err(AppError::tx)?;
|
||||||
|
|
||||||
// 精准失效相关缓存。
|
// 精准失效相关缓存。
|
||||||
crate::cache::invalidate_post_lists();
|
crate::cache::invalidate_post_lists();
|
||||||
crate::cache::invalidate_all_tags();
|
crate::cache::invalidate_all_tags();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user