Skip to content

WebSocket API 文档

项目:对面是AI吗
协议:WebSocket
数据格式:JSON(文本帧)
基础URL:wss://game.xfcode.top
心跳:客户端发送 {"type":"ping"},服务端回复 {"type":"pong"},10s 间隔,65s 超时断开
更新日期:2026-08-10


通用约定

消息格式

所有消息均为 JSON 文本帧,以 type 字段区分消息类型:

json
{"type": "消息类型", ...其他字段}

通用消息类型

所有游戏模式均支持以下通用消息:

type方向说明
pingC→S心跳请求
pongS→C心跳响应
get_stickersC→S获取表情包差异更新
stickers_listS→C表情列表(有变更时)
stickers_unchangedS→C表情无变化
errorS→C通用错误 {"type":"error","message":"..."}

认证机制

所有 WebSocket 端点共用同一套身份认证体系,由 BaseGameHandler 统一实现。

连接阶段(onOpen)

WebSocket 连接建立时不进行身份验证,仅做以下检查:

  1. IP 提取 — 优先级:CF-Connecting-IP > X-Forwarded-For > X-Real-IP > remote_addr
  2. IP 去重(可选,默认开启)— 同一 IP 已有活跃连接时关闭旧连接
  3. IP 级封禁检查 — 调用 BanRepository::isBanned(),被封则推送 {"type":"error","message":"您已被管理员封禁"} 并关闭连接
  4. 全服公告推送 — 如有生效中的 Redis 广播,推送给新连接

真正的身份验证发生在客户端发送第一条业务消息时(如 joinlobby_joingomoku_joinWhoisAI_match)。

业务消息认证(三级验证)

所有需要身份的消息均携带以下字段(三选一即可):

字段说明优先级
player_token已有的 JWT Token(老玩家)最高
password密码(新玩家注册/换设备恢复)
nickname昵称(兼容旧数据,需同设备 IP+FP 匹配)最低

Level 1:Token 验证(老玩家优先)

Client → Server: {type: "join", nickname: "小明", player_token: "eyJ..."}
  • Token 格式:base64(json_payload).hmac_sha256_sig
  • Payload 结构:{player_id, exp(365天), iat, jti}
  • HMAC 密钥:玩家在数据库中的 password_hash(bcrypt)
  • 验证内容:签名正确性 + 过期时间 + player_id 有效性

Token 可用跨模式复用 — 同一 player_id 可在 /ws/ws/lobby/ws/gomoku/ws/WhoisAI 之间通用。

Level 2:密码验证(新玩家/换设备)

Client → Server: {type: "join", nickname: "小明", password: "mypassword"}
  • 按昵称查数据库,password_verify($password, $record['password_hash'])
  • 密码正确 → 生成新 Token 并返回
  • 密码错误 → 返回 {"type":"error","message":"密码不正确"},不中断连接,可重试
  • 昵称不存在 → 视为新玩家注册,自动创建身份

Level 3:仅昵称(兼容旧迁移数据)

Client → Server: {type: "join", nickname: "小明"}
  • 按昵称查数据库
  • 存在且同设备(IP + Fingerprint 匹配)→ 允许复用旧账号,生成新 Token
  • 存在但设备不同 → 返回 {"type":"error","message":"该昵称已被占用,请换一个或输入密码"}
  • 不存在 → 视为新玩家,自动创建

新玩家注册

当昵称在数据库中不存在时,自动触发注册流程:

  1. 检查同 IP 账号数 ≤ 3(超出则拒绝)
  2. 调用 PlayerStatsRepository::createPlayer(nickname, ip, fp, password) 创建记录
  3. 密码使用传入的 password 或随机生成 bin2hex(random_bytes(8))
  4. BCrypt cost 10 加密存储
  5. 生成 JWT Token 返回给客户端

在线唯一性

