dingdanquanliucheng/backend/app/main.py

103 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""FastAPI 应用入口。
创建应用实例、配置 CORS 中间件、注册全局异常处理器、挂载 API 路由。
启动命令: uvicorn backend.app.main:app --reload
"""
import logging
import traceback
from contextlib import asynccontextmanager
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(levelname)s: %(message)s")
from fastapi import FastAPI
from fastapi import Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from pathlib import Path
from backend.app.api.routes import api_router
from backend.app.api.ws import router as ws_router
from backend.app.core.config import get_settings
from backend.app.core.exceptions import AppException
logger = logging.getLogger(__name__)
settings = get_settings()
@asynccontextmanager
async def lifespan(app: FastAPI):
"""管理应用生命周期:启动时自动初始化数据库和调度器,关闭时清理。"""
# 自动初始化数据库(检查/创建数据库、执行迁移、初始化种子数据)
try:
from backend.app.startup.db_init import auto_init_database
auto_init_database()
except Exception as e:
logger.error("数据库自动初始化失败: %s", e)
# 不阻止启动,允许手动修复
from backend.app.scheduler import init_scheduler, shutdown_scheduler
init_scheduler()
yield
shutdown_scheduler()
app = FastAPI(title=settings.app_name, lifespan=lifespan)
# 配置 CORS 跨域,允许前端开发服务器访问
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.exception_handler(AppException)
async def app_exception_handler(_: Request, exc: AppException) -> JSONResponse:
"""全局捕获 AppException返回统一的错误 JSON 格式。
避免每个路由重复构造错误响应,所有业务异常由此处理器统一格式化。
"""
# 统一业务异常返回格式,避免每个接口重复构造错误响应。
return JSONResponse(
status_code=exc.status_code,
content={
"code": exc.code,
"message": exc.message,
"data": {},
},
)
@app.exception_handler(Exception)
async def unhandled_exception_handler(_: Request, exc: Exception) -> JSONResponse:
"""捕获所有未处理异常,记录完整 traceback 到日志并返回 500。
避免 FastAPI 默认的 bare 500 响应,让运维能从日志中定位问题。
"""
logger.error("Unhandled exception: %s\n%s", exc, traceback.format_exc())
return JSONResponse(
status_code=500,
content={
"code": 500,
"message": f"服务器内部错误: {type(exc).__name__}: {exc}",
"data": {},
},
)
# 挂载所有 API 路由,路由前缀在 router.py 中统一定义
app.include_router(api_router)
# 挂载 WebSocket 路由(不通过 api_router直接挂载
app.include_router(ws_router)
# 挂载本地上传文件目录为静态文件服务(开发环境用,生产环境由 Nginx 代理)
_local_upload_dir = Path(settings.local_upload_dir)
_local_upload_dir.mkdir(parents=True, exist_ok=True)
app.mount("/uploads", StaticFiles(directory=str(_local_upload_dir)), name="local-uploads")