Files
im_server/docs/IM-bootstrap入口与未读数改造需求文档.md
T

447 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# IM bootstrap 入口与未读数改造需求文档
## 1. 背景
当前 `GET /v1/im/bootstrap` 已返回登录后的功能开关、权限、工作台模块、系统 Topic 和安全摘要。前端同事在接入时提出以下问题:
1. 新朋友未处理数、群列表、文件助手和系统消息入口是否都由 bootstrap 控制显示。
2. 未读数字段分别从哪里读取。
3. 工作台是否需要总未读数。
4. 文件助手和系统消息是否完全依赖 WebSocket Topic 推送,还是 bootstrap 也要返回入口和初始未读。
5. 文档需要给出完整响应示例,避免入口字段和里层结构继续口头确认。
现有前后端对接文档已有基础约定:
1. 登录后客户端必须执行 `sub me`,从 `meta.sub` 获取每个会话 Topic 的 `seq/read/recv`。
2. 普通聊天会话未读数使用 `max(seq - read, 0)`。
3. `bootstrap` 返回固定入口、功能开关、权限和系统 Topic。
4. `workbench.modules[].unread_count` 已在结构中定义,但当前实现固定为 0。
缺口在于:bootstrap 没有明确返回通讯录入口、系统消息入口、文件助手入口、工作台总未读和业务 badge;文档也没有明确哪些未读来自 Topic,哪些未读来自业务表统计。
## 2. 目标
1. 明确 `bootstrap` 是登录后首屏入口配置和初始 badge 的聚合接口。
2. 明确 Topic 类未读和业务类未读的分工。
3. 为前端提供稳定响应结构,减少后续沟通成本。
4. 保持聊天实时链路轻量:普通聊天、群聊、文件助手和系统消息的实时增量仍通过 Tinode WebSocket Topic 更新。
5. 为后续工作台 badge、系统通知和好友申请的实时事件留出兼容空间。
## 3. 非目标
1. 不把所有会话列表塞入 bootstrap;普通会话列表仍通过 `sub me` 和会话搜索/列表能力获取。
2. 不用 bootstrap 替代 WebSocket 增量更新。
3. 不让客户端根据本地按钮状态绕过服务端权限校验。
4. 不要求本期实现复杂实时聚合服务;可先返回准确初始值,再通过已有 Topic 或系统事件增量更新。
## 4. 概念定义
| 名称 | 含义 |
| --- | --- |
| 入口 entry | 登录后首页、通讯录、工作台等固定可见入口配置 |
| Topic 未读 | 由 Tinode Topic 的 `seq/read/recv` 推导出的消息未读 |
| 业务 badge | 不能用 Topic `seq/read` 表达的业务待处理数量,例如新朋友待处理申请 |
| 工作台模块未读 | `modules[].unread_count`,表示单个工作台模块的业务未读 |
| 工作台总未读 | 所有可见工作台模块未读汇总,供底部 Tab 或一级入口展示 |
| 系统消息 | 服务端可信系统通知,展示页可查 HTTP 列表,实时通知可走系统 Topic 或系统事件 |
| 文件助手 | 用户自己的固定会话入口,用于收藏/文件转发/个人文件消息;若实现为 Topic,则未读遵循 Topic 规则 |
## 5. 未读数来源规则
### 5.1 Topic 类未读
以下未读原则上来自 Tinode Topic:
1. 单聊会话。
2. 群聊会话。
3. 系统消息入口,如果系统消息以 `system_topic.topic` 作为可信 Topic 承载。
4. 文件助手入口,如果文件助手以固定 Topic 承载。
计算规则:
```text
topic_unread_count = max(seq - read, 0)
```
客户端登录后必须执行:
```text
WebSocket Upgrade -> hi -> token login -> sub me -> get sub/data
```
初始会话列表从 `meta.sub` 获取,后续通过 `{data}`、`{meta}`、`{pres}`、`{info}` 和 `note read/recv` 更新本地状态。
### 5.2 业务类未读
以下未读不适合用 Topic `seq/read` 计算,应由 HTTP 业务接口或 bootstrap 返回:
1. 新朋友待处理申请数。
2. 工作台模块待处理数,例如会议、日报、月报、总经理信箱。
3. 未来的审批、公告、任务等非聊天会话类 badge。
业务类未读的真源在业务表,bootstrap 返回登录后的初始聚合值;后续增量可通过系统事件通知前端刷新或局部更新。
## 6. bootstrap 响应结构扩展
### 6.1 顶层新增字段
`GET /v1/im/bootstrap` 的 `data` 建议新增:
```json
{
"entry_badges": {
"contacts_total_unread": 2,
"new_friends_pending_count": 2,
"groups_unread_count": 0,
"file_assistant_unread_count": 0,
"system_unread_count": 0,
"workbench_total_unread": 5
},
"entries": {
"new_friends": {
"enabled": true,
"route": "/contacts/new-friends",
"unread_count": 2
},
"groups": {
"enabled": true,
"route": "/contacts/groups",
"unread_count": 0
},
"file_assistant": {
"enabled": true,
"route": "/chat/file-assistant",
"topic": "slf",
"unread_count": 0,
"unread_source": "topic"
},
"system_messages": {
"enabled": true,
"route": "/chat/system",
"topic": "sys",
"unread_count": 0,
"unread_source": "business"
}
}
}
```
字段说明:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `entry_badges.contacts_total_unread` | int | 通讯录一级入口红点总数;本期可等于 `new_friends_pending_count` |
| `entry_badges.new_friends_pending_count` | int | 当前用户待处理入站好友申请数 |
| `entry_badges.groups_unread_count` | int | 群入口业务未读;若只是群聊消息未读,则可为 0,由会话列表负责 |
| `entry_badges.file_assistant_unread_count` | int | 文件助手入口初始未读 |
| `entry_badges.system_unread_count` | int | 系统消息入口初始未读 |
| `entry_badges.workbench_total_unread` | int | 工作台可见模块未读总和 |
| `entries.*.enabled` | bool | 是否展示入口 |
| `entries.*.route` | string | 前端内部路由,只允许白名单内部路径 |
| `entries.*.topic` | string | 若入口对应 Topic,则返回 Topic 名;没有 Topic 时省略 |
| `entries.*.unread_count` | int | 当前入口初始未读或待处理数 |
| `entries.*.unread_source` | string | `topic` 或 `business`,用于前端判断后续更新来源 |
### 6.2 unread_source 约定
| unread_source | 含义 | 后续更新方式 |
| --- | --- | --- |
| `topic` | 未读来自 Topic `seq/read` | WebSocket Topic 增量和 `note read/recv` |
| `business` | 未读来自业务表统计 | 业务 HTTP 刷新或系统事件 |
| `none` | 入口无未读概念 | 不展示 badge |
`new_friends.unread_source` 可省略,默认按 `business` 处理。若为了统一,也可以显式返回:
```json
{
"enabled": true,
"route": "/contacts/new-friends",
"unread_count": 2,
"unread_source": "business"
}
```
## 7. 工作台 unread 规则
当前 `modules` 结构保留:
```json
{
"code": "meetings",
"name": "会议通知",
"icon_file_id": "",
"route": "/meetings",
"unread_count": 3,
"enabled": true,
"permission": "enabled"
}
```
新增规则:
1. `modules[].unread_count` 是模块级业务未读。
2. `entry_badges.workbench_total_unread` 是所有可见且 `enabled=true` 模块的未读总和。
3. 隐藏或无权限模块不计入总未读。
4. 后续增量事件使用 `workbench.badge.updated`:
```json
{
"type": "system_event",
"event_type": "workbench.badge.updated",
"event_id": "evt_...",
"created_at": "2026-08-20T12:00:00.000Z",
"params": {
"module_code": "meetings",
"unread_count": 4,
"workbench_total_unread": 6,
"workbench_version": 1787153039
}
}
```
前端收到后只更新对应模块和工作台总未读;如果 `workbench_version` 大于本地版本,应重新调用 `GET /v1/im/workbench` 或 `GET /v1/im/bootstrap`。
## 8. 系统消息规则
系统消息有两个视角:
1. 列表查询:`GET /v1/im/system-notifications?cursor=` 返回历史通知列表。
2. 实时增量:通过服务端可信系统事件推送。
bootstrap 返回:
```json
{
"system_topic": {
"topic": "sys",
"type": "system"
},
"entries": {
"system_messages": {
"enabled": true,
"route": "/chat/system",
"topic": "sys",
"unread_count": 0,
"unread_source": "business"
}
}
}
```
实现要求:
1. 普通用户不能向系统 Topic 发布可信系统消息。
2. 客户端不能把普通用户伪造的 `system_event` 渲染为可信通知。
3. 本期系统消息未读使用 `im_system_notifications.read_at` 统计,`unread_source` 固定为 `business`,并由服务端返回准确 `system_unread_count`。
本期约定:系统消息入口保留 `topic:"sys"` 作为入口标识和后续事件通道标识,初始未读数以系统通知业务表为准。若后续确认普通用户不能订阅 `sys`,应改为每用户系统通知 Topic 或纯 HTTP 通知列表 + business badge。
## 9. 文件助手规则
文件助手需要先明确产品实现形态:
### 9.1 Topic 形态
如果文件助手是用户自己的固定 Topic,例如 `slf` 或专用 `file_assistant_topic`:
```json
{
"file_assistant": {
"enabled": true,
"route": "/chat/file-assistant",
"topic": "slf",
"unread_count": 0,
"unread_source": "topic"
}
}
```
未读通过 `sub me` 中该 Topic 的 `seq/read` 计算。
### 9.2 纯入口形态
如果文件助手只是本地入口,不对应消息 Topic:
```json
{
"file_assistant": {
"enabled": true,
"route": "/files/assistant",
"unread_count": 0,
"unread_source": "none"
}
}
```
此时不展示消息未读,只展示入口。
本期建议采用 Topic 形态,便于复用消息、文件、转发、未读和多端同步能力。
## 10. 通讯录入口规则
通讯录页包含以下固定入口:
1. 新朋友。
2. 群聊。
3. 文件助手。
4. 系统消息。
bootstrap 控制入口展示和初始 badge。具体列表仍由各自接口负责:
| 入口 | 展示控制 | 初始 badge | 列表数据 |
| --- | --- | --- | --- |
| 新朋友 | `entries.new_friends.enabled` | `new_friends_pending_count` | `GET /v1/im/friend-requests` |
| 群聊 | `entries.groups.enabled` | 群业务 badge,群消息未读走 Topic | 群列表接口或 `sub me` 中群 Topic |
| 文件助手 | `entries.file_assistant.enabled` | Topic 或 none | Topic 消息或文件助手专用接口 |
| 系统消息 | `entries.system_messages.enabled` | Topic 或 business | `GET /v1/im/system-notifications` |
## 11. 完整 bootstrap 示例
```json
{
"code": 0,
"data": {
"features": {
"create_group": true,
"message_edit": true,
"message_recall": true,
"scan_qr": true,
"system_notifications": true,
"user_search": true
},
"permissions": {
"create_group": true,
"scan_qr": true,
"user_search": true
},
"entry_badges": {
"contacts_total_unread": 2,
"new_friends_pending_count": 2,
"groups_unread_count": 0,
"file_assistant_unread_count": 0,
"system_unread_count": 1,
"workbench_total_unread": 5
},
"entries": {
"new_friends": {
"enabled": true,
"route": "/contacts/new-friends",
"unread_count": 2,
"unread_source": "business"
},
"groups": {
"enabled": true,
"route": "/contacts/groups",
"unread_count": 0,
"unread_source": "business"
},
"file_assistant": {
"enabled": true,
"route": "/chat/file-assistant",
"topic": "slf",
"unread_count": 0,
"unread_source": "topic"
},
"system_messages": {
"enabled": true,
"route": "/chat/system",
"topic": "sys",
"unread_count": 1,
"unread_source": "business"
}
},
"modules": [
{
"code": "organization",
"enabled": true,
"icon_file_id": "",
"name": "组织架构",
"permission": "enabled",
"route": "/organization",
"unread_count": 0
},
{
"code": "meetings",
"enabled": true,
"icon_file_id": "",
"name": "会议通知",
"permission": "enabled",
"route": "/meetings",
"unread_count": 3
}
],
"workbench_version": 1787153039,
"system_topic": {
"topic": "sys",
"type": "system"
},
"message_policy": {
"edit_window_seconds": 300,
"recall_window_seconds": 120,
"supported_actions": ["copy", "quote", "delete", "recall", "edit"]
},
"security": {
"realname": {
"allow_skip": true,
"remind_after": "24h",
"required": false,
"status": "unverified"
}
}
},
"request_id": "req_xxx",
"server_time": "2026-08-20T12:00:00.000Z"
}
```
## 12. 前端处理规则
1. bootstrap 成功后,先渲染固定入口和初始 badge。
2. 随后完成 WebSocket `sub me`,用 Topic `seq/read` 覆盖 Topic 类入口未读。
3. `unread_source=topic` 的入口以后以 Topic 状态为准。
4. `unread_source=business` 的入口以后以业务接口刷新或系统事件为准。
5. 切租户、退出登录、切账号时必须清空本地入口和 badge 缓存。
6. 前端不得把 `tenant_id`、内部数据库 ID 或未在白名单中的 route 写入跳转。
## 13. 服务端实现要求
1. `serveIMBootstrap` 增加 `entry_badges` 和 `entries`。
2. `serveIMBootstrap` 的入口数据必须按当前 Token 的租户和用户权限裁剪。
3. `route` 必须走服务端白名单,不能从数据库任意透传外部 URL。
4. 新朋友待处理数按当前租户、当前用户、入站、待处理状态统计。
5. 工作台 `modules[].unread_count` 从业务统计或模块 unread provider 获取,不再固定为 0。
6. `workbench_total_unread` 只汇总当前用户可见模块。
7. 文件助手和系统消息的 `topic` 必须是当前租户和当前用户有权限访问的 Topic。
8. 如果某入口当前未实现,仍可返回 `enabled:false`,前端隐藏入口。
## 14. 后端建议实现顺序
1. 扩展 bootstrap 响应结构,先返回固定入口和 0 badge,确保前端结构稳定。
2. 增加新朋友待处理数统计。
3. 增加工作台模块 unread provider 和总未读。
4. 明确文件助手 Topic,补齐入口 topic 和未读来源。
5. 明确系统消息未读来源;若 `sys` 不适合普通用户订阅,则改为业务 unread。
6. 增加系统事件 `workbench.badge.updated` 和入口 badge 更新事件。
7. 更新前后端对接文档,把本需求中的响应示例合并到 bootstrap 章节。
## 15. 验收用例
1. 新用户登录调用 bootstrap,返回 `entries`、`entry_badges`、`modules`、`workbench_version`。
2. 有 2 条待处理好友申请时,`new_friends_pending_count=2`,`entries.new_friends.unread_count=2`。
3. 没有待处理好友申请时,新朋友入口仍展示但 unread 为 0。
4. 工作台有两个可见模块未读分别为 3 和 2 时,`workbench_total_unread=5`。
5. 隐藏模块未读不计入 `workbench_total_unread`。
6. `unread_source=topic` 的入口在 `sub me` 返回后可用 Topic `seq/read` 覆盖初始 unread。
7. 切换租户后 bootstrap 返回另一租户入口和 badge,本地旧 badge 不复用。
8. 普通用户构造外部 route 或系统消息字段不能影响 bootstrap 返回。
9. 未实现入口返回 `enabled:false` 时前端隐藏入口。
10. 接口响应字段保持向后兼容,旧客户端忽略新增字段仍可正常工作。
## 16. 待确认问题
1. 文件助手最终使用 `slf` Topic,还是新增专用文件助手 Topic。
2. 普通用户系统消息入口是否继续使用 `sys`,还是改为每用户系统通知 Topic 或纯 HTTP 通知列表。
3. 群聊入口是否需要业务未读,还是完全由群 Topic 会话列表承担。
4. 工作台每个模块的 unread provider 由 IM 直接查库,还是由 HotGo/业务库提供统计。
5. 是否需要新增统一 badge 增量事件,例如 `entry.badge.updated`。