12 KiB
12 KiB
IM 服务端主动推送消息梳理
本文先按当前代码实现梳理,后续确认产品口径后再合并到《IM-前后端对接文档》。
1. 术语和通道
服务端主动下行分两层:
- Tinode 原生实时消息:通过 WebSocket/Long Polling 下发
{data}、{info}、{pres}、{meta}等帧,覆盖聊天消息、回执、输入中、在线状态、通话信令。 - IM 产品业务事件:HTTP 写操作或业务事务写入
im_business_events_outbox/ 群业务 outbox 后,由服务端投递为{info}帧,前端据此刷新业务接口或局部状态。
当前产品业务事件不是聊天消息,不占普通会话消息 seq。系统通知列表是否出现记录,取决于是否额外写入 im_system_notifications。
2. 产品业务事件统一帧格式
2.1 用户维度事件
来自 im_business_events_outbox,投递到用户自己的在线会话。
{
"info": {
"topic": "usr_target",
"from": "usr_actor",
"what": "im",
"event": "friend.request.accepted",
"payload": {
"user_id": "usr_actor"
},
"ts": "2026-08-25T10:00:00Z"
}
}
字段说明:
| 字段 | 说明 |
|---|---|
info.topic |
目标用户 ID,即事件接收人 |
info.from |
操作人用户 ID;系统事件可能为空 |
info.what |
固定为 im |
info.event |
产品事件类型 |
info.payload |
事件载荷,按事件类型变化 |
投递机制:
- 写入 outbox 后会立即尝试投递。
- Hub 不可用或队列满时保持 pending,后续 poller 每秒按在线租户重试。
- 投递成功后标记 delivered。
registration.invite_used与security.password_reset是内部消费事件,不作为普通 WS 业务事件下发。
2.2 群维度事件
来自群业务 outbox,投递到群 Topic,在线群成员收到。
{
"info": {
"topic": "grp_xxx",
"from": "usr_actor",
"what": "group",
"event": "group.member.joined",
"payload": {
"topic": "grp_xxx",
"user_id": "usr_xxx"
},
"ts": "2026-08-25T10:00:00Z"
}
}
当前群事件投递时,还会给除操作人外的群成员写入一条 im_system_notifications,标题为“群聊动态”,summary 暂为事件类型。
3. 好友和关系事件
| 触发点 | 接收方 | WS 事件 | payload | 系统通知 |
|---|---|---|---|---|
创建好友申请 POST /v1/im/friend-requests |
被申请人 | info.what="im", event="friend.request.created" |
{"user_id":"申请人"} |
有,写给被申请人,标题“新的好友申请” |
同意好友申请 POST /v1/im/friend-requests/{id}/accept |
申请人 | info.what="im", event="friend.request.accepted" |
{"user_id":"通过人"} |
当前有,写给申请人,标题“好友申请已处理” |
| 同意好友申请 | 通过人本人 | info.what="im", event="friend.request.accepted" |
{"user_id":"通过人"} |
当前无 |
拒绝好友申请 POST /v1/im/friend-requests/{id}/reject |
申请人 | info.what="im", event="friend.request.rejected" |
{"user_id":"拒绝人"} |
当前有,写给申请人,标题“好友申请已处理” |
| 拒绝好友申请 | 拒绝人本人 | info.what="im", event="friend.request.rejected" |
{"user_id":"拒绝人"} |
当前无 |
删除好友 DELETE /v1/im/friends/{user_id} |
被删除方 | info.what="im", event="friend.deleted" |
{"user_id":"操作人"} |
有,标题“好友关系已解除” |
| 删除好友 | 操作人本人 | info.what="im", event="friend.deleted" |
{"user_id":"操作人"} |
当前无 |
修改好友备注 PATCH /v1/im/friends/{user_id} |
操作人本人 | info.what="im", event="friend.remark.updated" |
{"user_id":"操作人"} |
当前无 |
拉黑用户 PUT /v1/im/blocks/{user_id} |
操作人本人 | info.what="im", event="relationship.blocked" |
{"user_id":"操作人"} |
当前无 |
取消拉黑 DELETE /v1/im/blocks/{user_id} |
操作人本人 | info.what="im", event="relationship.unblocked" |
{"user_id":"操作人"} |
当前无 |
前端建议动作:
- 收到
friend.request.created:刷新新朋友列表GET /v1/im/friend-requests,刷新GET /v1/im/bootstrap的entry_badges.new_friends_pending_count。 - 收到
friend.request.accepted/rejected:刷新新朋友列表;accepted 时刷新好友列表、P2P 会话 context。 - 收到
friend.deleted/relationship.blocked/relationship.unblocked:刷新好友列表、黑名单列表、会话 context,重新判断输入区can_send。 - 收到
friend.remark.updated:刷新好友列表和会话头显示名。
当前待确认点:
- 好友申请通过/拒绝、删除好友等操作人本人是否也要写入系统消息列表。当前代码只给对方或申请人写系统通知,操作人本人主要靠 WS 业务事件刷新。
- 对接文档现有示例写成
systopic{data}的system_event,但当前代码实际实时推送为{info what:"im"}。后续需要统一口径。
4. 消息编辑和撤回事件
| 触发点 | 接收方 | WS 事件 | payload | 系统通知 |
|---|---|---|---|---|
撤回消息 POST /v1/im/messages/{topic}/{seq}/recall |
Topic 在线成员 | info.what="im", event="message.recalled" |
{"topic":"...","seq":1,"version":2} |
无 |
编辑消息 POST /v1/im/messages/{topic}/{seq}/revisions |
Topic 在线成员 | info.what="im", event="message.edited" |
{"topic":"...","seq":1,"version":2} |
无 |
前端建议动作:
- 收到
message.recalled:按topic + seq + version重新拉取或本地标记该消息为已撤回。 - 收到
message.edited:按topic + seq + version重新拉取消息详情或修订记录,更新气泡内容。
注意:
- 这两个事件当前 target_type 是
topic,投递到 Topic,而不是某一个用户。 - 数据库层和 HTTP 层都可能写入同类 outbox,依赖
event_id幂等避免重复。
5. 群业务事件
| 触发点 | 接收方 | WS 事件 | payload 主要字段 | 系统通知 |
|---|---|---|---|---|
| 创建群 | 群成员 | info.what="group", event="group.created" |
topic、群基础信息 |
除操作人外写入 |
| 更新群设置 | 群成员 | event="group.settings.updated" |
topic |
除操作人外写入 |
| 成员资料/权限更新 | 群主、管理员、本人 | event="group.member.updated" |
user_id、member_sync=single |
不写群系统消息 |
| 成员加入 | 群成员 | event="group.member.joined" |
user_id、topic、member_sync=single |
写入群消息并参与个人未读与离线 Push |
| 入群申请创建 | 群主、管理员、申请人 | event="group.join_request.created" |
request_id、applicant_user_id、topic |
普通成员不可见 |
| 入群申请处理 | 群主、管理员、申请人 | event="group.join_request.accepted/rejected" |
request_id、status、topic |
普通成员不可见;接受时另发全群加入消息 |
| 群公告更新 | 群成员 | event="group.announcement.updated" |
topic、公告信息 |
除操作人外写入 |
| 群禁言更新 | 群主、管理员、被操作人 | event="group.mute.updated" |
topic、禁言信息、member_sync |
普通成员不可见 |
| 成员离群/移除 | 群主、管理员、离群成员 | event="group.member.left" |
user_id、member_sync=remove |
普通成员不可见 |
| 群角色更新 | 群主、管理员、被操作人 | event="group.role.updated" |
user_id、角色信息、member_sync |
普通成员不可见 |
| 群主转让 | 群成员 | event="group.owner.transferred" |
previous_owner、new_owner |
除操作人外写入 |
前端建议动作:
- 首次进入、断线重连、本地无快照或收到
member_sync=full:分页拉取完整群成员。 - 收到
member_sync=single:调用GET /v1/im/groups/{topic}/members/{user_id}只刷新一个成员。 - 收到
member_sync=remove:从本地成员快照删除user_id;若本地状态无法确认,再全量分页同步。 - 入群申请变化:管理员刷新入群申请列表和群 summary 待处理数。
- 群公告变化:刷新公告区。
- 成员加入/离开:刷新群成员数、成员列表和会话系统提示。
注意:
- 当前群事件实时帧
what="group",不是what="im"。 - 群系统通知的 summary 当前主要是事件类型,文案还比较粗,需要产品确认后优化。
6. 会议事件
| 触发点 | 接收方 | WS 事件 | payload | 系统通知 |
|---|---|---|---|---|
| 创建会议 | 所有参会人 | info.what="im", event="meeting.created" |
{"meeting_id":"...","version":1,"state":"scheduled"} |
除操作者外写入,标题“会议通知” |
| 更新会议 | 所有参会人 | event="meeting.updated" |
meeting_id/version/state |
除操作者外写入 |
| 取消会议 | 所有参会人 | event="meeting.canceled" |
meeting_id/version/state |
除操作者外写入 |
| 发送会议提醒 | 待响应参会人 | event="meeting.reminded" |
meeting_id/version/state |
除操作者外写入 |
| 更新参会响应 | 所有参会人 | event="meeting.response.updated" |
meeting_id/version/state |
除操作者外写入 |
前端建议动作:
- 收到会议事件后刷新会议详情或会议列表。
meeting.reminded可触发本地提醒 UI。
7. 安全事件
| 触发点 | 接收方 | WS 事件 | payload | 系统通知 |
|---|---|---|---|---|
| 风险挑战确认/取消 | 操作人本人 | info.what="im", event="security.risk.continue" 或 security.risk.cancel |
`{"risk_challenge_id":"...","decision":"continue | cancel"}` |
| 密码重置成功 | 无普通 WS 事件 | 内部事件 security.password_reset |
{"user_id":123} |
当前用于清退该用户在线 session |
前端建议动作:
- 收到
security.risk.*:刷新安全验证页面状态。 - 密码重置后旧 token/session 会被服务端清退,客户端按登录失效流程处理。
8. 邀请码统计事件
| 触发点 | 接收方 | WS 事件 | payload | 系统通知 |
|---|---|---|---|---|
| 注册成功使用邀请码 | 不下发给普通客户端 | 内部事件 registration.invite_used |
{"tenant_id":1,"invite_code_id":123,"user_id":456} |
无 |
说明:
- 该事件当前由 IM 侧消费,用于把租户库
im_registration_invite_uses聚合回 HotGo 控制库的邀请码统计。 - 普通 App 前端不需要监听。
9. Tinode 原生实时下行
这类不是产品 outbox 事件,但也是服务端主动下行,前端需要持续处理。
| 帧类型 | 触发点 | 接收方 | 前端动作 |
|---|---|---|---|
{data} |
普通聊天消息、文件消息、通话邀请 webrtc=started、服务端替换通话结果消息 |
Topic 订阅者 | 写入/更新会话时间线、更新未读 |
{info what:"read"} |
对端已读回执 | Topic 订阅者 | 更新消息已读状态 |
{info what:"recv"} |
对端送达回执 | Topic 订阅者 | 更新送达状态 |
{info what:"kp"} |
对端输入中 | Topic 订阅者 | 显示输入中状态,超时自动消失 |
{info what:"call"} |
WebRTC ringing/accept/offer/answer/ice-candidate/hang-up |
通话对端 | 驱动通话状态机和 SDP/ICE 交换 |
{pres} |
订阅、离线、在线、权限/订阅变化等 presence | 相关订阅者 | 更新在线状态、订阅状态、必要时刷新列表 |
{meta} |
服务端响应 get/sub 中的 desc/sub/data/del/cred 等查询结果 | 请求方 | 合并服务端状态 |
{ctrl} |
客户端请求结果 | 请求方 | 更新请求状态、错误提示、幂等结果 |
10. 当前文档与实现差异
当前《IM-前后端对接文档》部分位置把好友关系变化描述为 sys topic 的 {data head.x-im-type="system_event"}。代码当前更准确的模型是:
- 实时刷新事件:
{info what:"im"}或{info what:"group"}。 - 系统通知列表:通过
im_system_notifications暴露给GET /v1/im/system-notifications,并不是所有实时事件都会写入。 - 聊天/系统 Topic
{data}仍用于普通消息时间线和 Tinode 原生 Topic 消息,不等同于业务 outbox 实时事件。
建议后续合并到对接文档时明确:
- 前端必须监听所有
{info},按what + event分发业务刷新。 - 系统消息入口的未读数来自
im_system_notifications,不是{info}事件数量。 - 好友申请通过后是否双方都写系统通知,需要产品确认;若确认双方都有,需要同步补代码。