Files
im_server/docs/IM-系统设计文档.md
T

42 KiB
Raw Blame History

IM 系统设计文档

版本:v1.1
日期:2026-08-11
状态:后续开发基线
需求依据:IM 产品原型需求文档
关联文档:IM 产品需求服务端改造分析报告、IM 现有实现与新需求差异对照总结、IM 服务端多租户改造方案

版本 日期 变更
v1.0 2026-08-11 建立总体架构和业务模块设计
v1.1 2026-08-11 冻结 MySQL 范围;补齐认证、配额、消息变更、好友/群真源、审核、事务、Outbox、搜索和测试设计

1. 文档目的

本文作为后续 IM 产品开发的系统设计指导文件,用于统一服务端、客户端和测试团队对产品架构、模块边界、数据模型、接口风格、权限规则、消息事件和开发顺序的理解。

本文不替代详细接口文档和数据库 DDL。后续每个业务模块进入开发前,应在本文定义的边界内补充独立的 API 文档、状态机文档、表结构 SQL 和测试用例。

本文是产品业务开发的设计基线。关联分析报告用于记录调研过程;表名、唯一约束、状态机或技术选择与本文冲突时,以本文为准。设计发生变化时,必须先修改本文并记录版本,再修改接口和代码。

2. 设计目标

系统面向企业内部沟通与协作场景,核心目标如下:

  1. 支持企业码进入租户,所有账号、好友、群、消息、文件、组织和办公数据均按 tenant_id 强隔离。
  2. 复用现有 Tinode 实时通信内核,保留 WebSocket 消息、Topic、Subscription、回执、文件、Push 和 WebRTC 信令能力。
  3. 在实时内核外新增产品业务层,承载好友关系、群治理、收藏、搜索、组织、会议、日报、月报、实名、青少年、举报和反诈等业务。
  4. 服务端必须成为权限和状态机的唯一可信执行方,客户端只负责交互展示和本地体验优化。
  5. 所有敏感动作具备幂等、防重、审计和可测试的错误码。
  6. 支持 iOS、Android 多端一致同步,网络断开后可增量恢复。

2.1 首期技术范围

  1. 产品版首期只支持 MySQL 8,共享服务、共享数据库,通过 tenant_id 隔离租户。
  2. PostgreSQL、MongoDB 和 RethinkDB 不在首期产品业务支持范围内。启用产品模块但 Adapter 未实现完整租户业务接口时,服务必须拒绝启动,不能静默退回无租户接口。
  3. 实时消息继续使用现有 Tinode WebSocket 协议;事务型产品业务使用 HTTP API。
  4. 搜索首期使用 MySQL,达到第 11.3 节定义的切换条件后再引入独立搜索引擎。

3. 总体架构

系统采用“实时通信内核 + 产品业务服务 + 异步事件”的分层架构。

flowchart TB
    APP["iOS / Android 客户端"]

    subgraph Gateway["接入层"]
        WS["WebSocket 实时协议"]
        API["HTTP /v1/im/* 业务 API"]
        FILE["HTTP 文件上传下载"]
    end

    subgraph Core["通信内核"]
        SESSION["Session"]
        HUB["Hub / Topic Actor"]
        STORE["Tenant Store"]
        CALL["WebRTC Call Signaling"]
    end

    subgraph Product["产品业务层"]
        POLICY["策略引擎"]
        REL["好友与黑名单"]
        GROUP["群治理"]
        FAV["收藏与转发"]
        SEARCH["搜索索引"]
        ORG["组织与工作台"]
        SAFE["实名 / 青少年 / 举报 / 反诈"]
    end

    subgraph Infra["基础设施"]
        DB["MySQL"]
        CACHE["内存缓存 / 持久化缓存"]
        OUTBOX["Outbox 异步事件"]
        PUSH["Push / 短信"]
        MEDIA["对象存储"]
        PLUGIN["审核 / 风控插件"]
    end

    APP --> WS
    APP --> API
    APP --> FILE
    WS --> SESSION --> HUB --> STORE --> DB
    API --> Product --> DB
    FILE --> MEDIA
    HUB --> CALL
    HUB --> POLICY
    POLICY --> REL
    POLICY --> GROUP
    POLICY --> SAFE
    Product --> OUTBOX --> PUSH
    OUTBOX --> HUB
    POLICY --> PLUGIN

4. 核心分层职责

层 职责 禁止事项
接入层 解析 Token、Session、文件认证和租户上下文;做基础参数校验 不从客户端自报字段信任内部 tenant_id
通信内核 维护连接、Topic 路由、消息顺序、广播、回执、Push 触发和通话信令 不承载复杂审批、报表、组织树等事务业务
产品策略层 发消息前校验好友、黑名单、群禁言、青少年、文件权限和内容风险 不把策略只放客户端
业务服务层 处理好友申请、群审批、公告、收藏、组织、会议、日报、举报等 CRUD 和状态机 不把主数据塞进 Topic Public/Aux 任意 JSON
存储层 所有业务表带 tenant_id,更新删除必须带租户条件 不保留无租户业务查询入口
异步层 系统消息、Push、搜索索引、审核任务、定时提醒和审计导出 不在 Topic 事件循环中执行耗时远程调用;核心审计记录仍在业务事务内落库

