import asyncio import json import logging from contextlib import asynccontextmanager from pathlib import Path from typing import Any, AsyncGenerator from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from nyahome.router import admin_router, aii_router, chatroom_router, file_router, webui_router logger = logging.getLogger(__name__) @asynccontextmanager async def lifespan(_: FastAPI) -> AsyncGenerator[None, Any]: # 在生命周期函数内先加载环境变量,再局部导入 nyahome 核心模块 logger.info("🚀 服务启动中...") from nyahome.config import config_manager from nyahome.core.otp_store import email_otp_memory_store from nyahome.core.send_email import email_sender_queue from nyahome.core.task import init_admin_user from nyahome.database import create_db create_db() await asyncio.gather(init_admin_user(), config_manager.async_load_config()) email_sender_queue.start() email_otp_memory_store.start() logger.info("🌸 server 启动完成。") try: yield except Exception as e: logger.error(f"捕获到无法处理的异常,NyaHome 即将结束 - {e}") finally: logger.info("🌕 服务关闭中...") app = FastAPI(title="🌸 NyaHome ~", lifespan=lifespan) app.include_router(webui_router) app.include_router(chatroom_router, prefix="/api") app.include_router(admin_router, prefix="/api") app.include_router(file_router, prefix="/api") app.include_router(aii_router, prefix="/api") app.mount("/nyahome", StaticFiles(directory=Path.cwd() / "public"), name="public") app.mount("/download", StaticFiles(directory=Path.cwd() / ".nyahome/contents"), name="upload") # noinspection PyTypeChecker app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) def save_openapi_json(save_path: str | Path) -> None: try: from docstring_parser import DocstringStyle except ImportError as e: raise RuntimeError("开发依赖 docstring_parser 不存在,请使用 git clone 方式克隆 NyaHome。") from e from nyahome.cli.openapi_docstring import enrich_openapi_from_docstrings # 增强 openapi docstring 参数文档提取 enrich_openapi_from_docstrings(app, docstring_style=DocstringStyle.GOOGLE) with open(save_path, "w") as f: json.dump(app.openapi(), f, indent=2)