同一 player_id 通过 Redis SETNX 锁定(120s TTL),防止多地同时登录:

  • 登录时 claimOnlineLock() 尝试获取锁
  • 连接关闭时自动释放
  • 获取锁失败 → 关闭已有连接,推送 {"type":"system","text":"该账号已在其他地方登录..."}

验证失败处理汇总

端点消息类型失败响应 type失败示例连接
/wsjoinerror{"type":"error","message":"密码不正确"}不断开
/ws/lobbylobby_joinlobby_error{"type":"lobby_error","text":"密码不正确"}不断开
/ws/gomokugomoku_joingomoku_error{"type":"gomoku_error","data":"身份验证失败"}不断开
/ws/WhoisAIWhoisAI_matchWhoisAI_error{"type":"WhoisAI_error","text":"密码不正确"}不断开
全部onOpenerror{"type":"error","message":"您已被管理员封禁"}close
全部online locksystem{"type":"system","text":"该账号已在其他地方登录..."}close

Token 机制(内部)

生成: base64(json_encode({player_id, exp(365天), iat, jti(随机8字节)})) . "." . hmac_sha256(payload, password_hash)
验证: 解析 payload + sig → 查 DB 获取 password_hash → 重新计算 HMAC 对比
密钥: 每个玩家独立的 bcrypt password_hash
有效期: 365 天

1. "对面是AI吗" 主游戏

WebSocket 路径/ws
HandlerGameWebSocketHandler

1.1 通用消息

error

方向:S→C

json
{"type":"error","message":"ERROR_MESSAGE"}

ping / pong

C→S

json
{"type":"ping"}

S→C

json
{"type":"pong"}

说明:心跳机制,每 10 秒一次。65 秒无心跳则断开连接。


1.2 匹配与对局流程

join(加入匹配)

方向:C→S

json
{
  "type": "join",
  "nickname": "小明",
  "duration": 600,
  "fingerprint": "可选的设备指纹",
  "password": "密码(新玩家创建身份或恢复身份时)",
  "player_token": "已有的 token(已登录时)",
  "reconnect_session_id": "重连恢复时的 session_id"
}
字段类型必填说明
nicknamestring1-16 字符
durationint对局时长:300(5分钟)或 600(10分钟)
fingerprintstring设备指纹
passwordstring条件新玩家创建身份或恢复时
player_tokenstring条件已有 token 时,与 password 二选一
reconnect_session_idstring断线重连恢复

说明:加入匹配队列。身份验证成功后进入等待。


matched(匹配成功)

方向:S→C

json
{
  "type": "matched",
  "opponent_name": "小红",
  "duration": 600,
  "session_id": "sess_abc123",
  "player_id": "player_xxx",
  "token": "eyJ..."
}
字段说明
opponent_name对手昵称(可能为 AI 别名)
duration对局时长
session_id对局会话 ID
player_id你的玩家 ID
tokenJWT Token(新玩家首次匹配时下发)

说明:匹配成功后自动推送,开始聊天阶段。


message(发送聊天消息)

方向:C→S

json
{
  "type": "message",
  "text": "你好,请问你是 AI 吗?"
}
字段类型必填说明
textstring消息内容,最多 300 字符

触发条件:对局状态为 chatting(聊天阶段)。

对手收到

json
{"type":"message","text":"你好,请问你是 AI 吗?","sender":"对方"}

旁观者收到

json
{"type":"spectate_message","text":"...","sender":"玩家1","side":"left"}

sticker(发送表情)

方向:C→S

json
{"type":"sticker","id":"表情ID"}

对手收到

json
{"type":"sticker","id":"xxx","name":"大笑","url":"/stickers/xxx.png","sender":"对方"}

judge(判定)

方向:C→S

json
{
  "type": "judge",
  "guess": "human",
  "tag": "有逻辑"
}
字段类型必填说明
guessstring"human"(认为是真人)或 "ai"(认为是 AI)
tagstring标签,用于风格画像

触发条件:聊天阶段结束进入判定阶段。

对手判定进度通知(对手收到):

