Files
im_server/docs/IM-WS-TECH-DESIGN-CN.md
T
2026-08-11 15:59:57 +08:00

31 KiB
Raw Blame History

IM WebSocket 后端技术方案

本文档基于 IM WebSocket 前后端接口文档、Tinode 原生 API 文档、代码通读指南 与当前 Go 代码整理,作为后续后端开发依据。目标是保持 Tinode 现有协议和代码风格,默认全部前后端业务通信走 WebSocket,尽量复用现有 Topic、Session、Store、ACL、Drafty、文件上传和 JSON 扩展字段,减少侵入式改动。

一、目标与原则

1.1 建设目标

目标 说明
保持 Tinode 协议兼容 继续使用 {hi}、{login}、{sub}、{pub}、{get}、{set}、{del}、{note}
默认 WebSocket 通信 客户端统一连接 /v0/channels,服务端通过现有 Session -> Hub -> Topic 消息链路处理
小步扩展业务能力 好友、群聊、文件、收藏、通话优先映射到 Tinode 原生能力
小改动 第一阶段不新增数据库表,不改核心消息协议字段语义
可测试 所有新增逻辑必须补充 Go 单元测试,MySQL 持久化改动必须补 adapter 集成测试

1.2 设计原则

  1. 原生字段优先:用户资料使用 users.public,用户私有设置使用 subscriptions.private 或 users.private,群业务配置使用 topics.aux。
  2. 扩展字段隔离:所有产品扩展字段统一使用 x-im-*,避免污染 Tinode 原生字段。
  3. 权限使用 ACL:管理员、禁言、踢人、群主转让优先通过 Tinode modewant/modegiven 权限实现。
  4. 持久消息走 {pub}:文本、图片、文件、语音、名片、转发、收藏、系统通知都以持久化消息建模。
  5. 临时状态走 {note}:输入中、录音中、已收、已读、通话信令使用临时通知,不落库。
  6. 数据库变更后置:只有搜索、积分、签到这类确实需要索引或事务的能力才新增业务表。

二、系统设计

2.1 总体架构

Client
  |
  | WebSocket JSON frames
  v
server/hdl_websock.go
  |
  v
Session.dispatchRaw / Session.dispatch
  |
  +-- hi/login/acc: Session 内处理
  |
  +-- sub/get/set/del/pub/note
        |
        v
      Hub
        |
        v
      Topic.runLocal
        |
        +-- handleSubscription
        +-- handleClientMsg
        +-- handleMeta
        +-- handleServerMsg
        |
        v
      store.* interfaces
        |
        v
      server/db/mysql adapter

本方案不新增独立 HTTP 业务 API。除 Tinode 已有大文件上传 /v0/file/u/ 与下载 /v0/file/s/ 外,业务请求全部复用 WebSocket 消息包。

2.2 功能映射

原型能力 后端实现
登录、注册、忘记密码 复用 {hi}、{login}、{acc}、basic/token/code/rest auth
私聊 {sub topic="usrXXX"} 创建或进入 P2P topic
群聊 {sub topic="new"} 创建群,grpXXX 进入群
消息列表 订阅 me,{get what="sub"} 获取会话列表
文本、表情、@ {pub} + Drafty,head.mentions
图片、文件、语音 上传文件后 {pub} + Drafty IM/EX/AU entity + extra.attachments
已收、已读、输入中 {note what="recv/read/kp/kpa/kpv"}
撤回、删除 {del what="msg"}
好友申请 P2P {sub} + ACL 状态,申请备注写 private.x-im-applyText
备注、置顶、免打扰、拉黑 {set desc.private}
管理员、禁言、踢人 {set sub}、{del what="sub"} + aux.x-im-group
群公告、进群验证 {set aux} + 可选系统样式 {pub}
收藏 使用 slf topic 存储 head.x-im-type="favorite" 消息
通话 {note what="call"} 信令,结束后 {pub} 持久化通话记录;产品通话邀请和控制事件校验 callId
话题 使用 nch/chn 频道建模,话题元数据写 public.x-im-topic
举报、反馈 向 sys topic 发送 x-im-type="report/feedback" 消息
系统消息 系统账号或 sys topic 投递 x-im-type="system-notice"

