| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283 |
- """压缩机 3D 部件查看服务(FastAPI)。
- 公共接口(无需登录):
- POST /api/login 登录
- POST /api/logout 退出
- GET /api/auth-config 运行模式与(测试模式下的)默认账号密码
- GET /models/<file> GLB / glTF 模型文件(静态资源,不鉴权)
- GET /* 前端构建产物(frontend/dist),SPA 回退 index.html
- 需登录接口(Bearer token):
- /api/models、/api/alarms、/api/alarm-filters、/api/pks-series、
- /api/scene-positions、/api/fault-part-map
- """
- from typing import Any
- from fastapi import Depends, FastAPI, Header, HTTPException
- from fastapi.middleware.cors import CORSMiddleware
- from fastapi.responses import FileResponse
- from pydantic import BaseModel, Field
- from auth import create_token, require_auth, revoke_token, verify_credentials
- from config import APP_MODE, AUTH_PASSWORD, AUTH_USER, FRONTEND_DIST, IS_TEST
- from routers import alarms, fault_map, files, scenes, series
- class LoginInput(BaseModel):
- username: str = Field(min_length=1)
- password: str = Field(min_length=1)
- app = FastAPI(title="压缩机 3D 部件查看服务", version="0.1.0")
- app.add_middleware(
- CORSMiddleware,
- allow_origins=["*"],
- allow_methods=["*"],
- allow_headers=["*"],
- )
- @app.post("/api/login")
- def login(payload: LoginInput) -> dict[str, Any]:
- if not verify_credentials(payload.username, payload.password):
- raise HTTPException(status_code=401, detail="用户名或密码错误")
- return {"token": create_token(), "username": payload.username}
- @app.post("/api/logout")
- def logout(authorization: str | None = Header(default=None)) -> dict[str, Any]:
- token = (authorization or "").removeprefix("Bearer ").strip()
- revoke_token(token)
- return {"ok": True}
- @app.get("/api/auth-config")
- def auth_config() -> dict[str, Any]:
- # 测试模式把默认账号密码给到前端登录页自动填入;生产不返回。
- if IS_TEST:
- return {"mode": APP_MODE, "test": True, "username": AUTH_USER, "password": AUTH_PASSWORD}
- return {"mode": APP_MODE, "test": False}
- # 数据接口需登录;files 里只有 /api/models 需登录,模型文件保持公开。
- app.include_router(alarms.router, dependencies=[Depends(require_auth)])
- app.include_router(fault_map.router, dependencies=[Depends(require_auth)])
- app.include_router(scenes.router, dependencies=[Depends(require_auth)])
- app.include_router(series.router, dependencies=[Depends(require_auth)])
- app.include_router(files.router)
- @app.get("/{full_path:path}")
- def serve_frontend(full_path: str) -> FileResponse:
- if full_path.startswith(("api/", "models/")):
- raise HTTPException(status_code=404, detail="Not Found")
- resolved_dist = FRONTEND_DIST.resolve()
- if full_path:
- candidate = (FRONTEND_DIST / full_path).resolve()
- if resolved_dist in candidate.parents and candidate.is_file():
- return FileResponse(candidate)
- index = FRONTEND_DIST / "index.html"
- if index.is_file():
- return FileResponse(index)
- raise HTTPException(status_code=404, detail="前端未构建:请先 cd 3d/frontend && npm run build")
|