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

16 KiB
Raw Blame History

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 承载。

计算规则:

topic_unread_count = max(seq - read, 0)

客户端登录后必须执行:

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 建议新增:

{
  "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 处理。若为了统一,也可以显式返回:

{
  "enabled": true,
  "route": "/contacts/new-friends",
  "unread_count": 2,
  "unread_source": "business"
}

7. 工作台 unread 规则

当前 modules 结构保留:

{
  "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:
{
  "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 返回:

{
  "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:

{
  "file_assistant": {
    "enabled": true,
    "route": "/chat/file-assistant",
    "topic": "slf",
    "unread_count": 0,
    "unread_source": "topic"
  }
}

未读通过 sub me 中该 Topic 的 seq/read 计算。

9.2 纯入口形态

如果文件助手只是本地入口,不对应消息 Topic:

{
  "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 示例

{
  "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。