Files
aiagent/backend/app/services/scheduler.py
renjianbo f2e65a8fbb feat: virtual company module, team projects, PWA dishes app, and startup scripts overhaul
- Add company module (3-tier org, CEO planning, parallel departments)
- Add company orchestrator, knowledge extractor, presets, scheduler
- Add company API endpoints, models, and frontend views
- Add 今天吃啥 PWA app (69 dishes, real images, offline support)
- Add team_projects output directory structure
- Add unified manage.ps1 for service lifecycle
- Add Windows startup guide v1.0
- Add TTS troubleshooting doc
- Update frontend (AgentChat UX overhaul, new views)
- Update backend (voice engine fix, multi-tenant, RBAC)
- Remove deprecated startup scripts and old docs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-10 22:37:56 +08:00

116 lines
3.6 KiB
Python

"""
公司定时调度服务 — 基于 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()