json
{
  "type": "judge_notify",
  "message": "对方已做出判断,你需要在 60 秒内完成判定,否则判负",
  "seconds_remaining": 60
}

判定结果(双方都判定完成后,双方收到):

json
{
  "type": "judged",
  "truth": "human",
  "opponent_guess": "ai",
  "opponent_tag": "机器人",
  "session_id": "sess_abc123",
  "opponent_name": "小红",
  "player_id": "player_xxx"
}
字段说明
truth你的真实身份:"human""ai"
opponent_guess对手对你的判断
opponent_tag对手给你的标签

leave(主动离开)

方向:C→S

json
{"type":"leave"}

对手收到

json
{"type":"system","text":"对方已离开"}

然后:

json
{
  "type":"timeout",
  "reason":"opponent_left",
  "opponent_truth":"ai",
  "session_id":"sess_abc123",
  "opponent_name":"小红"
}

leave_result(确认离开结果页)

方向:C→S

json
{"type":"leave_result","session_id":"sess_abc123"}

说明:查看对战结果后确认离开,不主动断连,仅标记离开意向用于跨进程同步清理。


save_history(保存聊天记录)

方向:C→S

json
{"type":"save_history","session_id":"sess_abc123"}

响应

json
{"type":"save_history_status","success":true,"message":"聊天记录已保存","id":123}

leave_message(留言)

方向:C→S

json
{"type":"leave_message","text":"打得很精彩!"}

响应

json
{"type":"leave_message_status","success":true,"message":"留言已保存"}

report(举报)

方向:C→S

json
{"type":"report","reason":"对方使用了不文明用语"}
字段类型必填说明
reasonstring举报原因,最多 100 字符

响应

json
{"type":"report_result","success":true,"message":"举报已提交,管理员将尽快处理"}

update_nickname(更新昵称)

方向:C→S

json
{
  "type": "update_nickname",
  "nickname": "新昵称",
  "fp": "设备指纹"
}

响应

json
{"type":"update_nickname_result","success":true}
// 或
{"type":"update_nickname_result","error":"昵称已被占用"}

1.3 服务器主动推送

timeout(超时)

方向:S→C

json
{
  "type": "timeout",
  "reason": "chat_expired",
  "session_id": "sess_abc123",
  "opponent_name": "小红"
}
reason 值说明
chat_expired聊天时间到,进入判定阶段
you_timeout你判定超时,判负
opponent_timeout对手判定超时,你判胜
both_timeout双方判定超时,平局
opponent_left对手离开了对局
no_mutual_chat60 秒内双方无互发消息,对局终止

system(系统消息,对局内)

方向:S→C

json
{"type":"system","text":"已进入判定阶段,请判断对方是真人还是 AI"}

1.4 管理员消息

admin_ban(管理员封禁)

方向:C→S

json
{"type":"admin_ban","token":"管理员token","reason":"违规行为"}

被禁者收到

json
{"type":"banned","text":"你已被管理员封禁。原因:违规行为"}

admin_verify(验证管理员)

方向:C→S

json
{"type":"admin_verify","token":"管理员token"}

响应

json
{"type":"admin_config","ws_url":"/admin/ws","super_admin":true}

2. 大厅聊天室

WebSocket 路径/ws/lobby
HandlerLobbyChatWebSocketHandler

2.1 连接建立

服务端推送(连接成功后):

json
{"type":"lobby_history","messages":[...]}
{"type":"lobby_online_count","players":[{"name":"小明","fd":1},...]}

2.2 用户消息

lobby_join(加入聊天室)

方向:C→S

json
{
  "type": "lobby_join",
  "nickname": "小明",
  "password": "密码",
  "player_token": "已有 token"
}

响应

json
{"type":"lobby_joined","nickname":"小明","token":"eyJ..."}

广播(在线人数变化时):

json
{"type":"lobby_online_count","players":[...]}

广播(进入通知):

json
{"type":"lobby_system","text":"小明 进入了聊天室"}

lobby_chat(发送聊天消息)

