Initial commit: 网页联机狼人杀(FastAPI+WebSocket 后端 / React+TS+Sass 前端)

This commit is contained in:
hhhge committed 2026-10-03 20:47:44 +08:00
commit 7f5ea6bee8
55 files changed
+6999

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
"""FastAPI 入口:REST 接口 + WebSocket 端点 + 托管前端构建产物。"""
from __future__ import annotations
from pathlib import Path
import uvicorn
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from pydantic import BaseModel
from .config import CONFIG
from .game.room import MANAGER
app = FastAPI(title="狼人杀", version="0.1.0")
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
ROOT = Path(__file__).resolve().parents[2]
DIST = ROOT / "frontend" / "dist"
# ---------------------------------------------------------------- REST
class CreateRoomReq(BaseModel):
board: str = "classic12"
host_name: str = "房主"
@app.get("/api/health")
async def health():
return {
"status": "ok",
"llm_configured": CONFIG.llm.configured,
"model": CONFIG.llm.model if CONFIG.llm.configured else None,
}
@app.get("/api/boards")
async def boards():
from .game.board import BOARDS
return [{"key": b.key, "name": b.name, "seat_count": b.seat_count}
for b in BOARDS.values()]
@app.post("/api/rooms")
async def create_room(req: CreateRoomReq):
code = MANAGER.create(req.board)
return {"code": code}
# ---------------------------------------------------------------- WebSocket
@app.websocket("/ws/{code}")
async def websocket_endpoint(ws: WebSocket, code: str):
await ws.accept()
room = MANAGER.get(code)
if room is None:
await ws.send_json({"type": "error", "message": "房间不存在或已解散"})
await ws.close(code=4404)
return
name = (ws.query_params.get("name") or "玩家").strip()[:12] or "玩家"
token = ws.query_params.get("token") or None
member, err = await room.attach(ws, name, token)
await room.send_snapshot_to(member)
if err:
await room.send_error(member, err)
try:
while True:
try:
data = await ws.receive_json()
except ValueError:
continue
await room.handle_message(member, data)
except WebSocketDisconnect:
await room.detach(member, ws)
except Exception:
await room.detach(member, ws)
# ---------------------------------------------------------------- 静态前端
@app.get("/{full_path:path}", include_in_schema=False)
async def spa(full_path: str):
if full_path.startswith("api/") or full_path == "api":
raise HTTPException(status_code=404)
if full_path and (DIST / full_path).is_file():
return FileResponse(DIST / full_path)
index = DIST / "index.html"
if index.is_file():
return FileResponse(index)
raise HTTPException(status_code=404, detail="前端未构建:请先在 frontend 目录执行 pnpm build")
def main() -> None:
uvicorn.run(app, host=CONFIG.server.host, port=CONFIG.server.port)
if __name__ == "__main__":
main()