|
|
@@ -1,19 +1,33 @@
|
|
|
"""压缩机 3D 部件查看服务(FastAPI)。
|
|
|
|
|
|
-接口:
|
|
|
- GET /api/models 可用模型列表
|
|
|
- GET /api/state?model=x 读取配色状态
|
|
|
- POST /api/state?model=x 保存配色状态
|
|
|
- GET /models/<file> GLB / glTF 模型文件
|
|
|
+公共接口(无需登录):
|
|
|
+ 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 fastapi import FastAPI, HTTPException
|
|
|
+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)
|
|
|
|
|
|
-from config import FRONTEND_DIST
|
|
|
-from routers import alarms, fault_map, files, scenes, series, state
|
|
|
|
|
|
app = FastAPI(title="压缩机 3D 部件查看服务", version="0.1.0")
|
|
|
app.add_middleware(
|
|
|
@@ -23,12 +37,35 @@ app.add_middleware(
|
|
|
allow_headers=["*"],
|
|
|
)
|
|
|
|
|
|
-app.include_router(alarms.router)
|
|
|
-app.include_router(fault_map.router)
|
|
|
+
|
|
|
+@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.include_router(scenes.router)
|
|
|
-app.include_router(series.router)
|
|
|
-app.include_router(state.router)
|
|
|
|
|
|
|
|
|
@app.get("/{full_path:path}")
|