Init
This commit is contained in:
+181
@@ -0,0 +1,181 @@
|
||||
"""应用入口:FastAPI 应用创建与生命周期管理
|
||||
|
||||
职责:
|
||||
- 构建服务容器(依赖注入)
|
||||
- 管理应用生命周期(启动/关闭浏览器池和会话存储)
|
||||
- 注册路由和全局异常处理器
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.exceptions import RequestValidationError
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import ValidationError
|
||||
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||
|
||||
from app.api.routes.health import router as health_router
|
||||
from app.api.routes.scrape import router as scrape_router
|
||||
from app.core.config import get_settings
|
||||
from app.core.container import ServiceContainer
|
||||
from app.core.errors import AppError
|
||||
from app.core.logging_setup import configure_logging
|
||||
from app.models.scrape import ApiResponse
|
||||
from app.services.browser_pool import BrowserPool
|
||||
from app.services.cloudflare_session import CloudflareSessionManager
|
||||
from app.services.session_store import SessionStore
|
||||
from app.services.surugaya_client import SurugayaClient
|
||||
from app.services.cargo_service import CargoService
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _format_validation_msg(errors: list[dict]) -> str:
|
||||
"""将校验错误整理为便于前端展示的消息。"""
|
||||
if not errors:
|
||||
return "Validation error"
|
||||
|
||||
messages: list[str] = []
|
||||
for error in errors:
|
||||
loc = ".".join(str(part) for part in error.get("loc", []) if part != "body")
|
||||
msg = str(error.get("msg", "Validation error"))
|
||||
messages.append(f"{loc}: {msg}" if loc else msg)
|
||||
|
||||
return "; ".join(messages)
|
||||
|
||||
|
||||
def build_container() -> ServiceContainer:
|
||||
"""构建服务容器,组装所有依赖"""
|
||||
settings = get_settings()
|
||||
session_store = SessionStore(settings)
|
||||
browser_pool = BrowserPool(settings)
|
||||
session_manager = CloudflareSessionManager(settings, browser_pool, session_store)
|
||||
surugaya_client = SurugayaClient(settings, browser_pool, session_manager, session_store)
|
||||
cargo_service = CargoService(settings)
|
||||
return ServiceContainer(
|
||||
settings=settings,
|
||||
session_store=session_store,
|
||||
browser_pool=browser_pool,
|
||||
session_manager=session_manager,
|
||||
surugaya_client=surugaya_client,
|
||||
cargo_service=cargo_service,
|
||||
)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
"""应用生命周期管理:启动时初始化各服务,关闭时释放资源"""
|
||||
container = build_container()
|
||||
app.state.container = container
|
||||
|
||||
configure_logging(container.settings)
|
||||
logger.info("应用启动:%s:%s", container.settings.app_host, container.settings.app_port)
|
||||
logger.info("日志级别:%s", container.settings.log_level)
|
||||
logger.info("当前环境:%s", container.settings.app_env)
|
||||
await container.session_store.start()
|
||||
await container.browser_pool.start()
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
await container.browser_pool.close()
|
||||
await container.session_store.close()
|
||||
|
||||
|
||||
def create_app() -> FastAPI:
|
||||
"""创建 FastAPI 应用实例,注册路由和异常处理器"""
|
||||
app = FastAPI(title="Surugaya Scraper Service", lifespan=lifespan)
|
||||
app.include_router(health_router)
|
||||
app.include_router(scrape_router)
|
||||
|
||||
@app.exception_handler(AppError)
|
||||
async def app_error_handler(_: Request, exc: AppError) -> JSONResponse:
|
||||
"""业务异常处理器:返回结构化的错误响应"""
|
||||
return JSONResponse(
|
||||
status_code=exc.status_code,
|
||||
content=ApiResponse[None](
|
||||
success=False,
|
||||
code=exc.err_code,
|
||||
msg=exc.message,
|
||||
data=None,
|
||||
).model_dump(),
|
||||
headers=exc.headers,
|
||||
)
|
||||
|
||||
@app.exception_handler(RequestValidationError)
|
||||
async def validation_error_handler(_: Request, exc: RequestValidationError) -> JSONResponse:
|
||||
"""请求参数校验异常处理器"""
|
||||
errors = exc.errors()
|
||||
return JSONResponse(
|
||||
status_code=422,
|
||||
content=ApiResponse[object](
|
||||
success=False,
|
||||
msg=_format_validation_msg(errors),
|
||||
data=errors,
|
||||
code=1002,
|
||||
).model_dump(),
|
||||
)
|
||||
|
||||
@app.exception_handler(ValidationError)
|
||||
async def pydantic_validation_error_handler(_: Request, exc: ValidationError) -> JSONResponse:
|
||||
"""Pydantic 模型校验异常处理器。"""
|
||||
errors = exc.errors()
|
||||
return JSONResponse(
|
||||
status_code=422,
|
||||
content=ApiResponse[object](
|
||||
success=False,
|
||||
msg=_format_validation_msg(errors),
|
||||
data=errors,
|
||||
code=1002,
|
||||
).model_dump(),
|
||||
)
|
||||
|
||||
@app.exception_handler(StarletteHTTPException)
|
||||
async def http_exception_handler(_: Request, exc: StarletteHTTPException) -> JSONResponse:
|
||||
"""HTTP 异常处理器(404、500 等)"""
|
||||
status_code = int(getattr(exc, "status_code", 500) or 500)
|
||||
err_code = 1404 if status_code == 404 else 1500
|
||||
return JSONResponse(
|
||||
status_code=status_code,
|
||||
content=ApiResponse[None](
|
||||
success=False,
|
||||
msg=str(getattr(exc, "detail", "HTTP error")),
|
||||
data=None,
|
||||
code=err_code,
|
||||
).model_dump(),
|
||||
headers=getattr(exc, "headers", None),
|
||||
)
|
||||
|
||||
@app.exception_handler(Exception)
|
||||
async def unhandled_exception_handler(_: Request, __: Exception) -> JSONResponse:
|
||||
"""兜底异常处理器:捕获所有未处理的异常"""
|
||||
return JSONResponse(
|
||||
status_code=500,
|
||||
content=ApiResponse[None](
|
||||
success=False,
|
||||
msg="Internal server error",
|
||||
data=None,
|
||||
code=1500,
|
||||
).model_dump(),
|
||||
)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
app = create_app()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
settings = get_settings()
|
||||
configure_logging(settings)
|
||||
uvicorn.run(
|
||||
"app.main:app",
|
||||
host=settings.app_host,
|
||||
port=settings.app_port,
|
||||
log_config=None,
|
||||
timeout_keep_alive=120,
|
||||
)
|
||||
Reference in New Issue
Block a user