171 lines
5.7 KiB
Python
171 lines
5.7 KiB
Python
"""定时任务管理后台路由。"""
|
|
|
|
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 "已禁用"}
|