"""FastAPI 主应用 - with SPA static file serving""" from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles from fastapi.responses import FileResponse from pathlib import Path from contextlib import asynccontextmanager from .config import settings from .database import Base, engine from .api.v1 import jobs, upload, results, tasks, examples @asynccontextmanager async def lifespan(app: FastAPI): """应用生命周期管理""" # 启动时 print("🚀 Starting BtToxin Pipeline API...") # 创建数据库表 Base.metadata.create_all(bind=engine) print("✅ Database tables created") yield # 关闭时 print("👋 Shutting down BtToxin Pipeline API...") app = FastAPI( title=settings.APP_NAME, version=settings.APP_VERSION, description="Automated Bacillus thuringiensis toxin mining pipeline", lifespan=lifespan, docs_url="/api/docs", redoc_url="/api/redoc", openapi_url="/api/openapi.json" ) # CORS app.add_middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # 路由 app.include_router(jobs.router, prefix=f"{settings.API_V1_STR}/jobs", tags=["jobs"]) app.include_router(tasks.router, prefix=f"{settings.API_V1_STR}/tasks", tags=["tasks"]) app.include_router(upload.router, prefix=f"{settings.API_V1_STR}/upload", tags=["upload"]) app.include_router(examples.router, prefix=f"{settings.API_V1_STR}/examples", tags=["examples"]) app.include_router(results.router, prefix=f"{settings.API_V1_STR}/results", tags=["results"]) @app.get("/api/meta") async def root_meta(): return { "name": settings.APP_NAME, "version": settings.APP_VERSION, "status": "healthy" } @app.get("/health") async def health(): return {"status": "ok"} # Mount static files for frontend (in production) # The frontend dist files are served from /app/frontend/dist in the container frontend_dist_path = Path("/app/frontend/dist") if frontend_dist_path.exists(): app.mount("/assets", StaticFiles(directory=str(frontend_dist_path / "assets")), name="assets") @app.get("/{full_path:path}", include_in_schema=False) async def serve_spa(full_path: str): """Serve frontend SPA - all routes go to index.html""" # 如果是 API 路径,且没有被前面路由捕获,返回 404 if full_path.startswith("api/"): return {"detail": "Not Found"}, 404 # 处理根路径 "" if full_path == "" or full_path == "/": return FileResponse(frontend_dist_path / "index.html") file_path = frontend_dist_path / full_path if file_path.exists() and file_path.is_file(): return FileResponse(file_path) # SPA 路由:返回 index.html return FileResponse(frontend_dist_path / "index.html")