diff --git a/docs/IM-WS-TECH-DESIGN-CN.md b/docs/IM-WS-TECH-DESIGN-CN.md deleted file mode 100644 index 364ca97..0000000 --- a/docs/IM-WS-TECH-DESIGN-CN.md +++ /dev/null @@ -1,672 +0,0 @@ -# IM WebSocket 后端技术方案 - -> 本文档基于 [IM WebSocket 前后端接口文档](/Users/superysh/im/chat-0.25.3/docs/IM-WS-API-CN.md:1)、Tinode 原生 [API 文档](/Users/superysh/im/chat-0.25.3/docs/API.md:1)、[代码通读指南](/Users/superysh/im/chat-0.25.3/docs/CODE-WALKTHROUGH.md:1) 与当前 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 总体架构 - -```text -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](/Users/superysh/im/chat-0.25.3/server/hdl_websock.go:117) | 保持 WebSocket 入口不变 | -| [server/session.go](/Users/superysh/im/chat-0.25.3/server/session.go:431) | 复用消息分发、鉴权、topic 展开逻辑 | -| [server/hub.go](/Users/superysh/im/chat-0.25.3/server/hub.go:93) | 复用 topic 创建、加载、路由 | -| [server/topic.go](/Users/superysh/im/chat-0.25.3/server/topic.go:568) | 复用订阅、发布、meta、presence、call 处理 | -| [server/datamodel.go](/Users/superysh/im/chat-0.25.3/server/datamodel.go:336) | 复用 C2S/S2C 消息结构 | -| [server/store/store.go](/Users/superysh/im/chat-0.25.3/server/store/store.go:505) | 复用 Topic/Sub/Message/File/PCache 抽象 | -| [server/db/mysql/adapter.go](/Users/superysh/im/chat-0.25.3/server/db/mysql/adapter.go:1) | 复用 MySQL adapter、迁移机制、业务索引与推荐查询 | -| [server/drafty](/Users/superysh/im/chat-0.25.3/server/drafty/drafty.go:1) | 复用富文本纯文本提取与预览能力 | -| [server/store/mock_store](/Users/superysh/im/chat-0.25.3/server/store/mock_store/mock_store.go:1) | 新增测试时继续使用 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`,只做轻量解析,不改变原有消息结构语义。 - -```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 建议只返回类型与布尔值,不在业务层直接类型断言散落代码: - -```go -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](/Users/superysh/im/chat-0.25.3/server/datamodel.go:40) 显式增加字段: - -```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` 进入持久化前做小的策略检查: - -```go -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。 - -```sql -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 用户积分表 - -用途:话题发布积分扣减、签到积分发放。 - -```sql -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 用户每日事件表 - -用途:签到幂等、每日任务、青少年模式时长限制等按日事件。 - -```sql -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](/Users/superysh/im/chat-0.25.3/server/db/mysql/adapter.go:44) 中 `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](/Users/superysh/im/chat-0.25.3/server/db/mysql/schema.sql:1) 已同步。 -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 已有测试继续保留 - -后续开发前后建议固定跑: - -```bash -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 变更补跑: - -```bash -go test -count=1 -tags mysql ./server/db/mysql/tests -``` - -构建检查: - -```bash -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. 文档中的接口示例与实际后端行为一致。 - -## 十、推荐测试命令 - -```bash -go test -count=1 ./server ./server/db/common ./server/drafty ./server/media ./server/ringhash ./server/store/types -``` - -```bash -go test -count=1 -tags mysql ./server/db/mysql/tests -``` - -```bash -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$') -``` - -```bash -go build -tags mysql ./server ./tinode-db -``` - -本地 WebSocket 联调: - -```bash -go run ./keygen -salt 'T713/rYYgW7g4m3vG6zGRh7+FM1t0T8j13koXScOAj4=' -``` - -```bash -go run -tags mysql ./server -config=server/tinode.conf -static_data=- -``` - -```bash -go run ./tools/wsmock -apikey '' -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`,会写入真实文件和数据库附件引用。 diff --git a/docs/IM-WS-API-CN.md b/docs/obsolete/IM-WS-API-CN.md similarity index 100% rename from docs/IM-WS-API-CN.md rename to docs/obsolete/IM-WS-API-CN.md