diff --git a/src/main.rs b/src/main.rs index 56e75b1..69521e7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,6 +64,11 @@ fn main() { tasks::session_cleanup::run_cleanup().await; }); + // 启动后台定时任务:回收站自动清理 + tokio::spawn(async { + tasks::post_purge::run_purge().await; + }); + // 配置增量渲染缓存,默认缓存 3600 秒,可通过 SSR_CACHE_SECS 覆盖 let config = ServeConfig::builder().incremental( dioxus::server::IncrementalRendererConfig::default().invalidate_after( diff --git a/src/tasks/mod.rs b/src/tasks/mod.rs index 63c4af2..f8a3887 100644 --- a/src/tasks/mod.rs +++ b/src/tasks/mod.rs @@ -8,3 +8,6 @@ pub mod ip_purge; /// 定时删除已过期会话,避免 `sessions` 表无限增长。 #[cfg(feature = "server")] pub mod session_cleanup; +/// 定时清理回收站中超过保留期的已删除文章。 +#[cfg(feature = "server")] +pub mod post_purge; diff --git a/src/tasks/post_purge.rs b/src/tasks/post_purge.rs new file mode 100644 index 0000000..58107a5 --- /dev/null +++ b/src/tasks/post_purge.rs @@ -0,0 +1,74 @@ +//! 回收站自动清理后台任务。 +//! +//! 仅在 `server` feature 启用时编译,每天运行一次。 +//! 每次执行前读取 settings 表:若自动清理关闭则跳过,否则按保留天数物理删除过期文章。 + +use std::time::Duration; + +use tokio::time::interval; + +use crate::db::pool::get_conn; +use crate::models::settings::TrashSettings; + +/// 启动回收站自动清理循环,每天触发一次。 +/// +/// 每次读取最新配置:若 `trash_auto_purge_enabled` 关闭则 no-op, +/// 否则删除 `deleted_at < NOW() - retention_days` 的文章。 +/// 任何错误只记录日志,不中断循环。 +pub async fn run_purge() { + let mut ticker = interval(Duration::from_secs(86400)); + loop { + ticker.tick().await; + match get_conn().await { + Ok(client) => { + match purge_expired(&client).await { + Ok(n) => { + if n > 0 { + tracing::info!("Post auto-purge: removed {} expired trashed posts", n); + } + } + Err(e) => tracing::error!("Post auto-purge error: {:?}", e), + } + } + Err(e) => tracing::error!("Failed to get DB connection for post purge: {:?}", e), + } + } +} + +/// 读取配置并删除过期回收站文章,返回删除行数。 +/// +/// 抽取为独立函数便于单元测试(虽需 DB,但逻辑集中)。 +async fn purge_expired(client: &tokio_postgres::Client) -> Result { + // 读取配置,缺键时回退默认值。 + let enabled: bool = client + .query_opt( + "SELECT value FROM settings WHERE key = 'trash_auto_purge_enabled'", + &[], + ) + .await? + .and_then(|r| r.get::<_, String>("value").parse().ok()) + .unwrap_or(false); + + if !enabled { + return Ok(0); + } + + let days: i32 = client + .query_opt( + "SELECT value FROM settings WHERE key = 'trash_retention_days'", + &[], + ) + .await? + .and_then(|r| r.get::<_, String>("value").parse().ok()) + .unwrap_or(30); + + let days = TrashSettings::clamp_retention(days); + + let n = client + .execute( + "DELETE FROM posts WHERE deleted_at IS NOT NULL AND deleted_at < NOW() - make_interval(days => $1)", + &[&days], + ) + .await?; + Ok(n) +}