Files
QMDSearch/app/main.py
T
kplam dce9e31bde feat: 新增多格式文件上传入库与认证体系
- 新增 JWT 认证模块,支持登录/注册/用户管理
- 新增文件上传接口,支持 .txt/.md/.html/.pdf/.docx 等格式解析入库
- 新增检索结果 AI 总结功能
- 新增文本去重缓存机制
- 新增全局认证夹具简化测试
- 新增配置项与环境变量支持
- 完善文档与测试覆盖
2026-07-30 10:30:15 +08:00

92 lines
3.2 KiB
Python

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from pathlib import Path
import structlog
from fastapi import FastAPI, Request
from fastapi.exceptions import RequestValidationError
from fastapi.responses import FileResponse, JSONResponse
from app.api.response import ApiError, error
from app.api.v1.auth import router as auth_router
from app.api.v1.document import router as document_router
from app.api.v1.knowledge import router as knowledge_router
from app.api.v1.search import router as search_router
from app.config import settings
from app.core.auth import ensure_default_admin
from app.services.qdrant import QdrantService
logger = structlog.get_logger()
@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
"""应用生命周期:启动时初始化 Qdrant 集合与默认管理员
初始化失败仅记录日志、不阻止启动(本地开发可能无 Qdrant/Redis)。
"""
try:
await QdrantService().ensure_collections()
logger.info("Qdrant 集合初始化完成")
except Exception:
logger.error("Qdrant 集合初始化失败,跳过初始化继续启动")
try:
await ensure_default_admin()
except Exception:
logger.warning("默认管理员初始化失败,跳过", exc_info=True)
try:
Path(settings.upload_dir).mkdir(parents=True, exist_ok=True)
logger.info("上传目录已就绪", upload_dir=settings.upload_dir)
except Exception:
logger.warning("上传目录初始化失败,文件落盘将按需创建", exc_info=True)
yield
app = FastAPI(
title=settings.app_name,
description="AI Agent 分层信息检索服务",
version="0.1.0",
lifespan=lifespan,
)
app.include_router(auth_router)
app.include_router(search_router)
app.include_router(document_router)
app.include_router(knowledge_router)
@app.exception_handler(ApiError)
async def api_error_handler(request: Request, exc: ApiError) -> JSONResponse:
"""业务异常 → 统一错误响应(code 取自异常)"""
return JSONResponse(content=error(exc.code, exc.message))
@app.exception_handler(RequestValidationError)
async def validation_error_handler(request: Request, exc: RequestValidationError) -> JSONResponse:
"""请求参数校验失败 → code 1001"""
errors = exc.errors()
message = f"请求参数校验失败: {errors[0]['msg']}" if errors else "请求参数校验失败"
return JSONResponse(content=error(1001, message))
@app.exception_handler(Exception)
async def unhandled_error_handler(request: Request, exc: Exception) -> JSONResponse:
"""未捕获异常 → code 2000"""
logger.exception("未捕获异常", path=request.url.path)
return JSONResponse(content=error(2000, "服务器内部错误"))
@app.get("/api/v1/health")
async def health() -> dict[str, str]:
"""健康检查"""
return {"status": "ok"}
_ADMIN_HTML = Path(__file__).resolve().parent / "static" / "admin.html"
@app.get("/admin", include_in_schema=False)
async def admin_page() -> FileResponse:
"""管理后台单页(单文件静态 HTML,零外部依赖)"""
return FileResponse(_ADMIN_HTML, media_type="text/html")