Files
planet/backend/app/main.py
rayd1o 5bf5c73ca0
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / delivery (push) Has been cancelled
release / images (push) Has been cancelled
release: bump version to 0.66.0
2026-05-26 03:41:47 +08:00

131 lines
4.1 KiB
Python

from contextlib import asynccontextmanager
from pathlib import Path
from uuid import uuid4
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from starlette.middleware.base import BaseHTTPMiddleware
from app.api.main import api_router
from app.api.v1 import websocket
from app.core.config import settings
from app.core.logging import configure_logging
from app.core.request_context import set_request_id
from app.core.websocket.broadcaster import broadcaster
from app.db.session import init_db
from app.services.scheduler import (
cleanup_stale_running_tasks,
start_scheduler,
stop_scheduler,
sync_scheduler_with_datasources,
)
from app.services.earth_news_worker import (
start_earth_news_target_worker,
stop_earth_news_target_worker,
)
from app.services.earth_db_change_listener import (
start_earth_db_change_listener,
stop_earth_db_change_listener,
)
from app.services.data_jobs import start_data_job_worker, stop_data_job_worker
configure_logging()
class WebSocketCORSMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
if request.url.path.startswith("/ws") and request.method == "GET":
response = await call_next(request)
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "GET, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "*"
return response
return await call_next(request)
class RequestContextMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
request_id = request.headers.get("X-Request-ID") or uuid4().hex
set_request_id(request_id)
try:
response = await call_next(request)
finally:
set_request_id(None)
response.headers["X-Request-ID"] = request_id
return response
@asynccontextmanager
async def lifespan(app: FastAPI):
await init_db()
await cleanup_stale_running_tasks()
start_scheduler()
await sync_scheduler_with_datasources()
broadcaster.start()
start_data_job_worker()
start_earth_db_change_listener()
start_earth_news_target_worker()
yield
await stop_earth_news_target_worker()
await stop_earth_db_change_listener()
await stop_data_job_worker()
broadcaster.stop()
stop_scheduler()
app = FastAPI(
title=settings.PROJECT_NAME,
version=settings.VERSION,
description="智能星球计划 - 态势感知系统\n\n## 功能模块\n\n- **用户认证**: JWT-based authentication\n- **数据源管理**: 多源数据采集器管理\n- **任务调度**: 定时任务调度与监控\n- **实时更新**: WebSocket实时数据推送\n- **告警系统**: 多级告警管理\n\n## 数据层级\n\n- **L1**: 核心数据 (TOP500, Epoch AI GPU)\n- **L2**: 扩展数据 (HuggingFace, PeeringDB, 海缆)\n- **L3**: 分析数据\n- **L4**: 决策支持",
lifespan=lifespan,
docs_url=None,
redoc_url="/docs",
openapi_url="/openapi.json",
)
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.add_middleware(RequestContextMiddleware)
app.add_middleware(WebSocketCORSMiddleware)
app.include_router(api_router, prefix="/api/v1")
app.include_router(websocket.router)
EARTH_BRAND_ASSET_DIR = Path(__file__).resolve().parents[2] / "data" / "earth-brand"
EARTH_BRAND_ASSET_DIR.mkdir(parents=True, exist_ok=True)
app.mount(
"/earth-brand-assets",
StaticFiles(directory=str(EARTH_BRAND_ASSET_DIR)),
name="earth-brand-assets",
)
@app.get("/health")
async def health_check():
return {"status": "healthy", "version": settings.VERSION}
@app.get("/")
async def root():
return {
"name": settings.PROJECT_NAME,
"version": settings.VERSION,
"docs": "/docs",
"redoc": "/redoc",
}
@app.get("/api/v1/scheduler/jobs")
async def get_scheduler_jobs():
from app.services.scheduler import get_scheduler_jobs
return {"jobs": get_scheduler_jobs()}