""" 调度服务 - APScheduler 管理定时采集任务 """ import logging from datetime import datetime from typing import Dict, Optional from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.interval import IntervalTrigger from apscheduler.executors.pool import ThreadPoolExecutor from sqlalchemy.orm import Session from app.database import SessionLocal from app.services.collector import fetch_symbol_data from app.services.cache import save_market_data, update_task_status from app.config import SCHEDULER_MAX_INSTANCES, MAX_WORKERS logger = logging.getLogger(__name__) scheduler = BackgroundScheduler( executors={"default": ThreadPoolExecutor(max_workers=MAX_WORKERS)}, job_defaults={ "max_instances": SCHEDULER_MAX_INSTANCES, "misfire_grace_time": 60, }, ) def job_handler(task_id: int): """ 定时任务的执行函数。 每个任务独立创建 DB session,避免跨线程问题。 """ db: Session = SessionLocal() try: from app.services.cache import get_task task = get_task(db, task_id) if not task or not task.enabled: logger.warning(f"任务 {task_id} 不存在或已禁用,停止执行") return periods = task.periods.split(",") if task.periods else [] logger.info(f"[定时任务] 开始采集 {task.symbol} (periods={periods})") result = fetch_symbol_data( symbol=task.symbol, data_type=task.data_type, periods=periods, max_workers=MAX_WORKERS, ) if result.get("timeframes"): save_market_data(db, task.symbol, result) update_task_status(db, task_id, "success") logger.info(f"[定时任务] {task.symbol} 采集成功") else: update_task_status(db, task_id, "failed") logger.error(f"[定时任务] {task.symbol} 采集失败: {result.get('error')}") except Exception as e: logger.error(f"[定时任务] 执行异常 task_id={task_id}: {e}") try: update_task_status(db, task_id, "failed") except Exception: pass finally: db.close() def start_scheduler(): """启动调度器""" if not scheduler.running: scheduler.start() logger.info("调度器已启动") def stop_scheduler(): """停止调度器""" if scheduler.running: scheduler.shutdown(wait=False) logger.info("调度器已停止") def add_job(task_id: int, interval_seconds: int) -> str: """ 添加定时任务到调度器。 Returns: job_id """ job_id = f"task_{task_id}" # 如果已存在,先移除 if scheduler.get_job(job_id): scheduler.remove_job(job_id) scheduler.add_job( func=job_handler, trigger=IntervalTrigger(seconds=interval_seconds), args=[task_id], id=job_id, name=f"auto_collect_{task_id}", replace_existing=True, ) logger.info(f"已添加定时任务: job_id={job_id}, interval={interval_seconds}s") return job_id def remove_job(task_id: int) -> bool: """移除定时任务""" job_id = f"task_{task_id}" job = scheduler.get_job(job_id) if job: scheduler.remove_job(job_id) logger.info(f"已移除定时任务: {job_id}") return True return False def is_job_running(task_id: int) -> bool: """检查任务是否正在调度器中运行""" job_id = f"task_{task_id}" return scheduler.get_job(job_id) is not None def get_all_jobs() -> Dict[str, dict]: """获取所有活跃任务信息""" jobs = scheduler.get_jobs() result = {} for job in jobs: nrt = getattr(job, 'next_run_time', None) result[job.id] = { "name": job.name, "next_run_time": nrt.isoformat() if nrt else None, "trigger": str(job.trigger), } return result