Skip to main content

yggdrasil/api/database/
tasks.rs

1#![allow(clippy::unused_unit, deprecated)]
2
3//! 备份/恢复的异步任务进度表(DashMap)。
4//!
5//! create_backup/restore_backup 立即返回 task_id,后台任务跑时通过
6//! `update` 更新进度,前端轮询 [`get_task_progress`][crate::api::database::tasks::get_task_progress]。
7//! 已完成超过 1 小时的任务惰性清理,避免内存累积。
8
9use chrono::{DateTime, Utc};
10use dioxus::prelude::*;
11use serde::{Deserialize, Serialize};
12
13// admin 鉴权仅 server 构建用到。
14#[cfg(feature = "server")]
15use crate::api::auth::get_current_admin_user;
16// DashMap / LazyLock 仅 server 构建持有任务进度表;WASM 端只序列化 TaskProgress。
17#[cfg(feature = "server")]
18use dashmap::DashMap;
19#[cfg(feature = "server")]
20use std::sync::LazyLock;
21
22#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
23pub enum TaskKind {
24    Backup,
25    Restore,
26}
27
28#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
29pub enum TaskStatus {
30    Running,
31    Done,
32    Failed,
33}
34
35#[derive(Clone, Serialize, Deserialize, Debug)]
36pub struct TaskProgress {
37    pub id: String,
38    pub kind: TaskKind,
39    pub stage: String,
40    pub percent: u8,
41    pub detail: Option<String>,
42    pub status: TaskStatus,
43    pub error: Option<String>,
44    pub created_at: DateTime<Utc>,
45    /// 完成后的备份文件名(下载/恢复用)。
46    pub result_filename: Option<String>,
47}
48
49#[cfg(feature = "server")]
50static TASKS: LazyLock<DashMap<String, TaskProgress>> = LazyLock::new(DashMap::new);
51
52/// 注册新任务(初始 Running,0%)。
53///
54/// pub(crate):定时备份调度任务([`crate::tasks::backup`])也需要注册,
55/// 让 admin 打开面板时能看到进行中的自动备份。
56#[cfg(feature = "server")]
57pub(crate) fn insert(id: String, kind: TaskKind) {
58    TASKS.insert(
59        id.clone(),
60        TaskProgress {
61            id,
62            kind,
63            stage: "排队中".to_string(),
64            percent: 0,
65            detail: None,
66            status: TaskStatus::Running,
67            error: None,
68            created_at: Utc::now(),
69            result_filename: None,
70        },
71    );
72}
73
74/// 更新任务进度(后台任务调用)。
75#[cfg(feature = "server")]
76pub(super) fn update(
77    id: &str,
78    stage: &str,
79    percent: u8,
80    status: TaskStatus,
81    detail: Option<String>,
82    error: Option<String>,
83    result_filename: Option<String>,
84) {
85    if let Some(mut p) = TASKS.get_mut(id) {
86        p.stage = stage.to_string();
87        p.percent = percent;
88        p.status = status;
89        p.detail = detail;
90        p.error = error;
91        p.result_filename = result_filename;
92    }
93}
94
95/// 惰性清理已完成超过 1 小时的任务(查询时顺便清)。
96#[cfg(feature = "server")]
97fn gc_old() {
98    let cutoff = Utc::now() - chrono::Duration::hours(1);
99    TASKS.retain(|_, p| {
100        !(matches!(p.status, TaskStatus::Done | TaskStatus::Failed) && p.created_at < cutoff)
101    });
102}
103
104/// 查询任务进度(轮询用)。
105#[server(GetTaskProgress, "/api")]
106pub async fn get_task_progress(task_id: String) -> Result<TaskProgress, ServerFnError> {
107    let _user = get_current_admin_user().await?;
108    #[cfg(feature = "server")]
109    {
110        gc_old();
111        TASKS
112            .get(&task_id)
113            .map(|p| p.clone())
114            .ok_or_else(|| crate::api::error::AppError::NotFound("任务不存在").into())
115    }
116    #[cfg(not(feature = "server"))]
117    {
118        Ok(TaskProgress {
119            id: task_id,
120            kind: TaskKind::Backup,
121            stage: String::new(),
122            percent: 0,
123            detail: None,
124            status: TaskStatus::Done,
125            error: None,
126            created_at: Utc::now(),
127            result_filename: None,
128        })
129    }
130}