2.3 分阶段实现

阶段 范围 数据库
Phase 1 登录、私聊、群聊、消息、附件、回执、基础好友、群管理、收藏、通话信令、举报反馈 不新增表
Phase 2 聊天记录关键词搜索、按文件类型搜索、话题积分、签到幂等、运营后台检索 新增少量 im_* 业务表
Phase 3 反诈风控、内容审核、推荐排序、全文搜索优化 MVP 复用 messages/topics/topictags 与 im_message_index,MySQL schema 119 新增全文索引

当前 Phase 1、Phase 2 与 Phase 3 MVP 已按小改动路线接入。更复杂的风控模型、人工审核工作台、离线推荐服务和独立搜索引擎仍作为后续演进,不影响现有 WebSocket 对接。

三、代码结构设计

3.1 现有代码复用点

文件/模块 复用方式
server/hdl_websock.go 保持 WebSocket 入口不变
server/session.go 复用消息分发、鉴权、topic 展开逻辑
server/hub.go 复用 topic 创建、加载、路由
server/topic.go 复用订阅、发布、meta、presence、call 处理
server/datamodel.go 复用 C2S/S2C 消息结构
server/store/store.go 复用 Topic/Sub/Message/File/PCache 抽象
server/db/mysql/adapter.go 复用 MySQL adapter、迁移机制、业务索引与推荐查询
server/drafty 复用富文本纯文本提取与预览能力
server/store/mock_store 新增测试时继续使用 gomock

3.2 建议新增文件

第一阶段新增文件控制在少量 helper 与测试文件:

文件 作用
server/imext.go 定义 x-im-* 常量、消息类型枚举、Drafty/JSON 解析辅助方法
server/imext_test.go 测试扩展字段解析、消息类型判断、搜索文本提取
server/im_policy.go 封装业务策略:群禁言、进群验证、拉黑、反诈标记判断
server/im_policy_test.go 业务策略单元测试

第二阶段如需数据库索引,再新增:

文件 作用
server/store/imindex.go 定义业务索引查询接口,避免把搜索逻辑塞进 Topic
server/db/mysql/imindex.go MySQL 业务索引表读写实现
server/db/mysql/imindex_test.go MySQL 业务索引集成测试

3.3 最小代码变更点

3.3.1 扩展字段解析

新增 server/imext.go,只做轻量解析,不改变原有消息结构语义。

const (
	imHeadType      = "x-im-type"
	imHeadClientMID = "x-im-client-mid"
	imHeadAntiFraud = "x-im-anti-fraud"
)

type imMessageType string

const (
	imMsgText              imMessageType = "text"
	imMsgImage             imMessageType = "image"
	imMsgAudio             imMessageType = "audio"
	imMsgVideo             imMessageType = "video"
	imMsgFile              imMessageType = "file"
	imMsgCard              imMessageType = "card"
	imMsgFavorite          imMessageType = "favorite"
	imMsgForwardBundle     imMessageType = "forward-bundle"
	imMsgGroupJoinApply    imMessageType = "group-join-apply"
	imMsgGroupAnnouncement imMessageType = "group-announcement"
	imMsgCall              imMessageType = "call"
	imMsgTopicShare        imMessageType = "topic-share"
	imMsgSystemNotice      imMessageType = "system-notice"
	imMsgReport            imMessageType = "report"
	imMsgFeedback          imMessageType = "feedback"
	imMsgCheckin           imMessageType = "checkin"
)

解析 helper 建议只返回类型与布尔值,不在业务层直接类型断言散落代码:

func imGetMessageType(head map[string]any) imMessageType
func imIsAttachmentType(tp imMessageType) bool
func imExtractSearchText(head map[string]any, content any) string
func imValidateCallPayload(payload json.RawMessage) error

3.3.2 查询扩展字段