方向:C→S

json
{
  "type": "lobby_chat",
  "content": "大家好!",
  "reply_to_id": 123,
  "reply_to_name": "小红",
  "reply_to_text": "原始消息内容"
}
字段类型必填说明
contentstring消息内容
reply_to_idint回复的消息 ID
reply_to_namestring回复对象的昵称
reply_to_textstring回复消息的原文摘要

触发条件:已加入聊天室 + 未被禁言 + 通过频率限制。

广播

json
{
  "type": "lobby_chat",
  "id": 456,
  "sender_name": "小明",
  "sender_id": "player_xxx",
  "content": "大家好!",
  "reply_to": {"id":123,"name":"小红","text":"原始消息内容"},
  "mentions": ["小红"],
  "time": "14:30",
  "created_at": "2026-08-10 14:30:00"
}

@提醒(被 @ 的用户收到):

json
{
  "type": "lobby_mentioned",
  "message_id": 456,
  "sender_name": "小明",
  "content": "大家好!"
}

lobby_sticker(发送表情)

方向:C→S

json
{"type":"lobby_sticker","id":"表情ID"}

广播

json
{
  "type": "sticker",
  "id": "xxx",
  "name": "大笑",
  "url": "/stickers/xxx.png",
  "sender": "小明",
  "sender_id": "player_xxx"
}

lobby_nudge(拍一拍)

方向:C→S

json
{"type":"lobby_nudge","target_fd":123,"target_nickname":"小红"}

目标收到

json
{"type":"lobby_nudged","sender_name":"小明"}

广播

json
{"type":"lobby_system","text":"小明 拍了拍 小红"}

lobby_revoke(撤回消息)

方向:C→S

json
{"type":"lobby_revoke","message_id":456}

限制:只能撤回自己 3 分钟内的消息。

广播

json
{"type":"lobby_revoke","message_id":456,"sender_name":"小明"}

lobby_report(举报消息)

方向:C→S

json
{"type":"lobby_report","message_id":456,"reason":"不文明用语"}

响应

json
{"type":"lobby_report_ok","message":"举报已提交,管理员将尽快处理"}

2.3 点歌系统

lobby_song_search(搜索歌曲)

方向:C→S

json
{"type":"lobby_song_search","keyword":"周杰伦"}

响应

json
{"type":"lobby_song_search_result","songs":[{...}]}

lobby_song_request(点歌)

方向:C→S

json
{"type":"lobby_song_request","song_id":"xxx","nickname":"小明"}

响应

json
{"type":"lobby_song_requested","song":{...}}

lobby_song_vote / lobby_song_remove_vote(投票/取消投票)

json
{"type":"lobby_song_vote","song_id":"xxx"}
json
{"type":"lobby_song_remove_vote","song_id":"xxx"}

广播

json
{"type":"lobby_vote_update","song_id":"xxx","votes":5}
{"type":"lobby_remove_vote_update","song_id":"xxx","remove_votes":3}

lobby_song_list / lobby_song_current(歌单查询)

方向:C→S

json
{"type":"lobby_song_list"}
{"type":"lobby_song_current"}

响应(歌单)

json
{"type":"lobby_song_list","playlist":[...],"pool":[...],"playing":{...}}

响应(当前播放)

json
{"type":"lobby_song_current","song":{...}}
// 或等待中
{"type":"lobby_song_current","waiting":true}

广播(列表更新)

json
{"type":"list_update","playlist":[...],"pool":[...],"playing":{...}}

2.4 管理员消息

lobby_delete(管理员删消息)

json
{"type":"lobby_delete","message_id":456}

广播

json
{"type":"lobby_message_deleted","message_id":456}

lobby_mute(管理员禁言)

json
{"type":"lobby_mute","target_fd":123,"minutes":10}

目标收到

json
{"type":"lobby_system","text":"你已被管理员禁言 10 分钟"}

广播

json
{"type":"lobby_system","text":"小明 已被管理员禁言 10 分钟"}

