|
|
from contextlib import asynccontextmanager |
|
|
from pathlib import Path |
|
|
|
|
|
from fastapi import FastAPI |
|
|
from fastapi.staticfiles import StaticFiles |
|
|
from fastapi.templating import Jinja2Templates |
|
|
|
|
|
from app.config.config import settings, sync_initial_settings |
|
|
from app.database.connection import connect_to_db, disconnect_from_db |
|
|
from app.database.initialization import initialize_database |
|
|
from app.exception.exceptions import setup_exception_handlers |
|
|
from app.log.logger import get_application_logger, setup_access_logging |
|
|
from app.middleware.middleware import setup_middlewares |
|
|
from app.router.routes import setup_routers |
|
|
from app.scheduler.scheduled_tasks import start_scheduler, stop_scheduler |
|
|
from app.service.key.key_manager import get_key_manager_instance |
|
|
from app.service.update.update_service import check_for_updates |
|
|
from app.utils.helpers import get_current_version |
|
|
|
|
|
logger = get_application_logger() |
|
|
|
|
|
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent |
|
|
STATIC_DIR = PROJECT_ROOT / "app" / "static" |
|
|
TEMPLATES_DIR = PROJECT_ROOT / "app" / "templates" |
|
|
|
|
|
|
|
|
templates = Jinja2Templates(directory="app/templates") |
|
|
|
|
|
|
|
|
|
|
|
def update_template_globals(app: FastAPI, update_info: dict): |
|
|
|
|
|
|
|
|
|
|
|
app.state.update_info = update_info |
|
|
logger.info(f"Update info stored in app.state: {update_info}") |
|
|
|
|
|
|
|
|
|
|
|
async def _setup_database_and_config(app_settings): |
|
|
"""Initializes database, syncs settings, and initializes KeyManager.""" |
|
|
initialize_database() |
|
|
logger.info("Database initialized successfully") |
|
|
await connect_to_db() |
|
|
await sync_initial_settings() |
|
|
await get_key_manager_instance(app_settings.API_KEYS, app_settings.VERTEX_API_KEYS) |
|
|
logger.info("Database, config sync, and KeyManager initialized successfully") |
|
|
|
|
|
|
|
|
async def _shutdown_database(): |
|
|
"""Disconnects from the database.""" |
|
|
await disconnect_from_db() |
|
|
|
|
|
|
|
|
def _start_scheduler(): |
|
|
"""Starts the background scheduler.""" |
|
|
try: |
|
|
start_scheduler() |
|
|
logger.info("Scheduler started successfully.") |
|
|
except Exception as e: |
|
|
logger.error(f"Failed to start scheduler: {e}") |
|
|
|
|
|
|
|
|
def _stop_scheduler(): |
|
|
"""Stops the background scheduler.""" |
|
|
stop_scheduler() |
|
|
|
|
|
|
|
|
async def _perform_update_check(app: FastAPI): |
|
|
"""Checks for updates and stores the info in app.state.""" |
|
|
update_available, latest_version, error_message = await check_for_updates() |
|
|
current_version = get_current_version() |
|
|
update_info = { |
|
|
"update_available": update_available, |
|
|
"latest_version": latest_version, |
|
|
"error_message": error_message, |
|
|
"current_version": current_version, |
|
|
} |
|
|
if not hasattr(app, "state"): |
|
|
from starlette.datastructures import State |
|
|
|
|
|
app.state = State() |
|
|
app.state.update_info = update_info |
|
|
logger.info(f"Update check completed. Info: {update_info}") |
|
|
|
|
|
|
|
|
@asynccontextmanager |
|
|
async def lifespan(app: FastAPI): |
|
|
""" |
|
|
Manages the application startup and shutdown events. |
|
|
|
|
|
Args: |
|
|
app: FastAPI应用实例 |
|
|
""" |
|
|
logger.info("Application starting up...") |
|
|
try: |
|
|
await _setup_database_and_config(settings) |
|
|
await _perform_update_check(app) |
|
|
_start_scheduler() |
|
|
|
|
|
except Exception as e: |
|
|
logger.critical( |
|
|
f"Critical error during application startup: {str(e)}", exc_info=True |
|
|
) |
|
|
|
|
|
yield |
|
|
|
|
|
logger.info("Application shutting down...") |
|
|
_stop_scheduler() |
|
|
await _shutdown_database() |
|
|
|
|
|
|
|
|
def create_app() -> FastAPI: |
|
|
""" |
|
|
创建并配置FastAPI应用程序实例 |
|
|
|
|
|
Returns: |
|
|
FastAPI: 配置好的FastAPI应用程序实例 |
|
|
""" |
|
|
|
|
|
|
|
|
current_version = get_current_version() |
|
|
app = FastAPI( |
|
|
title="Gemini Balance API", |
|
|
description="Gemini API代理服务,支持负载均衡和密钥管理", |
|
|
version=current_version, |
|
|
lifespan=lifespan, |
|
|
) |
|
|
|
|
|
if not hasattr(app, "state"): |
|
|
from starlette.datastructures import State |
|
|
|
|
|
app.state = State() |
|
|
app.state.update_info = { |
|
|
"update_available": False, |
|
|
"latest_version": None, |
|
|
"error_message": "Initializing...", |
|
|
"current_version": current_version, |
|
|
} |
|
|
|
|
|
|
|
|
app.mount("/static", StaticFiles(directory=str(STATIC_DIR)), name="static") |
|
|
|
|
|
|
|
|
setup_middlewares(app) |
|
|
|
|
|
|
|
|
setup_exception_handlers(app) |
|
|
|
|
|
|
|
|
setup_routers(app) |
|
|
|
|
|
|
|
|
setup_access_logging() |
|
|
|
|
|
return app |
|
|
|