当前 MsgGetQuery 没有 x-im-search 或 x-im-filter 字段,Go 反序列化会忽略未知字段。若第一阶段需要服务端处理聊天记录搜索,需要在 server/datamodel.go 显式增加字段:

type MsgGetQuery struct {
	What string `json:"what"`

	Desc *MsgGetOpts `json:"desc,omitempty"`
	Sub  *MsgGetOpts `json:"sub,omitempty"`
	Data *MsgGetOpts `json:"data,omitempty"`
	Del  *MsgGetOpts `json:"del,omitempty"`

	XIMSearch *MsgSearchOpts `json:"x-im-search,omitempty"`
	XIMFilter *MsgFilterOpts `json:"x-im-filter,omitempty"`
}

type MsgSearchOpts struct {
	Keyword string   `json:"keyword,omitempty"`
	Types   []string `json:"types,omitempty"`
}

type MsgFilterOpts struct {
	Types   []string `json:"types,omitempty"`
	Keyword string   `json:"keyword,omitempty"`
}

第一阶段可以先不实现数据库索引,Topic.replyGetData 仍按 data.limit 拉取消息,再在内存里用 imGetMessageType 和 imExtractSearchText 做轻量过滤。第二阶段再替换为索引表查询。

3.3.3 群禁言与拉黑策略

Tinode ACL 已支持写权限控制。业务层只需约定:

场景 实现
群禁言成员 将目标用户 mode 设置为不含 W 的完整权限,如 JRP;同时维护 aux.x-im-group.mutedUsers
解除群禁言 将目标用户 mode 设置为含 W 的完整权限,如 JRWP 或 JRWPS;同时清理 aux.x-im-group.mutedUsers
拉黑好友 当前用户 desc.private.x-im-blocked=true,同时可调整对方 P2P 权限
群全员禁言 aux.x-im-group.muteAll=true,发送消息时后端策略拒绝普通成员
单成员业务禁言 aux.x-im-group.mutedUsers,发送消息时后端策略拒绝列表中的普通成员
群文件上传限制 aux.x-im-group.allowMemberFileUpload=false,普通成员不能发送附件类消息
仅管理员邀请 aux.x-im-group.onlyAdminInvite=true,非管理员不能邀请或重新邀请成员

群级业务策略是 Tinode 原生 ACL 不能一次表达的约束,在 Topic.handlePubBroadcast 与 Topic.anotherUserSub 进入持久化前做小的策略检查:

if err := imCanPublish(t, asUid, msg); err != nil {
	msg.sess.queueOut(ErrPermissionDeniedReply(msg, msg.Timestamp))
	return
}

imCanPublish 与 imCanInvite 只读取 t.aux 与 t.perUser,不访问数据库,避免扩大影响面。

3.3.4 收藏

收藏不新增接口,不新增表。客户端向 slf 发送 x-im-type="favorite" 消息即可。

后端只需保证:

  1. slf topic 可正常创建和订阅。
  2. 收藏消息里的附件 URL 被写入 extra.attachments,复用文件 GC 引用计数。
  3. 收藏列表通过 {sub topic="slf" get.what="data"} 返回。

3.3.5 话题

话题用频道建模:

  1. 发布话题:{sub topic="nch"} 创建 channel topic。
  2. 话题详情:读取 desc.public.x-im-topic。
  3. 参与话题:订阅 chnXXX。
  4. 评论:根据业务授予 W 权限后使用 {pub}。

第一阶段不做积分扣减时,不需要新增表。第二阶段接入积分时增加 im_user_points。

3.4 不建议改动的地方

模块 原因
WebSocket handler 入口稳定,不应新增业务分支
Hub 路由模型 现有 channel/goroutine 模型清晰,业务应落在 Topic/Store
原生消息字段命名 客户端 SDK 与文档依赖现有字段
Adapter 核心接口 第一阶段避免给所有数据库后端增加方法
gRPC converter 本项目默认 WS,不做 gRPC 扩展

四、数据库设计与变更

4.1 当前可复用字段

现有 MySQL schema 已经覆盖多数业务扩展需求:

表 字段 用途
users public 昵称、头像、性别、生日、地区、个性签名
users tags + usertags 用户查找、手机号/用户名发现
topics public 群资料、话题资料、频道封面
topics aux 群公告、进群验证、群设置、全员禁言
subscriptions private 备注、置顶、免打扰、拉黑、草稿
messages head x-im-type、附件类型、引用、转发、@
messages content Drafty 或业务 JSON 内容
fileuploads 全表 上传文件记录
filemsglinks 全表 文件与消息/topic/user 绑定,防止 GC
kvmeta 全表 小型持久缓存,已有 PCache 封装

4.2 Phase 1 数据库变更

Phase 1 不新增表、不新增字段。

原因:

  1. 用户资料、群资料、群设置、收藏、举报、反馈都可用 JSON 字段或消息承载。
  2. 图片、文件、语音已经有 fileuploads 与 filemsglinks。
  3. 消息查询先复用 messages(topic, seqid) 分页。
  4. 小改动优先,先保证 WebSocket 主流程稳定。

4.3 Phase 2 数据库变更

聊天记录搜索、话题积分、签到等功能进入后端强一致实现时新增业务表,表名统一 im_ 前缀,避免与 Tinode 原生表混淆。当前 MySQL adapter 已实现 im_message_index、im_user_points、im_user_daily_events,数据库版本为 118。

4.3.1 消息业务索引表

用途:支持按消息类型、关键词、附件维度查询,避免频繁扫描 messages.content JSON。

CREATE TABLE im_message_index(
	id INT NOT NULL,
	createdat DATETIME(3) NOT NULL,
	updatedat DATETIME(3) NOT NULL,
	topic CHAR(25) NOT NULL,
	seqid INT NOT NULL,
	fromuid BIGINT NOT NULL,
	msgtype VARCHAR(32) NOT NULL,
	search_text TEXT,
	attachment_count INT NOT NULL DEFAULT 0,
	deletedat DATETIME(3),

	PRIMARY KEY(id),
	FOREIGN KEY(id) REFERENCES messages(id) ON DELETE CASCADE,
	UNIQUE INDEX im_message_index_topic_seqid(topic, seqid),
	INDEX im_message_index_topic_type_seq(topic, msgtype, seqid),
	INDEX im_message_index_topic_createdat(topic, createdat),
	FOREIGN KEY(topic) REFERENCES topics(name) ON DELETE CASCADE
);

说明:id 与 Tinode 现有 messages.id 保持一致使用 INT。中文全文检索初期使用 LIKE,MySQL FULLTEXT 是否启用 ngram parser 作为后续部署决策,不作为第一版强依赖。

4.3.2 用户积分表

用途:话题发布积分扣减、签到积分发放。

CREATE TABLE im_user_points(
	userid BIGINT NOT NULL,
	createdat DATETIME(3) NOT NULL,
	updatedat DATETIME(3) NOT NULL,
	balance BIGINT NOT NULL DEFAULT 0,
	frozen BIGINT NOT NULL DEFAULT 0,

	PRIMARY KEY(userid),
	FOREIGN KEY(userid) REFERENCES users(id) ON DELETE CASCADE
);

4.3.3 用户每日事件表

用途:签到幂等、每日任务、青少年模式时长限制等按日事件。

CREATE TABLE im_user_daily_events(
	id BIGINT NOT NULL AUTO_INCREMENT,
	createdat DATETIME(3) NOT NULL,
	userid BIGINT NOT NULL,
	event_date DATE NOT NULL,
	event_type VARCHAR(32) NOT NULL,
	payload JSON,

	PRIMARY KEY(id),
	UNIQUE INDEX im_user_daily_events_user_date_type(userid, event_date, event_type),
	FOREIGN KEY(userid) REFERENCES users(id) ON DELETE CASCADE
);

4.4 MySQL 迁移方式

