| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- """
- __init__.py -
- auther: tanlie
- date: 2026/8/30
- """
- import logging
- import os
- from logging.handlers import RotatingFileHandler
- import traceback
- import re
- from fastapi import FastAPI, Request, HTTPException
- from fastapi.exceptions import RequestValidationError
- from fastapi.responses import JSONResponse
- from app.core.redis_lifespan_fixed import lifespan
- from app.mobile import register_mobile_routes
- from app.core.response import error_response
- from app.core.logger import logger
- def _clean_message(s: str) -> str:
- """Normalize exception messages by removing wrapper prefixes like "ValueError,"."""
- if not s:
- return s
- return re.sub(r'^\s*(?:ValueError|Value\s*error)[:,]?\s*', '', str(s), flags=re.I)
- def create_app():
- app = FastAPI(lifespan=lifespan)
- # Attach a DB session to each request via middleware so handlers can use request.state.db
- from app.core.db import SessionLocal
- @app.middleware("http")
- async def db_session_middleware(request: Request, call_next):
- # Provide a request-scoped SQLAlchemy session
- request.state.db = SessionLocal()
- try:
- response = await call_next(request)
- return response
- except Exception:
- # If an unhandled exception occurred, rollback any pending transaction
- try:
- request.state.db.rollback()
- except Exception:
- pass
- raise
- finally:
- request.state.db.close()
- register_mobile_routes(app)
- @app.exception_handler(RequestValidationError)
- async def validation_exception_handler(request: Request, exc: RequestValidationError):
- # Build a friendly, JSON-serializable summary of validation errors
- errors = []
- for e in exc.errors():
- loc = e.get("loc", [])
- # remove leading location indicators like 'body', 'query', 'path', 'header'
- if loc and len(loc) > 0 and str(loc[0]) in ("body", "query", "path", "header"):
- loc_parts = loc[1:]
- else:
- loc_parts = loc
- # join remaining location parts into a dotted path for readability
- loc_str = ".".join([str(p) for p in loc_parts if p is not None]) if loc_parts else ""
- cleaned_msg = _clean_message(e.get("msg", ""))
- errors.append({"field": loc_str, "message": cleaned_msg})
- # Log validation details for debugging
- logger.info("Validation error for %s: %s", request.url.path, errors)
- return JSONResponse(
- status_code=422,
- content=error_response(msg="参数校验失败", code=422, data=errors),
- )
- @app.exception_handler(HTTPException)
- async def http_exception_handler(request: Request, exc: HTTPException):
- # Use HTTPException's status_code and detail
- detail = exc.detail if exc.detail is not None else ""
- logger.warning("HTTPException for %s: %s", request.url.path, detail)
- return JSONResponse(
- status_code=getattr(exc, "status_code", 500),
- content=error_response(msg=str(detail), code=getattr(exc, "status_code", 500)),
- )
- @app.exception_handler(Exception)
- async def generic_exception_handler(request: Request, exc: Exception):
- # Map common ValueError to a friendlier 400 parameter error
- if isinstance(exc, ValueError):
- # Log with stack for debugging
- tb = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
- raw_msg = str(exc) or ""
- cleaned_msg = _clean_message(raw_msg)
- logger.warning("ValueError on %s: %s\n%s", request.url.path, cleaned_msg, tb)
- return JSONResponse(
- status_code=400,
- content=error_response(msg=cleaned_msg or "参数错误", code=400),
- )
- # Generic fallback to avoid leaking internal stack traces
- # Log full stack trace for internal debugging
- tb = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
- logger.error("Unhandled exception on %s: %s\n%s", request.url.path, str(exc), tb)
- return JSONResponse(
- status_code=500,
- content=error_response(msg="服务器内部错误", code=500),
- )
- return app
|