lobby_ban(管理员封禁)

json
{"type":"lobby_ban","target_fd":123,"reason":"违规"}

执行封禁并断开目标 WebSocket 连接。


lobby_admin_verify(管理员验证)

json
{"type":"lobby_admin_verify","token":"管理员token"}

响应

json
{
  "type": "lobby_admin_verified",
  "is_admin": true,
  "username": "admin",
  "role": "super_admin",
  "super_admin": true
}

3. "谁是AI"

WebSocket 路径/ws/WhoisAI
HandlerWhoisAIWebSocketHandler

3.1 连接建立

S→C(连接成功后):

json
{"type":"WhoisAI_connected"}

3.2 匹配与游戏流程

WhoisAI_match(加入匹配)

方向:C→S

json
{
  "type": "WhoisAI_match",
  "nickname": "小明",
  "password": "密码",
  "player_token": "已有 token",
  "fp": "设备指纹"
}
字段类型必填说明
nicknamestring1-12 字符
passwordstring条件新玩家或恢复时
player_tokenstring条件已有 token 时
fpstring设备指纹

响应(匹配成功,开房)

json
{
  "type": "WhoisAI_matched",
  "pool_count": 5,
  "nickname": "小明",
  "token": "eyJ..."
}

广播(给匹配池其他人)

json
{"type":"WhoisAI_pool_count","pool_count":5}

匹配规则

  • 满 6 人立即开局
  • 满 4 人等待 3 秒后开局

WhoisAI_cancel_match(取消匹配)

方向:C→S

json
{"type":"WhoisAI_cancel_match"}

响应

json
{"type":"WhoisAI_match_cancelled"}

WhoisAI_connect_ack(连接确认)

方向:C→S

json
{"type":"WhoisAI_connect_ack","room_id":"room_xxx"}

说明:连接检查阶段,全部在线玩家确认后进入讨论。超时未确认的会被标记离线,不影响开局。


WhoisAI_connect_check(开局通知)

方向:S→C

json
{
  "type": "WhoisAI_connect_check",
  "room_id": "room_xxx",
  "room_code": "ABCDE",
  "identity": "human",
  "seat": 1,
  "players": [
    {"seat":1,"name":"玩家1"},
    {"seat":2,"name":"玩家2"},
    {"seat":3,"name":"玩家3"},
    {"seat":4,"name":"玩家4"},
    {"seat":5,"name":"玩家5"},
    {"seat":6,"name":"玩家6"}
  ],
  "player_count": 6
}
字段说明
identity你的身份:"human""ai"
seat你的座位号,1-6
players所有玩家列表(匿名,不透露身份)
room_code房间码,用于旁观

WhoisAI_phase_discussion(讨论阶段开始)

方向:S→C

json
{
  "type": "WhoisAI_phase_discussion",
  "room_id": "room_xxx",
  "round": 1,
  "duration": 180,
  "identity": "human",
  "my_seat": 1,
  "players": [
    {"seat":1,"name":"玩家1","avatar":"..."}
  ]
}

同时广播系统消息

json
{"type":"WhoisAI_system","text":"第 1 轮讨论开始,你有 180 秒时间找出 AI"}

WhoisAI_chat(讨论发言)

方向:C→S

json
{"type":"WhoisAI_chat","text":"我觉得 3 号玩家很可疑"}
字段类型必填说明
textstring消息内容,最多 300 字符

触发条件:玩家存活 + 房间状态为讨论中。

广播

json
{
  "type": "WhoisAI_message",
  "sender_seat": 1,
  "sender_name": "玩家1",
  "text": "我觉得 3 号玩家很可疑",
  "time": "14:30"
}

WhoisAI_sticker(发送表情)

方向:C→S

json
{"type":"WhoisAI_sticker","id":"表情ID"}

广播

json
{
  "type": "WhoisAI_sticker",
  "id": "xxx",
  "name": "困惑",
  "url": "/stickers/xxx.png",
  "sender_seat": 1,
  "sender_name": "玩家1"
}