Phase 2/3 按现有 MySQL adapter 风格升级。当前 MySQL adapter 已从 116 升级到 119,其中 117 新增 im_message_index,118 新增 im_user_points 与 im_user_daily_events,119 为 im_message_index.search_text 增加全文索引:

  1. server/db/mysql/adapter.go 中 adpVersion 已更新为 119。
  2. CreateDb 的建表流程已加入三张 im_* 表,并在 im_message_index 上创建 FULLTEXT INDEX im_message_index_search_text(search_text)。
  3. UpgradeDb 已增加 116 -> 117、117 -> 118 与 118 -> 119 迁移块。
  4. 升级旧库到 117 时会扫描历史 messages 与 filemsglinks 回填索引,避免历史消息搜索缺失。
  5. 升级旧库到 118 时创建积分表和每日事件表。
  6. 升级旧库到 119 时通过 ALTER TABLE im_message_index ADD FULLTEXT INDEX im_message_index_search_text(search_text) 优化全文搜索。
  7. 迁移成功后通过 bumpVersion(a, 119) 更新 kvmeta.version。
  8. schema 文档 server/db/mysql/schema.sql 已同步。
  9. MySQL adapter 集成测试已覆盖索引保存、搜索、全文索引存在性、推荐排序、软删除过滤、积分增扣和每日事件幂等。

不建议在第一阶段修改 adapter.Adapter 总接口,因为这会要求 PostgreSQL、MongoDB、RethinkDB 同步实现,改动面会变大。业务索引可以在第二阶段单独设计 imstore 接口,再按实际支持的数据库接入。

五、核心业务设计

5.1 登录注册与资料

使用现有认证模块:

功能 实现
手机号密码登录 basic 或 rest 认证
Token 登录 token 认证
验证码 code 临时认证或 credential validator
忘记密码 {login scheme="reset"}
实名认证 me.desc.private.x-im-realname 保存申请,审核通过后 root 写 trusted.verified=true

代码改动:无核心改动。若对接外部账号系统,优先配置 auth/rest,避免改 auth/basic。

5.2 好友关系

P2P topic 即好友/联系人关系。添加好友通过 {sub topic="usrXXX"},是否需要审批由 ACL 决定。

建议规则:

状态 ACL/字段
申请中 private.x-im-applyStatus="pending",ACL 仍记录当前 want/given
已同意 private.x-im-applyStatus="accepted",双方 P2P 权限满足可聊条件
已拒绝/过期 private.x-im-applyStatus="rejected/expired",前端从 me.sub.private 过滤
已好友 双方 P2P 权限满足 JRWPA
已拉黑 当前用户 private.x-im-blocked=true,必要时移除对方 W
已删除 {leave unsub=true}

5.3 群聊与群权限

复用 Tinode group topic:

群角色 Tinode 权限
群主 O
管理员 A/S/D
普通成员 J/R/W/P
禁言成员 移除 W
被踢成员 删除订阅

群设置统一写 topics.aux.x-im-group。前端展示用 get what="desc sub aux" 获取。

进群审批使用已有 subscriptions 表表达状态,不新增表:开启 aux.x-im-group.joinApproval=true 且群默认 defacs.auth 不含 J 时,申请者 {sub set.sub.mode="JRWPS"} 会创建 want=JRWPS/given=N 的待审批订阅,并保存 private.x-im-joinApplyText 与 private.x-im-joinStatus="pending"。管理员 {get what="sub"} 可看到待审批用户及其 private;通过时 {set sub user=... mode="JRWPS"} 自动把状态更新为 accepted,拒绝时 {set sub user=... mode="N"} 自动更新为 rejected。

5.4 消息与附件

所有消息仍由 Topic.handlePubBroadcast 保存为 messages:

  1. 文本、表情、@ 使用 Drafty。
  2. 图片、文件、语音、视频先上传文件,再发送 Drafty entity。
  3. extra.attachments 必填,复用 store.Messages.Save 中的附件绑定逻辑。
  4. head.x-im-client-mid 用于前端本地发送状态关联。
  5. head.x-im-type 用于客户端渲染和后续索引。

5.5 收藏

收藏使用 slf topic,不新增接口:

操作 实现
收藏消息 {pub topic="slf" head.x-im-type="favorite"}
查看收藏 {sub topic="slf" get.what="data"}
删除收藏 {del topic="slf" what="msg"}
分享收藏 普通转发 {pub}