5. 多租户基础规则

多租户是本系统的底座,后续所有业务模块必须遵守:

  1. 首次 hi.tenant 使用企业码解析租户,Session 固定 TenantID。
  2. Token、认证记录、设备、Push、文件和插件调用均携带服务端可信 TenantID。
  3. 普通业务 Store 只能通过租户作用域访问,禁止全局查用户、Topic、消息、文件。
  4. 新增租户业务表必须包含 tenant_id BIGINT NOT NULL。平台级表和租户根表 im_tenant 例外;例外表必须在 DDL 评审中明确说明作用域。
  5. 唯一索引优先使用 (tenant_id, business_key)。
  6. 子表外键优先使用 (tenant_id, parent_id) 复合约束。
  7. 跨租户通信首期禁止;搜索、加好友、入群、转发和文件访问均限制在当前租户。
  8. 越权访问统一返回 NotFound/Denied,不泄露目标对象是否存在于其他租户。

5.1 租户配置与配额

im_tenant_config 是租户配额的配置真源,首期使用以下字段:

字段 含义 0 的含义
max_users 租户有效用户数上限 不限制
max_groups 租户有效群数上限 不限制
max_group_members 单群有效成员数上限 不限制

配额必须由服务端在事务内强制执行:

  1. 注册或启用用户时锁定租户配置记录,统计有效用户后再创建账号。
  2. 创建群时锁定租户配置记录,统计有效群后再创建 Topic 和群业务数据。
  3. 邀请、扫码入群或审批入群时锁定目标群业务记录,统计有效成员并校验 max_group_members 后再写成员关系和 Subscription。
  4. 配额检查和资源创建必须在同一事务中完成,禁止先查询、提交后再创建。
  5. 超限返回稳定错误码,并记录 tenant_id、当前用量、限制值和操作类型;响应不得泄露其他租户信息。

6. 通道与接口风格

6.1 WebSocket 实时协议

继续使用现有 Tinode 协议承载以下实时能力:

能力 协议方向 说明
登录、注册、Token 登录 client -> server 已租户化,继续复用
发消息 pub 增加产品策略校验、消息类型和客户端幂等 ID
拉消息、拉元数据 get 保留原序号查询;复杂搜索走 HTTP
已读、送达、输入中 note 保留并增加隐私和节流规则
订阅/退订 Topic sub / leave 由好友、群审批等业务控制是否允许
通话信令 note 或现有 call 信令 增加 call_id、通话类型和结束原因
系统消息 服务端发布的 system 类型消息 个人事件进入服务端管理的系统通知 Topic;群内事件进入对应群 Topic;普通用户不能发布该类型

6.2 HTTP 业务 API