WhoisAI_phase_voting(投票阶段开始)

方向:S→C

json
{
  "type": "WhoisAI_phase_voting",
  "room_id": "room_xxx",
  "round": 1,
  "duration": 30,
  "candidates": [
    {"seat":1,"name":"玩家1"},
    {"seat":2,"name":"玩家2"},
    {"seat":3,"name":"玩家3"}
  ]
}

WhoisAI_vote(投票淘汰)

方向:C→S

json
{"type":"WhoisAI_vote","target_seat":3}
字段类型必填说明
target_seatint要投出局的玩家座位号,不能投自己且目标必须存活

响应(投票确认)

json
{"type":"WhoisAI_vote_ok","target_seat":3}

广播(全局进度)

json
{"type":"WhoisAI_vote_progress","voted_count":3,"alive_count":5}

WhoisAI_vote_result(投票结果)

方向:S→C

有人被淘汰

json
{
  "type": "WhoisAI_vote_result",
  "eliminated_seat": 3,
  "eliminated_name": "玩家3",
  "identity": "ai",
  "text": "玩家3 被投票淘汰,真实身份是 AI!",
  "players": [
    {"seat":1,"name":"玩家1","alive":true}
  ]
}

平票无人淘汰

json
{"type":"WhoisAI_system","text":"投票结果为平票,本轮无人淘汰"}

WhoisAI_game_over(游戏结束)

方向:S→C

json
{
  "type": "WhoisAI_game_over",
  "room_id": "room_xxx",
  "winner": "human",
  "text": "所有 AI 已被淘汰,人类胜利!",
  "reason": "vote",
  "players": [
    {"seat":1,"name":"玩家1","identity":"human","alive":true}
  ],
  "my_seat": 1,
  "player_id": "player_xxx",
  "messages": [...]
}
字段说明
winner"human"(人类胜)或 "ai"(AI 胜)
reason结束原因:"vote"(投票结束)、"disconnect"(断线)
players所有玩家的完整身份信息

WhoisAI_report(举报)

方向:C→S

json
{
  "type": "WhoisAI_report",
  "target_name": "玩家3",
  "message_text": "不当言论内容",
  "reason": "违规"
}

响应

json
{"type":"WhoisAI_report_ok","message":"举报已提交,管理员将尽快处理"}

3.3 断线处理

  • 匹配中掉线 → 自动离开匹配池
  • 连接确认阶段掉线 → 不影响开局(标记离线)
  • 对局中掉线 → 自动淘汰,广播 WhoisAI_system
  • 仅剩人类/AI → 对应的另一方获胜

4. 五子棋

WebSocket 路径/ws/gomoku
HandlerGomokuWebSocketHandler

4.1 连接建立

连接建立后仅初始化连接记录(IP、去重、封禁检查),不发送额外消息。


4.2 游戏消息

gomoku_join(加入五子棋)

方向:C→S

json
{
  "type": "gomoku_join",
  "fp": "设备指纹",
  "nickname": "小明",
  "password": "密码",
  "player_token": "已有 token"
}

响应

json
{"type":"gomoku_joined","data":{"token":"eyJ..."}}

gomoku_create_room(创建房间)

方向:C→S

json
{
  "type": "gomoku_create_room",
  "firstMove": 1,
  "boardSize": 15
}
字段类型必填说明
firstMoveint先手:1=黑先,2=让对手先
boardSizeint棋盘大小,5-30

触发条件:已通过 gomoku_join 验证身份。

响应

json
{"type":"gomoku_room_created","data":{"roomId":"ABCDE","color":1}}
字段说明
roomId5位房间码,用于分享和加入
color你的棋子颜色:1=黑棋,2=白棋

gomoku_join_room(加入房间)

方向:C→S

json
{"type":"gomoku_join_room","roomId":"ABCDE"}

触发条件:已通过 gomoku_join 验证身份。

