refactor: sync running-container source back to repo for reproducible docker compose build; remove dead code (orphan pipeline.py + 104 stale files from iterative dev) and drop unused app/ mirror dir
This commit is contained in:
1 parent
1346fbbd4b
commit
5d7f32db0a
138 files changed
+12846
-84248
No files matched your search
@@ -1,662 +0,0 @@
|
||||
"""
|
||||
自动化任务调度管理 API
|
||||
Phase 3.2: 支持定时任务(cron)和条件触发(webhook/event)的自动化调度
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Body, BackgroundTasks
|
||||
from sqlalchemy import or_, func, desc, asc, text
|
||||
from sqlalchemy.orm import Session
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime, timedelta
|
||||
import uuid
|
||||
import json
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
from ...database import get_db
|
||||
from ...models import (
|
||||
gen_uuid,
|
||||
)
|
||||
from ...auth import get_current_admin
|
||||
from ...middleware.log_service import LogService
|
||||
|
||||
router = APIRouter()
|
||||
logger = logging.getLogger("aiflow")
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Pydantic Models
|
||||
# ============================================================
|
||||
|
||||
class AutomationTaskCreate(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=200)
|
||||
description: Optional[str] = ""
|
||||
trigger_type: str = Field(default="cron", description="cron / webhook / event")
|
||||
cron_expression: Optional[str] = None # e.g. "*/5 * * * *"
|
||||
event_condition: Optional[Dict[str, Any]] = None # for event/webhook triggers
|
||||
action_type: str = Field(default="ai_pipeline", description="ai_pipeline / webhook / notification / skill_execute")
|
||||
action_config: Dict[str, Any] = Field(default_factory=dict)
|
||||
is_enabled: bool = True
|
||||
max_retries: int = Field(default=3, ge=0, le=10)
|
||||
timeout_seconds: int = Field(default=300, ge=10, le=3600)
|
||||
|
||||
|
||||
class AutomationTaskUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=200)
|
||||
description: Optional[str] = None
|
||||
trigger_type: Optional[str] = None
|
||||
cron_expression: Optional[str] = None
|
||||
event_condition: Optional[Dict[str, Any]] = None
|
||||
action_type: Optional[str] = None
|
||||
action_config: Optional[Dict[str, Any]] = None
|
||||
is_enabled: Optional[bool] = None
|
||||
max_retries: Optional[int] = Field(None, ge=0, le=10)
|
||||
timeout_seconds: Optional[int] = Field(None, ge=10, le=3600)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Helper Functions
|
||||
# ============================================================
|
||||
|
||||
TRIGGER_TYPE_MAP = {
|
||||
"cron": "定时触发",
|
||||
"webhook": "Webhook",
|
||||
"event": "事件触发",
|
||||
}
|
||||
|
||||
ACTION_TYPE_MAP = {
|
||||
"ai_pipeline": "AI流水线",
|
||||
"webhook": "Webhook回调",
|
||||
"notification": "发送通知",
|
||||
"skill_execute": "技能执行",
|
||||
}
|
||||
|
||||
|
||||
def task_to_dict(task) -> dict:
|
||||
"""将AutomatedTask模型转换为字典"""
|
||||
result = {
|
||||
"id": task.id,
|
||||
"name": task.name,
|
||||
"description": getattr(task, "description", "") or "",
|
||||
"trigger_type": task.trigger_type,
|
||||
"trigger_type_label": TRIGGER_TYPE_MAP.get(task.trigger_type, task.trigger_type),
|
||||
"cron_expression": getattr(task, "cron_expression", "") or "",
|
||||
"action_type": task.action_type,
|
||||
"action_type_label": ACTION_TYPE_MAP.get(task.action_type, task.action_type),
|
||||
"is_enabled": task.is_enabled,
|
||||
"status": task.status or "idle",
|
||||
"max_retries": getattr(task, "max_retries", 3),
|
||||
"timeout_seconds": getattr(task, "timeout_seconds", 300),
|
||||
"last_run_at": task.last_run_at.isoformat() if task.last_run_at else None,
|
||||
"next_run_at": task.next_run_at.isoformat() if task.next_run_at else None,
|
||||
"total_runs": getattr(task, "total_runs", 0),
|
||||
"success_runs": getattr(task, "success_runs", 0),
|
||||
"created_at": task.created_at.isoformat() if task.created_at else None,
|
||||
"updated_at": task.updated_at.isoformat() if task.updated_at else None,
|
||||
}
|
||||
|
||||
# Action config
|
||||
action_config = getattr(task, "action_config", None)
|
||||
if action_config:
|
||||
try:
|
||||
result["action_config"] = json.loads(action_config) if isinstance(action_config, str) else action_config
|
||||
except Exception:
|
||||
result["action_config"] = {}
|
||||
else:
|
||||
result["action_config"] = {}
|
||||
|
||||
# Event condition
|
||||
event_condition = getattr(task, "event_condition", None)
|
||||
if event_condition:
|
||||
try:
|
||||
result["event_condition"] = json.loads(event_condition) if isinstance(event_condition, str) else event_condition
|
||||
except Exception:
|
||||
result["event_condition"] = {}
|
||||
else:
|
||||
result["event_condition"] = {}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def log_to_dict(log) -> dict:
|
||||
"""将TaskExecutionLog转换为字典"""
|
||||
return {
|
||||
"id": log.id,
|
||||
"task_id": log.task_id,
|
||||
"task_name": getattr(log, "task_name", ""),
|
||||
"status": log.status,
|
||||
"trigger_type": getattr(log, "trigger_type", ""),
|
||||
"started_at": log.started_at.isoformat() if log.started_at else None,
|
||||
"finished_at": log.finished_at.isoformat() if log.finished_at else None,
|
||||
"duration_ms": getattr(log, "duration_ms", 0),
|
||||
"result_summary": getattr(log, "result_summary", "") or "",
|
||||
"error_message": getattr(log, "error_message", "") or "",
|
||||
"created_at": log.created_at.isoformat() if log.created_at else None,
|
||||
}
|
||||
|
||||
|
||||
def calculate_next_run(cron_expression: str) -> Optional[datetime]:
|
||||
"""计算cron表达式的下次执行时间(简化版)"""
|
||||
if not cron_expression:
|
||||
return None
|
||||
|
||||
parts = cron_expression.strip().split()
|
||||
if len(parts) != 5:
|
||||
return None
|
||||
|
||||
now = datetime.now()
|
||||
try:
|
||||
minute, hour, day, month, weekday = parts
|
||||
|
||||
# Handle wildcards
|
||||
next_run = now.replace(second=0, microsecond=0)
|
||||
|
||||
if minute != "*":
|
||||
target_min = int(minute)
|
||||
if target_min > now.minute:
|
||||
next_run = next_run.replace(minute=target_min)
|
||||
else:
|
||||
next_run = next_run.replace(minute=target_min) + timedelta(hours=1)
|
||||
|
||||
if hour != "*":
|
||||
target_hour = int(hour)
|
||||
if target_hour < next_run.hour or (target_hour == next_run.hour and next_run.minute <= now.minute):
|
||||
next_run = next_run.replace(hour=target_hour, minute=int(minute) if minute != "*" else 0) + timedelta(days=1)
|
||||
else:
|
||||
next_run = next_run.replace(hour=target_hour)
|
||||
|
||||
# Handle */N patterns for minute
|
||||
if minute.startswith("*/"):
|
||||
interval = int(minute[2:])
|
||||
next_minute = ((now.minute // interval) + 1) * interval
|
||||
if next_minute >= 60:
|
||||
next_run = next_run.replace(minute=0) + timedelta(hours=1)
|
||||
else:
|
||||
next_run = next_run.replace(minute=next_minute)
|
||||
if hour != "*":
|
||||
next_run = next_run.replace(hour=int(hour))
|
||||
|
||||
# Handle */N patterns for hour
|
||||
if hour.startswith("*/"):
|
||||
interval = int(hour[2:])
|
||||
next_hour = ((now.hour // interval) + 1) * interval
|
||||
if next_hour >= 24:
|
||||
next_run = next_run.replace(hour=0, minute=int(minute) if minute != "*" and not minute.startswith("*/") else 0) + timedelta(days=1)
|
||||
else:
|
||||
next_run = next_run.replace(hour=next_hour, minute=int(minute) if minute != "*" and not minute.startswith("*/") else 0)
|
||||
|
||||
if next_run <= now:
|
||||
next_run += timedelta(minutes=1)
|
||||
|
||||
return next_run
|
||||
except Exception as e:
|
||||
logger.warning(f"Failed to calculate next run for '{cron_expression}': {e}")
|
||||
return None
|
||||
|
||||
|
||||
# ============================================================
|
||||
# API Endpoints
|
||||
# ============================================================
|
||||
|
||||
@router.get("/stats/overview", summary="自动化任务统计概览")
|
||||
async def get_automation_stats(
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取自动化任务的统计信息"""
|
||||
from ...models import AutomatedTask, TaskExecutionLog
|
||||
|
||||
total_tasks = db.query(func.count(AutomatedTask.id)).scalar() or 0
|
||||
enabled_tasks = db.query(func.count(AutomatedTask.id)).filter(
|
||||
AutomatedTask.is_enabled == True
|
||||
).scalar() or 0
|
||||
running_tasks = db.query(func.count(AutomatedTask.id)).filter(
|
||||
AutomatedTask.status == "running"
|
||||
).scalar() or 0
|
||||
|
||||
# By trigger type
|
||||
by_trigger = {}
|
||||
triggers = db.query(AutomatedTask.trigger_type, func.count(AutomatedTask.id)).group_by(
|
||||
AutomatedTask.trigger_type
|
||||
).all()
|
||||
for t, c in triggers:
|
||||
by_trigger[t] = c
|
||||
|
||||
# Recent execution stats (last 24h)
|
||||
since = datetime.now() - timedelta(hours=24)
|
||||
total_executions = db.query(func.count(TaskExecutionLog.id)).filter(
|
||||
TaskExecutionLog.created_at >= since
|
||||
).scalar() or 0
|
||||
success_executions = db.query(func.count(TaskExecutionLog.id)).filter(
|
||||
TaskExecutionLog.created_at >= since,
|
||||
TaskExecutionLog.status == "success"
|
||||
).scalar() or 0
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": {
|
||||
"total_tasks": total_tasks,
|
||||
"enabled_tasks": enabled_tasks,
|
||||
"running_tasks": running_tasks,
|
||||
"by_trigger": by_trigger,
|
||||
"recent_executions": total_executions,
|
||||
"recent_success": success_executions,
|
||||
},
|
||||
"message": "ok"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/", summary="获取自动化任务列表")
|
||||
async def list_automation_tasks(
|
||||
trigger_type: str = Query(None),
|
||||
is_enabled: bool = Query(None),
|
||||
keyword: str = Query(None),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取自动化任务列表"""
|
||||
from ...models import AutomatedTask
|
||||
|
||||
query = db.query(AutomatedTask)
|
||||
|
||||
if trigger_type:
|
||||
query = query.filter(AutomatedTask.trigger_type == trigger_type)
|
||||
if is_enabled is not None:
|
||||
query = query.filter(AutomatedTask.is_enabled == is_enabled)
|
||||
if keyword:
|
||||
keyword_filter = or_(
|
||||
AutomatedTask.name.ilike(f"%{keyword}%"),
|
||||
AutomatedTask.description.ilike(f"%{keyword}%"),
|
||||
)
|
||||
query = query.filter(keyword_filter)
|
||||
|
||||
total = query.count()
|
||||
items = query.order_by(desc(AutomatedTask.updated_at)) \
|
||||
.offset((page - 1) * page_size).limit(page_size).all()
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": {
|
||||
"items": [task_to_dict(item) for item in items],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
},
|
||||
"message": "ok"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{task_id}", summary="获取任务详情")
|
||||
async def get_automation_task(
|
||||
task_id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取单个自动化任务详情"""
|
||||
from ...models import AutomatedTask
|
||||
|
||||
task = db.query(AutomatedTask).filter(AutomatedTask.id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": task_to_dict(task),
|
||||
"message": "ok"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/", summary="创建自动化任务")
|
||||
async def create_automation_task(
|
||||
req: AutomationTaskCreate,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""创建新的自动化任务"""
|
||||
from ...models import AutomatedTask
|
||||
|
||||
# 检查名称重复
|
||||
existing = db.query(AutomatedTask).filter(AutomatedTask.name == req.name).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="任务名称已存在")
|
||||
|
||||
# 验证cron表达式格式
|
||||
if req.trigger_type == "cron" and req.cron_expression:
|
||||
parts = req.cron_expression.strip().split()
|
||||
if len(parts) != 5:
|
||||
raise HTTPException(status_code=400, detail="Cron表达式格式错误,需要5个字段")
|
||||
|
||||
next_run = None
|
||||
if req.trigger_type == "cron" and req.cron_expression:
|
||||
next_run = calculate_next_run(req.cron_expression)
|
||||
|
||||
task = AutomatedTask(
|
||||
id=gen_uuid(),
|
||||
name=req.name,
|
||||
description=req.description or "",
|
||||
trigger_type=req.trigger_type,
|
||||
cron_expression=req.cron_expression,
|
||||
action_type=req.action_type,
|
||||
action_config=req.action_config,
|
||||
is_enabled=req.is_enabled,
|
||||
status="idle",
|
||||
max_retries=req.max_retries,
|
||||
timeout_seconds=req.timeout_seconds,
|
||||
next_run_at=next_run,
|
||||
event_condition=req.event_condition,
|
||||
total_runs=0,
|
||||
success_runs=0,
|
||||
)
|
||||
|
||||
db.add(task)
|
||||
db.commit()
|
||||
db.refresh(task)
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="create_automation_task",
|
||||
target_type="automated_task",
|
||||
target_id=task.id,
|
||||
detail=f"创建了自动化任务: {task.name}"
|
||||
)
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": task_to_dict(task),
|
||||
"message": "创建成功"
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{task_id}", summary="更新自动化任务")
|
||||
async def update_automation_task(
|
||||
task_id: str,
|
||||
req: AutomationTaskUpdate,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""更新自动化任务"""
|
||||
from ...models import AutomatedTask
|
||||
|
||||
task = db.query(AutomatedTask).filter(AutomatedTask.id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
|
||||
# 名称重复检查
|
||||
if req.name is not None and req.name != task.name:
|
||||
existing = db.query(AutomatedTask).filter(
|
||||
AutomatedTask.name == req.name,
|
||||
AutomatedTask.id != task_id
|
||||
).first()
|
||||
if existing:
|
||||
raise HTTPException(status_code=400, detail="任务名称已存在")
|
||||
|
||||
update_fields = ["name", "description", "trigger_type", "cron_expression",
|
||||
"action_type", "is_enabled", "max_retries", "timeout_seconds"]
|
||||
for field in update_fields:
|
||||
value = getattr(req, field, None)
|
||||
if value is not None:
|
||||
setattr(task, field, value)
|
||||
|
||||
if req.action_config is not None:
|
||||
task.action_config = req.action_config
|
||||
if req.event_condition is not None:
|
||||
task.event_condition = req.event_condition
|
||||
|
||||
# 重新计算下次执行时间
|
||||
if req.cron_expression is not None or req.trigger_type is not None:
|
||||
cron = req.cron_expression if req.cron_expression is not None else task.cron_expression
|
||||
trigger = req.trigger_type if req.trigger_type is not None else task.trigger_type
|
||||
if trigger == "cron" and cron:
|
||||
task.next_run_at = calculate_next_run(cron)
|
||||
|
||||
task.updated_at = datetime.now()
|
||||
|
||||
db.commit()
|
||||
db.refresh(task)
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="update_automation_task",
|
||||
target_type="automated_task",
|
||||
target_id=task.id,
|
||||
detail=f"更新了自动化任务: {task.name}"
|
||||
)
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": task_to_dict(task),
|
||||
"message": "更新成功"
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/{task_id}", summary="删除自动化任务")
|
||||
async def delete_automation_task(
|
||||
task_id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""删除自动化任务"""
|
||||
from ...models import AutomatedTask
|
||||
|
||||
task = db.query(AutomatedTask).filter(AutomatedTask.id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
|
||||
name = task.name
|
||||
db.delete(task)
|
||||
db.commit()
|
||||
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="delete_automation_task",
|
||||
target_type="automated_task",
|
||||
target_id=task_id,
|
||||
detail=f"删除了自动化任务: {name}"
|
||||
)
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": None,
|
||||
"message": f"已删除任务: {name}"
|
||||
}
|
||||
|
||||
|
||||
@router.put("/{task_id}/toggle", summary="启用/禁用自动化任务")
|
||||
async def toggle_automation_task(
|
||||
task_id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""切换任务启用/禁用状态"""
|
||||
from ...models import AutomatedTask
|
||||
|
||||
task = db.query(AutomatedTask).filter(AutomatedTask.id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
|
||||
task.is_enabled = not task.is_enabled
|
||||
if not task.is_enabled:
|
||||
task.next_run_at = None
|
||||
elif task.trigger_type == "cron" and task.cron_expression:
|
||||
task.next_run_at = calculate_next_run(task.cron_expression)
|
||||
|
||||
task.updated_at = datetime.now()
|
||||
db.commit()
|
||||
db.refresh(task)
|
||||
|
||||
action = "启用" if task.is_enabled else "禁用"
|
||||
LogService.log_audit(
|
||||
admin_id=admin_user.id,
|
||||
admin_name=admin_user.username,
|
||||
action="toggle_automation_task",
|
||||
target_type="automated_task",
|
||||
target_id=task.id,
|
||||
detail=f"{action}了自动化任务: {task.name}"
|
||||
)
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": task_to_dict(task),
|
||||
"message": f"已{action}"
|
||||
}
|
||||
|
||||
|
||||
@router.post("/{task_id}/execute", summary="手动执行任务")
|
||||
async def execute_task_now(
|
||||
task_id: str,
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""立即手动执行一个自动化任务"""
|
||||
from ...models import AutomatedTask, TaskExecutionLog
|
||||
|
||||
task = db.query(AutomatedTask).filter(AutomatedTask.id == task_id).first()
|
||||
if not task:
|
||||
raise HTTPException(status_code=404, detail="任务不存在")
|
||||
|
||||
if task.status == "running":
|
||||
raise HTTPException(status_code=400, detail="任务正在执行中")
|
||||
|
||||
# 创建执行日志
|
||||
exec_log = TaskExecutionLog(
|
||||
id=gen_uuid(),
|
||||
task_id=task.id,
|
||||
task_name=task.name,
|
||||
status="running",
|
||||
trigger_type="manual",
|
||||
started_at=datetime.now(),
|
||||
)
|
||||
db.add(exec_log)
|
||||
|
||||
# 更新任务状态
|
||||
task.status = "running"
|
||||
task.total_runs = (task.total_runs or 0) + 1
|
||||
db.commit()
|
||||
db.refresh(exec_log)
|
||||
|
||||
# 异步执行(简化版: 直接模拟执行)
|
||||
import time
|
||||
start = time.time()
|
||||
|
||||
try:
|
||||
action_type = task.action_type
|
||||
action_config = task.action_config or {}
|
||||
|
||||
# 模拟执行不同类型的操作
|
||||
result_summary = ""
|
||||
if action_type == "ai_pipeline":
|
||||
result_summary = f"AI流水线执行完成: 处理了 {action_config.get('pipeline_name', 'default')} 管道"
|
||||
elif action_type == "webhook":
|
||||
url = action_config.get("url", "")
|
||||
result_summary = f"Webhook调用完成: {url}"
|
||||
elif action_type == "notification":
|
||||
result_summary = f"通知发送完成: {action_config.get('channel', 'system')}"
|
||||
elif action_type == "skill_execute":
|
||||
result_summary = f"技能执行完成: {action_config.get('skill_name', 'unknown')}"
|
||||
else:
|
||||
result_summary = f"任务执行完成: {action_type}"
|
||||
|
||||
duration = int((time.time() - start) * 1000)
|
||||
|
||||
# 更新执行日志
|
||||
exec_log.status = "success"
|
||||
exec_log.finished_at = datetime.now()
|
||||
exec_log.duration_ms = duration
|
||||
exec_log.result_summary = result_summary
|
||||
|
||||
# 更新任务
|
||||
task.status = "idle"
|
||||
task.last_run_at = datetime.now()
|
||||
task.success_runs = (task.success_runs or 0) + 1
|
||||
if task.trigger_type == "cron" and task.cron_expression:
|
||||
task.next_run_at = calculate_next_run(task.cron_expression)
|
||||
|
||||
db.commit()
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": log_to_dict(exec_log),
|
||||
"message": "执行成功"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
duration = int((time.time() - start) * 1000)
|
||||
exec_log.status = "failed"
|
||||
exec_log.finished_at = datetime.now()
|
||||
exec_log.duration_ms = duration
|
||||
exec_log.error_message = str(e)
|
||||
task.status = "idle"
|
||||
db.commit()
|
||||
|
||||
raise HTTPException(status_code=500, detail=f"执行失败: {str(e)}")
|
||||
|
||||
|
||||
@router.get("/{task_id}/history", summary="获取任务执行历史")
|
||||
async def get_task_history(
|
||||
task_id: str,
|
||||
status: str = Query(None),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取任务的执行历史记录"""
|
||||
from ...models import TaskExecutionLog
|
||||
|
||||
query = db.query(TaskExecutionLog).filter(TaskExecutionLog.task_id == task_id)
|
||||
if status:
|
||||
query = query.filter(TaskExecutionLog.status == status)
|
||||
|
||||
total = query.count()
|
||||
items = query.order_by(desc(TaskExecutionLog.created_at)) \
|
||||
.offset((page - 1) * page_size).limit(page_size).all()
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": {
|
||||
"items": [log_to_dict(item) for item in items],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
},
|
||||
"message": "ok"
|
||||
}
|
||||
|
||||
|
||||
@router.get("/history/all", summary="获取所有执行历史")
|
||||
async def get_all_history(
|
||||
status: str = Query(None),
|
||||
task_id: str = Query(None),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
admin_user=Depends(get_current_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
"""获取所有任务的执行历史"""
|
||||
from ...models import TaskExecutionLog
|
||||
|
||||
query = db.query(TaskExecutionLog)
|
||||
if status:
|
||||
query = query.filter(TaskExecutionLog.status == status)
|
||||
if task_id:
|
||||
query = query.filter(TaskExecutionLog.task_id == task_id)
|
||||
|
||||
total = query.count()
|
||||
items = query.order_by(desc(TaskExecutionLog.created_at)) \
|
||||
.offset((page - 1) * page_size).limit(page_size).all()
|
||||
|
||||
return {
|
||||
"code": 0,
|
||||
"data": {
|
||||
"items": [log_to_dict(item) for item in items],
|
||||
"total": total,
|
||||
"page": page,
|
||||
"page_size": page_size,
|
||||
},
|
||||
"message": "ok"
|
||||
}
|
||||
Reference in new issue
Block a user