from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware from starlette.exceptions import HTTPException as StarletteHTTPException from app.database import init_db, async_session import app.models # noqa: F401 - 注册所有模型 from app.routers import ( auth_router, conversation_router, document_router, task_router, forum_router, graph_router, agent_router, todo_router, reminder_router, goal_router, schedule_center_router, settings_router, folder_router, skill_router, log_router, system_router, brain_router, ) from app.routers.scheduler import router as scheduler_router from app.services.scheduler_service import start_scheduler, stop_scheduler, get_scheduler_status from app.services.admin_bootstrap_service import ensure_admin_user, ensure_builtin_skills from app.config import settings from app.logging_utils import ( setup_logging, request_logging_middleware, log_http_exception, log_validation_exception, log_unhandled_exception, persist_system_log, ) import os INSECURE_SECRET_KEYS = { 'change-me-in-production', 'change-me-to-a-random-secret-key', 'jarvis-secret-key-change-in-production', } def validate_startup_security() -> None: if not settings.DEBUG and settings.SECRET_KEY in INSECURE_SECRET_KEYS: raise RuntimeError('SECRET_KEY must be changed before running with DEBUG disabled') async def run_startup() -> None: validate_startup_security() await init_db() async with async_session() as session: await ensure_admin_user(session, settings) await ensure_builtin_skills(session) await persist_system_log( message="application_started", source="app", operation="app.startup", details={"version": settings.APP_VERSION}, ) start_scheduler() @asynccontextmanager async def lifespan(app: FastAPI): # 启动 setup_logging(settings.DEBUG) os.makedirs(settings.DATA_DIR, exist_ok=True) os.makedirs(settings.UPLOAD_DIR, exist_ok=True) os.makedirs(settings.CHROMA_PERSIST_DIR, exist_ok=True) await run_startup() yield # 关闭 stop_scheduler() app = FastAPI( title=settings.APP_NAME, version=settings.APP_VERSION, lifespan=lifespan, ) # CORS app.add_middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.middleware("http")(request_logging_middleware) app.add_exception_handler(StarletteHTTPException, log_http_exception) app.add_exception_handler(RequestValidationError, log_validation_exception) app.add_exception_handler(Exception, log_unhandled_exception) # 注册路由 app.include_router(auth_router) app.include_router(conversation_router) app.include_router(document_router) app.include_router(task_router) app.include_router(forum_router) app.include_router(graph_router) app.include_router(agent_router) app.include_router(todo_router) app.include_router(reminder_router) app.include_router(goal_router) app.include_router(schedule_center_router) app.include_router(settings_router) app.include_router(folder_router) app.include_router(skill_router) app.include_router(log_router) app.include_router(system_router) app.include_router(brain_router) app.include_router(scheduler_router) @app.get("/api/health") async def health(): return { "status": "ok", "version": settings.APP_VERSION, "llm_provider": settings.LLM_PROVIDER, "scheduler": get_scheduler_status(), }