"""应用入口:FastAPI 装配、异常处理、静态资源挂载、开发服务器。""" from pathlib import Path from fastapi import FastAPI, Request from fastapi.exceptions import RequestValidationError from fastapi.staticfiles import StaticFiles from huamenchuang.api.router import api_router from huamenchuang.core.database import init_db from huamenchuang.core.deps import ModuleLockedError, NotAuthedError from huamenchuang.core.response import fail, ok, paywall app = FastAPI(title="华门窗云平台", version="0.1.0") @app.on_event("startup") async def startup() -> None: await init_db() @app.exception_handler(NotAuthedError) async def auth_handler(request: Request, exc: NotAuthedError): return _json({"code": 401, "message": str(exc), "data": "", "other_data": "", "other_data2": ""}) @app.exception_handler(ModuleLockedError) async def module_locked_handler(request: Request, exc: ModuleLockedError): return _json(paywall(exc.module)) @app.exception_handler(RequestValidationError) async def validation_handler(request: Request, exc: RequestValidationError): """参数校验失败 → 422 语义 envelope(列出字段,不泄漏内部细节)。""" fields = [f"{'.'.join(str(x) for x in e['loc'][1:])}" for e in exc.errors()] return _json(fail(f"参数错误: {', '.join(fields)}")) @app.exception_handler(Exception) async def unhandled_handler(request: Request, exc: Exception): """兜底异常:统一 500 envelope(不回栈信息)。""" return _json(fail("服务器内部错误,请稍后再试")) def _json(payload: dict): from fastapi.responses import JSONResponse return JSONResponse(payload) @api_router.get("/health", tags=["系统"]) async def health(): return ok(data={"status": "up"}, message="ok") app.include_router(api_router) from fastapi.responses import FileResponse # noqa: E402 # 静态资源:样板平面图(data/windoor-images) _imgdir = Path(__file__).resolve().parents[2] / "data" / "windoor-images" if _imgdir.exists(): app.mount("/static/windoor-images", StaticFiles(directory=str(_imgdir)), name="windoor-images") # 前端构建产物挂载(前后端规范:构建到后端目录)+ history 路由回退 _dist = Path(__file__).resolve().parents[2] / "frontend" / "dist" if _dist.exists(): app.mount("/assets", StaticFiles(directory=str(_dist / "assets")), name="assets") @app.get("/{full_path:path}", include_in_schema=False) async def spa_fallback(full_path: str): """非 API/静态路径一律返回 index.html(前端 history 模式)。""" if full_path.startswith(("api/", "static/")): return JSONResponse( {"code": 0, "message": "接口不存在", "data": "", "other_data": "", "other_data2": ""}) target = _dist / full_path if full_path and target.is_file(): return FileResponse(target) return FileResponse(_dist / "index.html") def main() -> None: import uvicorn uvicorn.run("huamenchuang.main:app", host="0.0.0.0", port=8000, reload=True) if __name__ == "__main__": main()