新增业务 API 使用 /v1/im/* 前缀,按模块划分:

/v1/im/friends/*
/v1/im/groups/*
/v1/im/favorites/*
/v1/im/search/*
/v1/im/org/*
/v1/im/meetings/*
/v1/im/reports/*
/v1/im/profile/*
/v1/im/settings/*
/v1/im/security/*
/v1/im/reports-abuse/*
/v1/im/feedback/*

HTTP API 通用规则:

  1. HTTP 请求使用 Authorization: Bearer <token>;统一认证中间件校验 Token 后生成只读 Principal,并从中获取 tenant_id 和 uid。请求参数中的同名字段不参与授权。
  2. 写接口必须支持幂等,使用 Idempotency-Key 或业务唯一键。HTTP 幂等键作用域固定为 tenant_id + uid + method + route + key;相同键但请求体摘要不同返回冲突错误。
  3. 列表接口统一使用游标分页,不使用不稳定的大 offset。
  4. 错误响应使用稳定业务错误码,客户端不解析数据库错误。
  5. 敏感接口记录审计日志。

6.3 文件 API

继续复用现有文件上传下载入口,但必须满足:

  1. 文件记录带 tenant_id。
  2. 上传路径使用 tenants/{tenant_id}/yyyy/mm/{file_id}。
  3. 下载前校验文件上传者或关联消息/Topic 读取权限。
  4. 群文件、收藏文件、举报证据和反馈附件复用同一文件基础设施,但业务用途单独记录。
  5. 客户端不可见内部 location。

7. 核心领域与认证设计

7.1 已有通信模型

模型 继续复用 新增约束
User 用户账号和基础资料 租户归属、实名状态、隐私设置、青少年状态
Topic 单聊、群聊、系统 Topic、收藏 Topic 租户归属、产品业务类型、群治理策略
Subscription 用户与 Topic 的关系和 ACL 好友/群状态不能只靠 ACL 表达
Message 话题内顺序消息 消息类型、幂等 ID、审核状态、搜索索引
FileDef 上传文件元数据 租户归属、业务用途、访问授权、扫描状态

7.2 新增产品模型

模块 目标表 主要职责
好友关系 im_friendships、im_friend_requests、im_user_blocks 好友申请、同意、拒绝、删除、拉黑
用户资料 im_user_profiles、im_user_privacy_settings 昵称、生日、签名、可见性和添加方式
群治理 im_group_settings、im_group_join_requests、im_group_announcements、im_group_mutes、im_group_admin_logs 入群审批、公告、禁言、管理员、转让群主
群文件 im_group_files 群文件目录、搜索、删除权限
收藏 im_favorites 收藏去重、分类、来源、搜索和分享
搜索 im_message_index 或外部搜索索引 关键词、类型、附件、链接、图片、视频检索
通话 im_call_records、im_call_participants 通话记录、状态、时长、结束原因
组织 im_org_units、im_org_members 部门树、成员、岗位、数据范围
工作台 im_workbench_apps、im_workbench_user_apps 企业应用入口和权限
会议 im_meetings、im_meeting_participants 会议通知、确认状态、提醒和取消
日报月报 im_reports、im_report_read_states 日报/月报填写、提交、查看和统计
总经理信箱 im_manager_mailbox 意见提交、匿名规则、处理状态
实名认证 im_realname_verifications 实名信息加密、审核状态、脱敏展示
青少年模式 im_youth_mode_settings 启用状态、PIN 哈希、策略版本
举报反馈 im_abuse_reports、im_feedbacks、im_evidence_files 举报、证据、反馈和处理状态
审计 im_audit_logs 敏感操作追加日志
异步事件 im_outbox_events 系统消息、Push、搜索索引、定时任务

新增以下基础模型,避免各业务模块重复实现认证、会话设置和消息变更:

模块 表 主要职责
账号安全 im_user_security auth_version、密码更新时间、登录失败和锁定状态
短信验证 im_verification_codes 验证码用途、目标号码哈希、有效期、错误次数和发送频控
二次验证 im_auth_challenges 敏感操作挑战、一次性票据哈希、绑定操作和过期时间
登录设备 im_login_sessions 设备会话、Token 标识、最近活动和撤销状态
会话设置 im_conversation_settings 置顶、免打扰、隐藏和用户侧会话状态
群成员 im_group_members 群成员业务状态、角色、入群来源和操作者
消息版本 im_message_revisions 编辑前内容、版本、操作者和变更原因

7.3 企业码与登录流程

  1. 未认证客户端先通过 hi.tenant 提交企业码;服务端只按 im_tenant.code 查找启用租户并把 TenantID 固定到 Session。
  2. 企业不存在、已停用或不允许登录时返回统一企业不可用错误,不能暴露内部状态和数据库信息。
  3. 后续注册、登录、验证码和找回密码全部使用该服务端租户上下文;不接受客户端在业务请求中重新指定租户。
  4. HTTP 场景使用 /v1/im/tenants/resolve 完成企业码解析,只返回公开品牌信息和服务地址。首期不设计 tenant_ticket;注册、登录等未认证请求由服务端根据企业码重新解析租户,认证成功后只信任正式用户 Token。
  5. 用户 Token 必须包含可信 tenant_id、uid、auth_version、签发时间和过期时间。Token 租户与当前接入租户不一致时拒绝认证。

7.4 验证码、密码重置和二次验证

验证码按 register、login、reset_password、sensitive_action 用途隔离,相同验证码不能跨用途使用。数据库只保存验证码强哈希和手机号规范化哈希,明文只在发送时短暂存在。

密码重置必须在同一事务中完成:

  1. 消费有效验证码或二次验证挑战。
  2. 更新密码强哈希和密码更新时间。
  3. 递增 im_user_security.auth_version。
  4. 撤销该用户现有 im_login_sessions。
  5. 写安全审计和 Outbox 通知。

二次验证成功后签发一次性操作票据。票据必须绑定 tenant_id + uid + action + target + expires_at,只保存票据哈希,成功使用后立即失效。青少年模式独立密码不与登录密码共用,哈希保存在 im_youth_mode_settings。

7.5 认证安全约束

  1. 用户名和手机号唯一约束均包含 tenant_id。
  2. 验证码发送和验证按租户、手机号、IP、设备分别限频。
  3. 登录错误使用统一提示;服务端仍按账号、IP 和设备记录失败次数并执行指数退避或临时锁定。
  4. 新设备登录、密码修改、会话撤销和异常登录通过 Outbox 发送安全通知。
  5. 手机号、验证码、密码、票据、Token 和证件信息不得写入普通日志或消息内容。

8. 消息体系设计

8.1 消息类型

服务端需要定义稳定的消息类型,不允许各端随意约定 JSON 字段。

产品消息协议 v1 使用现有 pub,固定字段放在 head:

字段 规则
x-im-schema 产品消息必填,首版固定为 1
x-im-type 产品消息必填,值必须在下表白名单中
x-im-client-mid 普通客户端消息必填,对应服务端 client_msg_id
x-im-reply-to 引用消息时填写同 Topic 的 SeqId
x-im-forward-mode 转发时为 single 或 merged
x-im-risk-state 仅服务端生成;客户端提交时删除并记录安全日志

content 只保存对应消息类型的业务内容。每种类型进入开发前必须冻结字段、长度、必填条件和兼容规则;客户端未知字段应忽略,未知消息类型显示安全占位而不能崩溃。

类型 用途 关键字段
text 普通文本和 Unicode 表情 text、mentions
image 图片消息 file_id、width、height、thumb_file_id
voice 语音消息 file_id、duration_ms、waveform
video 视频消息 file_id、duration_ms、thumb_file_id
file 普通文件 file_id、name、size、mime
contact_card 名片 user_id、display_name_snapshot、avatar_snapshot
call 通话结果消息 call_id、call_type、result、duration_sec
system 系统提示 event_type、actor_id、target_ids、params
forward_single 单条转发 source_message_ref、snapshot
forward_merged 合并转发 summary、items、source_refs

8.2 消息幂等

客户端发消息必须带不超过 64 字符的 client_msg_id,推荐使用 UUID/ULID。服务端在当前租户和发送人范围内保证唯一,不把 Topic 放入唯一键,避免客户端误把同一个发送请求投递到多个会话:

UNIQUE(tenant_id, from_uid, client_msg_id)

断线重试时,如果服务端发现重复 client_msg_id,返回原消息的 Topic、seq_id、状态和响应摘要,不能再次插入消息。服务端生成的系统消息也必须生成稳定幂等 ID,并以 Outbox 事件 ID 作为去重来源。

8.3 发消息前策略校验

每条消息持久化前执行统一策略链:

  1. Session 租户和 Topic 租户一致。
  2. 发送人是同租户有效用户。
  3. Topic 存在且未删除。
  4. Subscription 具备写权限。
  5. 单聊检查好友关系、黑名单、删除状态和隐私策略。
  6. 群聊检查禁言、全员禁言、群状态和成员身份。
  7. 文件消息检查文件归属、扫描状态和目标 Topic 权限。
  8. 青少年模式和反诈策略检查。
  9. 内容审核通过或进入待审核策略。

8.4 消息编辑、引用、撤回与删除

消息在 Topic 中的 seq_id 和原发送人不可变。产品消息增加 message_type、client_msg_id、version、moderation_state、recalled_at 和 recalled_by 等明确字段,不能依赖客户端任意 JSON 决定服务端状态。

操作 服务端规则 多端同步
引用 保存 topic + seq_id 引用和发送时的安全摘要;禁止跨租户引用 新消息正常广播;原消息删除后仍显示受控摘要
编辑 仅原发送人、允许类型、规定时限内可编辑;事务内递增 version 并写 im_message_revisions 广播带版本号的编辑事件,旧版本事件不得覆盖新版本
全员撤回 校验发送人或群管理员权限及撤回时限;保留受保护审计内容,普通查询只返回撤回占位 广播撤回事件,收藏、搜索和引用按保留策略更新
仅自己删除 使用现有用户侧删除日志,不改变其他成员可见性 只同步当前用户的其他设备
批量删除/撤回 限制单次数量,逐项授权,返回逐项结果 每个成功项产生独立事件和审计记录

编辑、撤回与索引更新必须使用乐观版本控制。请求携带期望 version,版本不一致返回冲突错误,客户端重新拉取当前消息状态。

8.5 内容审核时序

发送链路先执行本地权限、限频和硬规则,再按租户及消息类型选择以下一种审核模式:

模式 处理方式 适用场景
sync_block 在短超时内同步审核,通过后持久化和广播;超时按租户配置拒绝或降级 高风险文本、小请求
async_hold 以 pending_review 持久化但不广播;Outbox 审核通过后发布,拒绝后标记 rejected 文件、图片和高风险内容
async_post_audit 先发布,异步审核;命中后执行系统撤回并审计 租户明确允许的低风险内容

状态转换固定为:

pending_review -> approved -> published
pending_review -> rejected
published -> recalled_by_review

客户端提交的审核状态一律忽略。审核 Worker 必须幂等,同一消息只能完成一次终态转换。

9. 好友与黑名单设计

好友关系是单聊产品状态的真源,不直接用 P2P Topic 是否存在表示好友。

9.1 好友申请与关系状态

stateDiagram-v2
    [*] --> none
    none --> requested: 发起申请
    requested --> accepted: 通过
    requested --> rejected: 拒绝
    requested --> expired: 过期
    accepted --> friends: 事务创建好友关系
    friends --> deleted: 删除好友关系
    deleted --> requested: 重新申请
    rejected --> requested: 重新申请

im_friend_requests 记录申请状态,im_friendships 记录双方好友关系,im_user_blocks 记录单向拉黑。拉黑不修改好友关系状态;取消拉黑只删除当前用户的黑名单记录,不自动恢复已经删除的好友关系。

9.2 服务端规则

  1. 申请、同意、拒绝、删除、拉黑必须幂等。
  2. 好友申请保存验证留言、来源、过期时间和处理人。
  3. 拉黑是单向行为,唯一约束为 (tenant_id, blocker_id, blocked_id);被拉黑人发消息、通话、名片、查看资料时按策略阻断。
  4. 删除好友后历史消息默认保留,但双方不能继续发送好友消息。
  5. 好友资料字段由关系和隐私设置共同决定。
  6. 接受申请必须在一个事务内更新申请、创建好友关系、创建或激活 P2P Topic/Subscription,并写入 Outbox。
  7. 单聊策略按“双方账号有效 -> 好友关系有效 -> 任一方向黑名单 -> 隐私/青少年/风控”的顺序判定。

10. 群治理设计

现有 Group Topic 继续作为群消息容器。im_group_settings 与 im_group_members 是群业务状态真源;Topic、Subscription 和 ACL 是通信内核投影。所有成员和角色变化必须通过产品业务接口完成,并在同一 MySQL 事务内同步业务真源与通信投影。

10.1 群角色

角色 业务真源 通信投影 权限
群主 im_group_settings.owner_user_id + im_group_members.role=owner Topic Owner + Owner ACL 全部群管理、转让、解散
管理员 im_group_members.role=admin Admin ACL 审批、公告、禁言、成员管理
普通成员 im_group_members.role=member 有效 Subscription 收发消息、查看公告和群文件
游客/待审批 im_group_join_requests 无 Subscription 不具备群消息读写权限

禁止客户端直接修改群 Topic Owner 或管理员 ACL。服务启动或定时一致性任务发现业务真源与通信投影不一致时,记录告警并以业务真源修复投影。

10.2 群能力

能力 设计
创建群 同一事务创建 Topic、群设置、群主成员记录、Subscription、ACL、审计和 Outbox
入群邀请 写邀请/申请记录,审批通过后在同一事务写成员记录和 Subscription
入群验证 群设置控制是否需要审批
群公告 公告表保存版本,群内发送系统消息
群二维码 签名短 Token,包含群 ID、租户、过期时间和用途
管理员 更新 im_group_members.role,同一事务同步 ACL
禁言 全员禁言和成员禁言都在发消息前强制检查
群文件 文件消息成功后进入群文件索引,按群权限查询
转让群主 ���次确认;同一事务更新 owner、双方角色、Topic Owner/ACL、审计和 Outbox
解散群聊 标记群状态、踢出成员、停用二维码和审批入口

11. 收藏、转发与搜索设计

11.1 收藏

收藏使用独立 im_favorites 表作为查询和去重真源,不仅依赖 slf Topic。

收藏记录需要保存:

  1. 租户、收藏人、来源 Topic、来源 SeqId、来源消息 ID。
  2. 消息类型、摘要、文件 ID、发送人、发送时间。
  3. 收藏时间、去重键和删除状态。
  4. 原消息删除后的可见策略。

11.2 转发

逐条转发和合并转发都必须重新执行目标会话权限和文件权限。

转发类型 规则
逐条转发 每条源消息生成一条新消息;保持顺序;每条使用新 client_msg_id
合并转发 生成不可变快照;限制条数、总大小和敏感字段;支持点击查看详情
文件转发 源文件和目标 Topic 必须同租户;必要时创建新的引用关系

11.3 搜索

搜索分三类:

  1. 用户和群搜索:使用租户内规范化 Tags,并执行隐私策略。
  2. 聊天记录搜索:im_message_index 保存 tenant_id、Topic、SeqId、消息类型、发送人、时间、规范化检索文本和资源元数据,返回前二次校验 ACL。
  3. 文件/图片/视频/链接搜索:依赖消息类型索引和文件元数据索引。

首期固定使用 MySQL 8:结构化条件使用 B-Tree 复合索引,消息正文使用独立 im_message_index.search_text 和适合中文的 FULLTEXT/ngram 索引。分页游标使用稳定的 created_at + message_id,不能使用大 Offset。

搜索索引不是授权来源。查询必须先限定 tenant_id 和当前用户可读 Topic 集合,返回前再次校验 Topic Read ACL、成员状态、用户侧删除、全员撤回和审核状态。退群、拉黑或权限变更后必须立即阻断查询,即使异步索引尚未删除。

出现以下任一情况时重新评估 OpenSearch/Elasticsearch:单租户可检索消息达到千万级、MySQL 搜索 P95 超过 500ms、需要复杂中文相关性/高亮,或索引写入明显影响消息主库。切换时保持 HTTP 搜索契约不变。

12. 通话设计

现有 P2P WebRTC 信令继续复用,新增业务通话记录。

12.1 通话状态

stateDiagram-v2
    [*] --> ringing: 发起呼叫
    ringing --> accepted: 接听
    ringing --> canceled: 发起方取消
    ringing --> rejected: 接收方拒绝
    ringing --> timeout: 超时
    ringing --> busy: 对方忙线
    accepted --> ended: 正常挂断
    accepted --> interrupted: 异常断开

12.2 关键字段

im_call_records 至少包含:

tenant_id, call_id, topic, caller_id, callee_id, call_type,
state, start_at, accepted_at, ended_at, duration_sec,
end_reason, created_at, updated_at

通话结束后生成 call 类型消息,双方看到的文案由统一结果码生成。

13. 企业工作台设计

工作台是独立业务域,不能依赖聊天 Topic 保存主数据。

模块 入口 通知方式
组织架构 /v1/im/org/* 组织变更一般不发聊天消息,必要时发系统通知
会议通知 /v1/im/meetings/* 创建、变更、取消和提醒通过系统消息 + Push
日报/月报 /v1/im/reports/* 未提交提醒、提交通知和管理员提醒通过 Outbox
总经理信箱 /v1/im/manager-mailbox/* 提交成功给本人通知;处理结果按匿名规则通知
工作台应用 /v1/im/workbench/apps 返回租户配置和用户权限范围内的入口

组织数据权限用于日报、月报、会议统计和通讯录查询。管理员只能查看权限范围内成员数据。

14. 个人资料、隐私与青少年模式

14.1 个人资料

基础展示资料可以同步到 User Public,但可索引、可审计或敏感字段应进入独立表。

字段 存储建议
头像、昵称、签名 User Public + im_user_profiles
生日、地区、性别 im_user_profiles,按隐私设置返回
实名姓名、证件号 实名表加密存储,不进入 Public
群昵称 im_group_members,按需投影到 Subscription Private

14.2 隐私设置

隐私设置影响以下入口:

  1. 账号/手机号/二维码/群聊添加好友。
  2. 个人主页字段展示。
  3. 在线状态和最后在线时间展示。
  4. 非好友消息和通话发起。

客户端可以隐藏入口,但最终阻断必须在服务端执行。

14.3 青少年模式

青少年模式状态保存在服务端,PIN 使用强哈希,不保存明文。

受限能力由租户策略决定,首期建议覆盖:

  1. 添加好友。
  2. 加群和创建群。
  3. 文件传输。
  4. 音视频通话。
  5. 工作台外部链接。

14.4 会话与通知设置

im_conversation_settings 以 (tenant_id, user_id, topic) 唯一,保存置顶、免打扰、隐藏等用户侧设置。它不改变其他成员的会话状态,也不能替代 Topic ACL。

Push 发送前同时检查租户通知策略、用户全局通知设置、会话免打扰、青少年模式和系统权限登记状态。客户端系统通知权限只用于展示引导,服务端保存的是用户偏好和最近一次上报状态,不把客户端上报当作可靠授权依据。

15. 安全、实名、举报与反诈

15.1 实名认证

实名认证采用独立状态机:

none -> pending -> approved
none -> pending -> rejected
approved -> appeal_pending -> approved/rejected

证件号、姓名等敏感字段加密存储,普通查询只返回脱敏状态。

15.2 反诈策略

反诈由“本地规则 + 外部风控插件”共同组成:

  1. 本地规则用于稳定阻断和提示,例如陌生人、敏感词、频繁加好友、异常文件。
  2. 外部插件用于内容审核、风险评分和策略建议。
  3. 插件调用遵守第 8.5 节的三种审核模式、超时和降级规则,不能在 Topic Actor 中执行无界远程等待。
  4. 风险提示和二次确认结果写审计。

15.3 举报与反馈

举报和反馈都按工单处理。

类型 特点
举报 绑定被举报对象、会话、消息、原因、证据和处理状态
意见反馈 绑定提交人、设备、版本、问题描述、附件和处理状态

举报证据应固化消息快照和文件引用,防止原消息删除后无法处理。

16. 系统消息与通知设计

需要产生异步副作用的业务状态变化,在同一数据库事务中写入 Outbox,由 Worker 生成系统消息、Push、索引或定时任务。没有异步副作用的更新不要求创建空事件。

16.1 系统消息格式

系统消息固定使用以下结构;业务模块只能登记 event_type 和参数 Schema,不能自行定义另一套消息信封:

{
  "type": "system",
  "event_type": "group_join_approved",
  "template_version": 1,
  "actor_id": "usr...",
  "target_ids": ["usr..."],
  "params": {
    "group_id": "grp...",
    "group_name": "项目群"
  },
  "dedupe_key": "tenant:1:group:grpxxx:join:request123"
}

16.2 事件类型

首期至少定义:

业务 事件
账号安全 新设备登录、密码修改、实名提醒
好友 新申请、通过、拒绝、删除
群 邀请、申请、审批、公告、禁言、管理员变更、群主转让、解散
文件 群文件上传、文件失效
通话 未接、拒绝、取消、忙线、通话结束
工作台 会议通知、会议变更、日报提醒、月报提醒、信箱处理
安全 反诈提示、举报受理、反馈受理

17. 权限模型

17.1 角色层级

层级 角色
平台 platform_admin
租户 tenant_owner、tenant_admin
组织 部门负责人、报表管理员
群 群主、管理员、成员
关系 好友、非好友、黑名单
账号 普通用户、实名用户、青少年模式用户

17.2 权限判定顺序

租户状态 -> 用户状态 -> 业务角色 -> Topic ACL -> 产品策略 -> 风控策略

任何一步失败,写操作不得继续。

18. 数据一致性与异步任务

18.1 事务边界

必须在同一事务中完成:

  1. 好友申请处理和关系状态更新。
  2. 群审批通过和 Subscription 写入。
  3. 群主转让和权限更新。
  4. 消息保存和附件引用建立。
  5. 文件记录从 uploaded 切换为 ready、附件引用和业务索引写入。
  6. 举报提交和证据引用。

业务层不能在 Handler 中连续调用多个独立 Store 方法来模拟事务。imstore 必须提供按用例划分的事务方法,例如 AcceptFriendRequest、ApproveGroupJoin、TransferGroupOwner 和 SaveMessageWithAttachments,方法内部同时更新产品业务表、Tinode 核心表、审计记录和 Outbox。

对象存储不参与 MySQL 事务,文件采用以下状态机:

pending -> uploaded -> ready
pending/uploaded -> failed
uploaded(超时且无引用) -> orphaned -> deleted

对象上传成功后,再在 MySQL 事务中把文件切换为 ready 并建立业务引用。若数据库事务失败,文件保持 uploaded,由带宽受控的租户级垃圾回收任务清理;消息只能引用 ready 文件。

18.2 Outbox

需要通知、Push、索引或审计的业务写操作,在事务内写入 im_outbox_events。异步 Worker 负责:

  1. 发送系统消息。
  2. 推送离线通知。
  3. 更新搜索索引。
  4. 调用外部审核/反诈。
  5. 发送会议、日报、月报定时提醒。
  6. 记录可观测指标。

im_outbox_events 至少包含:

id, tenant_id, aggregate_type, aggregate_id, event_type,
payload, dedupe_key, state, available_at, attempts,
locked_by, locked_at, last_error, created_at, processed_at

Outbox 处理规则:

  1. dedupe_key 在租户内唯一,生产事件和消费副作用都必须幂等。
  2. Worker 使用 SELECT ... FOR UPDATE SKIP LOCKED 分批领取事件,设置最大批量和租户公平调度,避免单个租户占满队列。
  3. 失败采用有上限的指数退避;超过上限进入 dead_letter,触发告警并支持人工重放。
  4. 系统消息通过集群安全的内部发布接口进入目标 Topic,不能直接假设 Outbox Worker 与目标 Topic 位于同一进程。
  5. 标记事件成功前必须确认目标副作用已获得稳定幂等结果;进程崩溃后的重复执行不得产生重复消息或 Push。
  6. 外部审核事件必须按第 8.5 节推进消息审核状态,不能绕过状态机直接广播。

19. API 与错误码规范

19.1 成功响应

{
  "code": 0,
  "data": {},
  "request_id": "..."
}

19.2 错误响应

{
  "code": 30001,
  "message": "permission denied",
  "reason": "GROUP_MUTED",
  "request_id": "..."
}

错误码按模块分段:

范围 模块
10000-19999 通用、认证、租户
20000-29999 好友、黑名单、隐私
30000-39999 群、公告、群文件
40000-49999 消息、收藏、搜索、转发
50000-59999 文件、媒体、通话
60000-69999 组织、会议、日报、月报
70000-79999 实名、青少年、安全、举报、反馈

HTTP 状态码表达协议层结果,业务 code 表达稳定失败原因:参数错误使用 400,未认证使用 401,无权限使用 403,不可见资源使用 404,版本或幂等冲突使用 409,限频使用 429,服务端故障使用 5xx。客户端不得只判断 HTTP 状态而忽略业务 code。

20. 可观测性与审计

20.1 日志字段

认证后的请求日志至少包含:

request_id, tenant_id, uid, sid, topic, action, result, latency_ms

手机号、证件号、Token、验证码、文件签名和设备 Push Token 不写明文日志。

20.2 审计事件

必须审计:

  1. 租户、组织和管理员变更。
  2. 登录、密码修改、实名审核和二次验证。
  3. 好友删除、拉黑、黑名单解除。
  4. 群主转让、管理员变更、禁言、解散群。
  5. 文件下载、批量转发、收藏导出。
  6. 举报处理、反馈处理和反诈二次确认。
  7. 平台管理员代操作。

21. 开发阶段规划

阶段 0:基础底座收口

目标:让后续业务都站在正确地基上。

交付:

  1. 多租户核心链路完成并通过双租户隔离测试。
  2. 消息 client_msg_id 幂等。
  3. 文件下载资源级授权。
  4. 系统消息结构和 Outbox 基础。
  5. 统一错误码和请求日志。
  6. 产品版明确只支持 MySQL 8,不支持的 Adapter 在启动时快速失败。
  7. im_tenant_config 三项配额在注册、建群和入群入口强制生效。
  8. 建立专用业务事务 Store,禁止跨多个 Store 调用拼接关键事务。

阶段 1:账号、资料、好友和黑名单

交付:

  1. 注册、登录、找回密码、二次验证。
  2. auth_version、登录设备、Token 撤销和安全通知。
  3. 个人资料、会话设置和隐私设置。
  4. 好友申请、通讯录、主页、删除好友。
  5. 独立黑名单和单聊发送/通话策略。

阶段 2:群聊产品化

交付:

  1. 群列表、创建群、群资料、群头像。
  2. 入群审批、邀请、群二维码。
  3. 群公告、管理员、群主转让。
  4. 全员禁言、成员禁言、群文件。
  5. 群业务真源与 Topic/Subscription/ACL 投影一致性检查。

阶段 3:消息增强、收藏、搜索和转发

交付:

  1. 结构化消息类型。
  2. @成员、引用、编辑、撤回、批量删除及消息版本事件。
  3. 收藏分类和分享。
  4. 聊天记录、文件、图片、视频和链接搜索。
  5. 逐条转发和合并转发。
  6. sync_block、async_hold 和 async_post_audit 审核状态机。

阶段 4:通话和通知体验

交付:

  1. call_id 和通话记录。
  2. 通话状态消息。
  3. 忙线、拒绝、未接、超时和异常断开。
  4. Push 文案、免打扰、锁屏隐私。

阶段 5:企业工作台

交付:

  1. 工作台应用配置。
  2. 组织架构。
  3. 会议通知和参会确认。
  4. 日报、月报。
  5. 总经理信箱。

阶段 6:安全合规

交付:

  1. 实名认证。
  2. 青少年模式。
  3. 反诈策略。
  4. 举报和意见反馈。
  5. 审计后台和处置流程。

22. 测试策略

22.1 自动化测试

每个模块至少包含:

  1. 单元测试:状态机、权限判断、错误码。
  2. Store 测试:租户隔离、唯一约束、分页、软删除。
  3. API 测试:参数校验、幂等、越权、并发。
  4. WebSocket 测试:消息顺序、回执、离线重连、系统消息。
  5. 安全测试:跨租户、跨用户、跨群、文件越权和枚举。
  6. 事务测试:在每个数据库写入点注入失败,验证业务真源、通信投影、审计和 Outbox 不出现部分提交。
  7. Worker 测试:重复消费、锁超时、进程崩溃、指数退避、死信和人工重放。
  8. Schema 测试:MySQL 初始化 DDL、升级 DDL 和 Adapter SQL 保持一致,唯一约束与外键真实生效。
  9. 性能测试:大群广播、并发发消息、重连风暴、文件上传下载、中文搜索和 Outbox 积压恢复。

性能测试不能只记录平均值。每次发布前根据部署规模给出 P95/P99 延迟、错误率、吞吐、数据库连接和队列积压报告;未确定正式 SLO 前,以当前稳定版本基线为回归门槛。

22.2 重点验收场景

场景 预期
两个租户使用相同手机号注册 均成功,互不影响
A 租户尝试搜索 B 租户用户 返回空或通用 NotFound
被拉黑用户发送消息 不进入对方会话,返回稳定失败原因
禁言成员发群消息 服务端拒绝,其他成员不可见
文件 URL 被其他用户重放 无权限返回 403/404
断线重试同一消息 不重复入库,返回原消息状态
同一发送人把相同 client_msg_id 投递到不同 Topic 第二次返回首次投递结果,不写入另一 Topic
并发注册超过 max_users 成功数不超过配额,失败请求返回同一超限错误
并发审批入群超过 max_group_members 最终有效成员数不超过配额
群主转让并发提交 最终只有一个成功,审计完整
群主转让在任一写入点失败 owner、成员角色和 Topic ACL 全部回滚
async_hold 消息等待审核 审核前其他成员不可见,通过后只广播一次
Outbox 发送成功但标记前进程崩溃 重启重放不产生重复系统消息或 Push
用户退群后搜索旧群消息 即使索引尚在也不返回结果
举报消息后原消息被撤回 举报证据仍可处理

23. 开发约束

  1. 新业务表一律以 im_ 前缀命名。
  2. 所有租户业务表必须带 tenant_id;平台级表和 im_tenant 等根表例外,例外必须经过 DDL 评审。
  3. 新接口不使用无租户 Store。
  4. 不在 Topic Actor 中执行慢查询、外部 HTTP 或复杂报表统计。
  5. 不把敏感字段放入 User Public、Topic Public、普通消息 JSON 或日志。
  6. 新增消息类型必须先定义 schema,再实现客户端渲染。
  7. 所有危险操作必须有二次确认、幂等和审计。
  8. 所有批量操作必须限制数量并返回逐项结果。
  9. 所有列表必须分页。
  10. 所有外部服务调用必须有超时、重试上限和降级策略。
  11. 关键业务状态变化必须通过专用事务 Store 完成,不允许 Handler 直接修改 Topic ACL 绕过业务真源。
  12. 产品模块只允许在 TenantBusinessReady() 为真且数据库版本满足要求时启动。

24. 待产品确认事项

以下问题会影响接口和表结构,开发前需要产品或业务方确认:

对应阶段冻结接口和 DDL 前必须确认本节事项。在产品未给出结论时,可以按本文其他章节声明的默认行为开发内部能力,但不得将未确认交互作为正式验收口径。

  1. 删除好友后是否同时删除双方会话,还是仅禁止继续发送。
  2. 拉黑后是否允许被拉黑人看到历史消息和个人资料。
  3. 群聊已读回执是否展示具体已读成员,是否只对管理员开放。
  4. 消息撤回时限和管理员撤回范围。
  5. 合并转发是否允许包含已撤回、已删除或涉敏消息。
  6. 群文件是否等同于群内所有文件消息,还是独立上传目录。
  7. 实名认证是否强制,未实名能否继续登录、聊天、加好友、入群。
  8. 青少年模式限制范围和默认策略。
  9. 总经理信箱是否匿名,以及哪些角色可以查看真实提交人。
  10. 日报/月报是否支持草稿、补交、撤回和修改。

25. 总结

后续开发应以现有 Tinode 服务端作为实时通信内核,在其外建设清晰的产品业务层。核心原则是:聊天实时链路保持轻,业务状态机走 HTTP 和数据库事务,状态变化通过系统消息和 Push 回到聊天体验中。

多租户、消息幂等、文件授权、系统消息和审计是所有功能之前的基础工程。好友、群治理、收藏搜索、通话、工作台和安全合规应按阶段推进,每个阶段都必须同时完成数据模型、接口、权限、通知和测试闭环。