"""定时任务管理后台路由。""" import json from fastapi import APIRouter, Depends, HTTPException, Path from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_admin_user, get_db from app.models.scheduled_task import ScheduledTask from app.models.user import User from app.utils.id_gen import generate_id router = APIRouter(prefix="/admin/scheduled-tasks", tags=["admin-scheduled-tasks"]) def _task_to_dict(task: ScheduledTask) -> dict: return { "id": task.id, "name": task.name, "task_type": task.task_type, "schedule": task.schedule, "config": task.config, "is_active": task.is_active, "last_run_at": task.last_run_at, "last_status": task.last_status, "last_error": task.last_error, "created_by": task.created_by, "created_at": task.created_at.isoformat() if task.created_at else None, "updated_at": task.updated_at.isoformat() if task.updated_at else None, } @router.get("") async def list_tasks( admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): """列出所有定时任务。""" result = await db.execute(select(ScheduledTask).order_by(ScheduledTask.created_at.desc())) tasks = result.scalars().all() return {"items": [_task_to_dict(t) for t in tasks]} @router.post("") async def create_task( body: dict, admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): """创建定时任务。""" name = (body.get("name") or "").strip() task_type = (body.get("task_type") or "internal_method").strip() schedule = (body.get("schedule") or "").strip() if not name or not schedule: raise HTTPException(status_code=400, detail="任务名称、调度表达式不能为空") if task_type != "internal_method": raise HTTPException(status_code=400, detail="任务类型仅支持 internal_method") config = body.get("config") if isinstance(config, dict): config = json.dumps(config, ensure_ascii=False) elif isinstance(config, str): # 验证 JSON 合法性 try: json.loads(config) except json.JSONDecodeError: raise HTTPException(status_code=400, detail="config 不是合法的 JSON") task = ScheduledTask( id=generate_id(), name=name, task_type=task_type, schedule=schedule, config=config, is_active=bool(body.get("is_active", True)), created_by=admin.id, ) db.add(task) await db.commit() return {"id": task.id, "message": "创建成功"} @router.put("/{task_id}") async def update_task( task_id: str = Path(..., description="任务 ID"), body: dict = ..., admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): """更新定时任务。""" result = await db.execute(select(ScheduledTask).where(ScheduledTask.id == task_id)) task = result.scalar_one_or_none() if task is None: raise HTTPException(status_code=404, detail="任务不存在") if "name" in body: task.name = str(body["name"]).strip() if "task_type" in body: t = body["task_type"] if t != "internal_method": raise HTTPException(status_code=400, detail="任务类型仅支持 internal_method") task.task_type = t if "schedule" in body: task.schedule = str(body["schedule"]).strip() if "is_active" in body: task.is_active = bool(body["is_active"]) if "config" in body: config = body["config"] if isinstance(config, dict): config = json.dumps(config, ensure_ascii=False) elif isinstance(config, str): try: json.loads(config) except json.JSONDecodeError: raise HTTPException(status_code=400, detail="config 不是合法的 JSON") task.config = config await db.commit() return {"message": "更新成功"} @router.delete("/{task_id}") async def delete_task( task_id: str = Path(..., description="任务 ID"), admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): """删除定时任务。""" result = await db.execute(select(ScheduledTask).where(ScheduledTask.id == task_id)) task = result.scalar_one_or_none() if task is None: raise HTTPException(status_code=404, detail="任务不存在") await db.delete(task) await db.commit() return {"message": "删除成功"} @router.post("/{task_id}/run") async def run_task( task_id: str = Path(..., description="任务 ID"), admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): """手动执行一次定时任务。""" result = await db.execute(select(ScheduledTask).where(ScheduledTask.id == task_id)) task = result.scalar_one_or_none() if task is None: raise HTTPException(status_code=404, detail="任务不存在") from app.tasks.scheduled_tasks import execute_scheduled_task execute_scheduled_task.apply_async(args=[task_id]) return {"message": "任务已提交执行"} @router.post("/{task_id}/toggle") async def toggle_task( task_id: str = Path(..., description="任务 ID"), admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): """启用/禁用定时任务。""" result = await db.execute(select(ScheduledTask).where(ScheduledTask.id == task_id)) task = result.scalar_one_or_none() if task is None: raise HTTPException(status_code=404, detail="任务不存在") task.is_active = not task.is_active await db.commit() return {"is_active": task.is_active, "message": "已启用" if task.is_active else "已禁用"}