feat: 加固运行期可靠性并补齐调度与观测接口

- 运行句柄按 run_id 归属,修复取消后重跑时闸门被旧运行删除的竞态
- 调度循环改用取消令牌退出,启动时对账遗留运行,校验 rows_affected
- 三类任务统一参与调度,新增 /api/v1/stats 与 /api/v1/schedule
- SQLite 启用 WAL 与 busy_timeout,日志清理跳过活跃文件,单行上限 256 KiB
- 前端缓存管理令牌并在 403 时重试一次,仪表盘改用统计接口
This commit is contained in:
2026-10-26 00:38:16 +08:00
parent 773196d689
commit 815258c19b
19 changed files with 990 additions and 93 deletions
+23 -6
View File
@@ -119,20 +119,22 @@ pub async fn create_skipped(
})
}
/// 返回是否真的更新到了运行记录;记录被删除(例如任务被删)时返回 false。
pub async fn mark_running(
pool: &SqlitePool,
run_id: i64,
started_at: DateTime<Utc>,
) -> ApiResult<()> {
sqlx::query("UPDATE task_runs SET status = ?, started_at = ? WHERE id = ?")
) -> ApiResult<bool> {
let result = sqlx::query("UPDATE task_runs SET status = ?, started_at = ? WHERE id = ?")
.bind(RunStatus::Running.as_str())
.bind(started_at)
.bind(run_id)
.execute(pool)
.await?;
Ok(())
Ok(result.rows_affected() > 0)
}
/// 返回是否真的更新到了运行记录。
pub async fn complete(
pool: &SqlitePool,
run_id: i64,
@@ -141,8 +143,8 @@ pub async fn complete(
duration_ms: u64,
exit_code: Option<i32>,
error_message: Option<&str>,
) -> ApiResult<()> {
sqlx::query(
) -> ApiResult<bool> {
let result = sqlx::query(
"UPDATE task_runs SET
status = ?, finished_at = ?, duration_ms = ?,
exit_code = ?, error_message = ?
@@ -156,7 +158,22 @@ pub async fn complete(
.bind(run_id)
.execute(pool)
.await?;
Ok(())
Ok(result.rows_affected() > 0)
}
/// 启动时收敛上一次进程遗留下来的非终态运行,避免界面永远显示"运行中"。
pub async fn fail_interrupted_runs(pool: &SqlitePool) -> ApiResult<u64> {
let result = sqlx::query(
"UPDATE task_runs SET status = ?, finished_at = ?, error_message = ?
WHERE status IN ('queued', 'running')",
)
.bind(RunStatus::Cancelled.as_str())
.bind(Utc::now())
.bind("TaskPilot 进程重启,运行被中断")
.execute(pool)
.await?;
Ok(result.rows_affected())
}
pub async fn get(pool: &SqlitePool, run_id: i64) -> ApiResult<Option<TaskRunResponse>> {