处理断线 await manager.handle_disconnect(room_id, color, username)4.1.2 消息类型

处理断线 await manager.handle_disconnect(room_id, color, username)4.1.2 消息类型 类型方向数据说明move客户端→服务器落子pass客户端→服务器虚手undo客户端→服务器{}悔棋resign客户端→服务器认输territory_request客户端→服务器{}请求点目state_update服务器→客户端状态更新game_over服务器→客户端游戏结束error服务器→客户端错误消息4.1.3 消息处理async def handle_websocket_message(room_id: str,username: str,data: dict,websocket: WebSocket):处理WebSocket消息消息类型- move: 落子- pass: 虚手- undo: 悔棋- resign: 认输- territory_request: 点目请求msg_type data.get(type)game manager.games.get(room_id)if msg_type move:# 验证轮次color data.get(color)if not _is_player_turn(game, color):await websocket.send_json({type: error,message: 不是你的回合})return# 执行落子x, y data[x], data[y]success game.place_stone(x, y)if success:# 广播状态更新await manager.broadcast_state(room_id)# 检查游戏是否结束if game.game_over:await manager.handle_game_over(room_id)# AI落子if game.ai_enabled and not game.game_over:await manager.schedule_ai_move(room_id)else:await websocket.send_json({type: error,message: 非法落子})elif msg_type pass:# 执行虚手game.pass_turn()# 检查是否双pass游戏结束if game.consecutive_passes 2:await manager.handle_game_over(room_id)else:await manager.broadcast_state(room_id)elif msg_type undo:# 执行悔棋success game.undo_move()if success:await manager.broadcast_state(room_id)else:await websocket.send_json({type: error,message: 无法悔棋})elif msg_type resign:# 处理认输winner W if data[color] black else Bawait manager.handle_game_over(room_id, winnerwinner, reasonresign)elif msg_type territory_request:# 执行点目result game.calculate_final_score()await websocket.send_json({type: territory_result,data: result})4.1.4 积分计算def calculate_score_change(winner_username: str,loser_username: str) - Tuple[int, int]:计算积分变化规则- 同段位胜方1负方-1- 不同段位- 高段位胜不加积分- 高段位负-1- 低段位胜1- 低段位负不减积分- AI固定为9段不计算积分- 10段积分到100后胜不加负减1返回(胜方积分变化, 负方积分变化)winner get_user(winner_username)loser get_user(loser_username)# AI不计算积分if winner_username.startswith(AI_) or loser_username.startswith(AI_):return 0, 0winner_rank winner[rank]loser_rank loser[rank]if winner_rank loser_rank:# 同段位return 1, -1elif winner_rank loser_rank:# 高段位胜if winner[score] 100 and winner_rank 10:return 0, -1return 0, -1else:# 低段位胜return 1, 04.2 routers/game.py - 游戏路由文件位置:routers/game.py文件大小: 13261 字节 (362 行)功能: 游戏相关路由4.2.1 端点列表方法路径功能权限GET/api/online-users获取在线用户登录GET/api/generate-room生成房间ID登录GET/api/user-status用户对局状态登录POST/api/invite发送对弈邀请登录POST/api/accept-invite接受邀请登录POST/api/reject-invite拒绝邀请登录GET/api/games/history棋谱历史登录GET/api/games/{id}/sgf下载SGF登录4.2.2 核心实现router.get(/api/online-users)async def get_online_users(token: str Query(...)):获取在线用户列表username decode_token(token)if not username:raise HTTPException(status_code401, detail未登录)users manager.get_online_users()return {users: users}router.post(/api/invite)async def send_invite(data: InviteRequest,token: str Query(...)):发送对弈邀请流程1. 验证token2. 检查目标用户是否在线3. 发送邀请通知WebSocketusername decode_token(token)if not username:raise HTTPException(status_code401, detail未登录)# 检查目标用户if data.to not in manager.online_users:raise HTTPException(status_code404, detail用户不在线)# 发送邀请websocket manager.online_users[data.to]await websocket.send_json({type: invite,from: username,room_id: data.room_id})return {message: 邀请已发送}router.get(/api/games/history)async def get_game_history(token: str Query(...)):获取棋谱历史username decode_token(token)if not username:raise HTTPException(status_code401, detail未登录)games get_user_games(username)return {games: games}router.get(/api/games/{game_id}/sgf)async def download_sgf(game_id: int, token: str Query(...)):下载SGF棋谱username decode_token(token)if not username:raise HTTPException(status_code401, detail未登录)sgf_content get_game_sgf(game_id)if not sgf_content:raise HTTPException(status_code404, detail棋谱不存在)return Response(contentsgf_content,media_typeapplication/x-go-sgf,headers{Content-Disposition: fattachment; filenamegame_{game_id}.sgf})4.3 routers/auth.py - 认证路由文件位置:routers/auth.py文件大小: 3307 字节 (107 行)功能: 认证相关路由4.3.1 端点列表方法路径功能权限POST/api/register用户注册公开POST/api/login用户登录公开POST/api/change-password修改密码登录POST/api/update-email更新邮箱登录POST/api/request-reset-password请求重置密码公开POST/api/reset-password重置密码公开4.3.2 核心实现router.post(/api/register)async def register(data: RegisterRequest):用户注册请求{username: player1,password: secure123,email: player1example.com}响应{message: 注册成功,username: player1}success register_user(data.username, data.password, data.email)if not success:raise HTTPException(status_code400, detail用户名已存在)return {message: 注册成功, username: data.username}router.post(/api/login)async def login(data: LoginRequest):用户登录请求{username: player1,password: secure123}响应{access_token: eyJ...,token_type: bearer,username: player1,role: user,is_vip: false}token login_user(data.username, data.password)if not token:raise HTTPException(status_code401, detail用户名或密码错误)user get_user(data.username)return {access_token: token,token_type: bearer,username: data.username,role: user[role],is_vip: is_vip(data.username)}4.4 routers/admin.py - 管理员路由文件位置:routers/admin.py文件大小: 3268 字节 (98 行)功能: 管理员相关路由4.4.1 端点列表方法路径功能权限GET/api/admin/users用户列表管理员PUT/api/admin/users/{username}/role修改角色管理员DELETE/api/admin/users/{username}删除用户管理员GET/api/admin/games棋谱列表管理员DELETE/api/admin/games/{id}删除棋谱管理员POST/api/admin/init初始化管理员公开4.5 routers/vip.py - VIP路由文件位置:routers/vip.py文件大小: 6017 字节 (171 行)功能: VIP相关路由4.5.1 端点列表方法路径功能权限GET/api/vip/infoVIP信息登录POST/api/vip/create-order创建订单登录GET/api/vip/order-status订单状态登录POST/api/vip/notify支付回调微信GET/api/vip/settings功能开关管理员4.6 routers/doc.py - 文档路由文件位置:routers/doc.py文件大小: 3900 字节 (105 行)功能: 文档管理路由4.6.1 端点列表方法路径功能权限POST/api/doc/markdown/upload上传文档登录GET/api/doc/markdown/files文档列表登录GET/api/doc/markdown/{id}获取文档登录PUT/api/doc/markdown/{id}更新文档所有者DELETE/api/doc/markdown/{id}删除文档管理员POST/api/doc/markdown/{id}/approve审核文档管理员4.6.2 核心实现router.post(/api/doc/markdown/upload)async def upload_markdown(data: DocMarkdownUpload, token: str):上传Markdown文档请求{filename: tutorial.md,content: # 教程\n\n...}username check_doc_permission(token)file_id upload_doc_markdown(data.filename, data.content, username)return {message: 上传成功, file_id: file_id}router.put(/api/doc/markdown/{file_id})async def update_markdown(file_id: int, data: DocMarkdownUpdate, token: str):更新Markdown文档权限仅文档所有者可编辑username check_doc_permission(token)file_data get_doc_markdown_content(file_id)if not file_data:raise HTTPException(status_code404, detail文件不存在)# 权限检查if file_data.get(uploaded_by) ! username:raise HTTPException(status_code403, detail只有文件所有者可以编辑)success update_doc_markdown(file_id, data.content)if not success:raise HTTPException(status_code500, detail更新失败)return {message: 更新成功}4.7 routers/deps.py - 路由依赖项文件位置:routers/deps.py文件大小: 1278 字节 (36 行)功能: 路由依赖项_manager: ConnectionManager Nonedef init_manager(manager: ConnectionManager):初始化连接管理器global _manager_manager managerdef get_manager() - ConnectionManager:获取连接管理器return _managerasync def get_current_user(token: str Query(...)) - str:获取当前用户username decode_token(token)if not username:raise HTTPException(status_code401, detail未登录)return usernameasync def require_admin(token: str Query(...)) - str:要求管理员权限username decode_token(token)if not username:raise HTTPException(status_code401, detail未登录)if not is_admin(username):raise HTTPException(status_code403, detail需要管理员权限)return username五、前端实现5.1 static/script.js - 前端主逻辑 ⭐核心文件位置:static/script.js文件大小: 194157 字节 (4371 行)功能: 前端主逻辑脚本5.1.1 核心模块1. WebSocket连接管理let ws null;let reconnectTimer null;function connectWebSocket(roomId) {/*** 连接WebSocket** 流程* 1. 创建WebSocket连接* 2. 设置事件处理器* 3. 启动心跳检测*/const wsUrl ws://${window.location.host}/ws/${roomId}?token${authToken};ws new WebSocket(wsUrl);ws.onopen function() {console.log(WebSocket连接成功);startHeartbeat();};ws.onmessage function(event) {const data JSON.parse(event.data);handleMessage(data);};ws.onclose function() {console.log(WebSocket断开);scheduleReconnect();};ws.onerror function(error) {console.error(WebSocket错误:, error);};}function startHeartbeat() {/*** 心跳检测* 每30秒发送一次ping*/setInterval(() {if (ws ws.readyState WebSocket.OPEN) {ws.send(JSON.stringify({type: ping}));}}, 30000);}