5.6 通话

通话信令复用 {note what="call"}:

事件 note.event
已振铃 ringing
接受通话 accept
SDP offer offer
SDP answer answer
ICE 候选 ice-candidate
拒绝/挂断 hang-up

通话结束后发送持久化 {pub},head.webrtc 与 head.webrtc-duration 使用 Tinode 原生字段。

5.7 话题

话题使用 channel topic:

操作 实现
发布话题 {sub topic="nch"}
话题列表 fnd 搜索 topic tags
话题详情 {sub topic="chnXXX" get.what="desc data aux"}
参与话题 订阅 chnXXX
转发帖子 {pub head.x-im-type="topic-share"}

积分能力进入 Phase 2 后,通过 im_user_points 做事务扣减;当前签到已通过 {pub topic="slf" head.x-im-type="checkin"} 接入 im_user_daily_events 与 im_user_points,首次签到固定发放 5 积分,重复签到返回 304 no action。话题发布已通过 {sub topic="nch"} 接入 public.x-im-topic.pointsCost,MySQL 后端在同一事务中创建频道 topic、作者订阅并扣减积分,余额不足返回 422 policy violation。

5.8 举报、反馈与安全

举报和反馈向 sys topic 发送持久化消息。root/admin 后台订阅 sys 处理。

反诈与内容审核 Phase 3 MVP 已在发布链路内置轻量规则:Topic.handlePubBroadcast 保存消息前调用 imReviewPubMessage,命中疑似转账、验证码、外链、违禁词等关键词时追加 head.x-im-anti-fraud=true、head.x-im-risk-level 与 head.x-im-moderation。当前策略只标记不拦截,避免误伤聊天主流程;复杂风控评分、人工审核队列、模型服务可在后续通过相同 head 字段向前端兼容扩展。

5.9 推荐排序与搜索优化

话题推荐仍复用 Tinode fnd topic,避免新增 HTTP 接口或修改通用 adapter 接口。前端发送 {get topic="fnd" what="sub" x-im-recommend={...}},topic.go 在 plugin find 未返回结果时检测 MySQL adapter 是否实现 FindRecommendedTopics,再按关键词、类型标签、业务标签、订阅数和活跃时间排序返回。

聊天记录全文搜索继续走 im_message_index。MySQL schema 119 在 search_text 上增加 FULLTEXT 索引,查询条件使用 MATCH ... AGAINST 与 LIKE ESCAPE 组合:优先利用全文索引,仍保留中文短词和简单子串查询的兼容性。

六、单元测试与集成测试方案

6.1 已有测试继续保留

后续开发前后建议固定跑:

go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$')

MySQL adapter 变更补跑:

go test -count=1 -tags mysql ./server/db/mysql/tests

构建检查:

go build -tags mysql ./server ./tinode-db

6.2 新增单元测试用例

server/imext_test.go

用例 目的
TestIMGetMessageType 从 head.x-im-type 正确读取消息类型
TestIMGetMessageTypeMissing 缺失类型时回退为普通文本或空类型
TestIMIsAttachmentType 判断 image/audio/video/file 是否为附件类型
TestIMExtractSearchTextPlain 从普通字符串内容提取搜索文本
TestIMExtractSearchTextDrafty 从 Drafty txt 提取搜索文本
TestIMExtractSearchTextJSON 从业务 JSON 的 title/summary/text 提取搜索文本
TestIMValidateCallPayload 校验 call payload 必填字段
TestIMValidateCallPayloadMalformed 非法 JSON 或缺失 callId 返回错误
TestIMReviewPubMessageFlagsFraudRisk 风控规则命中后追加反诈、风险等级和审核标记

server/session_test.go

用例 目的
TestDispatchGetWithXIMSearch 确认 {get} 中 x-im-search 不会被丢弃
TestDispatchPublishWithXIMClientMID 确认 head.x-im-client-mid 原样进入 topic
TestDispatchReportToSys 普通用户可向 sys 发布举报消息
TestDispatchFavoriteToSlf 用户可向 slf 发布收藏消息

