Files
video-gen/video-gen-api/app/tasks/scheduled_tasks.py
T

107 lines
3.5 KiB
Python

"""定时任务执行器。
仅支持内部方法执行类型,供 Celery Beat 定时调用。
配置 JSON 格式(示例):
{
"module": "app.services.bank.sync_service",
"function": "sync_bank_transactions",
"url": "http://...",
"api_key": "...",
"acct_no": "...",
"start_date": "2026-08-01",
"end_date": "2026-08-14"
}
其中 module 和 function 指定要调用的函数,其余字段作为 kwargs 传给该函数。
"""
import json
import logging
import time
from datetime import datetime, timezone
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_internal_method(config: str | None) -> dict:
"""执行内部方法调用。
配置 JSON 中 module 和 function 指定要调用的函数,其余字段作为 kwargs 传入。
"""
cfg = json.loads(config or "{}")
module_path = cfg.pop("module", "").strip()
function_name = cfg.pop("function", "").strip()
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}")
# 剩余字段作为 kwargs 传给函数
start = time.monotonic()
result = func(**cfg)
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 任务入口)。"""
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
try:
exec_result = _execute_internal_method(task_config)
_update_task_status(task_id, "success")
logger.info("定时任务执行成功: %s -> %s", task_id, exec_result)
except Exception as e:
error_msg = str(e)
_update_task_status(task_id, "error", error_msg)
logger.exception("定时任务执行失败: %s", task_id)
run_async(_run())