from datetime import datetime from fastapi import FastAPI from fastapi.concurrency import asynccontextmanager from config.settings import settings from scheduler import job_story_portal from utils.logger import logger from scheduler.scheduler import scheduler import scheduler.jobs as jobs from config.app import create_app """ 启动方式: python -m uvicorn main:app --reload 说明: - 使用 FastAPI lifespan 管理 APScheduler 生命周期(替代已废弃的 on_event) - 避免 Uvicorn reload 模式下调度器重复启动 - 所有 Job 在应用启动时统一注册 """ def _add_jobs(): """ 注册所有定时任务。 注意: - 增加重复检查,避免 reload 或多进程导致重复添加。 - replace_existing=True 保证任务更新时无需手动删除。 """ if not scheduler.get_job("heartbeat-job"): scheduler.add_job( jobs.job_heartbeat, trigger="interval", seconds=10, id="heartbeat-job", replace_existing=True, ) logger.info("Job 'heartbeat-job' registered.") else: logger.info("Job 'heartbeat-job' already exists. Skipped.") if not scheduler.get_job("generate-daily-article-job"): scheduler.add_job( job_story_portal.job_generate_daily_article, trigger="interval", seconds=86400, # 每天运行一次 id="generate-daily-article-job", replace_existing=True, next_run_time=datetime.now(), # 启动即执行一次 ) logger.info("Job 'generate-daily-article-job' registered.") else: logger.info("Job 'generate-daily-article-job' already exists. Skipped.") @asynccontextmanager async def lifespan(app: FastAPI): """ FastAPI 应用生命周期管理。 startup: - 注册定时任务 - 启动 APScheduler(避免 reload 环境下重复启动) shutdown: - 安全关闭 APScheduler yield: - 让 FastAPI 在此期间正常运行 """ logger.info("==== Lifespan: startup ====") # 防止 Uvicorn reload 启动两个进程导致重复启动 scheduler if scheduler.running: logger.warning("Scheduler already running. Skip start.") else: _add_jobs() try: scheduler.start() logger.info("APScheduler started.") except Exception: logger.exception("Failed to start APScheduler:") raise yield # ---- 应用运行中 ---- logger.info("==== Lifespan: shutdown ====") # 仅在 scheduler 正常运行时关闭 if scheduler.running: scheduler.shutdown(wait=False) logger.info("APScheduler stopped.") else: logger.info("Scheduler was not running. Skip shutdown.") # 创建 FastAPI 应用(注入 lifespan 管理逻辑) app = create_app(lifespan=lifespan)