server/topic_test.go

用例 目的
TestHandleBroadcastDataWithXIMType 消息保存后 head.x-im-type 原样广播
TestHandleBroadcastDataWithAttachments 已有附件测试继续覆盖 extra.attachments
TestHandleBroadcastDataGroupMuteAll 群全员禁言时普通成员发送被拒绝
TestHandleBroadcastDataGroupMuteAllAdminAllowed 管理员在全员禁言时可发送
TestHandleBroadcastCardMessage 名片消息 JSON 内容原样投递
TestReplyGetDataWithXIMTypeFilter 第一阶段内存过滤按类型返回
TestReplyGetDataWithXIMKeywordSearch 第一阶段内存过滤按关键词返回
TestHandleCallNoteMediaEvent 通话媒体状态 {note} 正确转发为 {info}
TestGroupJoinApplyMessage 加群申请消息可持久化并广播给管理员
TestGroupAnnouncementAuxUpdate 群公告写入 aux 后产生 pres what="aux"

server/store/types 或 server/db/common

用例 目的
TestMergeMapsWithXIMNestedValues 嵌套 x-im-* 设置更新不破坏已有值
TestNullValueClearsXIMSetting 使用 types.NullValue 清理扩展字段

6.3 Phase 2 MySQL 集成测试

im_* 表需要补充 MySQL 集成测试:

用例 目的
TestIMMessageIndexCreateDb CreateDb(reset=true) 创建索引表
TestIMMessageIndexUpgradeDb 旧版本升级到新版本后表存在
TestIMMessageIndexUpsert 消息索引写入/更新成功
TestIMMessageIndexSearchByType 按消息类型查询
TestIMMessageIndexSearchByKeyword 按关键词查询
TestIMUserPointsAddAndConsume 积分增加与扣减
TestIMUserPointsInsufficient 积分不足时事务回滚
TestIMDailyEventIdempotent 同一用户同一天同类事件只能记录一次
TestIMDailyEventCreateWithPoints 每日事件与积分发放在同一事务中完成,重复事件不重复加分
TestIMMessageIndexFullTextExists MySQL schema 119 已创建全文索引
TestFindRecommendedTopics 按关键词、类型和标签返回推荐话题

6.4 手工 WebSocket 联调用例

后端联调时至少覆盖:

  1. 登录后订阅 me/fnd。
  2. 创建 P2P 并发送文本、图片、文件、语音。
  3. 发送 recv/read/kp/kpa,对端收到 {info}。
  4. 创建群、邀请成员、设置管理员、禁言、解除禁言。
  5. 设置群公告,客户端收到 pres what="aux"。
  6. 收藏消息到 slf 并读取收藏列表。
  7. 发起音视频通话,完成 offer/answer/hangup。
  8. 发布话题频道并转发到私聊。
  9. 举报消息发送到 sys。
  10. 发送疑似风险文本,接收方收到 x-im-anti-fraud 与 x-im-moderation。
  11. 通过 fnd + x-im-recommend 获取推荐话题,频道 topic 以 chnXXX 返回。

七、开发顺序建议

7.1 第一迭代

  1. 新增 server/imext.go 与测试。
  2. 明确 x-im-* 常量和消息类型解析。
  3. 增加 MsgGetQuery.XIMSearch/XIMFilter 字段。
  4. 在 Topic.replyGetData 中实现第一阶段内存过滤。
  5. 增加群全员禁言、单成员禁言、成员文件上传限制策略 imCanPublish。
  6. 增加 x-im-type、private.x-im-*、aux.x-im-group 轻量校验。
  7. 支持创建群时保存 set.aux.x-im-group 到 topics.aux。
  8. 增加仅管理员邀请策略 imCanInvite。
  9. 补齐 session/topic 单元测试。

7.2 第二迭代

  1. 完成好友申请状态、备注、拉黑、黑名单联调。
  2. 完成群公告、群设置、管理员、禁言、踢人联调。
  3. 完成收藏、文件助手、聊天记录分类筛选联调。
  4. 完成通话信令和通话记录落库。

