# WebSocket 事件协议 > 通用规范(认证方式、响应格式、错误码)见 [README.md](README.md) > 本文档定义 EchoChat 系统中所有 WebSocket 实时通信事件。 --- ## 连接说明 ### 连接地址 | 环境 | 地址 | |------|------| | 开发环境 | `ws://localhost:8080/ws?token=` | | 生产环境 | `wss://api.echochat.com/ws?token=` | ### 连接认证 通过 URL 查询参数 `token` 携带 JWT Access Token,服务端验证通过后建立连接。 ### 心跳机制 - 客户端每 **30 秒** 发送一次 ping 帧 - 服务端响应 pong 帧 - 如果 **90 秒** 内未收到客户端心跳,服务端主动断开连接 ### 断线重连 - 客户端检测到连接断开后自动重连 - 重连间隔采用指数退避:1s → 2s → 4s → 8s → 16s → 最大 30s - 重连成功后拉取离线消息 --- ## 消息格式 ### 客户端发送格式 ```json { "event": "im.message.send", "seq": 1001, "data": { ... }, "time": "2026-02-27 18:06:40" } ``` | 字段 | 类型 | 说明 | |------|------|------| | event | string | 事件名称,格式:`{模块}.{对象}.{动作}` | | seq | int | 消息序列号,客户端自增,用于匹配请求和响应 | | data | object | 事件数据 | | time | string | 发送时间,格式:`yyyy-MM-dd HH:mm:ss`,时区 Asia/Shanghai | ### 服务端响应格式(ACK) ```json { "event": "im.message.send.ack", "seq": 1001, "code": 0, "message": "ok", "data": { "msg_id": 10086 } } ``` ### 服务端推送格式 ```json { "event": "im.message.new", "data": { ... }, "time": "2026-02-27 18:06:40" } ``` 推送类消息没有 seq 字段(不需要客户端确认)。 --- ## 即时通讯事件 ### im.message.send **方向:** 客户端 → 服务端 **说明:** 发送消息到会话。`conversation_id` 和 `target_user_id` 二选一:首次发消息使用 `target_user_id`(自动创建会话),后续使用 `conversation_id`。 **data 参数:** | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | conversation_id | int | 否 | 已有会话 ID(与 target_user_id 二选一) | | target_user_id | int | 否 | 对方用户 ID(首次发消息时使用) | | type | int | 是 | 消息类型:1=文本 | | content | string | 是 | 文本内容 | | client_msg_id | string | 否 | 客户端消息唯一 ID,用于幂等去重 | **ACK 响应 data:** ```json { "id": 10086, "conversation_id": 1, "sender_id": 1, "type": 1, "content": "你好", "status": 1, "client_msg_id": "xxxx-xxxx", "created_at": "2026-03-03 10:30:00" } ``` --- ### im.message.new **方向:** 服务端 → 客户端(推送) **说明:** 收到新消息推送 **data 内容:** ```json { "id": 10086, "conversation_id": 1, "sender_id": 2, "sender_name": "李四", "sender_avatar": "https://...", "type": 1, "content": "你好", "client_msg_id": "", "created_at": "2026-03-03 10:30:00" } ``` --- ### im.message.recall **方向:** 客户端 → 服务端 **说明:** 撤回消息(发送后 2 分钟内)。撤回成功后若该消息是会话最后一条,会同步更新会话预览为"XX 撤回了一条消息"。 **data 参数:** `{ "message_id": 10086 }` --- ### im.message.recalled **方向:** 服务端 → 客户端(推送) **说明:** 消息被撤回通知 **data 内容:** `{ "message_id": 10086, "conversation_id": 1, "sender_id": 2 }` --- ### im.conversation.read **方向:** 客户端 → 服务端 **说明:** 标记会话已读(清零未读数 + 更新 Redis 全局未读数) **data 参数:** `{ "conversation_id": 1 }` --- ### im.typing **方向:** 双向(客户端发送 → 服务端转发给对方) **说明:** 正在输入通知。客户端发送后服务端转发给对方,前端收到后设置 3 秒超时自动清除。 **data 参数(客户端发送):** `{ "conversation_id": 1 }` **data 内容(服务端推送):** `{ "conversation_id": 1, "user_id": 2 }` --- ### im.offline.sync **方向:** 服务端 → 客户端(推送) **说明:** WebSocket 连接成功后服务端主动推送离线未读摘要 **data 内容:** ```json { "total_unread": 5, "conversations": [ { "conversation_id": 1, "unread_count": 3, "last_msg_content": "你好", "last_msg_time": "2026-03-03 10:30:00" } ] } ``` --- ## 会议信令事件(Phase 2e-2 Task 6 已落地) > **SSOT**:会议相关的全部 WS 事件详细契约(请求/ACK/广播载荷、权限、错误处理、资源追踪)见 [`docs/api/frontend/meeting.md`](./frontend/meeting.md) §WebSocket 信令协议。本节仅列事件总览。 | # | 方向 | 事件 | 用途 | |---|------|------|------| | 1 | C→S | `meeting.room.join` | 声明加入某会议 WS 频道(需已在 REST 层完成 `/join`)| | 2 | C→S | `meeting.room.leave` | 离开 WS 频道并清理媒体资源(不改 participant 表)| | 3 | C→S | `meeting.member.state.changed` | 更新自己的 audio/video/hand_raised;host 可指定 `target_user_id` 强制静音他人 | | 4 | C→S | `meeting.transport.create` | 创建 mediasoup WebRtcTransport(`direction: send/recv`)| | 5 | C→S | `meeting.transport.connect` | 提交 DTLS Parameters | | 6 | C→S | `meeting.produce.start` | 创建 Producer | | 7 | C→S | `meeting.consume.start` | 创建 Consumer | | 8 | C→S | `meeting.producer.close` | 关闭自己的 Producer | | 9 | S→C | `meeting.member.joined` | 新成员加入广播(REST /join 触发)| | 10 | S→C | `meeting.member.left` | 成员离开广播(REST /leave /kick 或 WS 资源清理)| | 11 | S→C | `meeting.member.kicked` | 定向通知被踢者 | | 12 | S→C | `meeting.host.changed` | 主持人变更 | | 13 | S→C | `meeting.room.ended` | 会议被结束 | | 14 | S→C | `meeting.member.state.changed` | 成员状态变化广播 | | 15 | S→C | `meeting.member.producer.new` | 成员开启/关闭媒体流(`closed=true` 表示关闭)| | 16 | S→C | `meeting.chat` | 会议内聊天(REST /chats 触发)| ### 协议约定 - **C→S 白名单**:客户端仅可发起上表 C→S 列的 8 个事件,其余 `meeting.*` 事件若由客户端发送被静默丢弃。 - **ACK 规则**:每个 C→S 事件服务端必回 `.ack`;成功 `code=0`,业务失败 `code=-1` + 中文 `message`(与 REST 领域错误口径一致)。 - **错误码示例**:`会议不存在` / `你当前未在会议中` / `仅主持人可执行此操作` / `会议已结束`。 - **资源追踪**:服务端对每用户在每会议的 transport/producer/consumer 用 Redis Set `echo:meeting:resources:{room_id}:{user_id}` 记录,WS 断开或 `room.leave` 时自动清理。 --- ## 用户状态事件 ### user.status.online **方向:** 服务端 → 客户端 **说明:** 好友上线通知 **data 内容:** `{ "user_id": 2, "nickname": "李四" }` --- ### user.status.offline **方向:** 服务端 → 客户端 **说明:** 好友离线通知 **data 内容:** `{ "user_id": 2 }` --- ## 通知事件 ### notify.new **方向:** 服务端 → 客户端 **说明:** 新通知推送(通用) **data 内容:** 与 Notify API 获取通知列表中的单条通知格式一致 --- ### notify.meeting.invite **方向:** 服务端 → 客户端 **说明:** 会议邀请推送 **data 内容:** ```json { "room_code": "123-456-789", "title": "产品需求讨论", "from_user_id": 1, "from_nickname": "张三" } ``` --- ### notify.friend.request **方向:** 服务端 → 客户端 **说明:** 好友申请推送 **data 内容:** ```json { "friendship_id": 5, "from_user_id": 2, "from_nickname": "李四", "from_avatar": "https://...", "message": "我是你的同事" } ``` --- ## 群聊事件(Phase 2c) ### im.group.read **方向:** 客户端 → 服务端 **说明:** 标记群聊消息已读(消息级已读回执) **data 内容:** ```json { "conversation_id": 100, "message_ids": [501, 502, 503] } ``` **ACK 响应:** `{ "code": 0, "message": "ok" }` --- ### group.created **方向:** 服务端 → 客户端 **说明:** 群聊创建成功,推送给所有初始成员 **data 内容:** ```json { "group_id": 10, "conversation_id": 100, "name": "项目讨论组", "owner_id": 1 } ``` --- ### group.info.update **方向:** 服务端 → 客户端 **说明:** 群信息更新(名称、头像、公告等),推送给所有群成员 **data 内容:** ```json { "group_id": 10, "conversation_id": 100, "operator_id": 1 } ``` --- ### group.dissolved **方向:** 服务端 → 客户端 **说明:** 群聊已解散,推送给所有群成员 **data 内容:** ```json { "group_id": 10, "conversation_id": 100, "operator_id": 1 } ``` --- ### group.member.join **方向:** 服务端 → 客户端 **说明:** 新成员加入群聊(邀请加入或审批通过),推送给所有群成员 **data 内容:** ```json { "group_id": 10, "conversation_id": 100, "user_ids": [5, 6] } ``` --- ### group.member.kicked **方向:** 服务端 → 客户端 **说明:** 成员被移出群聊,推送给所有群成员 **data 内容:** ```json { "group_id": 10, "conversation_id": 100, "user_id": 5, "operator_id": 1 } ``` --- ### group.member.leave **方向:** 服务端 → 客户端 **说明:** 成员主动退出群聊,推送给其余群成员 **data 内容:** ```json { "group_id": 10, "conversation_id": 100, "user_id": 5 } ``` --- ### group.role.update **方向:** 服务端 → 客户端 **说明:** 成员角色变更(设为/取消管理员),推送给所有群成员 **data 内容:** ```json { "group_id": 10, "conversation_id": 100, "user_id": 5, "new_role": 1, "operator_id": 1 } ``` > `new_role`:0=普通成员,1=管理员,2=群主 --- ### group.mute.update **方向:** 服务端 → 客户端 **说明:** 禁言状态变更(个人禁言或全体禁言),推送给所有群成员 **data 内容(个人禁言):** ```json { "group_id": 10, "conversation_id": 100, "user_id": 5, "is_muted": true, "operator_id": 1 } ``` **data 内容(全体禁言):** ```json { "group_id": 10, "conversation_id": 100, "is_all_muted": true, "operator_id": 1 } ``` --- ### group.owner.transfer **方向:** 服务端 → 客户端 **说明:** 群主转让,推送给所有群成员 **data 内容:** ```json { "group_id": 10, "conversation_id": 100, "old_owner_id": 1, "new_owner_id": 5 } ``` --- ### group.join.request **方向:** 服务端 → 客户端 **说明:** 新的入群申请,推送给群主和管理员 **data 内容:** ```json { "group_id": 10, "request_id": 20, "user_id": 8, "user_nickname": "新用户", "message": "请让我加入" } ```