main.py 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. from contextlib import asynccontextmanager
  2. from pathlib import Path
  3. from fastapi import FastAPI, Request
  4. from fastapi.exceptions import RequestValidationError
  5. from fastapi.middleware.cors import CORSMiddleware
  6. from fastapi.responses import JSONResponse
  7. from fastapi.staticfiles import StaticFiles
  8. from app.api.auth import router as auth_router
  9. from app.api.public import router as public_router
  10. from app.core.config import get_settings
  11. from app.core.database import SessionLocal, init_db
  12. from app.core.response import AppHTTPError, error_response, success_response
  13. from app.services.seed import seed_if_empty
  14. settings = get_settings()
  15. @asynccontextmanager
  16. async def lifespan(app: FastAPI):
  17. init_db()
  18. db = SessionLocal()
  19. try:
  20. seed_if_empty(db)
  21. finally:
  22. db.close()
  23. yield
  24. app = FastAPI(title=settings.APP_NAME, lifespan=lifespan)
  25. _cors_origins = [o.strip() for o in settings.CORS_ORIGINS.split(",") if o.strip()]
  26. if not _cors_origins:
  27. _cors_origins = ["*"]
  28. app.add_middleware(
  29. CORSMiddleware,
  30. allow_origins=_cors_origins,
  31. allow_credentials=_cors_origins != ["*"],
  32. allow_methods=["*"],
  33. allow_headers=["*"],
  34. )
  35. app.include_router(auth_router, prefix=settings.API_V1_PREFIX)
  36. app.include_router(public_router, prefix=settings.API_V1_PREFIX)
  37. _upload_dir = Path(settings.LOCAL_STORAGE_DIR)
  38. _upload_dir.mkdir(parents=True, exist_ok=True)
  39. app.mount("/uploads", StaticFiles(directory=str(_upload_dir)), name="uploads")
  40. @app.exception_handler(AppHTTPError)
  41. async def app_http_error_handler(_request: Request, exc: AppHTTPError):
  42. return JSONResponse(
  43. status_code=exc.status_code,
  44. content=error_response(exc.status_code, exc.message, exc.data),
  45. )
  46. @app.exception_handler(RequestValidationError)
  47. async def request_validation_error_handler(
  48. _request: Request, exc: RequestValidationError
  49. ):
  50. # 全局约定:校验失败统一 HTTP 400 + 信封,避免 FastAPI 默认 422/detail
  51. msg = "请求参数校验失败"
  52. errors = exc.errors()
  53. if errors:
  54. first = errors[0]
  55. loc = ".".join(str(x) for x in first.get("loc", ()) if x != "body")
  56. detail = first.get("msg") or msg
  57. msg = f"{loc}: {detail}" if loc else str(detail)
  58. return JSONResponse(
  59. status_code=400,
  60. content=error_response(400, msg),
  61. )
  62. @app.get(f"{settings.API_V1_PREFIX}/health")
  63. def health_check():
  64. return success_response({"status": "ok"})