from __future__ import annotations from collections.abc import AsyncIterator from contextlib import asynccontextmanager from logging import Logger import threading from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.api.router import api_router from app.core.config import get_settings from app.core.logging import get_logger, setup_logging from app.core.openapi import API_DESCRIPTION, OPENAPI_TAGS from app.db.session import get_session_factory from app.middleware.logging import AccessLogMiddleware from app.schemas.common import RootStatusRead from app.services.agent_foundation import prepare_agent_foundation from app.services.digital_employee_reminder_scheduler import digital_employee_reminder_scheduler from app.services.employee import prepare_employee_directory from app.services.employee import EmployeeService from app.services.employee_profile_scheduler import employee_profile_scheduler from app.services.finance_dashboard_scheduler import finance_dashboard_scheduler from app.services.finance_report_scheduler import finance_report_scheduler from app.services.hermes_sync import sync_repository_hermes_skills from app.services.knowledge import prepare_knowledge_library from app.services.knowledge_index_tasks import knowledge_index_task_manager from app.services.knowledge_rag import shutdown_knowledge_rag_runtime from app.services.knowledge_scheduler import knowledge_index_scheduler from app.services.settings import SettingsService from app.services.user_session_metrics import UserSessionMetricService def _effective_server_workers(settings: object) -> int: server_workers = getattr(settings, "server_workers", None) web_concurrency = getattr(settings, "web_concurrency", None) workers = web_concurrency if int(server_workers or 1) <= 1 and web_concurrency else server_workers try: return max(1, int(workers or 1)) except (TypeError, ValueError): return 1 def _should_start_background_schedulers(settings: object) -> bool: if not bool(getattr(settings, "background_schedulers_enabled", True)): return False return _effective_server_workers(settings) <= 1 def _run_startup_bootstrap(logger: Logger) -> None: steps = ( ("employee_directory", prepare_employee_directory), ("agent_foundation", prepare_agent_foundation), ("knowledge_library", prepare_knowledge_library), ("hermes_skills", sync_repository_hermes_skills), ) for name, step in steps: try: step() except Exception: logger.exception("Startup bootstrap step failed; continuing degraded name=%s", name) def _warm_startup_caches(logger: Logger) -> None: try: session_factory = get_session_factory() with session_factory() as db: SettingsService(db).ensure_settings_ready() EmployeeService(db).ensure_directory_ready() UserSessionMetricService(db).ensure_storage_ready() logger.info("Startup cache warmup complete") except Exception: logger.exception("Startup cache warmup failed; continuing without warm cache") def _start_cache_warmup_thread(logger: Logger) -> None: thread = threading.Thread( target=_warm_startup_caches, args=(logger,), name="x-financial-startup-cache-warmup", daemon=True, ) thread.start() @asynccontextmanager async def lifespan(_: FastAPI) -> AsyncIterator[None]: settings = get_settings() logger = get_logger("app.main") if settings.startup_bootstrap_enabled: _run_startup_bootstrap(logger) else: logger.warning("Startup bootstrap skipped because STARTUP_BOOTSTRAP_ENABLED=false") if settings.startup_cache_warmup_enabled: _start_cache_warmup_thread(logger) schedulers_started = _should_start_background_schedulers(settings) if schedulers_started: knowledge_index_scheduler.start() finance_dashboard_scheduler.start() employee_profile_scheduler.start() digital_employee_reminder_scheduler.start() finance_report_scheduler.start() else: logger.warning( "Background schedulers skipped - workers=%s enabled=%s", _effective_server_workers(settings), settings.background_schedulers_enabled, ) logger.info( "Server ready - host=%s port=%s prefix=%s", settings.app_host, settings.app_port, settings.api_v1_prefix, ) yield if schedulers_started: finance_report_scheduler.shutdown() digital_employee_reminder_scheduler.shutdown() employee_profile_scheduler.shutdown() finance_dashboard_scheduler.shutdown() knowledge_index_scheduler.shutdown() knowledge_index_task_manager.shutdown() shutdown_knowledge_rag_runtime() def create_app() -> FastAPI: settings = get_settings() setup_logging( level=settings.log_level, log_dir=settings.log_dir, enable_file=settings.log_file_enabled, ) logger = get_logger("app.main") logger.info( "Starting %s (env=%s, debug=%s)", settings.app_name, settings.app_env, settings.app_debug ) app = FastAPI( title=settings.app_name, debug=settings.app_debug, version="0.1.0", description=API_DESCRIPTION, openapi_tags=OPENAPI_TAGS, lifespan=lifespan, ) app.add_middleware(AccessLogMiddleware) if settings.cors_origins: app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(api_router, prefix=settings.api_v1_prefix) @app.get( "/", tags=["root"], response_model=RootStatusRead, summary="服务根检查", description="用于快速确认后端服务进程已启动。", ) def root() -> RootStatusRead: return RootStatusRead(message=f"{settings.app_name} is running") return app app = create_app()