场景一:作为对手加入,发送给双方

房主收到:

json
{
  "type": "gomoku_game_start",
  "data": {
    "roomId": "ABCDE",
    "settings": {"boardSize":15,"firstMove":1},
    "currentTurn": 1,
    "myColor": 1
  }
}

加入者收到:

json
{
  "type": "gomoku_game_start",
  "data": {
    "roomId": "ABCDE",
    "settings": {"boardSize":15,"firstMove":1},
    "currentTurn": 1,
    "myColor": 2
  }
}

场景二:作为旁观者加入

json
{
  "type": "gomoku_spectate_start",
  "data": {
    "roomId": "ABCDE",
    "settings": {"boardSize":15},
    "board": [[0,0,...],[0,0,...]],
    "currentTurn": 1
  }
}

gomoku_place_piece(落子)

方向:C→S

json
{"type":"gomoku_place_piece","r":7,"c":7}
字段类型必填说明
rint行坐标(0-based)
cint列坐标(0-based)

广播(双方+旁观者)

json
{
  "type": "gomoku_piece_placed",
  "data": {"r":7,"c":7,"color":1,"nextTurn":2}
}

gomoku_game_over(游戏结束)

方向:S→C

json
{
  "type": "gomoku_game_over",
  "data": {
    "winner": 1,
    "reason": "win",
    "winPath": [[3,3],[4,4],[5,5],[6,6],[7,7]]
  }
}
字段说明
winner1=黑棋胜,2=白棋胜,0=平局
reason"win"(五连)、"认输""超时""opponent_disconnected"
winPath获胜的 5 个棋子坐标

gomoku_surrender(认输)/ gomoku_timeout(超时)

方向:C→S

json
{"type":"gomoku_surrender"}
json
{"type":"gomoku_timeout"}

广播

json
{
  "type": "gomoku_game_over",
  "data": {"winner":2,"reason":"认输","winPath":[]}
}

gomoku_request_rematch(请求重赛)

方向:C→S

json
{"type":"gomoku_request_rematch"}

说明:双方都发送 gomoku_request_rematch 后自动重新开局。

重新开局

  • 双方角色互换,收到 gomoku_game_start
  • 旁观者收到 gomoku_spectate_start

gomoku_cancel_wait(取消等待)

方向:C→S

json
{"type":"gomoku_cancel_wait"}

说明:房主取消等待,删除房间。


gomoku_chat_message(局内聊天)

方向:C→S

json
{
  "type": "gomoku_chat_message",
  "msg": "好棋!"
}
字段类型必填说明
msgstring消息内容,最多 300 字符

广播(对手+旁观者,不含发送者)

json
{
  "type": "gomoku_chat_message",
  "data": {"msg":"好棋!","from":1,"time":"14:30"}
}
字段说明
from1=黑棋方,2=白棋方

gomoku_error(错误)

方向:S→C

json
{"type":"gomoku_error","data":"错误描述"}

4.3 断线处理

对局中

  • 断线方自动判负
  • 对手收到 gomoku_opponent_disconnected
  • 旁观者收到 gomoku_opponent_disconnected
  • 房间自动删除
  • 双方战绩异步写入

对局已结束:仅清理连接绑定。


5. 管理后台 WebSocket

WebSocket 路径/{adminPath}/ws(默认 /admin/ws

管理后台 WebSocket 主要用于管理员操作(封禁管理、广播、用户管理、观战等),具体协议不在本文档范围内,详见 api-admin.md。


附录:WebSocket 路径与 Handler 对照表

WebSocket 路径Handler前缀说明
/wsGameWebSocketHandler"对面是AI吗" 主游戏
/ws/lobbyLobbyChatWebSocketHandlerlobby_大厅聊天室
/ws/WhoisAIWhoisAIWebSocketHandlerWhoisAI_"谁是AI"
/ws/gomokuGomokuWebSocketHandlergomoku_五子棋
/{adminPath}/wsAdminWebSocketHandler管理后台
最近更新