import project
All checks were successful
Gitea Actions Demo / deploy (push) Successful in 35s

This commit is contained in:
2025-11-20 21:41:13 +08:00
commit 32d15e8c92
21 changed files with 401 additions and 0 deletions

84
main.py Normal file
View File

@ -0,0 +1,84 @@
from fastapi import FastAPI
from fastapi.concurrency import asynccontextmanager
from config.settings import settings
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.")
@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)