完善后台任务中心历史摘要视图

- 为系统任务和插件调度补充批量历史摘要查询,支持最近成功时间、最近失败原因与累计成功失败次数

- 任务列表接口合并内存运行态与数据库日志态,服务重启后后台仍可回看最近执行结果

- 系统任务页与插件调度页新增健康状态、历史执行摘要与插件调度快捷启停入口

- 更新工程优化文档,记录 7.3 第一阶段当前进展
This commit is contained in:
liuwei
2026-04-30 16:21:29 +08:00
parent 0d7fe5d6f0
commit 1db8681636
8 changed files with 496 additions and 7 deletions

View File

@@ -209,6 +209,47 @@ class PluginScheduleManager:
return False
return latest_log_at < (expected_at - timedelta(seconds=self._compensation_tolerance_seconds))
@staticmethod
def _build_schedule_health_status(
*,
enabled: bool,
running: bool,
last_status: str,
latest_success_at,
latest_failure_summary: str,
) -> str:
"""根据调度任务运行态和历史态生成后台健康标签。"""
if not enabled:
return "disabled"
if running:
return "running"
# 只有“最近一次执行仍是失败”时才把健康态打成 failed
# 避免历史上曾失败过、但后面已经恢复成功的任务一直显示异常。
if str(last_status or "").strip().lower() == "failed":
return "failed"
if latest_success_at or str(last_status or "").strip().lower() == "success":
return "healthy"
if str(latest_failure_summary or "").strip():
return "degraded"
return "idle"
@staticmethod
def _build_schedule_health_message(*, health_status: str, latest_success_at, latest_failure_summary: str) -> str:
"""生成调度任务列表里展示的简短健康说明。"""
if health_status == "disabled":
return "任务已停用"
if health_status == "running":
return "任务正在执行中"
if health_status in ("failed", "degraded"):
return str(latest_failure_summary or "最近存在失败记录").strip()
if health_status == "healthy":
if isinstance(latest_success_at, datetime):
return f"最近成功于 {latest_success_at.strftime('%Y-%m-%d %H:%M:%S')}"
if latest_success_at:
return f"最近成功于 {latest_success_at}"
return "任务近期执行正常"
return "暂无执行记录"
async def _run_one_schedule(self, schedule_row: Dict[str, Any]) -> Dict[str, Any]:
schedule_id = int(schedule_row["id"])
action_key = schedule_row.get("action_key")
@@ -297,6 +338,7 @@ class PluginScheduleManager:
# 日志兜底:进程重启后内存态 last_run_at 会丢失,任务页需要从数据库最新日志恢复显示。
schedule_ids = [int(row.get("id")) for row in db_rows if row.get("id") is not None]
latest_log_by_schedule = self.db.get_latest_logs_map(schedule_ids)
history_summary_by_schedule = self.db.get_schedule_history_summary_map(schedule_ids)
data = []
for row in db_rows:
@@ -304,6 +346,7 @@ class PluginScheduleManager:
key = f"plugin_schedule:{schedule_id}"
runtime = runtime_by_key.get(key, {})
latest_log = latest_log_by_schedule.get(schedule_id) or {}
history_summary = history_summary_by_schedule.get(schedule_id) or {}
merged = dict(row)
merged["runtime_job_id"] = runtime.get("id")
merged["running"] = runtime.get("running", False)
@@ -319,6 +362,24 @@ class PluginScheduleManager:
merged["run_count"] = runtime.get("run_count", 0)
merged["success_count"] = runtime.get("success_count", 0)
merged["fail_count"] = runtime.get("fail_count", 0)
merged["latest_success_at"] = history_summary.get("latest_success_at")
merged["latest_failed_at"] = history_summary.get("latest_failed_at")
merged["latest_failure_summary"] = str(history_summary.get("latest_failure_summary") or "").strip()
merged["history_success_count"] = int(history_summary.get("history_success_count", 0) or 0)
merged["history_fail_count"] = int(history_summary.get("history_fail_count", 0) or 0)
merged["history_total_count"] = int(history_summary.get("history_total_count", 0) or 0)
merged["health_status"] = self._build_schedule_health_status(
enabled=bool(row.get("enabled", 0)),
running=bool(runtime.get("running", False)),
last_status=str(merged.get("last_status") or ""),
latest_success_at=history_summary.get("latest_success_at"),
latest_failure_summary=str(history_summary.get("latest_failure_summary") or ""),
)
merged["health_message"] = self._build_schedule_health_message(
health_status=merged["health_status"],
latest_success_at=history_summary.get("latest_success_at"),
latest_failure_summary=str(history_summary.get("latest_failure_summary") or ""),
)
data.append(merged)
return data