feat: add redis cache and fanout support
@@ -0,0 +1,595 @@
|
||||
# IM Redis 三层数据分层改造方案
|
||||
|
||||
日期:2026-08-25
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
当前 IM 服务需要接入 Redis,但不希望出现“进程内缓存一份、Redis 缓存一份、MySQL 再存一份”的重复状态,避免后续一致性维护困难。
|
||||
|
||||
本方案按业务数据特征拆成三层:
|
||||
|
||||
- 进程内:只放低频变更配置和本节点运行态。
|
||||
- Redis:放小体量、高频读取、需要跨节点一致的热数据。
|
||||
- MySQL:放最终持久化数据、事务数据、审计数据和大体量列表。
|
||||
|
||||
第一阶段不把消息正文作为 Redis 的长期存储。万人大群可以把 24 小时内热消息放入 Redis 作为读加速,私聊消息最终来源是租户库 `im_messages`,群聊消息最终来源是租户库 `im_group_messages`。
|
||||
|
||||
## 2. 核心原则
|
||||
|
||||
1. 同一类业务状态只确定一个主读取层,不在进程内和 Redis 同时维护两套可变状态。
|
||||
2. 进程内缓存只承载低频配置或本机连接态,不承载跨节点业务状态。
|
||||
3. 在线、离线、最近活跃等跨节点状态统一从 Redis 查询。
|
||||
4. MySQL 仍是需要持久化、事务和审计的数据最终来源。
|
||||
5. Redis 缓存 MySQL 数据时采用 cache-aside 模式,写入以 MySQL 事务成功为准。
|
||||
6. Redis key 必须带租户标识,虽然业务库是一租户一库,但 Redis 是共享基础设施,需要避免不同租户数据串扰。
|
||||
|
||||
## 3. 三层数据分类
|
||||
|
||||
| 层级 | 数据类型 | 典型数据 | 读写特点 | 一致性要求 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 进程内 | 静态或低频配置、本机运行态 | 系统配置、字典、租户配置、OSS/短信/内容安全/声网配置、本机 WebSocket 连接、topic channel | 启动加载,定时刷新;连接态只在本机维护 | 配置允许秒级延迟;连接态只代表本节点 |
|
||||
| Redis | 小体量热数据、跨节点状态、短期状态 | 用户在线状态、用户基础信息、群基础信息、群成员集合、好友/黑名单集合、24 小时热消息、验证码、临时 token、限流计数 | 高频读,TTL 或主动失效 | 跨节点查询一致,允许短暂最终一致 |
|
||||
| MySQL | 持久化和事务数据 | 消息、文件记录、群申请审批、举报、审核任务、操作日志、账务类数据、大分页列表 | 事务写入,分页查询,审计追溯 | 最终权威来源 |
|
||||
|
||||
## 4. 进程内层设计
|
||||
|
||||
### 4.1 适合放进程内的数据
|
||||
|
||||
- 系统配置:后台 hotgo 中的系统参数。
|
||||
- 字典配置:`hg_sys_dict_type`、`hg_sys_dict_data` 等字典项。
|
||||
- 租户配置:租户启停、基础配置、默认策略。
|
||||
- 第三方配置:OSS、阿里云内容安全、短信、声网等接入参数。
|
||||
- 本节点运行态:当前进程内的 WebSocket session、local topic channel、本机订阅关系、本机连接数。
|
||||
|
||||
### 4.2 刷新策略
|
||||
|
||||
- 服务启动时加载一次配置。
|
||||
- 定时刷新,建议 60 秒。
|
||||
- 后台配置变更后,后续可补充主动失效或广播刷新。
|
||||
- 配置读取失败时保留旧值,并打印告警日志。
|
||||
|
||||
### 4.3 边界说明
|
||||
|
||||
进程内层不保存用户在线状态的全局结果。进程内只知道“本节点有哪些连接”,跨节点判断用户是否在线必须查 Redis。
|
||||
|
||||
## 5. Redis 层设计
|
||||
|
||||
### 5.1 第一阶段必须接入
|
||||
|
||||
#### 用户在线状态
|
||||
|
||||
在线状态是 Redis 第一优先级改造内容。原因是 WebSocket 长连接固定落在某个节点上,本机内存可以管理本机连接,但其他节点无法直接知道该用户是否在线。
|
||||
|
||||
建议 key:
|
||||
|
||||
```text
|
||||
im:{tenant_id}:presence:user:{uid}
|
||||
im:{tenant_id}:presence:user:{uid}:sessions
|
||||
im:{tenant_id}:presence:node:{node_id}:users
|
||||
```
|
||||
|
||||
`presence:user:{uid}` 建议使用 hash:
|
||||
|
||||
```text
|
||||
online = 1
|
||||
node_id = 当前节点 ID
|
||||
session_count = 当前会话数
|
||||
last_active_at = 最近活跃时间
|
||||
last_seen_at = 最近可见时间
|
||||
device = 设备类型
|
||||
ua = 客户端 UA
|
||||
```
|
||||
|
||||
生命周期:
|
||||
|
||||
1. WebSocket 登录成功后写入 Redis 在线状态。
|
||||
2. 心跳或业务包刷新 TTL。
|
||||
3. 断开连接时扣减 session 数,最后一个 session 断开后标记离线。
|
||||
4. 节点异常退出时依赖 TTL 自动过期,避免永久在线。
|
||||
5. 其他节点查询用户在线状态时只查 Redis。
|
||||
|
||||
TTL 建议:
|
||||
|
||||
- 在线状态 hash:60 到 120 秒。
|
||||
- session 集合:60 到 120 秒,随心跳刷新。
|
||||
- node 用户集合:可不强依赖 TTL,用于辅助排查和节点清理。
|
||||
|
||||
当前代码已经接入基础 PresenceStore:
|
||||
|
||||
- 登录成功后写入 `presence:user:{uid}`、`presence:user:{uid}:sessions`、`presence:node:{node_id}:users`。
|
||||
- 客户端每次上行业务包刷新 `last_active_at` 和 TTL。
|
||||
- session 清理时移除当前 session,最后一个 session 离线后将 `online` 置为 `0`。
|
||||
- 对外提供 `store.GetUserPresence` 作为后续用户在线状态查询入口。
|
||||
- 群 Topic 在线成员已同步维护 `topic:{topic}:online_members` 和 `topic:{topic}:node:{node_id}:online_members`,订阅/离开时更新,客户端心跳或任意上行业务包会刷新已订阅群的在线成员 TTL。
|
||||
- 订阅列表中的用户在线状态已改为 Redis 优先读取;Redis 未启用或读取失败时,回退到原有进程内在线判断。
|
||||
|
||||
#### 短期状态
|
||||
|
||||
以下数据适合放 Redis,减少 MySQL 压力并天然支持过期:
|
||||
|
||||
- 注册验证码、登录验证码。
|
||||
- 邀请码短期校验结果。
|
||||
- 企业码登录临时 token。
|
||||
- 登录失败次数、风控计数、接口限流计数。
|
||||
- 幂等 key、短期操作锁。
|
||||
|
||||
### 5.2 第二阶段建议接入
|
||||
|
||||
#### 用户基础信息
|
||||
|
||||
适合缓存:
|
||||
|
||||
- 用户昵称、头像、状态签名。
|
||||
- 用户公开资料。
|
||||
- 用户账号到 UID 的映射。
|
||||
|
||||
不建议缓存:
|
||||
|
||||
- 密码 hash。
|
||||
- 高敏认证字段。
|
||||
- KYC 原始材料。
|
||||
|
||||
读写规则:
|
||||
|
||||
1. 查询先读 Redis。
|
||||
2. 未命中读 MySQL。
|
||||
3. MySQL 命中后写入 Redis。
|
||||
4. 用户资料变更时先提交 MySQL,再删除或更新 Redis。
|
||||
|
||||
#### 群相关数据
|
||||
|
||||
群数据体量相对可控,且聊天过程中读取频繁,适合逐步放入 Redis:
|
||||
|
||||
- 群基础信息:群名、头像、简介、人数、群类型。
|
||||
- 群配置:入群审批、禁言策略、邀请权限、公告权限。
|
||||
- 群成员集合:成员 UID 集合。
|
||||
- 群成员角色:群主、管理员、普通成员。
|
||||
- 群成员状态:禁言、移出、黑名单。
|
||||
|
||||
建议 key:
|
||||
|
||||
```text
|
||||
im:{tenant_id}:group:{group_id}:profile
|
||||
im:{tenant_id}:group:{group_id}:settings
|
||||
im:{tenant_id}:group:{group_id}:members
|
||||
im:{tenant_id}:group:{group_id}:admins
|
||||
im:{tenant_id}:group:{group_id}:muted
|
||||
```
|
||||
|
||||
#### 关系数据
|
||||
|
||||
好友、黑名单、关注类关系适合用 Redis set 承载热读:
|
||||
|
||||
```text
|
||||
im:{tenant_id}:relation:friends:{uid}
|
||||
im:{tenant_id}:relation:blocklist:{uid}
|
||||
```
|
||||
|
||||
用于快速判断:
|
||||
|
||||
- 是否好友。
|
||||
- 是否被拉黑。
|
||||
- 是否允许发起会话。
|
||||
- 是否允许查看在线状态或最近在线时间。
|
||||
|
||||
### 5.3 第三阶段可选接入
|
||||
|
||||
- 首页 bootstrap 聚合结果:短 TTL 缓存,减少多表聚合查询。
|
||||
- 群成员分页快照深度优化:基础 HTTP 结果快照已接入,后续大群可继续补 sorted set、稳定游标和主动失效。
|
||||
- 未读数深度优化:基础总未读 Redis 热缓存已接入,后续可继续补 Topic 维度未读计数、bootstrap 聚合缓存和主动失效事件。
|
||||
|
||||
### 5.4 万人大群实时推送专项
|
||||
|
||||
万人大群的实时推送需要和 Redis 改造一起考虑。核心目标是避免普通小群模式下的全量 fanout 压垮服务端,同时保证在线用户实时可达、离线用户通过极光 Push 提醒、客户端重连后可按 seq 补齐消息。
|
||||
|
||||
该部分作为 Redis 改造的近期实现项处理,不再仅作为未实现增强项记录。
|
||||
|
||||
#### 数据分层
|
||||
|
||||
| 层级 | 承载内容 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 进程内 | 本节点在线连接、本节点 topic 订阅、本节点待发送队列 | 只负责当前节点连接和发送,不保存全局群成员在线状态。 |
|
||||
| Redis | 群在线成员索引、用户所在节点、群分片、原子 seq 发号器、24 小时热消息、短期去重 key | 用于判断哪些在线用户需要实时推送、原子获取群消息 seq,以及近期高频消息读取加速。 |
|
||||
| MySQL 租户库 | `im_group_messages` 群消息正文、群消息 seq 结果、消息索引、离线同步游标、审计数据 | 作为群消息最终存储,保存 Redis 分配后的 seq,不负责实时发号。 |
|
||||
| 极光 Push | 离线提醒 | 只做提醒,不承载消息正文最终一致性。 |
|
||||
|
||||
#### 建议 Redis key
|
||||
|
||||
```text
|
||||
im:{tenant_id}:presence:user:{uid}
|
||||
im:{tenant_id}:topic:{topic}:online_members
|
||||
im:{tenant_id}:topic:{topic}:node:{node_id}:online_members
|
||||
im:{tenant_id}:topic:{topic}:shard:{shard_id}:members
|
||||
im:{tenant_id}:topic:{topic}:last_seq
|
||||
im:{tenant_id}:topic:{topic}:hot_msg_seqs
|
||||
im:{tenant_id}:topic:{topic}:hot_msg:{seq}
|
||||
im:{tenant_id}:topic:{topic}:uniq:msg:{from_uid}:{client_msg_id}
|
||||
im:{tenant_id}:push:dedup:{topic}:{seq}:{uid}
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `topic:online_members` 保存当前群在线成员集合,可由用户上线、订阅群、退订群、断开连接维护。
|
||||
- `topic:node:{node_id}:online_members` 用于跨节点推送时按节点拆分,减少广播范围。
|
||||
- `topic:shard:{shard_id}:members` 可用于万人群成员分片,后续支持分批扫描和补偿。
|
||||
- `topic:last_seq` 是群消息 seq 发号器,使用 Redis `INCR` 或 Lua 脚本原子获取下一个 seq。
|
||||
- `topic:hot_msg_seqs` 使用 sorted set 保存 24 小时内热消息 seq,score 为 seq 或发送时间。
|
||||
- `topic:hot_msg:{seq}` 保存单条热消息正文快照,TTL 建议 24 小时。
|
||||
- `topic:uniq:msg:{from_uid}:{client_msg_id}` 保存群消息发送唯一 key,用于客户端重试幂等和服务端去重。
|
||||
- `push:dedup` 用于极光 Push 或实时推送去重,TTL 建议按分钟级设置。
|
||||
|
||||
#### 唯一 key 与幂等规则
|
||||
|
||||
Redis 唯一 key 用于挡住短时间内的重复提交,MySQL 唯一索引作为最终兜底。不能只依赖 Redis 做永久唯一性,因为 Redis key 会过期,也可能被运维清理。
|
||||
|
||||
群消息发送唯一 key:
|
||||
|
||||
```text
|
||||
im:{tenant_id}:topic:{topic}:uniq:msg:{from_uid}:{client_msg_id}
|
||||
```
|
||||
|
||||
建议 value:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "pending|done|failed",
|
||||
"seq": 12345,
|
||||
"message_id": 987654321,
|
||||
"created_at": "2026-08-25T12:00:00.000+08:00"
|
||||
}
|
||||
```
|
||||
|
||||
处理规则:
|
||||
|
||||
1. 客户端必须传 `client_msg_id`,同一发送人、同一 Topic 内唯一。
|
||||
2. 服务端收到群消息后先用 `SET key value NX EX ttl` 抢占唯一 key,初始状态为 `pending`。
|
||||
3. 抢占成功才继续 Redis `INCR` 获取群 `seq`。
|
||||
4. 抢占失败时读取 key:
|
||||
- `done`:直接返回已生成的 `seq/message_id`。
|
||||
- `pending`:返回处理中或短暂等待后重试,避免重复发号。
|
||||
- `failed`:允许客户端用同一个 `client_msg_id` 重试,或要求重新生成 `client_msg_id`。
|
||||
5. 消息写入 `im_group_messages` 成功后,将唯一 key 更新为 `done`,记录 `seq/message_id`。
|
||||
6. 消息写入失败时,将唯一 key 更新为 `failed` 并设置较短 TTL;已经获取的 `seq` 不回退,允许空洞。
|
||||
7. `im_group_messages` 仍保留 `UNIQUE(topic, from_user, client_msg_id)`,防止 Redis key 过期后重复落库。
|
||||
|
||||
TTL 建议:
|
||||
|
||||
- `pending`:30 到 120 秒。
|
||||
- `done`:24 小时到 7 天,按客户端重试窗口决定。
|
||||
- `failed`:5 到 30 分钟。
|
||||
- Push 去重 key:5 到 30 分钟。
|
||||
|
||||
#### Seq 发号规则
|
||||
|
||||
- 群消息 seq 由 Redis 维护,不从 MySQL 查询最大 seq。
|
||||
- 每个 Topic 独立一个 `topic:last_seq`,使用 `INCR im:{tenant_id}:topic:{topic}:last_seq` 原子获取。
|
||||
- 只要求单 Topic 内单调递增,不要求全局连续。
|
||||
- 如果取到 seq 后消息持久化失败,该 seq 可以形成空洞;客户端按 seq 补拉时允许跳过服务端确认不存在的 seq。
|
||||
- Topic 创建或 Redis key 丢失时,当前代码使用 topic 内存态 `lastID` 作为 Redis 初始化基线;该值来自 topic 加载结果或当前进程已确认的最新 seq,然后再执行原子 `INCR`。
|
||||
- Redis 不可用时不能用数据库临时发号继续写大群消息,应返回可重试错误,避免多源发号造成 seq 冲突。
|
||||
|
||||
#### 发送流程
|
||||
|
||||
1. 服务端收到群消息后,先完成权限校验和禁言校验。
|
||||
2. 使用 Redis 唯一 key 抢占 `topic + from_uid + client_msg_id`,防止客户端重试重复发送。
|
||||
3. 抢占成功后,使用 Redis `INCR` 或 Lua 脚本从 `topic:last_seq` 原子获取下一个群 `seq`。
|
||||
4. 消息正文带着该 `seq` 写入租户库 `im_group_messages`。
|
||||
5. 落库成功后将唯一 key 更新为 `done`,记录 `seq/message_id`。
|
||||
6. 对万人大群或高热群,将消息快照写入 `topic:hot_msg:{seq}`,并把 seq 加入 `topic:hot_msg_seqs`,TTL 保持 24 小时。
|
||||
7. 查询 Redis 中该群在线成员,按 `node_id` 分组。
|
||||
8. 本节点在线成员直接从进程内连接表推送。
|
||||
9. 其他节点在线成员通过 Redis Pub/Sub 通知目标节点推送;当前代码使用 `im:0:group_fanout` 广播精简后的 `ServerComMessage`,订阅节点收到后投递到本机 `hub.routeSrv`,只发送给本节点已加载 Topic 的在线连接。
|
||||
10. 不在线成员不做服务端全量离线消息 fanout,只更新离线游标或依赖消息 seq 补拉,并触发极光 Push。
|
||||
11. 客户端重连后按 Topic seq 拉取缺失消息,优先读 Redis 热消息,未命中再回源租户库 `im_group_messages`。
|
||||
|
||||
#### 读取流程
|
||||
|
||||
客户端消息读取仍以 seq 为准,Redis 只优化 24 小时内的热读:
|
||||
|
||||
1. 在线实时收到消息后,客户端本地落库并更新本地最大 seq。
|
||||
2. 重连、切换设备或发现 seq 不连续时,调用历史消息接口按 `topic + after_seq` 补拉。
|
||||
3. 服务端先按 seq 范围读取 Redis 热消息。
|
||||
4. Redis 命中的消息直接返回;缺失、过期或不连续的 seq 回源租户库 `im_group_messages`。
|
||||
5. 回源命中后可按策略重新写入 Redis,保持 24 小时 TTL。
|
||||
6. 极光 Push 唤醒后,客户端仍走正常登录和 seq 补拉链路。
|
||||
|
||||
#### 热消息缓存边界
|
||||
|
||||
- 只缓存 24 小时内高频读取的群消息,普通群可不开启或按群规模阈值开启。
|
||||
- Redis 中的消息快照不作为审计和历史同步最终来源。
|
||||
- 删除、撤回、编辑消息后,必须同步删除或更新对应 `topic:hot_msg:{seq}`,避免客户端读到旧正文。
|
||||
- 大消息、文件二进制、音视频内容不直接放 Redis,只缓存消息元数据和 OSS object key。
|
||||
- Redis 容量按租户和群设置上限,超过阈值时优先淘汰低热群或旧 seq。
|
||||
- 当前代码只在 Redis 能完整命中一页群消息时直接返回;如果 Redis 缺失、过期、不连续或读取带有用户软删除视角,则回源 MySQL,避免缓存缺口导致消息缺失或读到用户已删除内容。
|
||||
|
||||
#### 离线 Push 口子
|
||||
|
||||
极光 Push 只用于离线提醒,建议 payload 只放最小必要信息:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "group_message",
|
||||
"topic": "grp_xxx",
|
||||
"seq": 12345,
|
||||
"tenant_id": "tenant_xxx"
|
||||
}
|
||||
```
|
||||
|
||||
不建议在 Push payload 中放完整消息正文,避免隐私泄露、厂商通道截断和多端一致性问题。
|
||||
|
||||
#### 一致性边界
|
||||
|
||||
- Redis 的在线成员集合允许短暂不准,最终由心跳 TTL 和订阅变更修正。
|
||||
- Redis 唯一 key 负责短期幂等和去重,MySQL 唯一索引负责最终唯一性兜底。
|
||||
- seq 是否已分配以 Redis `topic:last_seq` 为准;群消息是否真实存在,以租户库 `im_group_messages` 为准。
|
||||
- Redis 发号成功但消息持久化失败时,允许 seq 空洞,不回退 `topic:last_seq`。
|
||||
- 实时推送失败不影响消息持久化,通过客户端 seq 补拉恢复;Redis 热消息缺失时回源租户库 `im_group_messages`。
|
||||
- 极光 Push 失败不影响消息存储,只影响离线提醒,需要记录失败并可观测。
|
||||
- Redis Pub/Sub 只负责在线实时投递,不作为可靠消息队列;节点短暂断线错过实时消息时,客户端仍以 Topic seq 补拉恢复。
|
||||
|
||||
## 6. MySQL 层设计
|
||||
|
||||
### 6.1 必须保留在 MySQL 的数据
|
||||
|
||||
- 私聊消息正文、私聊消息索引、离线同步游标。
|
||||
- 群消息正文、群消息索引、群消息审计数据。
|
||||
- 文件记录、OSS object key、文件安全审核结果。
|
||||
- 群邀请、进群申请、审批记录。
|
||||
- 群公告、公告确认、公告历史。
|
||||
- 用户认证、KYC 记录、企业注册资料。
|
||||
- 举报、投诉、内容安全审核任务。
|
||||
- 操作日志、审计日志、管理后台变更记录。
|
||||
- 需要事务一致性的业务状态。
|
||||
|
||||
### 6.2 消息表拆分
|
||||
|
||||
当前 `im_messages` 后续只作为私聊、文件助手等非群聊消息的主存储。群聊消息新增租户库业务表 `im_group_messages`,避免所有消息挤在一张表里,也方便后续针对大群按 `topic + seq`、时间、热度做分区、归档和缓存。
|
||||
|
||||
建议表结构:
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS im_group_messages (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
topic VARCHAR(64) NOT NULL COMMENT '群 Topic,如 grp_xxx',
|
||||
group_id BIGINT NOT NULL COMMENT '业务群 ID,便于分区、治理和统计',
|
||||
seqid BIGINT NOT NULL COMMENT '群内消息序号,由 Redis 原子发号',
|
||||
from_user BIGINT NOT NULL COMMENT '发送人用户 ID',
|
||||
client_msg_id VARCHAR(64) NULL COMMENT '客户端幂等消息 ID',
|
||||
msg_type VARCHAR(32) NOT NULL DEFAULT 'text' COMMENT 'text/image/file/audio/video/system 等',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'normal' COMMENT 'normal/recalled/deleted/blocked',
|
||||
content JSON NULL COMMENT '消息正文或 Drafty 内容',
|
||||
head JSON NULL COMMENT '消息头,包含 @、引用、客户端能力等',
|
||||
mentioned_user_ids JSON NULL COMMENT '@ 用户列表,便于提醒和检索',
|
||||
mention_all TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否 @ 全体',
|
||||
reply_seqid BIGINT NULL COMMENT '回复的群消息 seq',
|
||||
root_seqid BIGINT NULL COMMENT '线程或合并上下文根消息 seq',
|
||||
file_count INT NOT NULL DEFAULT 0 COMMENT '附件数量',
|
||||
content_size INT NOT NULL DEFAULT 0 COMMENT '正文 JSON 序列化后的字节数',
|
||||
has_risk TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否命中风控或内容安全',
|
||||
audit_status VARCHAR(32) NOT NULL DEFAULT 'pass' COMMENT 'pass/pending/reject',
|
||||
version INT NOT NULL DEFAULT 1 COMMENT '编辑版本',
|
||||
recalled_at DATETIME(3) NULL,
|
||||
recalled_by BIGINT NULL,
|
||||
deleted_at DATETIME(3) NULL,
|
||||
expires_at DATETIME(3) NULL COMMENT '可选保留期,便于过期归档或清理',
|
||||
created_at DATETIME(3) NOT NULL,
|
||||
updated_at DATETIME(3) NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_im_group_messages_group_seq (group_id, seqid),
|
||||
UNIQUE KEY uk_im_group_messages_topic_seq (topic, seqid),
|
||||
UNIQUE KEY uk_im_group_messages_topic_sender_client_mid (topic, from_user, client_msg_id),
|
||||
KEY idx_im_group_messages_group_created (group_id, created_at),
|
||||
KEY idx_im_group_messages_group_status_seq (group_id, status, seqid),
|
||||
KEY idx_im_group_messages_topic_created (topic, created_at),
|
||||
KEY idx_im_group_messages_topic_from_seq (topic, from_user, seqid),
|
||||
KEY idx_im_group_messages_audit (audit_status, created_at),
|
||||
KEY idx_im_group_messages_expires (expires_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
```
|
||||
|
||||
设计说明:
|
||||
|
||||
- `seqid` 使用 `BIGINT`,避免大群长期高频消息导致 `INT` 不够用。
|
||||
- `group_id + seqid` 和 `topic + seqid` 都保持唯一,业务治理优先用 `group_id`,协议补拉仍可用 `topic`。
|
||||
- `client_msg_id` 用于发送幂等,索引带上 `topic`,避免不同群复用客户端消息 ID 时互相冲突。
|
||||
- `status` 标记普通、撤回、删除、拦截状态,拉历史时可以返回占位或过滤。
|
||||
- `mentioned_user_ids`、`mention_all` 单独存,方便 @ 提醒、离线 Push 和后台治理,不必每次解析正文。
|
||||
- `audit_status`、`has_risk` 为内容安全和风控留字段,高风险消息可先 pending 后再放行。
|
||||
- `reply_seqid`、`root_seqid` 为引用回复、线程、合并转发上下文预留。
|
||||
- 文件二进制不进表,表内只保存消息内容和 OSS object key 等元数据;群消息附件会同步登记到 `im_group_files`,并记录 `source_topic/source_seq` 追溯来源消息。
|
||||
- 超大规模租户后续可按 `group_id` hash 分表,或按 `created_at/expires_at` 做归档分区。
|
||||
|
||||
读写规则:
|
||||
|
||||
1. 私聊消息继续写 `im_messages`。
|
||||
2. 群聊消息写 `im_group_messages`。
|
||||
3. 群消息发号由 Redis `topic:last_seq` 原子递增完成,`im_group_messages.seqid` 只保存结果。
|
||||
4. Redis 24 小时热消息命中时直接返回;未命中按 `topic + seqid` 回源 `im_group_messages`。
|
||||
5. 撤回、编辑、删除群消息时,以 `im_group_messages` 为准,并同步更新或删除 Redis 热消息。
|
||||
|
||||
### 6.3 消息正文说明
|
||||
|
||||
第一阶段不把消息正文作为 Redis 长期存储。万人大群和高热群允许缓存 24 小时内热消息,作为客户端重连补拉、最近历史翻页和多端同步的读加速。
|
||||
|
||||
消息正文主要读取场景:
|
||||
|
||||
- 新设备登录后的历史消息同步。
|
||||
- 离线用户重新上线后的消息补拉。
|
||||
- 客户端本地消息缺失后的重拉。
|
||||
- 举报、风控、内容安全复查。
|
||||
- 管理后台审计或客服处理。
|
||||
|
||||
这些场景的最终来源仍是 MySQL 的消息表。私聊回源 `im_messages`,群聊回源 `im_group_messages`。Redis 热消息只覆盖短期高频读取,命中则减少数据库压力,未命中必须回源。
|
||||
|
||||
## 7. 读写一致性规则
|
||||
|
||||
### 7.1 进程内配置
|
||||
|
||||
- 进程启动加载配置。
|
||||
- 定时刷新配置。
|
||||
- 配置读取失败时使用旧值。
|
||||
- 配置变更允许短时间延迟生效。
|
||||
|
||||
### 7.2 Redis 运行态
|
||||
|
||||
- 在线状态以 Redis 为跨节点查询来源。
|
||||
- 本机内存只用于管理本机连接。
|
||||
- Redis 写入失败时,在线状态降级为未知或离线,不阻断基础连接能力。
|
||||
- 心跳持续刷新 TTL,节点异常时依赖 TTL 自动回收。
|
||||
|
||||
### 7.3 Redis 缓存 MySQL 热数据
|
||||
|
||||
- 读:先 Redis,未命中再 MySQL,最后回填 Redis。
|
||||
- 写:先 MySQL 事务提交,再删除或更新 Redis。
|
||||
- 消息 seq:群聊先通过 Redis `INCR` 或 Lua 脚本原子获取 seq,再带 seq 写租户库 `im_group_messages`;私聊仍按现有 `im_messages` 链路处理。
|
||||
- 消息热缓存:消息持久化成功后,再写 Redis 24 小时热消息;热消息写失败不影响消息发送成功。
|
||||
- Redis 故障:直接回源 MySQL,记录降级日志和指标。
|
||||
- 不允许因为 Redis 写失败回滚已经成功的 MySQL 业务事务,除非该业务本身强依赖 Redis 状态。
|
||||
|
||||
### 7.4 MySQL 持久化
|
||||
|
||||
- 需要审计、事务、历史追溯的数据直接写 MySQL。
|
||||
- Redis 和进程内缓存只做读取加速或运行态存储。
|
||||
- 私聊消息使用 `im_messages`,群聊消息使用 `im_group_messages`,避免单表无限膨胀。
|
||||
|
||||
## 8. 租户隔离
|
||||
|
||||
当前方案是一租户一库,业务表中不再保留 `tenant_id` 字段。但 Redis、进程内配置、日志指标仍属于共享运行环境,必须保留租户维度。
|
||||
|
||||
Redis key 统一包含租户标识:
|
||||
|
||||
```text
|
||||
im:{tenant_id}:...
|
||||
```
|
||||
|
||||
如果请求早期只有企业码或租户编码,应先解析到租户 ID 或租户库配置,再进入业务缓存读取。
|
||||
|
||||
## 9. 代码改造建议
|
||||
|
||||
### 9.1 基础设施
|
||||
|
||||
- 增加 Redis 配置:地址、密码、DB、连接池、超时、key 前缀。
|
||||
- 增加 Redis client 初始化和健康检查。
|
||||
- 增加统一 key builder,避免业务代码手写 key。
|
||||
- 增加缓存指标和日志。
|
||||
|
||||
当前代码将 Redis 配置挂在 `store_config.redis` 下;不配置则不启用 Redis 群消息发号和唯一 key,群消息仍兼容旧的本机 `lastID + 1` 发号。
|
||||
|
||||
配置示例:
|
||||
|
||||
```json
|
||||
{
|
||||
"store_config": {
|
||||
"redis": {
|
||||
"addr": "127.0.0.1:6379",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"db": 0,
|
||||
"key_prefix": "im",
|
||||
"dial_timeout_ms": 2000,
|
||||
"read_timeout_ms": 2000,
|
||||
"write_timeout_ms": 2000,
|
||||
"pending_ttl_seconds": 120,
|
||||
"done_ttl_seconds": 86400,
|
||||
"failed_ttl_seconds": 600,
|
||||
"hot_message_ttl_seconds": 86400,
|
||||
"presence_ttl_seconds": 120,
|
||||
"cache_ttl_seconds": 300,
|
||||
"short_state_ttl_seconds": 600,
|
||||
"group_fanout_enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`group_fanout_enabled` 默认关闭。启用后,群消息本节点投递完成后会发布 Redis Pub/Sub,其他节点订阅后只向本节点已在线的同群连接推送;如果节点没有加载该 Topic,则读取 Redis 本节点群在线成员集合做兜底直投,本地会话订阅表作为兼容兜底,实时投递失败再由客户端 seq 补拉恢复。
|
||||
|
||||
当前第二阶段已接入:
|
||||
|
||||
- 用户资料单对象缓存:`store.Users.Get` 优先读 Redis,用户更新/删除后失效。
|
||||
- Topic 单对象缓存:`store.Topics.Get` 优先读 Redis,Topic 更新、转让、删除后失效。
|
||||
- 单条订阅关系缓存:`store.Subs.Get` 优先读 Redis,订阅更新/删除后 active/any 两种缓存同时失效。
|
||||
- 群治理资料缓存:群设置 `GroupBusiness.GetSettings`、群成员 `GroupBusiness.GetMember` 优先读 Redis,更新、离群、角色变更后刷新或失效。
|
||||
- 群成员分页快照:`GET /v1/im/groups/{topic}/members` 按租户、Topic、操作者权限、筛选条件、cursor 和 limit 缓存响应快照,未命中回源订阅列表。
|
||||
- 用户总未读热缓存:`store.Users.GetUnreadCount` 先读 Redis,未命中回源 MySQL 并回填;在线用户 unread updater 更新内存计数时同步写 Redis。
|
||||
- 好友/黑名单 Redis Set 热缓存:关系上下文判断先查 `friends:{uid}`、`blocklist:{uid}` 正向 set,命中后减少 MySQL 查询;未命中仍回源 MySQL,新增/删除好友、拉黑/解除拉黑会同步更新 set。
|
||||
- 验证码短状态缓存:验证码创建、查询、验证、失败、消费会同步 Redis,`GetActive` 使用 `scene + phone_hash` 的 active key 做重发窗口热读。
|
||||
- Redis 频控:验证码创建按 `tenant + scene + phone_hash` 使用 Redis 原子 `INCR + EXPIRE` 做 1 小时 5 次限制,手机号只使用 hash,不写明文。
|
||||
- 动作 token 短状态缓存:创建时写 Redis,消费成功后删除缓存;最终消费仍以 MySQL 状态机为准,保留审计一致性。
|
||||
- SeqId 收口:消息、Topic 游标、订阅读/收游标、消息范围、收藏/举报/群文件引用、通话消息引用、JSON/gRPC 协议结构均改为 `int64`;MySQL 新建表 schema 中对应字段改为 `BIGINT`。
|
||||
- 进程内配置缓存:租户解析、注册短信开关、第三方配置读取已加本地 TTL 缓存,减少 HotGo 控制库频繁查询;缓存过期后按下一次读取刷新。
|
||||
|
||||
### 9.2 接口抽象
|
||||
|
||||
建议新增接口:
|
||||
|
||||
```go
|
||||
type PresenceStore interface {
|
||||
MarkOnline(ctx context.Context, tenantID string, uid string, sessionID string, meta PresenceMeta) error
|
||||
Refresh(ctx context.Context, tenantID string, uid string, sessionID string) error
|
||||
MarkOffline(ctx context.Context, tenantID string, uid string, sessionID string) error
|
||||
Get(ctx context.Context, tenantID string, uid string) (Presence, error)
|
||||
}
|
||||
|
||||
type ConfigCache interface {
|
||||
GetDict(ctx context.Context, key string) (DictValue, bool)
|
||||
GetTenantConfig(ctx context.Context, tenantID string) (TenantConfig, bool)
|
||||
Refresh(ctx context.Context) error
|
||||
}
|
||||
|
||||
type ReadModelCache interface {
|
||||
GetUserProfile(ctx context.Context, tenantID string, uid string) (UserProfile, bool)
|
||||
SetUserProfile(ctx context.Context, tenantID string, profile UserProfile) error
|
||||
DeleteUserProfile(ctx context.Context, tenantID string, uid string) error
|
||||
}
|
||||
```
|
||||
|
||||
### 9.3 业务接入顺序
|
||||
|
||||
1. 先接入 Redis client 和配置。
|
||||
2. 接入在线状态 PresenceStore。
|
||||
3. 群消息接入 Redis `SET NX` 唯一 key 与 `INCR` 原子 seq 发号,群消息正文写入 `im_group_messages`。
|
||||
4. WebSocket 登录、心跳、断开链路写入 PresenceStore。
|
||||
5. 用户在线状态查询接口改为读 Redis。
|
||||
6. 配置和字典改为进程内 TTL 缓存刷新。
|
||||
7. 再逐步接入用户、群、好友关系热数据缓存。
|
||||
|
||||
## 10. 故障降级
|
||||
|
||||
| 场景 | 降级策略 |
|
||||
| --- | --- |
|
||||
| Redis 不可用,查询在线状态 | 返回未知或离线,记录告警 |
|
||||
| Redis 不可用,读取用户/群缓存 | 回源 MySQL |
|
||||
| Redis 不可用,验证码或短期 token | 涉及安全的接口应失败关闭 |
|
||||
| Redis 不可用,万人大群发消息 | 无法原子获取群 seq,应返回可重试错误;不使用 MySQL 临时发号,避免 seq 冲突 |
|
||||
| 配置刷新失败 | 使用进程内旧配置 |
|
||||
| MySQL 不可用 | 持久化业务失败,不依赖 Redis 兜底 |
|
||||
|
||||
## 11. 监控指标
|
||||
|
||||
- Redis 连接状态。
|
||||
- Redis 命令耗时。
|
||||
- Redis 缓存命中率。
|
||||
- 在线用户 key 数量。
|
||||
- 心跳刷新失败次数。
|
||||
- Redis 回源 MySQL 次数。
|
||||
- 大群在线成员集合大小。
|
||||
- 大群按节点分发耗时。
|
||||
- 大群实时推送成功率和补拉率。
|
||||
- 极光 Push 触发数、成功数和失败数。
|
||||
- 配置刷新成功和失败次数。
|
||||
- Presence TTL 过期导致的离线数量。
|
||||
|
||||
## 12. 第一阶段落地范围
|
||||
|
||||
第一阶段建议只做以下内容:
|
||||
|
||||
- Redis 基础配置和 client 初始化。
|
||||
- 用户在线、离线、心跳刷新、跨节点查询。
|
||||
- 验证码、临时 token、限流计数等短期状态迁移到 Redis。
|
||||
- 系统配置、字典、租户配置、第三方配置改为进程内 TTL 缓存刷新。
|
||||
- 万人大群实时推送纳入 Redis 改造近期实现范围,先完成 Redis key、在线成员索引、节点分片和跨节点实时分发链路。
|
||||
- 明确消息正文不进入 Redis 长期存储;万人大群 24 小时热消息可进入 Redis 做读加速。
|
||||
- 群成员分页快照和用户总未读 Redis 热缓存已完成基础接入。
|
||||
|
||||
后续再逐步推进好友/黑名单全量预热、群成员分页快照、未读数等缓存改造。
|
||||
@@ -1,6 +1,6 @@
|
||||
# IM 产品原型需求文档
|
||||
|
||||
> 本文档根据 `/Users/superysh/im/第二项目/` 中的 156 张产品原型图整理。功能按业务域归类,每张图以原文件名作为标题,图片下方给出功能说明与界面内容描述。
|
||||
> 本文档根据 `/Users/superysh/im/第二项目/` 中的 160 张产品原型图整理。功能按业务域归类,每张图以原文件名作为标题,图片下方给出功能说明与界面内容描述。
|
||||
|
||||
## 产品总体定位
|
||||
|
||||
@@ -18,6 +18,38 @@
|
||||
|
||||
提供企业租户入口。页面展示产品标识、企业码输入框和“进入企业”按钮;提交后应校验企业码是否存在、是否启用,并将用户导向对应企业的登录环境。
|
||||
|
||||
### 1.1.1 企业注册-登录入口.png
|
||||
|
||||
<img src="images/企业注册-登录入口.png" alt="企业注册-登录入口" width="360">
|
||||
|
||||
功能说明:
|
||||
|
||||
企业码登录页右上角提供“企业注册”入口,底部提供“遇到问题?”反馈入口。点击“企业注册”进入企业资料提交流程;点击“遇到问题?”进入问题反馈页。入口文案应保持可见且易点击,避免与状态栏或背景装饰重叠。
|
||||
|
||||
### 1.1.2 企业注册-填写注册信息.png
|
||||
|
||||
<img src="images/企业注册-填写注册信息.png" alt="企业注册-填写注册信息" width="360">
|
||||
|
||||
功能说明:
|
||||
|
||||
企业注册页按步骤展示“填写企业信息、确定信息、提交审核”,当前处于第 1 步。用户需填写 18 位统一社会信用代码,支持扫描识别;可选上传营业执照,并填写企业邮箱用于接收审核结果及企业通知。提交前应校验统一社会信用代码格式、邮箱格式、图片大小和文件类型,并提示资料提交后预计审核时长。
|
||||
|
||||
### 1.1.3 企业注册-提交成功提示.png
|
||||
|
||||
<img src="images/企业注册-提交成功提示.png" alt="企业注册-提交成功提示" width="360">
|
||||
|
||||
功能说明:
|
||||
|
||||
企业注册资料提交成功后,在当前页展示明确 Toast 提示“提交成功,我们会尽快联系你”。提交成功状态应防止重复提交,并记录申请单状态;审核结果需通过企业邮箱或后续通知渠道触达。页面底部审核时长文案应与实际运营规则一致。
|
||||
|
||||
### 1.1.4 企业注册-问题反馈.png
|
||||
|
||||
<img src="images/企业注册-问题反馈.png" alt="企业注册-问题反馈" width="360">
|
||||
|
||||
功能说明:
|
||||
|
||||
企业入口问题反馈页支持填写问题描述并上传最多 9 张截图,便于定位企业码、注册资料、审核通知等问题。提交时应携带设备、版本、租户线索和可选联系方式;图片上传需显示数量限制、失败重试和大小校验,提交成功后给出明确受理反馈。
|
||||
|
||||
### 1.2 登录页面.png
|
||||
|
||||
<img src="images/登录页面.png" alt="登录页面" width="360">
|
||||
|
||||
@@ -334,16 +334,60 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
| 更新会话私有设置 | `{"set":{"id":"set-private-1","topic":"grp...","sub":{"user":"usr_me","private":{"pinned":true,"mute_notifications":false,"nickname":"群昵称","version":2}}}}` | `{"ctrl":{"id":"set-private-1","code":200,"text":"ok"}}` |
|
||||
| 设置搜索标签 | `{"set":{"id":"set-tags-1","topic":"me","tags":["usr:alice"]}}` | 普通用户无权限时 `{"ctrl":{"id":"set-tags-1","code":403,"text":"permission denied","params":{"what":"tags"}}}` |
|
||||
|
||||
##### 0.5.4.6 系统事件、好友和群业务下行
|
||||
##### 0.5.4.6 系统通知、好友和群业务下行
|
||||
|
||||
| 事件 | 服务端下行示例 | 客户端动作 |
|
||||
| --- | --- | --- |
|
||||
| 好友申请创建 | `{"data":{"topic":"sys","seq":101,"from":"usr_system","head":{"mime":"application/json","x-im-type":"system_event"},"content":{"event_type":"friend.request.created","request_id":"frq_1","applicant":{"user_id":"usr_a","display_name":"张三"},"message":"我是张三","action":{"type":"friend_request","request_id":"frq_1"}}}}` | 系统消息入库/展示,刷新 `friend-requests` 和 `bootstrap.entry_badges` |
|
||||
| 好友关系变化 | `{"data":{"topic":"sys","seq":102,"head":{"x-im-type":"system_event"},"content":{"event_type":"friend_relationship_changed","peer_user_id":"usr_a","relationship":"friend","topic":"p2p..."}}}` | 刷新会话 context,解锁或锁定输入区 |
|
||||
| 群邀请 | `{"data":{"topic":"sys","seq":103,"head":{"x-im-type":"system_event"},"content":{"event_type":"group_invitation","invitation_id":"ginv_1","group":{"topic":"grp...","name":"项目群"},"inviter":{"user_id":"usr_a","display_name":"张三"},"status":"pending","expires_at":"2026-08-27T09:00:00Z"}}}` | 展示邀请卡,点击后走邀请接受或入群申请接口 |
|
||||
| 入群申请 | `{"data":{"topic":"grp...","seq":20,"head":{"x-im-type":"system_event"},"content":{"event_type":"group.join_request.created","request_id":"gjr_1","applicant":{"user_id":"usr_a","display_name":"张三"},"source":"qr"}}}` | 管理员刷新 `join-requests` 和 `summary.pending_join_request_count` |
|
||||
| 群成员变化 | `{"data":{"topic":"grp...","seq":21,"head":{"x-im-type":"system_event"},"content":{"event_type":"group_membership_changed","operator":"usr_owner","target_users":["usr_a"],"action":"join","result":"joined"}}}` | 刷新群成员列表、群详情成员数和会话系统提示 |
|
||||
| 工作台未读变化 | `{"data":{"topic":"sys","seq":104,"head":{"x-im-type":"system_event"},"content":{"event_type":"workbench.badge.updated","modules":[{"code":"meetings","unread_count":2}],"workbench_total_unread":2}}}` | 更新工作台 badge;必要时重新拉 `GET /v1/im/workbench` |
|
||||
服务端主动下行分两类:Tinode 原生 Topic 消息使用 `{data}`、`{info}`、`{pres}` 等帧;IM 产品业务事件使用 `{info}` 帧下发,前端按 `info.what + info.event` 分发刷新。产品业务事件不占普通聊天 `seq`,不等同于系统通知列表记录;系统消息入口的未读数来自 `im_system_notifications` 和 `GET /v1/im/system-notifications`。
|
||||
|
||||
用户维度业务事件统一格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"info": {
|
||||
"topic": "usr_target",
|
||||
"from": "usr_actor",
|
||||
"what": "im",
|
||||
"event": "friend.request.accepted",
|
||||
"payload": {
|
||||
"user_id": "usr_actor"
|
||||
},
|
||||
"ts": "2026-08-25T10:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
群维度业务事件统一格式:
|
||||
|
||||
```json
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 事件 | 接收方 | 服务端下行示例 | 客户端动作 |
|
||||
| --- | --- | --- | --- |
|
||||
| 好友申请创建 | 被申请人 | `{"info":{"topic":"usr_target","from":"usr_applicant","what":"im","event":"friend.request.created","payload":{"user_id":"usr_applicant"}}}` | 刷新 `GET /v1/im/friend-requests` 和 `GET /v1/im/bootstrap` 的新朋友未处理数;系统通知列表也会新增记录 |
|
||||
| 好友申请通过 | 申请人、通过人本人 | `{"info":{"topic":"usr_target","from":"usr_actor","what":"im","event":"friend.request.accepted","payload":{"user_id":"usr_actor"}}}` | 刷新新朋友列表、好友列表、P2P 会话 context;申请人当前有系统通知,通过人本人当前无系统通知 |
|
||||
| 好友申请拒绝 | 申请人、拒绝人本人 | `{"info":{"topic":"usr_target","from":"usr_actor","what":"im","event":"friend.request.rejected","payload":{"user_id":"usr_actor"}}}` | 刷新新朋友列表和 `bootstrap.entry_badges`;申请人当前有系统通知,拒绝人本人当前无系统通知 |
|
||||
| 删除好友 | 被删除方、操作人本人 | `{"info":{"topic":"usr_target","from":"usr_actor","what":"im","event":"friend.deleted","payload":{"user_id":"usr_actor"}}}` | 刷新好友列表、会话 context,重新判断输入区 `can_send`;被删除方当前有系统通知,操作人本人当前无系统通知 |
|
||||
| 拉黑/取消拉黑 | 操作人本人 | `{"info":{"topic":"usr_actor","from":"usr_actor","what":"im","event":"relationship.blocked","payload":{"user_id":"usr_actor"}}}` | 刷新黑名单、好友列表和会话 context |
|
||||
| 修改好友备注 | 操作人本人 | `{"info":{"topic":"usr_actor","from":"usr_actor","what":"im","event":"friend.remark.updated","payload":{"user_id":"usr_actor"}}}` | 刷新好友列表和会话头显示名 |
|
||||
| 消息撤回 | Topic 在线成员 | `{"info":{"topic":"p2p_or_grp","from":"usr_actor","what":"im","event":"message.recalled","payload":{"topic":"p2p_or_grp","seq":12,"version":2}}}` | 按 `topic+seq+version` 重新拉取或本地标记已撤回 |
|
||||
| 消息编辑 | Topic 在线成员 | `{"info":{"topic":"p2p_or_grp","from":"usr_actor","what":"im","event":"message.edited","payload":{"topic":"p2p_or_grp","seq":12,"version":2}}}` | 按 `topic+seq+version` 重新拉取消息详情或修订记录 |
|
||||
| 群业务变化 | 群 Topic 在线成员 | `{"info":{"topic":"grp...","from":"usr_actor","what":"group","event":"group.member.joined","payload":{"topic":"grp...","user_id":"usr_xxx"}}}` | 按事件刷新群详情、成员列表、入群申请、公告、禁言或角色权限;除操作人外当前会写系统通知 |
|
||||
| 会议变化 | 会议参会人 | `{"info":{"topic":"usr_target","from":"usr_actor","what":"im","event":"meeting.updated","payload":{"meeting_id":"mtg_1","version":2,"state":"scheduled"}}}` | 刷新会议列表/详情;除操作者外当前会写系统通知 |
|
||||
| 安全风险确认 | 操作人本人 | `{"info":{"topic":"usr_actor","from":"usr_actor","what":"im","event":"security.risk.continue","payload":{"risk_challenge_id":"risk_1","decision":"continue"}}}` | 刷新安全验证页面;当前会写系统通知 |
|
||||
|
||||
业务事件写入 outbox 后会立即尝试投递;Hub 不可用或队列满时保持 pending,后台 poller 按在线租户重试。`registration.invite_used` 和 `security.password_reset` 是服务端内部消费事件,不作为普通业务事件下发给 App。
|
||||
|
||||
##### 0.5.4.7 WebRTC 通话信令
|
||||
|
||||
@@ -696,7 +740,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
|
||||
- 查询:`GET /v1/im/security/realname` 返回 `status=unverified|pending|verified|rejected`、脱敏姓名/证件号和 `can_submit`。
|
||||
- 提交:`POST /v1/im/security/realname/submissions`,Body `{"name":"张三","id_type":"cn_id","id_number":"..."}`,必须带幂等键。
|
||||
- 成功:返回 `submission_id`、`status:"pending"`;前端禁用重复提交,通过 `GET /v1/im/security/realname` 或 `security.realname.updated` 系统事件更新状态。
|
||||
- 成功:返回 `submission_id`、`status:"pending"`;前端禁用重复提交,当前以 `GET /v1/im/security/realname` 查询状态为准。后续如启用 `security.realname.updated`,应按 0.5.4.6 的 `info.what="im"` 业务事件下发。
|
||||
- 安全:证件号传输和存储加密、展示脱敏;普通日志、搜索索引和消息中禁止出现原值;重复身份由租户策略决定并返回稳定错误。
|
||||
|
||||
### 1.9 实名认证-已实名.png
|
||||
@@ -808,7 +852,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
|
||||
**状态:需改造。**
|
||||
|
||||
- `GET /v1/im/system-notifications?cursor=` 返回 `id,event_type,title,summary,created_at,read_at,action`;新通知通过服务端专用系统 Topic 的 `system_event` 消息推送。
|
||||
- `GET /v1/im/system-notifications?cursor=` 返回 `id,event_type,title,summary,created_at,read_at,action`;实时刷新以 0.5.4.6 的 WS `{info what:"im|group"}` 业务事件为准,系统通知列表记录不等同于每一条实时事件。
|
||||
- 新设备登录事件内容为 `device_name,platform,ip_masked,login_at,security_action`;IP 只下发必要脱敏值。
|
||||
- 已读:`POST /v1/im/system-notifications/{id}/read`,带幂等键;跳转目标只接受服务端白名单 `action.type` 和内部参数。
|
||||
- 当前普通认证用户可向 `sys` Topic 发布的路径必须封禁。客户端不得渲染普通用户伪造的系统卡片为可信通知。
|
||||
@@ -852,7 +896,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
|
||||
- 订阅 P2P Topic 后用 `{pres}` 更新在线/离线,`last_seen` 仅在对方隐私允许时展示。
|
||||
- `GET /v1/im/conversations/{topic}/context` 返回 `display_name,avatar_file_id,online_visibility,relationship`。
|
||||
- 好友关系变化由服务端生成 `system_event{event_type:"friend_relationship_changed"}`,客户端不得根据本地动作自行永久插入系统记录。
|
||||
- 好友关系变化由服务端生成 0.5.4.6 定义的 WS 业务事件,客户端不得根据本地动作自行永久插入系统通知记录。
|
||||
|
||||
### 3.3 个人对话框(我的状态).png
|
||||
|
||||
@@ -880,7 +924,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
|
||||
**状态:新增好友审批与 Topic 联动。**
|
||||
|
||||
- 好友申请通过后,服务端在同一事务内建立双方关系和 P2P 订阅,并生成 `friend_relationship_changed:accepted` 系统事件。
|
||||
- 好友申请通过后,服务端在同一事务内建立双方关系和 P2P 订阅,并向申请人、通过人本人下发 `info.what="im", event="friend.request.accepted"`;申请人当前会写入系统通知,通过人本人当前只收到实时业务事件。
|
||||
- 客户端收到事件或重新查询 `context.relationship=friend` 后解锁输入区;不得仅因本地“通过”按钮成功动画解锁。
|
||||
- 首次进入订阅 P2P Topic,再按 `since` 拉取申请上下文和正常消息。
|
||||
|
||||
@@ -1174,6 +1218,8 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
| `GET /v1/im/topics/{topic}/messages/{seq}/context?before=20&after=20` | Query only | `{"topic":"grp...","anchor_seq":12,"items":[{"seq":11,"content":{"text":"上一条"}},{"seq":12,"content":{"text":"命中消息"}}]}` |
|
||||
| `GET /v1/im/topics/{topic}/media?type=image&cursor=` | Query only | `{"topic":"grp...","items":[{"seq":12,"thumbnail_file_id":"file_thumb","file_id":"file_img","width":1080,"height":720,"ts":"2026-08-20T09:00:00Z","availability":"available"}],"next_cursor":""}` |
|
||||
|
||||
消息撤回和编辑成功后,Topic 在线成员会收到 0.5.4.6 定义的 `info.what="im"` 业务事件:`message.recalled` 或 `message.edited`。前端收到后按 `topic + seq + version` 刷新本地消息,不要等待用户重新进入会话才更新。
|
||||
|
||||
## 5. 通讯录、好友关系与黑名单
|
||||
|
||||
### 5.1 通讯录.png
|
||||
@@ -1196,6 +1242,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
- 列表:`GET /v1/im/friend-requests?direction=inbound&status=all&cursor=`,返回 `request_id,applicant,message,source,status,created_at,handled_at`。
|
||||
- 同意:`POST /v1/im/friend-requests/{request_id}/accept`;拒绝:`POST /v1/im/friend-requests/{request_id}/reject`,均带幂等键和可选 `version`。
|
||||
- 成功返回最终 `status` 和 P2P `topic`。重复审批返回原结果;并发冲突返回 `REQUEST_ALREADY_HANDLED` 并附当前状态,前端刷新该项。
|
||||
- 主动下行:创建申请时被申请人收到 `info.what="im", event="friend.request.created"`;通过/拒绝后申请人与处理人本人都收到 `friend.request.accepted/rejected`。前端收到后刷新新朋友列表、好友列表、会话 context 和 `bootstrap.entry_badges`。当前系统通知列表只给被申请人/申请人写记录,处理人本人主要靠实时业务事件刷新。
|
||||
|
||||
### 5.3 添加好友.png
|
||||
|
||||
@@ -1361,7 +1408,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
|
||||
- 列表:`GET /v1/im/groups?category=owned|managed|joined&query=&cursor=`,返回 `topic,name,avatar_file_id,member_count,my_role,updated_at`。
|
||||
- 创建:`POST /v1/im/groups`,Body `{"name":"...","description":"...","member_user_ids":[],"avatar_file_id":"..."}`;`description` 可省略或传空字符串,最长 200 字符;原子校验租户群数上限和群成员上限,返回 `topic,name,avatar_file_id,description,owner,member_count,version,permissions`。
|
||||
- 分类由服务端 `my_role` 决定;创建成功或角色变化后通过系统事件刷新,不由前端自行移动分组。
|
||||
- 分类由服务端 `my_role` 决定;创建成功或角色变化后通过 `info.what="group"` 业务事件刷新,不由前端自行移动分组。
|
||||
|
||||
### 6.2 空白页.png
|
||||
|
||||
@@ -1378,7 +1425,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
|
||||
**状态:新增邀请卡片和加入接口。**
|
||||
|
||||
- 邀请卡下行 `system_event{event_type:"group_invitation",invitation_id,group,inviter,status,expires_at}`。
|
||||
- 邀请卡展示以群邀请接口返回和系统通知记录为准;相关实时刷新使用 `info.what="group"` 群业务事件。
|
||||
- 点击加入:`POST /v1/im/group-invitations/{invitation_id}/accept`,带幂等键;返回 `status=joined|pending_approval` 和 `topic`。
|
||||
- 服务端校验邀请租户、有效期、群状态、成员资格和成员数上限;重复加入返回当前状态,过期返回 `INVITATION_EXPIRED`。
|
||||
|
||||
@@ -1389,8 +1436,8 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
**状态:新增群成员事件。**
|
||||
|
||||
- 邀请:`POST /v1/im/groups/{topic}/invitations`,Body `{"user_ids":[],"message":""}`;按群策略直接加入或创建待审批申请。
|
||||
- 加入、审批、拒绝等结果由服务端生成 `system_event{event_type:"group_membership_changed",operator,target_users,action,result}` 并写入群时间线。
|
||||
- 客户端只渲染可信系统事件;服务端同时更新成员表、Topic 订阅和成员数,不允许三者不一致。
|
||||
- 加入、审批、拒绝等结果由服务端生成 `info.what="group"` 群业务事件,例如 `group.member.joined`、`group.join_request.accepted/rejected`。
|
||||
- 客户端只渲染服务端可信事件;服务端同时更新成员表、Topic 订阅和成员数,不允许三者不一致。
|
||||
|
||||
### 6.5 邀请进群申请.png
|
||||
|
||||
@@ -1419,7 +1466,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
|
||||
- 查询最新公告:`GET /v1/im/groups/{topic}/announcements/latest`;发布/更新:`PUT /v1/im/groups/{topic}/announcements/current`,Body `{"content":"...","version":1}`。
|
||||
- 返回 `announcement_id,content,author,version,published_at`。仅有权限角色可写,使用乐观锁防覆盖。
|
||||
- 发布成功后服务端生成群系统事件;顶部摘要使用接口返回数据,不解析聊天文本猜测。
|
||||
- 发布成功后服务端生成 `group.announcement.updated` 群业务事件;顶部摘要使用接口返回数据,不解析聊天文本猜测。
|
||||
|
||||
### 6.8 群公告状态/邀请进群申请同时存在.png
|
||||
|
||||
@@ -1603,7 +1650,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
**状态:新增。**
|
||||
|
||||
- `POST /v1/im/groups/{topic}/owner-transfer`,Body `{"target_user_id":"usr...","version":n}`,必须带幂等键和二次认证 `X-Action-Token`。
|
||||
- 服务端事务内把旧群主降为管理员或成员、目标升为群主、更新权限和审计,再生成系统事件。
|
||||
- 服务端事务内把旧群主降为管理员或成员、目标升为群主、更新权限和审计,再生成 `group.owner.transferred` 群业务事件。
|
||||
- 成功返回 `owner,previous_owner,roles,version`;并发成员变化返回 `GROUP_VERSION_CONFLICT`,前端重新确认,不自动重试。
|
||||
|
||||
### 6.27 群文件.png
|
||||
@@ -2001,7 +2048,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
- `GET /v1/im/workbench` 返回 `modules[{code,name,icon_file_id,route,unread_count,enabled,permission}]`、布局版本和企业配置。
|
||||
- `GET /v1/im/bootstrap` 返回 `entry_badges.workbench_total_unread`,用于工作台一级入口总红点;总数只汇总当前用户可见且 `enabled=true` 的工作台模块。
|
||||
- 服务端按当前 Token 的租户、组织角色和功能开关裁剪;前端只接受白名单内部 `route`,禁止直接执行服务端任意 URL。
|
||||
- 未读数通过 `workbench.badge.updated` 系统事件增量更新;切租户必须重新加载,不复用另一企业工作台。
|
||||
- 工作台一级总未读以 `GET /v1/im/bootstrap` 和 `GET /v1/im/workbench` 返回为准;若后续启用 `workbench.badge.updated`,应按 0.5.4.6 的 `info.what="im"` 业务事件下发。切租户必须重新加载,不复用另一企业工作台。
|
||||
|
||||
### 9.2 部门组织框架.png
|
||||
|
||||
@@ -2022,6 +2069,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
- 创建:`POST /v1/im/meetings`,Body `{"title":"...","start_at":"...","end_at":"...","location":"...","attendee_user_ids":[],"reminder_minutes":[]}`。
|
||||
- 详情:`GET /v1/im/meetings/{meeting_id}` 返回会议、参会人的 `response=pending|accepted|declined|tentative`、权限和 `version`。
|
||||
- 更新/取消:`PATCH /v1/im/meetings/{id}`、`POST /v1/im/meetings/{id}/cancel`;提醒未响应者 `POST /v1/im/meetings/{id}/reminders`。所有写操作带幂等键并生成通知和审计。
|
||||
- 主动下行:参会人收到 `info.what="im"`,事件为 `meeting.created/updated/canceled/reminded/response.updated`,payload 包含 `meeting_id,version,state`;前端收到后刷新会议列表或会议详情。除操作者外,当前会写入系统通知列表。
|
||||
|
||||
### 9.4 会议通知-接收人状态.png
|
||||
|
||||
@@ -2030,7 +2078,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
**状态:新增。**
|
||||
|
||||
- 详情同 9.3;响应:`PUT /v1/im/meetings/{meeting_id}/my-response`,Body `{"response":"accepted|declined|tentative","version":n}`。
|
||||
- 返回我的最终响应和会议版本,并实时通知发起人;重复提交同状态幂等。
|
||||
- 返回我的最终响应和会议版本,并通过 `meeting.response.updated` 实时通知参会人;重复提交同状态幂等。
|
||||
- 会议过期返回 `MEETING_CLOSED` 并只读展示;客户端本地日历写入是可选系统动作,不替代服务端响应。
|
||||
|
||||
### 9.5 日报.png
|
||||
@@ -2410,7 +2458,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
|
||||
- 高风险动作在原业务接口返回 `409 RISK_CONFIRMATION_REQUIRED`,`details={risk_challenge_id,level,notice_code,notice_params,expires_at,action}`。
|
||||
- 客户端用 `notice_code` 映射经过审核的本地文案,不显示服务端规则、分数或命中特征;取消不重试原操作。
|
||||
- 低风险提示也可由可信 `security.risk_notice` 系统事件触发。普通消息不得伪装成系统反诈弹窗。
|
||||
- 低风险提示也可由服务端可信安全事件触发。当前风险确认/取消会按 0.5.4.6 下发 `security.risk.continue/cancel`;普通消息不得伪装成系统反诈弹窗。
|
||||
|
||||
### 11.2 反诈弹窗(二次提示安卓).png
|
||||
|
||||
@@ -2489,7 +2537,7 @@ X-Tinode-APIKey: <预置的普通 API Key>
|
||||
| --- | --- | --- |
|
||||
| P0 | `/v1/im` 统一认证中间件、响应封装、幂等键、租户 Principal | 全部 HTTP 产品能力 |
|
||||
| P0 | 企业码解析、验证码、密码重置、二次认证、Token `auth_version`、登录换新和撤销 | 1.1~1.11、10.7~10.8 |
|
||||
| P0 | 普通用户禁止发布系统 Topic;系统事件可信生产通道 | 2.5、3.1~3.5、6.4、11.1 |
|
||||
| P0 | 普通用户禁止发布系统 Topic;系统通知和业务事件可信生产通道 | 2.5、3.1~3.5、6.4、11.1 |
|
||||
| P0 | 消息 `x-im-schema/type/client-mid` 校验和服务端幂等 | 2~4 章 |
|
||||
| P0 | 文件资源级 ACL、Range、ETag、用途绑定和租户存储配额 | 3.6、4.10~4.14、6.27~7.16、11.4~11.5 |
|
||||
| P1 | 好友申请、好友关系、黑名单状态机 | 第 3、5 章 |
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
# IM 服务端主动推送消息梳理
|
||||
|
||||
> 本文先按当前代码实现梳理,后续确认产品口径后再合并到《IM-前后端对接文档》。
|
||||
|
||||
## 1. 术语和通道
|
||||
|
||||
服务端主动下行分两层:
|
||||
|
||||
1. Tinode 原生实时消息:通过 WebSocket/Long Polling 下发 `{data}`、`{info}`、`{pres}`、`{meta}` 等帧,覆盖聊天消息、回执、输入中、在线状态、通话信令。
|
||||
2. IM 产品业务事件:HTTP 写操作或业务事务写入 `im_business_events_outbox` / 群业务 outbox 后,由服务端投递为 `{info}` 帧,前端据此刷新业务接口或局部状态。
|
||||
|
||||
当前产品业务事件不是聊天消息,不占普通会话消息 `seq`。系统通知列表是否出现记录,取决于是否额外写入 `im_system_notifications`。
|
||||
|
||||
## 2. 产品业务事件统一帧格式
|
||||
|
||||
### 2.1 用户维度事件
|
||||
|
||||
来自 `im_business_events_outbox`,投递到用户自己的在线会话。
|
||||
|
||||
```json
|
||||
{
|
||||
"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,在线群成员收到。
|
||||
|
||||
```json
|
||||
{
|
||||
"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 业务事件刷新。
|
||||
- 对接文档现有示例写成 `sys` topic `{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` | 除操作人外写入 |
|
||||
| 成员加入 | 群成员 | `event="group.member.joined"` | `user_id`、`topic` | 除操作人外写入 |
|
||||
| 入群申请创建 | 群成员/管理员可见链路 | `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`、禁言信息 | 除操作人外写入 |
|
||||
| 成员离群 | 群成员 | `event="group.member.left"` | `user_id` | 除操作人外写入 |
|
||||
| 群角色更新 | 群成员 | `event="group.role.updated"` | `user_id`、角色信息 | 除操作人外写入 |
|
||||
| 群主转让 | 群成员 | `event="group.owner.transferred"` | `previous_owner`、`new_owner` | 除操作人外写入 |
|
||||
|
||||
前端建议动作:
|
||||
|
||||
- 群成员、角色、禁言、群主变化:刷新群详情、成员列表、当前用户群权限。
|
||||
- 入群申请变化:管理员刷新入群申请列表和群 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 实时事件。
|
||||
|
||||
建议后续合并到对接文档时明确:
|
||||
|
||||
1. 前端必须监听所有 `{info}`,按 `what + event` 分发业务刷新。
|
||||
2. 系统消息入口的未读数来自 `im_system_notifications`,不是 `{info}` 事件数量。
|
||||
3. 好友申请通过后是否双方都写系统通知,需要产品确认;若确认双方都有,需要同步补代码。
|
||||
@@ -1,6 +1,6 @@
|
||||
# IM 第一至十一章后续增强项
|
||||
|
||||
> 更新日期:2026-08-21
|
||||
> 更新日期:2026-08-25
|
||||
> 核对依据:`docs/IM-前后端对接文档.md`、`server/main.go` 当前 `/v1/im/*` 路由、`server/im_http.go` 当前 handler。
|
||||
> 范围:第一章登录注册与安全、第二章消息首页/导航/系统通知、第三章单聊关系、第四章消息输入/转发/检索、第五章通讯录/好友/黑名单、第六章群聊创建/资料/治理、第七章文件/收藏/聊天记录检索、第八章实时语音与视频通话、第九章企业工作台与组织架构、第十章个人设置与客户端能力、第十一章反诈安全/举报/反馈。
|
||||
> 用途:记录当前代码基线之外仍需排期的增强项。已经落地的 P0/P1 不再作为待办保留;已经抽入专项开发文档的项目,在本文中仅保留来源和现状说明,不再作为未分流待办处理。
|
||||
@@ -14,42 +14,38 @@
|
||||
3. `GET /v1/im/forward-targets` 已新增,返回文件助手和可转发 P2P/群会话目标。
|
||||
4. 总经理信箱配置与提交的 500 已修复,`GET /v1/im/executive-mailbox/config`、`POST /v1/im/executive-mailbox/submissions` 已可联调。
|
||||
5. 举报原因 500 已修复,`GET /v1/im/reports/abuse/reasons` 已按 `items` 返回。
|
||||
6. `GET /v1/im/groups/{topic}/files` 和 `/announcements` 不再误落到群详情;未实现路径返回 404。
|
||||
6. 群文件和群公告路径已从群详情兜底中拆出,群文件基础接口已落地,未实现的子路径会返回明确错误。
|
||||
7. 注册数据模型已改为 `im_users.username`、`im_users.phone`、`im_user_profiles.email`,不再依赖用户 `tags` 保存手机号/邮箱。
|
||||
8. 邀请码主数据在 HotGo 库,注册使用明细在 IM 租户库;邀请码可多人复用,不再“核销”。
|
||||
9. 用户搜索、用户主页、用户二维码已支持 `user_id/username/public_account` 作为 identifier。
|
||||
10. 第一批增强中的群文件列表/登记/删除、群邀请外部接受、实名更正申诉、组织全局成员接口、月报 reopen 已落地。
|
||||
11. 第一批增强中的主动创建/获取 P2P 会话、会话设置、多端云草稿已落地。
|
||||
|
||||
### 0.2 当前仍明确缺口及分流状态
|
||||
|
||||
| 优先级 | 缺口 | 对接文档位置 | 当前代码状态 | 分流状态 |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| P0 | 真实短信供应商发送、失败重试、发送审计 | 1.4、1.7、1.10 | 当前测试码 `123456` 可联调,但生产短信未完成 | 依赖第三方,暂留后续增强池 |
|
||||
| P0 | 群文件完整接口 | 6.27、6.28 | `/groups/{topic}/files` 已不误兜底,但列表/登记/删除未实现 | 已抽入 `IM-增强项第一批开发.md` |
|
||||
| P0 | 消息转发落地接口 | 4.18、4.19、4.20 | 仅 `forward-targets` 已实现,`POST /v1/im/messages/forward`、`GET /v1/im/forward-bundles/{id}` 未实现 | 已抽入 `IM-增强项第一批开发.md` |
|
||||
| P0 | 登出与会话撤销 | 10.7 | `POST /v1/im/auth/logout`、`DELETE /v1/im/security/sessions/{id}` 未见路由 | 已抽入 `IM-增强项第一批开发.md` |
|
||||
| P1 | 单聊主动建会话/会话设置/云草稿 | 3.2、4.5、4.21 | `GET /conversations/{topic}/context` 已有,`POST /conversations/p2p`、`/settings`、`PUT /topics/{topic}/draft` 未见路由 | 已抽入 `IM-增强项第一批开发.md` |
|
||||
| P1 | 群邀请外部接受接口 | 6.8 | 群内 `/groups/{topic}/invitations` 已有,`POST /v1/im/group-invitations/{id}/accept` 未见路由 | 已抽入 `IM-增强项第一批开发.md` |
|
||||
| P1 | 实名更正申诉 | 1.9、10.22 | `POST /v1/im/security/realname/appeals` 未见路由 | 已抽入 `IM-增强项第一批开发.md` |
|
||||
| P1 | 组织全局成员接口 | 5.1、9.2 | 部门成员与组织搜索已具备,`GET /v1/im/organization/members` 未见路由 | 已抽入 `IM-增强项第一批开发.md` |
|
||||
| P1 | 月报 reopen | 9.10 | 月报草稿/提交/详情已具备,`POST /reports/monthly/{id}/reopen` 未见路由 | 已抽入 `IM-增强项第一批开发.md` |
|
||||
|
||||
### 0.3 已抽入第一批开发的项目
|
||||
|
||||
以下项目已经从本文档的普通后续增强池中抽出,形成专项开发文档 `docs/IM-增强项第一批开发.md`。它们当前代码上仍未完全落地,但不再按“未分流的未实现项”处理,后续应以第一批开发文档作为实现、验收和同步更新依据。
|
||||
以下项目已经从本文档的普通后续增强池中抽出,形成专项开发文档 `docs/IM-增强项第一批开发.md`。其中已落地项已移入各章“当前已完成基线”;仍未完全落地的项目继续以第一批开发文档作为实现、验收和同步更新依据。
|
||||
|
||||
1. 登出与指定会话撤销。
|
||||
2. 普通消息转发。
|
||||
3. 合并转发 Bundle 详情。
|
||||
4. 群文件列表、登记和删除。
|
||||
5. 主动创建/获取 P2P 会话。
|
||||
6. 会话设置。
|
||||
7. 多端云草稿。
|
||||
8. 群邀请外部接受。
|
||||
9. 实名更正申诉。
|
||||
10. 组织全局成员接口。
|
||||
11. 月报 reopen。
|
||||
12. 列表 `has_more` 统一。
|
||||
13. 风控 token 单次消费接入业务接口。
|
||||
1. 仍待落地:登出与指定会话撤销。
|
||||
2. 仍待落地:普通消息转发。
|
||||
3. 仍待落地:合并转发 Bundle 详情。
|
||||
4. 已落地:群文件列表、登记和删除。
|
||||
5. 已落地:主动创建/获取 P2P 会话。
|
||||
6. 已落地:会话设置。
|
||||
7. 已落地:多端云草稿。
|
||||
8. 已落地:群邀请外部接受。
|
||||
9. 已落地:实名更正申诉。
|
||||
10. 已落地:组织全局成员接口。
|
||||
11. 已落地:月报 reopen。
|
||||
12. 仍待统一回归:列表 `has_more` 统一。
|
||||
13. 仍待落地:风控 token 单次消费接入业务接口。
|
||||
|
||||
## 1. 第一章:登录、注册与安全
|
||||
|
||||
@@ -70,6 +66,7 @@
|
||||
13. 产品 HTTP 已统一校验 API Key、Schema 和 Request ID。
|
||||
14. 登录态写接口已接入租户库幂等记录,支持请求摘要冲突检测和首次响应重放。
|
||||
15. 风险确认可生成安全事件和系统通知。
|
||||
16. 实名更正申诉 `POST /v1/im/security/realname/appeals` 已具备,申诉记录进入 pending 状态。
|
||||
|
||||
### 1.2 后续增强项
|
||||
|
||||
@@ -78,7 +75,7 @@
|
||||
| P0 | 真实短信供应商发送通道 | 当前为测试码联调模式;生产必须接短信服务商、模板、签名、失败重试和发送审计。 | 真实手机号收到验证码;供应商失败返回稳定 JSON,告警可观测。 |
|
||||
| P0 | 验证码多维限频 | 需要按租户、手机号、IP、设备、场景组合限频,并接 Redis 或落库。 | 高频请求返回 `VERIFICATION_RATE_LIMITED`,不同场景互不误伤。 |
|
||||
| P0 | 登出与会话撤销 | 对接文档已有 `POST /v1/im/auth/logout` 和 `DELETE /v1/im/security/sessions/{id}`,代码未见路由。 | 当前设备退出不影响其他设备;删除指定 session 后该设备 token 立即失效。 |
|
||||
| P1 | 实名审核与申诉 | 当前支持提交和状态查询,后续需要后台审核、驳回原因、通过状态变更和 `realname/appeals`。 | 审核通过后 `realname.status=verified`;驳回可再次提交;申诉有工单状态。 |
|
||||
| P1 | 实名后台审核流转 | 当前支持实名提交、状态查询和申诉提交,后续需要后台审核、驳回原因、通过状态变更和申诉处理工单。 | 审核通过后 `realname.status=verified`;驳回可再次提交;申诉有处理状态。 |
|
||||
| P1 | 安全事件通知覆盖扩展 | 风险确认已接入通知;密码重置、实名审核、独立密码变更、新设备登录仍需补齐。 | `/v1/im/system-notifications` 可看到对应事件且重试不重复入箱。 |
|
||||
| P1 | API Key 轮换策略 | 当前 API Key 校验兼容现有逻辑,尚未实现最低可接受序号或撤销表。 | 旧序号 Key 可按配置禁用,新 Key 不影响正常客户端。 |
|
||||
| P2 | 风控与反诈策略接入登录注册 | 安全动作后续应接入风险评分、反诈弹窗和二次确认策略。 | 高风险注册/登录/改密触发二次验证或反诈提示。 |
|
||||
@@ -103,12 +100,15 @@
|
||||
| 优先级 | 增强项 | 说明 | 建议验收点 |
|
||||
| --- | --- | --- | --- |
|
||||
| P0 | 消息变更查询占位策略 | 已撤回消息在普通拉取中应返回撤回占位,而不是仅靠客户端本地事件。 | 新设备登录拉历史时能看到撤回占位和版本。 |
|
||||
| P0 | 万人大群离线提醒与重连补偿 | 离线只走极光 Push 提醒,客户端重连后按 Topic seq 拉取补偿。 | 离线用户收到极光提醒;重连后按 seq 补齐且不丢不重。 |
|
||||
| P0 | 系统通知未读聚合实时化 | `bootstrap.entry_badges.system_unread_count` 已有,后续需确认真源与增量事件。 | 读通知后 bootstrap 和 WS badge 均一致。 |
|
||||
| P1 | 列表 `has_more` 统一 | 部分接口仍只返回 `items,next_cursor`,对接文档要求可统一补 `has_more`。 | 前端所有列表分页逻辑可复用同一协议。 |
|
||||
| P1 | 群回执隐私和角色策略 | 当前返回基础 read/recv 列表,后续需按租户策略、群规模、角色裁剪。 | 普通成员/管理员在不同策略下看到不同粒度回执。 |
|
||||
| P1 | `message_policy` 租户配置化 | 当前 recall/edit 窗口和菜单能力是固定值,后续需要来自租户配置。 | 不同租户可配置不同撤回/编辑时限和菜单能力。 |
|
||||
| P1 | 系统通知 action 白名单 | `action.type` 和参数需要统一注册、校验和版本化。 | 非白名单 action 写入失败;客户端只收到可识别跳转。 |
|
||||
| P1 | 会话搜索排序与类型过滤 | 当前按订阅数据过滤,后续需支持会话类型、置顶、免打扰、最近活跃排序。 | 搜索结果与消息首页排序、筛选规则一致。 |
|
||||
| P1 | 群成员分页快照深度优化 | 当前已接入 Redis 短 TTL 响应快照,后续大群需要 sorted set、稳定游标和成员变更主动失效。 | 万人大群成员翻页不重复不漏,角色/昵称/退群变更后缓存能及时失效。 |
|
||||
| P1 | 未读数 Redis 深度优化 | 当前已接入用户总未读 Redis 热缓存,后续需要 Topic 维度未读计数、bootstrap 聚合缓存和主动失效事件。 | 首页红点、会话未读和多端 read/recv 后的未读数实时一致。 |
|
||||
| P2 | 离线重连恢复编排测试 | 需要端到端覆盖 `hi -> token login -> sub me -> 恢复 Topic -> get data`。 | 断网重连后未丢消息、未重复消息、未错乱回执。 |
|
||||
|
||||
## 3. 第三章:单聊关系、权限与会话上下文
|
||||
@@ -121,15 +121,16 @@
|
||||
4. 拉黑/解除拉黑后关系上下文即时反映 `blocked_by_me`、`blocked_by_peer` 等状态。
|
||||
5. 好友申请、通过/拒绝、删除、拉黑/解除和备注已生成稳定关系事件,相关对端通知可去重。
|
||||
6. 非好友资料查询已执行 `profile_visibility`。
|
||||
7. 主动创建/获取 P2P 会话 `POST /v1/im/conversations/p2p` 已具备。
|
||||
8. 会话设置 `GET/PATCH /v1/im/conversations/{topic}/settings` 已具备。
|
||||
9. 多端云草稿 `GET/PUT /v1/im/topics/{topic}/draft` 已具备。
|
||||
|
||||
### 3.2 后续增强项
|
||||
|
||||
| 优先级 | 增强项 | 说明 | 建议验收点 |
|
||||
| --- | --- | --- | --- |
|
||||
| P0 | P2P 策略统一入口 | 当前核心路径已校验好友/黑名单,后续需要把发消息、通话、名片、文件、历史查看统一收敛到同一策略服务。 | 任一 P2P 业务入口对同一关系状态返回一致权限。 |
|
||||
| P1 | 主动建立 P2P 会话接口 | 对接文档有 `POST /v1/im/conversations/p2p`,当前未见路由。 | 好友关系成立时可显式获取/创建 P2P topic。 |
|
||||
| P1 | 会话设置接口 | 对接文档有 `GET/PATCH /v1/im/conversations/{topic}/settings`,当前未见路由。 | 置顶、免打扰、备注、保留策略跨端同步。 |
|
||||
| P1 | 多端草稿 | 对接文档有 `PUT /v1/im/topics/{topic}/draft`,当前仍建议本地加密保存。 | 同账号多端草稿同步,版本冲突可处理。 |
|
||||
| P1 | 会话策略与草稿联调回归 | P2P 会话、会话设置和云草稿接口已具备,仍需覆盖多端、冲突、非好友/黑名单状态下的联调回归。 | 多端草稿同步、版本冲突和权限拒绝都符合协议。 |
|
||||
| P1 | Pending 会话体验增强 | 待处理好友申请对应单聊页应携带申请摘要、来源、过期时间和快速处理入口。 | 进入 pending 会话可直接通过/拒绝。 |
|
||||
| P1 | 黑名单历史消息策略 | 需明确拉黑后历史消息、资料、文件和已读回执可见范围。 | 被拉黑人在不同策略下看到一致的历史与资料裁剪结果。 |
|
||||
| P1 | 好友关系审计 | 删除、拉黑、解除拉黑、申请处理等敏感关系操作写审计日志。 | 后台可按租户、操作者、目标用户查询操作流水。 |
|
||||
@@ -199,14 +200,15 @@
|
||||
8. 禁言候选、批量禁言、禁言列表和解除禁言接口已具备。
|
||||
9. 群写操作已使用 `im_group_idempotency` 和 `im_group_events_outbox`,提交后通过 Hub 派发,失败保留 pending 重试。
|
||||
10. WebSocket 群消息发布已校验成员禁言和全员禁言,支持过期恢复及管理员豁免。
|
||||
11. 群文件和群公告未实现路径已不再误返回群详情。
|
||||
11. 群文件列表、登记和删除接口 `GET/POST/DELETE /v1/im/groups/{topic}/files` 已具备。
|
||||
12. 群邀请外部接受 `POST /v1/im/group-invitations/{id}/accept` 已具备。
|
||||
|
||||
### 6.2 后续增强项
|
||||
|
||||
| 优先级 | 增强项 | 说明 | 建议验收点 |
|
||||
| --- | --- | --- | --- |
|
||||
| P0 | 群文件索引与管理 | 对接文档要求 `GET/POST/DELETE /v1/im/groups/{topic}/files`,当前未实现,只修复了错误兜底。 | 群文件列表可搜索/分页,上传登记和删除权限正确,退群后不可下载旧 URL。 |
|
||||
| P1 | 群邀请外部接受接口 | 对接文档有 `POST /v1/im/group-invitations/{id}/accept`,当前未见路由。 | 被邀请人可通过邀请卡入群或进入审批。 |
|
||||
| P0 | 群文件权限与下载闭环 | 群文件基础接口已具备,后续需与文件 ACL、退群后下载阻断、审核状态、OSS 短链失效联动。 | 群文件列表可搜索/分页,上传登记和删除权限正确,退群后不可下载旧 URL。 |
|
||||
| P1 | 群邀请外部接受体验回归 | 外部接受接口已具备,仍需覆盖邀请卡、多端重复接受、过期和审批模式切换。 | 被邀请人可通过邀请卡入群或进入审批;重复接受幂等返回稳定结果。 |
|
||||
| P1 | 进群申请隐私与限频 | 申请入口需要按群治理、租户策略、用户状态、来源、IP/设备限频。 | 高频申请被限制;普通成员不可看到敏感申请人信息。 |
|
||||
| P1 | 管理员/群主角色层级策略 | 管理员互相操作、管理员禁言、群主转让二次认证等需要完整策略矩阵。 | 群主、管理员、普通成员在每个治理动作上的权限稳定一致。 |
|
||||
| P1 | 群解散与二维码吊销 | 群解散、二维码撤销、邀请失效需要状态联动和权限回收。 | 解散后 Topic、二维码、邀请、文件新增权限全部失效。 |
|
||||
@@ -275,6 +277,8 @@
|
||||
8. 日报/月报草稿、提交、详情、列表、统计与提醒基线已具备。
|
||||
9. 总经理信箱配置与提交接口已具备,包含匿名策略、文案版本和幂等键。
|
||||
10. 会议创建、变更、取消、提醒和参会回应已生成在线事件与系统通知,关键变更在事务内写 Outbox。
|
||||
11. 组织全局成员接口 `GET /v1/im/organization/members` 已具备。
|
||||
12. 月报 reopen `POST /v1/im/reports/monthly/{id}/reopen` 已具备。
|
||||
|
||||
### 9.2 后续增强项
|
||||
|
||||
@@ -284,9 +288,9 @@
|
||||
| P0 | 工作台未读角标实时事件 | `workbench_total_unread` 已有,后续需要模块未读聚合和 `workbench.badge.updated` 增量事件。 | 会议、日报、信箱未读变化后工作台角标实时更新。 |
|
||||
| P0 | 组织数据后台维护链路 | 当前前台读接口和表已具备,后续需要后台导入、增删改部门、调整成员和离职状态。 | 后台修改组织后前台接口版本变化,客户端可刷新。 |
|
||||
| P0 | 组织权限与可见字段裁剪 | 成员手机号、邮箱、职位等字段需要按组织角色、隐私和企业策略裁剪。 | 普通员工和管理员看到的成员字段不同且不可绕过。 |
|
||||
| P1 | 组织全局成员接口 | 对接文档提到 `GET /v1/im/organization/members`,当前可由部门成员和组织搜索替代,未见独立路由。 | 前端如需要全局成员列表,可直接分页查询并按部门过滤。 |
|
||||
| P1 | 组织全局成员接口深度联调 | `GET /v1/im/organization/members` 已具备,仍需按部门、关键词、权限和分页做测试环境回归。 | 前端可直接分页查询并按部门过滤,字段裁剪符合权限。 |
|
||||
| P1 | 会议列表完整联调 | 对接文档标注 `GET /v1/im/meetings?cursor=` 待补列表;代码有 `serveIMMeetings`,仍需用测试数据确认返回结构。 | 会议列表、详情、响应、取消端到端一致。 |
|
||||
| P1 | 月报 reopen | 对接文档有 `POST /v1/im/reports/monthly/{id}/reopen`,当前未见路由。 | 已提交月报可按权限退回草稿并保留审计。 |
|
||||
| P1 | 月报 reopen 审计与权限扩展 | reopen 接口已具备,后续需要后台权限、原因记录、审计和通知闭环。 | 已提交月报可按权限退回草稿并保留审计。 |
|
||||
| P1 | 日报/月报后台统计口径 | 当前基础统计可用,后续需要按部门、周期、迟交、补交、未读等维度扩展。 | 管理员看板与业务口径一致。 |
|
||||
| P1 | 总经理信箱后台流转 | 当前前台配置/提交已具备,后续需要后台处理、回复、转派、归档和通知。 | 信件全生命周期可追踪,匿名身份受保护。 |
|
||||
| P1 | 办公协同审计 | 组织查询、会议变更、报告查看、信箱提交等敏感动作需要审计。 | 后台可按租户、用户、对象查询办公操作流水。 |
|
||||
@@ -341,7 +345,9 @@
|
||||
|
||||
## 12. 统一处理建议
|
||||
|
||||
1. 第一优先级处理生产可用性 P0:真实短信、登出/会话撤销、群文件、普通消息转发/合并转发、文件安全、通话强状态机、工作台权限、风控 token 消费。
|
||||
2. 第二优先级补齐前端文档缺口:`conversations/p2p`、会话设置、云草稿、群邀请外部接受、实名申诉、组织全局成员、月报 reopen。
|
||||
1. 第一优先级处理生产可用性 P0:真实短信、登出/会话撤销、普通消息转发/合并转发、文件安全、通话强状态机、工作台权限、风控 token 消费。
|
||||
2. 第二优先级补齐已落地接口的联调闭环:P2P 会话、会话设置、云草稿、群文件、群邀请外部接受、实名申诉、组织全局成员、月报 reopen。
|
||||
3. 第三优先级统一协议和治理能力:所有列表补齐 `has_more`、稳定游标、审计流水、字段级隐私矩阵、租户配置化策略。
|
||||
4. 每个增强项落地后同步更新本文档、`IM-系统设计文档.md`、`IM-前后端对接文档.md` 和接口审核报告,避免已完成项继续出现在待办清单。
|
||||
4. 万人大群未实现增强项仅保留离线能力:离线只走极光 Push 提醒,客户端重连后按 Topic seq 拉取补偿;实时推送能力归入 `IM-Redis三层数据分层改造方案.md` 后续直接实现。
|
||||
5. Redis 读性能深度优化继续保留:群成员分页 sorted set/稳定游标/主动失效、Topic 维度未读计数、bootstrap 聚合缓存和主动失效事件。
|
||||
6. 每个增强项落地后同步更新本文档、`IM-系统设计文档.md`、`IM-前后端对接文档.md` 和接口审核报告,避免已完成项继续出现在待办清单。
|
||||
|
||||
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 288 KiB |
|
After Width: | Height: | Size: 311 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
After Width: | Height: | Size: 288 KiB |
|
After Width: | Height: | Size: 311 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,12 @@
|
||||
-- IM SeqId BIGINT upgrade for existing MySQL tenant databases.
|
||||
-- New schemas have been updated in server/db/mysql/adapter.go and group_schema.go.
|
||||
|
||||
ALTER TABLE im_topics MODIFY seqid BIGINT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE im_subscriptions MODIFY recvseqid BIGINT DEFAULT 0;
|
||||
ALTER TABLE im_subscriptions MODIFY readseqid BIGINT DEFAULT 0;
|
||||
ALTER TABLE im_messages MODIFY seqid BIGINT NOT NULL;
|
||||
ALTER TABLE im_message_revisions MODIFY seqid BIGINT NOT NULL;
|
||||
ALTER TABLE im_favorites MODIFY source_seq BIGINT NOT NULL;
|
||||
ALTER TABLE im_abuse_reports MODIFY target_seq BIGINT NOT NULL DEFAULT 0;
|
||||
ALTER TABLE im_call_records MODIFY seq_id BIGINT NOT NULL;
|
||||
ALTER TABLE im_group_files MODIFY source_seq BIGINT NOT NULL DEFAULT 0;
|
||||
@@ -0,0 +1,37 @@
|
||||
CREATE TABLE IF NOT EXISTS im_group_messages (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT,
|
||||
topic VARCHAR(64) NOT NULL COMMENT '群 Topic,如 grp_xxx',
|
||||
group_id BIGINT NOT NULL COMMENT '业务群 ID,便于分区、治理和统计',
|
||||
seqid BIGINT NOT NULL COMMENT '群内消息序号,由 Redis 原子发号',
|
||||
from_user BIGINT NOT NULL COMMENT '发送人用户 ID',
|
||||
client_msg_id VARCHAR(64) NULL COMMENT '客户端幂等消息 ID',
|
||||
msg_type VARCHAR(32) NOT NULL DEFAULT 'text' COMMENT 'text/image/file/audio/video/system 等',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'normal' COMMENT 'normal/recalled/deleted/blocked',
|
||||
content JSON NULL COMMENT '消息正文或 Drafty 内容',
|
||||
head JSON NULL COMMENT '消息头,包含 @、引用、客户端能力等',
|
||||
mentioned_user_ids JSON NULL COMMENT '@ 用户列表,便于提醒和检索',
|
||||
mention_all TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否 @ 全体',
|
||||
reply_seqid BIGINT NULL COMMENT '回复的群消息 seq',
|
||||
root_seqid BIGINT NULL COMMENT '线程或合并上下文根消息 seq',
|
||||
file_count INT NOT NULL DEFAULT 0 COMMENT '附件数量',
|
||||
content_size INT NOT NULL DEFAULT 0 COMMENT '正文 JSON 序列化后的字节数',
|
||||
has_risk TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否命中风控或内容安全',
|
||||
audit_status VARCHAR(32) NOT NULL DEFAULT 'pass' COMMENT 'pass/pending/reject',
|
||||
version INT NOT NULL DEFAULT 1 COMMENT '编辑版本',
|
||||
recalled_at DATETIME(3) NULL,
|
||||
recalled_by BIGINT NULL,
|
||||
deleted_at DATETIME(3) NULL,
|
||||
expires_at DATETIME(3) NULL COMMENT '可选保留期,便于过期归档或清理',
|
||||
created_at DATETIME(3) NOT NULL,
|
||||
updated_at DATETIME(3) NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY uk_im_group_messages_group_seq (group_id, seqid),
|
||||
UNIQUE KEY uk_im_group_messages_topic_seq (topic, seqid),
|
||||
UNIQUE KEY uk_im_group_messages_topic_sender_client_mid (topic, from_user, client_msg_id),
|
||||
KEY idx_im_group_messages_group_created (group_id, created_at),
|
||||
KEY idx_im_group_messages_group_status_seq (group_id, status, seqid),
|
||||
KEY idx_im_group_messages_topic_created (topic, created_at),
|
||||
KEY idx_im_group_messages_topic_from_seq (topic, from_user, seqid),
|
||||
KEY idx_im_group_messages_audit (audit_status, created_at),
|
||||
KEY idx_im_group_messages_expires (expires_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
@@ -0,0 +1,110 @@
|
||||
# IM 第三方接入本地测试结果
|
||||
|
||||
测试日期:2026-08-24
|
||||
|
||||
测试环境:本地环境,MySQL `127.0.0.1:3306`,控制库 `hotgo`,业务库 `tinode`。
|
||||
|
||||
## 1. 测试结论
|
||||
|
||||
| 项 | 结果 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 第三方相关普通单元测试 | 通过 | OSS、文件安全配置、阿里云内容安全封装测试通过 |
|
||||
| MySQL build tag 单元测试 | 通过 | 文件审核表结构、MySQL 适配相关测试通过 |
|
||||
| 本地 API handler 测试 | 通过 | 群文件、群邀请接受、实名申诉、组织成员、月报 reopen、反馈/举报、信箱、会话设置、云草稿等接口级测试通过 |
|
||||
| 本地 MySQL 连通性 | 通过 | 可连接 `hotgo` 与 `tinode` |
|
||||
| 第三方配置读取 | 通过 | `im_third_party` 配置存在 18 条,已按本地第三方依赖配置启用 OSS、内容审核和恶意文件检测 |
|
||||
| 文件审核字典 | 通过 | provider 2 条、task type 2 条、status 5 条 |
|
||||
| 文件审核任务表 | 通过 | `tinode.im_file_review_tasks` 存在,且不包含 `tenant_id` |
|
||||
| 真实 OSS 上传/下载 | 通过 | 使用普通 Bucket endpoint 完成 put/get/sign URL,并清理测试对象 |
|
||||
| 真实内容安全 | 通过 | 使用阿里云内容安全增强版 `FileModeration + document_detection` 提交并轮询,返回 `clean/none` |
|
||||
| 真实恶意文件检测 | 阻塞 | SDK 请求 `tds.aliyuncs.com` 时本地网络连接被 reset,`curl` 直连同样失败 |
|
||||
|
||||
## 2. 执行命令
|
||||
|
||||
```bash
|
||||
go test -count=1 ./server/media/oss ./server/filesafety ./server/filesafety/aliyun ./server/db/mysql
|
||||
go test -count=1 -tags mysql ./server/media/oss ./server/filesafety ./server/filesafety/aliyun ./server/db/mysql
|
||||
go test -count=1 ./server -run 'TestServeIM(GroupFiles|GroupInvitationAccept|RealNameAppeal|OrganizationMembers|MonthlyReportReopen|DailyReport|MonthlyReport|Mailbox|Feedback|Abuse|ConversationSettings|TopicDraft|P2PConversation|ForwardTargets)'
|
||||
```
|
||||
|
||||
## 3. 命令结果
|
||||
|
||||
| 命令 | 结果 |
|
||||
| --- | --- |
|
||||
| `go test -count=1 ./server/media/oss ./server/filesafety ./server/filesafety/aliyun ./server/db/mysql` | 通过 |
|
||||
| `go test -count=1 -tags mysql ./server/media/oss ./server/filesafety ./server/filesafety/aliyun ./server/db/mysql` | 通过 |
|
||||
| `go test -count=1 ./server -run ...` | 通过 |
|
||||
|
||||
## 4. 本地数据库核验
|
||||
|
||||
| 核验项 | 结果 |
|
||||
| --- | --- |
|
||||
| MySQL ping | 通过 |
|
||||
| `hotgo` 数据库 | 存在 |
|
||||
| `tinode` 数据库 | 存在 |
|
||||
| `hotgo.hg_sys_config` 中 `im_third_party` 配置数量 | 18 |
|
||||
| `oss_enabled` | 1 |
|
||||
| `file_review_content_enabled` | 1 |
|
||||
| `file_review_malware_enabled` | 1 |
|
||||
| OSS 敏感配置 | 已配置,报告不输出具体值 |
|
||||
| 阿里云审核敏感配置 | 已配置,报告不输出具体值 |
|
||||
| `im_file_review_provider` 字典 | 2 条 |
|
||||
| `im_file_review_task_type` 字典 | 2 条 |
|
||||
| `im_file_review_status` 字典 | 5 条 |
|
||||
| `tinode.im_file_review_tasks` | 存在 |
|
||||
| `im_file_review_tasks.tenant_id` | 不存在,符合租户分库方案 |
|
||||
| `im_file_review_tasks` 当前记录数 | 0 |
|
||||
|
||||
## 5. 覆盖情况
|
||||
|
||||
| 测试范围 | 本次覆盖 | 结果 |
|
||||
| --- | --- | --- |
|
||||
| OSS 配置读取与对象路径规则 | 已覆盖单元测试 | 通过 |
|
||||
| `im_third_party` 配置读取 | 已覆盖单元测试和本地库只读核验 | 通过 |
|
||||
| `im_file_review_tasks` 表结构 | 已覆盖 MySQL tag 测试和本地库只读核验 | 通过 |
|
||||
| 上传完成创建审核任务 | 已覆盖代码路径和表结构测试;本地无真实上传任务 | 部分通过 |
|
||||
| 群文件 API handler | 已覆盖本地 handler 测试 | 通过 |
|
||||
| 举报/反馈/信箱附件相关 API handler | 已覆盖本地 handler 测试 | 通过 |
|
||||
| 日报/月报附件相关 API handler | 已覆盖本地 handler 测试 | 通过 |
|
||||
| P2P 会话、会话设置、云草稿 | 已覆盖本地 handler 测试 | 通过 |
|
||||
| 真实 OSS API | 已上传、读取、生成签名 URL、清理测试对象 | 通过 |
|
||||
| 真实内容安全 API | 已提交 OSS 文件审核并轮询结果 | 通过 |
|
||||
| 真实恶意文件检测 API | 已使用官方 SAS SDK 提交检测,受本地网络限制未到达服务端 | 阻塞 |
|
||||
| 风险审核结果影响下载/展示 | worker 和策略闭环未完成 | 未执行 |
|
||||
|
||||
## 6. 真实第三方调用补测
|
||||
|
||||
补测时间:2026-08-24
|
||||
|
||||
### 6.1 补测操作
|
||||
|
||||
1. 从本地第三方依赖文档读取测试账号、OSS Bucket 和地域信息。
|
||||
2. 将本地 `hotgo.hg_sys_config` 的 `im_third_party` 配置更新为启用状态。
|
||||
3. 使用普通 Bucket endpoint `oss-cn-shenzhen.aliyuncs.com` 上传测试 TXT 文件。
|
||||
4. 对同一 OSS 对象执行读取、签名 URL 生成和测试对象清理。
|
||||
5. 使用阿里云内容安全增强版 `FileModeration + document_detection` 提交该 OSS 对象并轮询结果。
|
||||
6. 使用阿里云云安全中心官方 SAS SDK 调用 `CreateFileDetect` 和 `GetFileDetectResult`。
|
||||
|
||||
### 6.2 补测结果
|
||||
|
||||
| 项 | 结果 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| 本地配置启用 | 通过 | `oss_enabled`、`file_review_content_enabled`、`file_review_malware_enabled` 已置为 1 |
|
||||
| OSS `PutObject` | 通过 | 测试文件成功写入 OSS |
|
||||
| OSS `GetObject` | 通过 | 测试文件可读取且内容一致 |
|
||||
| OSS `SignURL` | 通过 | 可生成短时下载 URL |
|
||||
| OSS 测试对象清理 | 通过 | `codex-live/` 测试前缀已清理 |
|
||||
| 内容安全提交 | 通过 | `FileModeration` 返回任务 ID |
|
||||
| 内容安全轮询 | 通过 | 轮询结果为 `status=clean,risk=none` |
|
||||
| 云安全中心提交 | 阻塞 | 本地网络访问 `https://tds.aliyuncs.com` 被 reset,未到达业务响应 |
|
||||
|
||||
### 6.3 SAS 阻塞说明
|
||||
|
||||
云安全中心 SDK 内置中国区 endpoint 为 `tds.aliyuncs.com`。本地环境中该域名解析到 `198.18.1.113`,无论 SDK 调用还是 `curl -I https://tds.aliyuncs.com/` 都返回 connection reset。备用 endpoint `tds.ap-southeast-1.aliyuncs.com` 在当前网络下同样 reset。因此本轮无法判断 SAS 账号权限或检测结果,只能确认本地网络到 SAS endpoint 不通。
|
||||
|
||||
## 7. 遗留问题
|
||||
|
||||
1. 本地网络无法访问云安全中心恶意文件检测 endpoint `tds.aliyuncs.com`,需要换网络、服务器环境或排查网络策略后复测。
|
||||
2. 文件安全 worker 尚未完成,pending 审核任务不会自动提交第三方、轮询或回写状态。
|
||||
3. 下载和业务展示侧尚未完整按 `im_file_review_tasks` 风险状态阻断或告警。
|
||||
4. 纯文本内容安全不属于文件审核链路,群简介、签名、举报/反馈正文、日报/月报正文仍需独立文本审核方案。
|
||||
@@ -2,46 +2,46 @@
|
||||
|
||||
## 1. 测试范围
|
||||
|
||||
- 测试时间:`2026-08-21 00:39:01 +0800`
|
||||
- 测试时间:`2026-08-24 11:15:16 +0800`
|
||||
- HTTP:`https://api.118im.com`
|
||||
- WebSocket:`wss://api.118im.com/v0/channels`
|
||||
- 租户:`default`
|
||||
- 用户 A:`usr7-zA3UMrw7c` / `wsa459000` / `15794459000`
|
||||
- 用户 B:`usrnDXQ3A2oQP0` / `wsb459000` / `15894459000`
|
||||
- 用户 A:`usrO4KoysjOOVE` / `wsa043000` / `15757043000`
|
||||
- 用户 B:`usricjD1vg9CXU` / `wsb043000` / `15857043000`
|
||||
- API Key 与 Token 已脱敏,不在文档中展开。
|
||||
|
||||
## 2. 总体结论
|
||||
|
||||
本轮专项验证 Tinode WebSocket 真实协议链路,共执行 `29` 项,通过 `27` 项,未通过 `2` 项。
|
||||
本轮专项验证 Tinode WebSocket 真实协议链路,共执行 `29` 项,通过 `28` 项,未通过 `1` 项。
|
||||
|
||||
| ID | 场景 | 方法 | 目标 | 结果 | 备注 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `WS-01` | 注册用户 A: HTTP 验证码 + WS acc | `WS` | `/v0/channels` | 通过 | user=usr7-zA3UMrw7c token=AwEA...18Om |
|
||||
| `WS-02` | 注册用户 B: HTTP 验证码 + WS acc | `WS` | `/v0/channels` | 通过 | user=usrnDXQ3A2oQP0 token=AwEA...NX6b |
|
||||
| `WS-03` | HTTP 建立好友关系生成 P2P Topic | `HTTP` | `/v1/im/friend-requests` | 通过 | request_id=frq_KAOkEfarCOA topic=p2p7-zA3UMrw7ecNdDcDahA_Q |
|
||||
| `WS-04` | A token login + sub me | `WS` | `/v0/channels` | 通过 | login user=usr7-zA3UMrw7c |
|
||||
| `WS-05` | B token login + sub me | `WS` | `/v0/channels` | 通过 | login user=usrnDXQ3A2oQP0 |
|
||||
| `WS-06` | A/B 订阅 P2P Topic | `WS` | `p2p7-zA3UMrw7ecNdDcDahA_Q` | 通过 | topic=p2p7-zA3UMrw7ecNdDcDahA_Q |
|
||||
| `WS-07` | A pub 文本消息,B 收到 data | `WS` | `p2p7-zA3UMrw7ecNdDcDahA_Q` | 通过 | seq=1 bob_topic=usr7-zA3UMrw7c |
|
||||
| `WS-08` | B 发送 recv/read 回执 | `WS` | `p2p7-zA3UMrw7ecNdDcDahA_Q` | 通过 | what=read seq=1 |
|
||||
| `WS-01` | 注册用户 A: HTTP 验证码 + WS acc | `WS` | `/v0/channels` | 通过 | user=usrO4KoysjOOVE token=AwEA...S/Xa |
|
||||
| `WS-02` | 注册用户 B: HTTP 验证码 + WS acc | `WS` | `/v0/channels` | 通过 | user=usricjD1vg9CXU token=AwEA...Reda |
|
||||
| `WS-03` | HTTP 建立好友关系生成 P2P Topic | `HTTP` | `/v1/im/friend-requests` | 通过 | request_id=frq_xYKajMBbqQM topic=p2pO4KoysjOOVGJyMPW-D0JdQ |
|
||||
| `WS-04` | A token login + sub me | `WS` | `/v0/channels` | 通过 | login user=usrO4KoysjOOVE |
|
||||
| `WS-05` | B token login + sub me | `WS` | `/v0/channels` | 通过 | login user=usricjD1vg9CXU |
|
||||
| `WS-06` | A/B 订阅 P2P Topic | `WS` | `p2pO4KoysjOOVGJyMPW-D0JdQ` | 通过 | topic=p2pO4KoysjOOVGJyMPW-D0JdQ |
|
||||
| `WS-07` | A pub 文本消息,B 收到 data | `WS` | `p2pO4KoysjOOVGJyMPW-D0JdQ` | 通过 | seq=1 bob_topic=usrO4KoysjOOVE |
|
||||
| `WS-08` | B 发送 recv/read 回执 | `WS` | `p2pO4KoysjOOVGJyMPW-D0JdQ` | 通过 | what=read seq=1 |
|
||||
| `WS-09` | basic login + sub me | `WS` | `/v0/channels` | 通过 | basic login ok |
|
||||
| `WS-10` | 重复 login 被拒绝 | `WS` | `/v0/channels` | 通过 | code=409 text=already authenticated |
|
||||
| `WS-11` | 订阅 fnd 搜索 Topic | `WS` | `fnd` | 通过 | sub fnd ok |
|
||||
| `WS-12` | get me desc/sub/tags/cred | `WS` | `me` | 通过 | get me ok |
|
||||
| `WS-13` | get P2P desc/sub/data/del | `WS` | `p2p7-zA3UMrw7ecNdDcDahA_Q` | 通过 | get p2p ok |
|
||||
| `WS-14` | set P2P private 设置 | `WS` | `p2p7-zA3UMrw7ecNdDcDahA_Q` | 通过 | set private ok |
|
||||
| `WS-15` | pub 产品 x-im 基础头 | `WS` | `p2p7-zA3UMrw7ecNdDcDahA_Q` | 通过 | seq=2 bob_topic=usr7-zA3UMrw7c |
|
||||
| `WS-16` | pub 产品 x-im-client-mid 幂等头 | `WS` | `p2p7-zA3UMrw7ecNdDcDahA_Q` | 未通过 | pub ctrl: ctrl id=pub-003857.244320000 code=500 text=internal error |
|
||||
| `WS-17` | note kp 输入中通知 | `WS` | `usr7-zA3UMrw7c` | 通过 | what=kp |
|
||||
| `WS-18` | del 当前用户消息视图 | `WS` | `p2p7-zA3UMrw7ecNdDcDahA_Q` | 通过 | deleted seq=1 |
|
||||
| `WS-19` | leave P2P detach 后重新订阅 | `WS` | `p2p7-zA3UMrw7ecNdDcDahA_Q` | 通过 | leave/resub ok |
|
||||
| `WS-20` | HTTP 创建群供 WS 群协议测试 | `HTTP` | `/v1/im/groups` | 通过 | topic=grpURzKYanXB1Y |
|
||||
| `WS-21` | A/B 订阅群 Topic | `WS` | `grpURzKYanXB1Y` | 通过 | sub group ok |
|
||||
| `WS-22` | 群 pub 文本广播 | `WS` | `grpURzKYanXB1Y` | 通过 | seq=1 topic=grpURzKYanXB1Y |
|
||||
| `WS-23` | get 群 desc/sub/data | `WS` | `grpURzKYanXB1Y` | 通过 | get group ok |
|
||||
| `WS-13` | get P2P desc/sub/data/del | `WS` | `p2pO4KoysjOOVGJyMPW-D0JdQ` | 通过 | get p2p ok |
|
||||
| `WS-14` | set P2P private 设置 | `WS` | `p2pO4KoysjOOVGJyMPW-D0JdQ` | 通过 | set private ok |
|
||||
| `WS-15` | pub 产品 x-im 基础头 | `WS` | `p2pO4KoysjOOVGJyMPW-D0JdQ` | 通过 | seq=2 bob_topic=usrO4KoysjOOVE |
|
||||
| `WS-16` | pub 产品 x-im-client-mid 幂等头 | `WS` | `p2pO4KoysjOOVGJyMPW-D0JdQ` | 通过 | seq=3 bob_topic=usrO4KoysjOOVE |
|
||||
| `WS-17` | note kp 输入中通知 | `WS` | `usrO4KoysjOOVE` | 通过 | what=kp |
|
||||
| `WS-18` | del 当前用户消息视图 | `WS` | `p2pO4KoysjOOVGJyMPW-D0JdQ` | 通过 | deleted seq=1 |
|
||||
| `WS-19` | leave P2P detach 后重新订阅 | `WS` | `p2pO4KoysjOOVGJyMPW-D0JdQ` | 通过 | leave/resub ok |
|
||||
| `WS-20` | HTTP 创建群供 WS 群协议测试 | `HTTP` | `/v1/im/groups` | 通过 | topic=grpNJqhCtAZwYM |
|
||||
| `WS-21` | A/B 订阅群 Topic | `WS` | `grpNJqhCtAZwYM` | 通过 | sub group ok |
|
||||
| `WS-22` | 群 pub 文本广播 | `WS` | `grpNJqhCtAZwYM` | 通过 | seq=1 topic=grpNJqhCtAZwYM |
|
||||
| `WS-23` | get 群 desc/sub/data | `WS` | `grpNJqhCtAZwYM` | 通过 | get group ok |
|
||||
| `WS-24` | 文件上传 /v0/file/u | `HTTP` | `/v0/file/u/` | 通过 | url=/v0/....bin |
|
||||
| `WS-25` | WS 文件消息下发 | `WS` | `p2p7-zA3UMrw7ecNdDcDahA_Q` | 通过 | seq=3 bob_topic=usr7-zA3UMrw7c |
|
||||
| `WS-26` | WebRTC 通话 invite pub | `WS` | `p2p7-zA3UMrw7ecNdDcDahA_Q` | 未通过 | pub ctrl: ctrl id=pub-003859.867810000 code=501 text=not implemented |
|
||||
| `WS-25` | WS 文件消息下发 | `WS` | `p2pO4KoysjOOVGJyMPW-D0JdQ` | 通过 | seq=4 bob_topic=usrO4KoysjOOVE |
|
||||
| `WS-26` | WebRTC 通话 invite pub | `WS` | `p2pO4KoysjOOVGJyMPW-D0JdQ` | 未通过 | pub ctrl: ctrl id=pub-111508.550364000 code=501 text=not implemented |
|
||||
| `WS-27` | 未 hi 直接 login 被拒绝 | `WS` | `/v0/channels` | 通过 | code=409 text=command out of sequence |
|
||||
| `WS-28` | hi 后切换 tenant 被拒绝 | `WS` | `/v0/channels` | 通过 | code=409 text=command out of sequence |
|
||||
| `WS-29` | 非法 API Key 拒绝建连 | `WS` | `/v0/channels` | 通过 | http_status=403 |
|
||||
@@ -53,6 +53,6 @@
|
||||
|
||||
## 4. 测试产生的数据
|
||||
|
||||
- P2P Topic:`p2p7-zA3UMrw7ecNdDcDahA_Q`
|
||||
- Bob 收到的 Topic:`usr7-zA3UMrw7c`
|
||||
- P2P Topic:`p2pO4KoysjOOVGJyMPW-D0JdQ`
|
||||
- Bob 收到的 Topic:`usrO4KoysjOOVE`
|
||||
- 消息 Seq:`1`
|
||||
|
||||