main.py 2.5 KB

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