""" FastAPI应用主入口 """ from contextlib import asynccontextmanager from fastapi import FastAPI, Request, status from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from fastapi.exceptions import RequestValidationError from starlette.exceptions import HTTPException as StarletteHTTPException from loguru import logger import sys from app.core.config import settings from app.core.exceptions import BusinessException from app.core.response import error_response from app.middleware.api_transform import api_transform_middleware from app.api.v1 import api_router from app.db.session import init_db, close_db # 配置日志 logger.remove() logger.add( sys.stderr, level=settings.LOG_LEVEL, format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}", colorize=True ) logger.add( settings.LOG_FILE, rotation=settings.LOG_ROTATION, retention=settings.LOG_RETENTION, level=settings.LOG_LEVEL, format="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}", encoding="utf-8" ) @asynccontextmanager async def lifespan(app: FastAPI): """应用生命周期管理""" # 启动时执行 logger.info("🚀 应用启动中...") logger.info(f"📦 环境: {settings.APP_ENVIRONMENT}") logger.info(f"🔗 数据库: {settings.DATABASE_URL}") # 初始化数据库(生产环境使用Alembic迁移) if settings.is_development: await init_db() logger.info("✅ 数据库初始化完成") yield # 关闭时执行 logger.info("🛑 应用关闭中...") await close_db() logger.info("✅ 数据库连接已关闭") # 创建FastAPI应用 app = FastAPI( title=settings.APP_NAME, version=settings.APP_VERSION, description="企业级资产管理系统后端API", docs_url="/docs" if settings.DEBUG else None, redoc_url="/redoc" if settings.DEBUG else None, openapi_url="/openapi.json" if settings.DEBUG else None, lifespan=lifespan ) # 配置CORS app.add_middleware( CORSMiddleware, allow_origins=settings.CORS_ORIGINS, allow_credentials=settings.CORS_ALLOW_CREDENTIALS, allow_methods=settings.CORS_ALLOW_METHODS, allow_headers=settings.CORS_ALLOW_HEADERS, ) # API request/response normalization app.middleware("http")(api_transform_middleware) # 自定义异常处理器 @app.exception_handler(BusinessException) async def business_exception_handler(request: Request, exc: BusinessException): """业务异常处理""" logger.warning(f"业务异常: {exc.message} - 错误码: {exc.error_code}") return JSONResponse( status_code=exc.code, content=error_response( code=exc.code, message=exc.message, errors=[{"field": k, "message": v} for k, v in exc.data.items()] if exc.data else None ) ) @app.exception_handler(StarletteHTTPException) async def http_exception_handler(request: Request, exc: StarletteHTTPException): """HTTP异常处理""" logger.warning(f"HTTP异常: {exc.status_code} - {exc.detail}") return JSONResponse( status_code=exc.status_code, content=error_response( code=exc.status_code, message=str(exc.detail) ) ) @app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): """请求验证异常处理""" errors = [] for error in exc.errors(): errors.append({ "field": ".".join(str(loc) for loc in error["loc"]), "message": error["msg"] }) logger.warning(f"验证异常: {errors}") return JSONResponse( status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, content=error_response( code=status.HTTP_422_UNPROCESSABLE_ENTITY, message="参数验证失败", errors=errors ) ) @app.exception_handler(Exception) async def general_exception_handler(request: Request, exc: Exception): """通用异常处理""" logger.error(f"未处理的异常: {type(exc).__name__} - {str(exc)}", exc_info=True) return JSONResponse( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=error_response( code=status.HTTP_500_INTERNAL_SERVER_ERROR, message="服务器内部错误" if not settings.DEBUG else str(exc) ) ) # 注册路由 app.include_router(api_router, prefix=settings.API_V1_PREFIX) # 健康检查 @app.get("/health", tags=["系统"]) async def health_check(): """健康检查接口""" return { "status": "ok", "app_name": settings.APP_NAME, "version": settings.APP_VERSION, "environment": settings.APP_ENVIRONMENT } # 根路径 @app.get("/", tags=["系统"]) async def root(): """根路径""" return { "message": f"欢迎使用{settings.APP_NAME} API", "version": settings.APP_VERSION, "docs": "/docs" if settings.DEBUG else None } if __name__ == "__main__": import uvicorn uvicorn.run( "app.main:app", host=settings.HOST, port=settings.PORT, reload=settings.DEBUG, log_level=settings.LOG_LEVEL.lower() )