""" 公司定时调度服务 — 基于 APScheduler 周期执行公司项目 """ from __future__ import annotations import logging from datetime import datetime from typing import Optional from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.triggers.cron import CronTrigger from sqlalchemy.orm import Session from app.models.company_schedule import CompanySchedule logger = logging.getLogger(__name__) # Global scheduler instance _scheduler: Optional[BackgroundScheduler] = None def get_scheduler() -> BackgroundScheduler: """获取全局调度器实例(懒初始化)。""" global _scheduler if _scheduler is None: _scheduler = BackgroundScheduler() _scheduler.start() logger.info("CompanyScheduler started") return _scheduler def load_schedules(db: Session): """从数据库加载所有启用的调度并注册到调度器。""" sched = get_scheduler() schedules = db.query(CompanySchedule).filter(CompanySchedule.enabled.is_(True)).all() for cs in schedules: register_schedule(cs) logger.info("Loaded %d company schedules from database", len(schedules)) def register_schedule(cs: CompanySchedule): """将单个调度注册到 APScheduler。""" if not cs.cron_expression: logger.warning("Schedule %s has no cron expression, skipping", cs.id) return sched = get_scheduler() job_id = f"company_schedule_{cs.id}" # Remove existing job if any if sched.get_job(job_id): sched.remove_job(job_id) try: trigger = CronTrigger.from_crontab(cs.cron_expression) sched.add_job( _execute_scheduled_project, trigger=trigger, id=job_id, args=[cs.id], name=cs.name, replace_existing=True, ) # Update next run time job = sched.get_job(job_id) if job and job.next_run_time: cs.next_run_at = job.next_run_time logger.info("Registered schedule: %s (cron: %s, next: %s)", cs.name, cs.cron_expression, cs.next_run_at) except Exception as e: logger.error("Failed to register schedule %s: %s", cs.id, e) def unregister_schedule(schedule_id: str): """从调度器移除单个调度。""" sched = get_scheduler() job_id = f"company_schedule_{schedule_id}" if sched.get_job(job_id): sched.remove_job(job_id) logger.info("Unregistered schedule: %s", schedule_id) def shutdown_scheduler(): """关闭调度器。""" global _scheduler if _scheduler: _scheduler.shutdown(wait=False) _scheduler = None logger.info("CompanyScheduler shut down") async def _execute_scheduled_project(schedule_id: str): """调度器回调:执行公司的定时项目。""" from app.core.database import SessionLocal from app.services.company_orchestrator import CompanyOrchestrator db = SessionLocal() try: cs = db.query(CompanySchedule).filter(CompanySchedule.id == schedule_id).first() if not cs or not cs.enabled: return logger.info("CompanyScheduler: Executing schedule '%s' for company %s", cs.name, cs.company_id) orchestrator = CompanyOrchestrator(db, cs.company_id, "system") result = await orchestrator.execute(cs.project_goal) cs.last_run_at = datetime.utcnow() db.commit() logger.info("CompanyScheduler: Schedule '%s' completed, success=%s", cs.name, result.get("success")) except Exception as e: logger.error("CompanyScheduler: Schedule '%s' failed: %s", schedule_id, e) finally: db.close()