| """FastAPI 应用主入口""" |
|
|
| import os |
| from contextlib import asynccontextmanager |
| from pathlib import Path |
|
|
| from fastapi import Depends, FastAPI, HTTPException, status |
| from fastapi.responses import FileResponse, RedirectResponse, Response |
| from fastapi.staticfiles import StaticFiles |
| from starlette.types import Scope |
|
|
| from qa_annotate.api.analysis import router as analysis_router |
| from qa_annotate.api.annotation import annotation_result_router |
| from qa_annotate.api.annotation import router as annotation_router |
| from qa_annotate.api.auth import get_optional_user |
| from qa_annotate.api.dataset import router as dataset_router |
| from qa_annotate.api.project import router as project_router |
| from qa_annotate.api.seed_question import router as seed_question_router |
| from qa_annotate.api.system_config import router as system_config_router |
| from qa_annotate.api.user import router as user_router |
| from qa_annotate.config import settings |
| from qa_annotate.bootstrap import ensure_llm_config, seed_demo_llm_analysis |
| from qa_annotate.database.base import init_db |
|
|
|
|
| class NoCachedStaticFiles(StaticFiles): |
| """带缓存头的静态文件服务""" |
|
|
| def file_response( |
| self, |
| full_path: str, |
| stat_result: os.stat_result, |
| scope: Scope, |
| status_code: int = 200, |
| ) -> Response: |
| response = super().file_response(full_path, stat_result, scope, status_code) |
| |
| response.headers["Cache-Control"] = "no-cache" |
| response.headers["Pragma"] = "no-cache" |
| response.headers["Expires"] = "0" |
| return response |
|
|
|
|
| @asynccontextmanager |
| async def lifespan(app: FastAPI): |
| """应用生命周期管理""" |
| |
| init_db() |
| ensure_llm_config() |
| seed_demo_llm_analysis() |
| yield |
| |
|
|
|
|
| |
| |
| docs_url = None if settings.is_production else "/docs" |
| redoc_url = None if settings.is_production else "/redoc" |
|
|
| app = FastAPI( |
| title="QA 标注系统 API", |
| description="QA对数据集标注系统 API 接口", |
| version="0.1.0", |
| lifespan=lifespan, |
| docs_url=docs_url, |
| redoc_url=redoc_url, |
| ) |
|
|
| |
| app.include_router(user_router, prefix="/api") |
| app.include_router(dataset_router, prefix="/api") |
| app.include_router(annotation_router, prefix="/api") |
| app.include_router(annotation_result_router, prefix="/api") |
| app.include_router(project_router, prefix="/api") |
| app.include_router(seed_question_router, prefix="/api") |
| app.include_router(system_config_router, prefix="/api") |
| app.include_router(analysis_router, prefix="/api") |
|
|
| |
| app.mount("/static", NoCachedStaticFiles(directory="qa_annotate/static"), name="static") |
|
|
|
|
| @app.get("/") |
| async def root(user=Depends(get_optional_user)): |
| """根据用户登录状态和权限返回不同页面""" |
| |
| if user is None: |
| |
| return RedirectResponse(url="/auth") |
| elif user.is_superuser: |
| |
| return RedirectResponse(url="/manager") |
| else: |
| |
| return RedirectResponse(url="/user") |
|
|
|
|
| @app.get("/{path}") |
| async def html(path: str): |
| """返回 html 目录中的文件""" |
| if not path.endswith(".html"): |
| path = path + ".html" |
| html_dir = Path(__file__).parent / "html" |
| file_path = (html_dir / path).resolve() |
| |
| html_dir_resolved = html_dir.resolve() |
| if not str(file_path).startswith(str(html_dir_resolved)): |
| raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden") |
| if not file_path.exists() or not file_path.is_file(): |
| raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Not Found") |
| return FileResponse(str(file_path)) |
|
|
|
|
| @app.get("/api/health") |
| async def health_check(): |
| """健康检查接口""" |
| return {"status": "healthy"} |
|
|
|
|
| def main(): |
| """启动应用的入口函数""" |
| import uvicorn |
|
|
| from qa_annotate.config import settings |
|
|
| uvicorn.run( |
| "qa_annotate.main:app", |
| host=settings.HOST, |
| port=settings.PORT, |
| reload=settings.RELOAD, |
| reload_dirs=["qa_annotate"], |
| ) |
|
|