139 lines
4.8 KiB
Python
139 lines
4.8 KiB
Python
"""定时任务执行器。
|
|
|
|
支持两种任务类型:
|
|
- external_api: 调用外部 HTTP 接口
|
|
- internal_method: 动态导入并执行内部函数
|
|
|
|
任务在 Celery Worker 中执行,通过 run_async 桥接异步操作。
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import time
|
|
from datetime import datetime, timezone
|
|
|
|
import httpx
|
|
from sqlalchemy import select
|
|
|
|
from app.models.base import async_session
|
|
from app.models.scheduled_task import ScheduledTask
|
|
from app.tasks.async_runner import run_async
|
|
from app.tasks.celery_app import celery_app
|
|
|
|
logger = logging.getLogger("video_gen")
|
|
|
|
|
|
def _update_task_status(task_id: str, status: str, error_msg: str | None = None) -> None:
|
|
"""更新任务最后执行状态。"""
|
|
|
|
async def _do():
|
|
async with async_session() as db:
|
|
result = await db.execute(select(ScheduledTask).where(ScheduledTask.id == task_id))
|
|
task = result.scalar_one_or_none()
|
|
if task is None:
|
|
return
|
|
task.last_run_at = datetime.now(timezone.utc).isoformat()
|
|
task.last_status = status
|
|
task.last_error = error_msg
|
|
await db.commit()
|
|
|
|
run_async(_do())
|
|
|
|
|
|
def _execute_external_api(config: str | None) -> dict:
|
|
"""执行外部 API 调用。"""
|
|
cfg = json.loads(config or "{}")
|
|
url = cfg.get("url", "").strip()
|
|
method = cfg.get("method", "GET").upper()
|
|
headers = cfg.get("headers") or {}
|
|
payload = cfg.get("payload")
|
|
timeout_val = float(cfg.get("timeout", 30))
|
|
|
|
if not url:
|
|
raise ValueError("外部接口地址 (url) 未配置")
|
|
|
|
start = time.monotonic()
|
|
try:
|
|
with httpx.Client(timeout=httpx.Timeout(timeout_val, connect=10.0)) as client:
|
|
if method == "GET":
|
|
resp = client.get(url, headers=headers, params=payload)
|
|
elif method == "DELETE":
|
|
resp = client.delete(url, headers=headers, params=payload)
|
|
else:
|
|
resp = client.request(method, url, headers=headers, json=payload)
|
|
duration_ms = int((time.monotonic() - start) * 1000)
|
|
resp.raise_for_status()
|
|
return {
|
|
"status_code": resp.status_code,
|
|
"duration_ms": duration_ms,
|
|
"body": resp.text[:2000],
|
|
}
|
|
except httpx.HTTPError as e:
|
|
duration_ms = int((time.monotonic() - start) * 1000)
|
|
raise RuntimeError(f"外部接口请求失败: {e} (耗时 {duration_ms}ms)") from e
|
|
|
|
|
|
def _execute_internal_method(config: str | None) -> dict:
|
|
"""执行内部方法调用。"""
|
|
cfg = json.loads(config or "{}")
|
|
module_path = cfg.get("module", "").strip()
|
|
function_name = cfg.get("function", "").strip()
|
|
args = cfg.get("args") or []
|
|
|
|
if not module_path or not function_name:
|
|
raise ValueError("内部方法需要指定 module 和 function")
|
|
|
|
import importlib
|
|
|
|
module = importlib.import_module(module_path)
|
|
func = getattr(module, function_name, None)
|
|
if func is None or not callable(func):
|
|
raise ValueError(f"模块 {module_path} 中不存在可调用函数 {function_name}")
|
|
|
|
start = time.monotonic()
|
|
result = func(*args)
|
|
duration_ms = int((time.monotonic() - start) * 1000)
|
|
return {
|
|
"duration_ms": duration_ms,
|
|
"result": str(result)[:1000] if result is not None else None,
|
|
}
|
|
|
|
|
|
@celery_app.task(name="execute_scheduled_task", bind=True, ignore_result=True) # type: ignore[call-arg]
|
|
def execute_scheduled_task(self, task_id: str):
|
|
"""执行定时任务(Celery 任务入口)。
|
|
|
|
通过 run_async 桥接到异步上下文读取任务配置并执行。
|
|
"""
|
|
|
|
async def _run():
|
|
async with async_session() as db:
|
|
result = await db.execute(select(ScheduledTask).where(ScheduledTask.id == task_id))
|
|
task = result.scalar_one_or_none()
|
|
if task is None:
|
|
logger.warning("定时任务不存在: %s", task_id)
|
|
return
|
|
if not task.is_active:
|
|
logger.info("定时任务已禁用,跳过执行: %s", task_id)
|
|
return
|
|
|
|
task_config = task.config
|
|
task_type = task.task_type
|
|
|
|
try:
|
|
if task_type == "external_api":
|
|
exec_result = _execute_external_api(task_config)
|
|
elif task_type == "internal_method":
|
|
exec_result = _execute_internal_method(task_config)
|
|
else:
|
|
raise ValueError(f"未知的任务类型: {task_type}")
|
|
|
|
_update_task_status(task_id, "success")
|
|
logger.info("定时任务执行成功: %s (%s) -> %s", task_id, task_type, exec_result)
|
|
except Exception as e:
|
|
error_msg = str(e)
|
|
_update_task_status(task_id, "error", error_msg)
|
|
logger.exception("定时任务执行失败: %s (%s)", task_id, task_type)
|
|
|
|
run_async(_run())
|