7.3 第三迭代

  1. 已实施 im_message_index 与 MySQL 全文索引。
  2. 已接入积分和签到表。
  3. 已增加后端内置反诈/内容审核标记 MVP。
  4. 已增加话题推荐轻量排序 MVP。
  5. 后续如需要更高召回率或运营可控性,再引入独立搜索服务、风控模型、人工审核台和离线推荐任务。

八、风险与决策点

风险/决策 建议
直接改 adapter 接口会牵连多数据库 第一阶段避免,第二阶段单独设计业务 store
JSON 字段缺少强校验 用 imext.go 做轻量校验,关键状态由 ACL 保证
聊天记录关键词搜索性能 第一阶段内存过滤,第二阶段索引表或搜索服务
群全员禁言不是原生 ACL 通过 aux + imCanPublish 策略补齐
积分扣减需要事务 不要写 JSON 字段,Phase 2 使用 im_user_points
规则风控存在误判 当前只标记不拦截,客户端提示可降级,后续再接审核工作台
推荐排序召回有限 当前使用 MySQL 轻量排序,后续可替换为异步索引或推荐服务
go test ./... 未指定 tag 会失败 使用测试报告中的 MySQL 标准命令
前端发送未知扩展字段被忽略 需要在 datamodel 显式声明查询扩展字段

九、验收标准

  1. 所有新增 Go 代码通过 gofmt。
  2. 不修改 Tinode 原生协议字段含义。
  3. 新增业务字段统一使用 x-im-*。
  4. 第一阶段不新增数据库表。
  5. 核心测试与 MySQL 测试通过。
  6. WebSocket 联调用例覆盖登录、私聊、群聊、文件、收藏、通话、举报、风控标记、推荐话题。
  7. 文档中的接口示例与实际后端行为一致。

十、推荐测试命令

go test -count=1 ./server ./server/db/common ./server/drafty ./server/media ./server/ringhash ./server/store/types
go test -count=1 -tags mysql ./server/db/mysql/tests
go test -count=1 -tags mysql $(go list ./... | grep -v '/server/db/mongodb/tests$' | grep -v '/server/db/postgres/tests$' | grep -v '/server/db/rethinkdb/tests$')
go build -tags mysql ./server ./tinode-db

本地 WebSocket 联调:

go run ./keygen -salt 'T713/rYYgW7g4m3vG6zGRh7+FM1t0T8j13koXScOAj4='
go run -tags mysql ./server -config=server/tinode.conf -static_data=-
go run ./tools/wsmock -apikey '<generated>' -scenario all -timeout 12s

tools/wsmock 当前覆盖:登录注册、slf 自存消息、双端 P2P 文本/文件消息、好友申请备注 x-im-applyText、好友审批状态 x-im-applyStatus、联系人 private.comment/x-im-muted/x-im-pinned/x-im-blocked 读写、黑名单视图、删除好友、个人 x-im-settings、收藏列表、文件助手、拉黑 ACL 阻断与取消拉黑、真实 /v0/file/u/ 上传、Drafty 附件消息、extra.attachments 防 GC 绑定、recv/read 回执、聊天记录 x-im-search、文件类型 x-im-filter、群创建/邀请/全员禁言/文件权限、进群审批 x-im-joinApplyText/x-im-joinStatus、群二维码公开资料、群主转让、群管理 aux 读取、仅管理员邀请、管理员授权、单成员禁言/解禁、管理员踢人、签到幂等、频道发布积分扣减、通话邀请参数校验、完整通话信令 started/ringing/accept/offer/answer/ice-candidate/hang-up/finished、举报到 sys。其中完整通话信令专项场景为 call-flow,需要服务端配置 webrtc.enabled=true;完整审批专项场景为 approvals;完整群管理专项场景为 group-admin;联系人状态专项场景为 contacts;页面型复用能力专项场景为 pages;群主转让专项场景为 group-owner;真实文件上传专项场景为 upload,会写入真实文件和数据库附件引用。