2047 lines
45 KiB
Markdown
2047 lines
45 KiB
Markdown
# IM WebSocket 前后端接口文档
|
||
|
||
> 本文档根据 `/Users/superysh/im/prototype` 下的产品原型图整理,用于移动端/前端与 Tinode 后端对接。协议风格遵循 Tinode `docs/API.md`:默认全部业务通信通过 WebSocket JSON 文本帧完成;大文件仍沿用 Tinode 现有 HTTP 上传/下载端点,上传完成后通过 WebSocket 发送附件引用。
|
||
|
||
## 目录
|
||
|
||
1. [适用范围](#适用范围)
|
||
2. [协议总则](#协议总则)
|
||
3. [公共数据约定](#公共数据约定)
|
||
4. [连接与认证](#连接与认证)
|
||
5. [消息与会话](#消息与会话)
|
||
6. [好友与通讯录](#好友与通讯录)
|
||
7. [群聊与群管理](#群聊与群管理)
|
||
8. [文件、收藏与聊天记录](#文件收藏与聊天记录)
|
||
9. [语音、视频与通话](#语音视频与通话)
|
||
10. [话题功能](#话题功能)
|
||
11. [系统消息、举报与安全提示](#系统消息举报与安全提示)
|
||
12. [个人中心与设置](#个人中心与设置)
|
||
13. [错误码与状态码](#错误码与状态码)
|
||
|
||
## 适用范围
|
||
|
||
### 原型覆盖页面
|
||
|
||
原型图覆盖以下业务域:
|
||
|
||
| 业务域 | 页面示例 |
|
||
|---|---|
|
||
| 登录注册 | 登录、注册、忘记密码、验证码、实名认证、身份验证、独立密码 |
|
||
| 消息 | 消息列表、私聊、群聊、文件助手、系统消息、长按菜单、多选、转发、撤回、删除 |
|
||
| 联系人 | 朋友、新朋友、添加好友、好友主页、备注、拉黑、黑名单、删除好友 |
|
||
| 群聊 | 创建群聊、加入群聊、群二维码、群资料、群公告、群文件、群管理员、禁言、群主转让、进群验证 |
|
||
| 文件与收藏 | 文件发送、下载、打开、群文件、我的收藏、收藏分享 |
|
||
| 通话 | 语音通话、视频通话、等待接听、通话中、关闭摄像头/麦克风/扬声器、悬浮状态 |
|
||
| 话题 | 话题列表、发布话题、话题详情、话题人数、话题类型、积分不足 |
|
||
| 设置 | 个人信息、个性签名、隐私设置、青少年模式、版本更新、清空缓存、意见反馈 |
|
||
| 安全 | 反诈弹窗、用户协议、推送权限、相机/麦克风权限、安全提醒、举报 |
|
||
|
||
### 与 Tinode 原生能力的关系
|
||
|
||
| 能力 | 接入方式 |
|
||
|---|---|
|
||
| 登录、注册、Token、验证码 | 使用 `{hi}`、`{acc}`、`{login}`,必要时接入 `code`/`rest` auth |
|
||
| 私聊、群聊、频道 | 使用 `{sub}`、`{leave}`、`{pub}`、`{get}`、`{set}`、`{del}` |
|
||
| 消息回执、输入中、语音录制中 | 使用 `{note}`,服务端转发为 `{info}` |
|
||
| 用户资料、群资料、备注、置顶、免打扰 | 使用 `public`、`private`、`aux` |
|
||
| 文件、图片、语音、视频 | 大文件先上传 `/v0/file/u/`,再通过 `{pub}` 发送 Drafty 附件 |
|
||
| 群权限、管理员、禁言、踢人 | 使用 Tinode ACL 与 `{set sub}`、`{del what="sub"}`,业务状态写入 `aux` |
|
||
| 收藏、话题、签到、青少年模式、反诈提示 | 使用本产品扩展命名空间 `x-im-*` |
|
||
|
||
## 协议总则
|
||
|
||
### WebSocket 端点
|
||
|
||
客户端统一连接:
|
||
|
||
```text
|
||
ws://{host}/v0/channels?apikey={api_key}
|
||
wss://{host}/v0/channels?apikey={api_key}
|
||
```
|
||
|
||
每个 WebSocket 文本帧只发送一个 JSON 消息。二进制帧不用于业务数据。
|
||
|
||
### 首包与登录顺序
|
||
|
||
客户端连接成功后必须先发送 `{hi}`,再发送 `{login}` 或 `{acc}`。登录后建议立即订阅 `me` 和 `fnd`:
|
||
|
||
```json
|
||
{"hi":{"id":"100","ver":"0.25","ua":"IM/1.0 (iOS)","platf":"ios","lang":"zh-CN","tenant":"acme"}}
|
||
```
|
||
|
||
```json
|
||
{"login":{"id":"101","scheme":"basic","secret":"MTg4MjY4Mjc3NTQ6MTIzNDU2"}}
|
||
```
|
||
|
||
```json
|
||
{"sub":{"id":"102","topic":"me","get":{"what":"desc sub tags cred"}}}
|
||
```
|
||
|
||
```json
|
||
{"sub":{"id":"103","topic":"fnd"}}
|
||
```
|
||
|
||
### 消息类型
|
||
|
||
客户端到服务端:
|
||
|
||
| 包 | 用途 |
|
||
|---|---|
|
||
| `{hi}` | 握手、声明企业码、客户端版本和设备信息 |
|
||
| `{acc}` | 注册、修改认证参数、提交验证码 |
|
||
| `{login}` | 登录、Token 登录、重置密码流程 |
|
||
| `{sub}` | 订阅/进入会话、创建群、创建私聊、拉取初始数据 |
|
||
| `{leave}` | 离开会话或退订会话 |
|
||
| `{pub}` | 发送持久化消息 |
|
||
| `{get}` | 查询资料、订阅、消息历史、删除记录、辅助数据 |
|
||
| `{set}` | 更新资料、备注、权限、群设置、辅助数据 |
|
||
| `{del}` | 删除消息、订阅、topic、凭证 |
|
||
| `{note}` | 发送临时通知,如已读、已收、输入中、通话信令 |
|
||
|
||
服务端到客户端:
|
||
|
||
| 包 | 用途 |
|
||
|---|---|
|
||
| `{ctrl}` | 请求结果,应答成功或失败 |
|
||
| `{data}` | 持久化消息投递 |
|
||
| `{meta}` | 资料、订阅、标签、凭证、删除记录、辅助数据返回 |
|
||
| `{pres}` | 在线、离线、权限、资料更新、消息到达等通知 |
|
||
| `{info}` | 由 `{note}` 转发而来的临时通知 |
|
||
|
||
### 请求 ID 规范
|
||
|
||
客户端必须为需要应答的请求设置 `id`。建议格式:
|
||
|
||
```text
|
||
{module}-{timestamp_ms}-{seq}
|
||
```
|
||
|
||
示例:
|
||
|
||
```json
|
||
{"get":{"id":"msg-1722400000000-1","topic":"grpAbC123","what":"data","data":{"limit":20}}}
|
||
```
|
||
|
||
### 扩展命名空间
|
||
|
||
Tinode 原生字段保持不变。本产品扩展字段统一使用 `x-im-*`:
|
||
|
||
| 位置 | 示例 | 说明 |
|
||
|---|---|---|
|
||
| `head.x-im-type` | `"text"`、`"card"`、`"topic-share"` | 消息业务类型 |
|
||
| `head.x-im-client-mid` | `"local-uuid"` | 客户端本地消息 ID,用于发送中/失败重试 |
|
||
| `head.x-im-anti-fraud` | `true` | 需要客户端展示反诈提醒 |
|
||
| `content.x-im-*` | `{...}` | 消息内容里的业务负载 |
|
||
| `public.x-im-*` | `{...}` | 对所有可见的业务资料 |
|
||
| `private.x-im-*` | `{...}` | 仅当前用户可见的配置 |
|
||
| `aux.x-im-*` | `{...}` | 群/频道管理员可写、成员可读的配置 |
|
||
|
||
第一阶段服务端已启用轻量校验:
|
||
|
||
| 字段 | 规则 |
|
||
|---|---|
|
||
| `head.x-im-type` | 必须是文档附录里的已知消息类型 |
|
||
| `private.x-im-applyText` | 字符串,最长 256 个字符 |
|
||
| `private.x-im-applyStatus`、`private.x-im-joinStatus` | 枚举:`pending`、`accepted`、`rejected`、`expired` |
|
||
| `private.x-im-joinApplyText` | 字符串,最长 256 个字符 |
|
||
| `private.x-im-blocked`、`private.x-im-muted`、`private.x-im-pinned` | 布尔值 |
|
||
| `aux.x-im-group.joinApproval`、`onlyAdminInvite`、`allowMemberFileUpload`、`muteAll` | 布尔值 |
|
||
| `aux.x-im-group.mutedUsers` | Tinode 用户 ID 数组 |
|
||
| `aux.x-im-group.announcement.text` | 字符串,最长 2000 个字符 |
|
||
|
||
## 公共数据约定
|
||
|
||
### 时间与 ID
|
||
|
||
| 字段 | 说明 |
|
||
|---|---|
|
||
| `ts`、`created`、`updated` | RFC3339 UTC 字符串,如 `"2026-07-31T08:00:00.000Z"` |
|
||
| `user` | Tinode 用户 ID,如 `usrAbCdEf123` |
|
||
| `topic` | Tinode topic,如 `usrAbCdEf123`、`grpAbCdEf123`、`chnAbCdEf123` |
|
||
| `seq` | topic 内递增消息序号 |
|
||
| `clear` | 删除事务序号 |
|
||
|
||
### 用户公开资料 `public`
|
||
|
||
用户资料页面、好友主页、群成员头像昵称均使用用户 `public`。
|
||
|
||
```json
|
||
{
|
||
"fn": "张三",
|
||
"photo": {
|
||
"ref": "/v0/file/s/avatar.jpg",
|
||
"type": "image/jpeg"
|
||
},
|
||
"note": "成人最懂什么都没留下",
|
||
"x-im-gender": "female",
|
||
"x-im-birthday": "2008-02-07",
|
||
"x-im-area": "广东省广州市",
|
||
"x-im-id-no": "84444646554"
|
||
}
|
||
```
|
||
|
||
### 用户私有资料 `private`
|
||
|
||
仅当前用户可见。
|
||
|
||
```json
|
||
{
|
||
"comment": "好友备注",
|
||
"tpins": ["usrPeer001", "grpTeam001"],
|
||
"x-im-settings": {
|
||
"teenMode": false,
|
||
"independentPassword": true,
|
||
"allowSearchByPhone": true,
|
||
"allowSearchByQr": true,
|
||
"allowSearchByGroup": true,
|
||
"pushEnabled": true
|
||
}
|
||
}
|
||
```
|
||
|
||
### 群公开资料 `public`
|
||
|
||
群资料、群二维码、群设置页读取群 topic 的 `public`。
|
||
|
||
```json
|
||
{
|
||
"fn": "新人交流群",
|
||
"photo": {
|
||
"ref": "/v0/file/s/group-avatar.jpg",
|
||
"type": "image/jpeg"
|
||
},
|
||
"note": "群主很懒,还没有群简介",
|
||
"x-im-ownerName": "王子民",
|
||
"x-im-qrcode": "im://group/grpAbCdEf123"
|
||
}
|
||
```
|
||
|
||
### 群业务配置 `aux`
|
||
|
||
群设置、群公告、群文件权限、进群验证、禁言列表使用 `aux`。
|
||
|
||
```json
|
||
{
|
||
"x-im-group": {
|
||
"joinApproval": true,
|
||
"onlyAdminInvite": false,
|
||
"onlyAdminEditInfo": true,
|
||
"allowMemberFileUpload": true,
|
||
"muteAll": false,
|
||
"mutedUsers": ["usrMuted001"],
|
||
"announcement": {
|
||
"text": "为帮助新用户熟悉群规则,请勿发布违规内容。",
|
||
"updatedBy": "usrOwner001",
|
||
"updatedAt": "2026-07-31T08:00:00.000Z"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 当前用户对会话的私有设置 `desc.private`
|
||
|
||
聊天设置页里的备注、免打扰、置顶、拉黑、本地草稿等使用当前用户对 topic 的 `private`。
|
||
|
||
```json
|
||
{
|
||
"comment": "王子民",
|
||
"arch": false,
|
||
"x-im-muted": false,
|
||
"x-im-pinned": true,
|
||
"x-im-blocked": false,
|
||
"x-im-draft": "稍后回复"
|
||
}
|
||
```
|
||
|
||
## 连接与认证
|
||
|
||
### 登录
|
||
|
||
原型页面:登录页面、注册页面。
|
||
|
||
客户端先发送 `{hi}`,再使用手机号/用户名 + 密码登录。`secret` 为 `base64(username + ":" + password)`。
|
||
|
||
```json
|
||
{"login":{"id":"login-1","scheme":"basic","secret":"MTg4MjY4Mjc3NTQ6MTIzNDU2"}}
|
||
```
|
||
|
||
成功响应:
|
||
|
||
```json
|
||
{
|
||
"ctrl": {
|
||
"id": "login-1",
|
||
"code": 200,
|
||
"text": "ok",
|
||
"params": {
|
||
"user": "usrUser001",
|
||
"token": "token-string",
|
||
"expires": "2026-08-30T08:00:00.000Z"
|
||
},
|
||
"ts": "2026-07-31T08:00:00.000Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Token 自动登录
|
||
|
||
```json
|
||
{"login":{"id":"login-token-1","scheme":"token","secret":"token-string"}}
|
||
```
|
||
|
||
### 注册
|
||
|
||
```json
|
||
{
|
||
"acc": {
|
||
"id": "acc-1",
|
||
"user": "new",
|
||
"scheme": "basic",
|
||
"secret": "MTg4MjY4Mjc3NTQ6MTIzNDU2",
|
||
"login": true,
|
||
"tags": ["tel:+8618826827754", "basic:18826827754"],
|
||
"cred": [
|
||
{"meth": "tel", "val": "+8618826827754", "resp": "123456"}
|
||
],
|
||
"desc": {
|
||
"defacs": {"auth": "JRWS", "anon": "N"},
|
||
"public": {
|
||
"fn": "用户1882",
|
||
"x-im-area": "广东省广州市"
|
||
},
|
||
"private": {
|
||
"x-im-settings": {
|
||
"pushEnabled": true,
|
||
"teenMode": false
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 忘记密码
|
||
|
||
发送重置请求:
|
||
|
||
```json
|
||
{"login":{"id":"reset-1","scheme":"reset","secret":"YmFzaWM6dGVsOis4NjE4ODI2ODI3NzU0"}}
|
||
```
|
||
|
||
使用收到的临时 token 修改密码:
|
||
|
||
```json
|
||
{
|
||
"acc": {
|
||
"id": "reset-2",
|
||
"token": "restricted-reset-token",
|
||
"scheme": "basic",
|
||
"secret": "OjEyMzQ1Njc4"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 实名认证
|
||
|
||
实名认证是业务扩展,建议写入用户 `private.x-im-realname`,服务端审核通过后可由 root 写入 `trusted.verified=true`。
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "realname-1",
|
||
"topic": "me",
|
||
"desc": {
|
||
"private": {
|
||
"x-im-realname": {
|
||
"name": "张三",
|
||
"idCard": "440100199001011234",
|
||
"status": "pending"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
服务端返回:
|
||
|
||
```json
|
||
{"ctrl":{"id":"realname-1","topic":"me","code":200,"text":"ok","ts":"2026-07-31T08:00:00.000Z"}}
|
||
```
|
||
|
||
## 消息与会话
|
||
|
||
### 获取消息列表
|
||
|
||
原型页面:消息、文件助手、系统消息、聊天会话列表。
|
||
|
||
订阅 `me` 后获取当前用户订阅的所有会话:
|
||
|
||
```json
|
||
{"get":{"id":"contacts-1","topic":"me","what":"sub","sub":{"limit":50}}}
|
||
```
|
||
|
||
响应:
|
||
|
||
```json
|
||
{
|
||
"meta": {
|
||
"id": "contacts-1",
|
||
"topic": "me",
|
||
"sub": [
|
||
{
|
||
"topic": "usrPeer001",
|
||
"updated": "2026-07-31T08:00:00.000Z",
|
||
"touched": "2026-07-31T08:10:00.000Z",
|
||
"seq": 18,
|
||
"read": 15,
|
||
"recv": 18,
|
||
"online": true,
|
||
"public": {"fn": "王子民", "photo": {"ref": "/v0/file/s/a.jpg"}},
|
||
"private": {"comment": "王子民", "x-im-muted": false, "x-im-pinned": true}
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 进入私聊
|
||
|
||
私聊 topic 使用对方用户 ID。首次 `{sub}` 会创建 P2P topic。
|
||
|
||
```json
|
||
{
|
||
"sub": {
|
||
"id": "p2p-sub-1",
|
||
"topic": "usrPeer001",
|
||
"get": {
|
||
"what": "desc sub data",
|
||
"data": {"limit": 20}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 进入群聊
|
||
|
||
```json
|
||
{
|
||
"sub": {
|
||
"id": "grp-sub-1",
|
||
"topic": "grpTeam001",
|
||
"get": {
|
||
"what": "desc sub data aux",
|
||
"data": {"limit": 20}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 发送文本消息
|
||
|
||
Tinode 推荐使用 Drafty。发送 Drafty 时必须设置 `head.mime="text/x-drafty"`。
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "pub-text-1",
|
||
"topic": "usrPeer001",
|
||
"head": {
|
||
"mime": "text/x-drafty",
|
||
"x-im-type": "text",
|
||
"x-im-client-mid": "local-001"
|
||
},
|
||
"content": {
|
||
"txt": "你好,我是王理娟,很高兴认识你,你怎么称呼"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
接收方收到:
|
||
|
||
```json
|
||
{
|
||
"data": {
|
||
"topic": "usrPeer001",
|
||
"from": "usrUser001",
|
||
"seq": 19,
|
||
"ts": "2026-07-31T08:10:00.000Z",
|
||
"head": {
|
||
"mime": "text/x-drafty",
|
||
"x-im-type": "text",
|
||
"x-im-client-mid": "local-001"
|
||
},
|
||
"content": {
|
||
"txt": "你好,我是王理娟,很高兴认识你,你怎么称呼"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 已收、已读、未读数
|
||
|
||
原型页面:个人对话已读状态、消息未读红点。
|
||
|
||
```json
|
||
{"note":{"topic":"usrPeer001","what":"recv","seq":19,"unread":3}}
|
||
```
|
||
|
||
```json
|
||
{"note":{"topic":"usrPeer001","what":"read","seq":19,"unread":0}}
|
||
```
|
||
|
||
对端收到:
|
||
|
||
```json
|
||
{"info":{"topic":"usrPeer001","from":"usrPeer002","what":"read","seq":19}}
|
||
```
|
||
|
||
### 输入中与录音中
|
||
|
||
```json
|
||
{"note":{"topic":"usrPeer001","what":"kp"}}
|
||
```
|
||
|
||
```json
|
||
{"note":{"topic":"usrPeer001","what":"kpa"}}
|
||
```
|
||
|
||
```json
|
||
{"note":{"topic":"usrPeer001","what":"kpv"}}
|
||
```
|
||
|
||
### @ 提醒
|
||
|
||
原型页面:`@人员-选择提醒人`、`@人员—选择提醒人-多选`。
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "pub-mention-1",
|
||
"topic": "grpTeam001",
|
||
"head": {
|
||
"mime": "text/x-drafty",
|
||
"mentions": ["usrA001", "usrB001"],
|
||
"x-im-type": "text"
|
||
},
|
||
"content": {
|
||
"txt": "@万先生 @钱哥你们看一下",
|
||
"fmt": [
|
||
{"at": 0, "len": 4, "key": 0},
|
||
{"at": 5, "len": 3, "key": 1}
|
||
],
|
||
"ent": [
|
||
{"tp": "MN", "data": {"val": "usrA001"}},
|
||
{"tp": "MN", "data": {"val": "usrB001"}}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 回复、引用、编辑
|
||
|
||
长按菜单中的引用、回复、编辑沿用 Tinode `head.reply`、`head.replace`。
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "pub-reply-1",
|
||
"topic": "grpTeam001",
|
||
"head": {
|
||
"mime": "text/x-drafty",
|
||
"reply": "grpTeam001:19",
|
||
"x-im-type": "text"
|
||
},
|
||
"content": {"txt": "我同意这个方案"}
|
||
}
|
||
}
|
||
```
|
||
|
||
编辑消息:
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "pub-edit-1",
|
||
"topic": "grpTeam001",
|
||
"head": {
|
||
"mime": "text/x-drafty",
|
||
"replace": ":19",
|
||
"x-im-type": "text"
|
||
},
|
||
"content": {"txt": "修改后的消息内容"}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 撤回与删除
|
||
|
||
单聊/群聊撤回使用硬删除,前提是用户有 `D` 权限或符合后端业务规则。
|
||
|
||
```json
|
||
{
|
||
"del": {
|
||
"id": "del-msg-1",
|
||
"topic": "grpTeam001",
|
||
"what": "msg",
|
||
"hard": true,
|
||
"delseq": [{"low": 19}]
|
||
}
|
||
}
|
||
```
|
||
|
||
仅自己删除:
|
||
|
||
```json
|
||
{
|
||
"del": {
|
||
"id": "del-msg-self-1",
|
||
"topic": "grpTeam001",
|
||
"what": "msg",
|
||
"hard": false,
|
||
"delseq": [{"low": 19}]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 转发
|
||
|
||
逐条转发:
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "forward-1",
|
||
"topic": "usrPeer002",
|
||
"head": {
|
||
"mime": "text/x-drafty",
|
||
"forwarded": "grpTeam001:19",
|
||
"x-im-type": "text"
|
||
},
|
||
"content": {"txt": "被转发的内容"}
|
||
}
|
||
}
|
||
```
|
||
|
||
合并转发:
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "forward-merge-1",
|
||
"topic": "usrPeer002",
|
||
"head": {
|
||
"mime": "application/json",
|
||
"x-im-type": "forward-bundle"
|
||
},
|
||
"content": {
|
||
"title": "群聊的聊天记录",
|
||
"items": [
|
||
{"topic": "grpTeam001", "seq": 19, "from": "usrA001", "text": "第一条"},
|
||
{"topic": "grpTeam001", "seq": 20, "from": "usrB001", "text": "第二条"}
|
||
]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 好友与通讯录
|
||
|
||
### 查找用户
|
||
|
||
原型页面:添加好友、输入精准用户 ID。
|
||
|
||
使用 `fnd` topic 查询标签。手机号、用户名、用户 ID 由后端查询重写或精确匹配。
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "find-1",
|
||
"topic": "fnd",
|
||
"desc": {
|
||
"public": "18826827754"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
```json
|
||
{"get":{"id":"find-2","topic":"fnd","what":"sub","sub":{"limit":20}}}
|
||
```
|
||
|
||
### 添加好友申请
|
||
|
||
P2P 建立关系使用 `{sub topic=userId}`。申请备注放入 `set.desc.private.x-im-applyText`,审批状态放入 `private.x-im-applyStatus`。服务端会校验状态枚举,真实可聊权限仍以 Tinode ACL 为准。
|
||
|
||
```json
|
||
{
|
||
"sub": {
|
||
"id": "friend-apply-1",
|
||
"topic": "usrPeer001",
|
||
"set": {
|
||
"desc": {
|
||
"private": {
|
||
"x-im-applyText": "我是王子民",
|
||
"x-im-applyStatus": "pending"
|
||
}
|
||
}
|
||
},
|
||
"get": {"what": "desc"}
|
||
}
|
||
}
|
||
```
|
||
|
||
对方在线时会收到 `me` topic 的 `pres what="acs"`;客户端可据此刷新新朋友列表。
|
||
|
||
### 新朋友列表
|
||
|
||
客户端从 `me` 的订阅列表中过滤待处理 P2P 权限:
|
||
|
||
```json
|
||
{"get":{"id":"new-friends-1","topic":"me","what":"sub","sub":{"limit":100}}}
|
||
```
|
||
|
||
列表状态建议:
|
||
|
||
| 条件 | 含义 |
|
||
|---|---|
|
||
| `private.x-im-applyStatus="pending"` | 申请待处理 |
|
||
| `private.x-im-applyStatus="accepted"` | 申请已同意 |
|
||
| `private.x-im-applyStatus="rejected"` | 申请已拒绝 |
|
||
| 对方订阅变更产生 `pres what="acs"` | 我收到的新申请 |
|
||
|
||
### 同意好友
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "friend-approve-1",
|
||
"topic": "usrPeer001",
|
||
"sub": {
|
||
"user": "usrPeer001",
|
||
"mode": "JRWPA"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
同意后建议双方把各自订阅 `private.x-im-applyStatus` 更新为 `accepted`;拒绝时更新为 `rejected`。非法状态会返回 `400 malformed`。
|
||
|
||
### 设置备注
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "remark-1",
|
||
"topic": "usrPeer001",
|
||
"desc": {
|
||
"private": {
|
||
"comment": "王子民",
|
||
"x-im-remarkUpdatedAt": "2026-07-31T08:00:00.000Z"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 拉黑与取消拉黑
|
||
|
||
客户端私有状态用于 UI 展示,服务端应同时调整 P2P ACL,阻止对方继续发起会话。
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "block-1",
|
||
"topic": "usrPeer001",
|
||
"desc": {
|
||
"private": {
|
||
"x-im-blocked": true
|
||
}
|
||
},
|
||
"sub": {
|
||
"user": "usrPeer001",
|
||
"mode": "JRPA"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
取消拉黑:
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "unblock-1",
|
||
"topic": "usrPeer001",
|
||
"desc": {
|
||
"private": {
|
||
"x-im-blocked": false
|
||
}
|
||
},
|
||
"sub": {
|
||
"user": "usrPeer001",
|
||
"mode": "JRWPA"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 删除好友
|
||
|
||
```json
|
||
{
|
||
"leave": {
|
||
"id": "friend-delete-1",
|
||
"topic": "usrPeer001",
|
||
"unsub": true
|
||
}
|
||
}
|
||
```
|
||
|
||
## 群聊与群管理
|
||
|
||
### 创建群聊
|
||
|
||
原型页面:创建群聊。
|
||
|
||
```json
|
||
{
|
||
"sub": {
|
||
"id": "group-create-1",
|
||
"topic": "new",
|
||
"set": {
|
||
"desc": {
|
||
"defacs": {"auth": "JRWP", "anon": "N"},
|
||
"public": {
|
||
"fn": "新人交流群",
|
||
"note": "群主很懒,还没有群简介"
|
||
},
|
||
"private": {}
|
||
},
|
||
"aux": {
|
||
"x-im-group": {
|
||
"joinApproval": false,
|
||
"onlyAdminInvite": false,
|
||
"allowMemberFileUpload": true,
|
||
"muteAll": false,
|
||
"mutedUsers": []
|
||
}
|
||
}
|
||
},
|
||
"get": {"what": "desc sub aux"}
|
||
}
|
||
}
|
||
```
|
||
|
||
成功响应的 `ctrl.topic` 为新群 ID:
|
||
|
||
说明:创建群时传入的 `set.aux.x-im-group` 会写入 `topics.aux`,后续可通过 `{get what="aux"}` 或 `{sub get.what="desc sub aux"}` 读取。
|
||
|
||
```json
|
||
{"ctrl":{"id":"group-create-1","topic":"grpNew001","code":200,"text":"ok","ts":"2026-07-31T08:00:00.000Z"}}
|
||
```
|
||
|
||
### 邀请成员入群
|
||
|
||
原型页面:邀请进群申请、开启群验证拉人状态。
|
||
|
||
无需审批时直接共享订阅:
|
||
|
||
如果 `aux.x-im-group.onlyAdminInvite=true`,服务端只允许群管理员或群主执行邀请。
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "group-invite-1",
|
||
"topic": "grpNew001",
|
||
"sub": {
|
||
"user": "usrInvitee001",
|
||
"mode": "JRWP"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
需要审批时,申请人发送 `{sub}`,请求 `JRWPS`,申请备注写入自己的订阅 `private.x-im-joinApplyText`。若群 `aux.x-im-group.joinApproval=true` 且默认 `defacs.auth` 不包含 `J`,服务端会保存一条 `want=JRWPS/given=N` 的待审批订阅并返回 `200 ok`,但不会把申请人 attach 到群。
|
||
|
||
```json
|
||
{
|
||
"sub": {
|
||
"id": "group-join-apply-1",
|
||
"topic": "grpNew001",
|
||
"set": {
|
||
"sub": {"mode": "JRWPS"},
|
||
"desc": {
|
||
"private": {
|
||
"x-im-joinApplyText": "申请加入群聊",
|
||
"x-im-joinStatus": "pending"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
管理员获取待审批列表:
|
||
|
||
```json
|
||
{"get":{"id":"group-apply-list-1","topic":"grpNew001","what":"sub","sub":{"limit":100}}}
|
||
```
|
||
|
||
返回中待审批用户表现为 `acs.want` 包含 `J`、`acs.given="N"`、`private.x-im-joinStatus="pending"`:
|
||
|
||
```json
|
||
{
|
||
"meta": {
|
||
"id": "group-apply-list-1",
|
||
"topic": "grpNew001",
|
||
"sub": [
|
||
{
|
||
"user": "usrInvitee001",
|
||
"acs": {"want": "JRWPS", "given": "N", "mode": "N"},
|
||
"private": {
|
||
"x-im-joinApplyText": "申请加入群聊",
|
||
"x-im-joinStatus": "pending"
|
||
}
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
管理员审批通过:
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "group-approve-1",
|
||
"topic": "grpNew001",
|
||
"sub": {
|
||
"user": "usrInvitee001",
|
||
"mode": "JRWP"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
服务端会把申请人的 `private.x-im-joinStatus` 自动更新为 `accepted`。如果拒绝,管理员设置 `mode="N"`,服务端会把状态更新为 `rejected`:
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "group-reject-1",
|
||
"topic": "grpNew001",
|
||
"sub": {
|
||
"user": "usrInvitee001",
|
||
"mode": "N"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
如需在群内留痕,管理员可额外发布审批结果消息:
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "group-join-decision-1",
|
||
"topic": "grpNew001",
|
||
"head": {
|
||
"mime": "application/json",
|
||
"x-im-type": "group-join-approve"
|
||
},
|
||
"content": {
|
||
"applicant": "usrInvitee001",
|
||
"status": "accepted",
|
||
"reviewedAt": "2026-07-31T08:00:00.000Z"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 群资料与群公告
|
||
|
||
更新群名称、头像、简介:
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "group-profile-1",
|
||
"topic": "grpNew001",
|
||
"desc": {
|
||
"public": {
|
||
"fn": "新人交流群",
|
||
"photo": {"ref": "/v0/file/s/group-avatar.jpg", "type": "image/jpeg"},
|
||
"note": "欢迎交流,禁止广告。"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
更新群公告:
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "group-ann-1",
|
||
"topic": "grpNew001",
|
||
"aux": {
|
||
"x-im-group": {
|
||
"announcement": {
|
||
"text": "为帮助新用户熟悉群规则,请勿发布违规内容。",
|
||
"updatedBy": "usrOwner001",
|
||
"updatedAt": "2026-07-31T08:00:00.000Z"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
公告也可以发送一条系统样式消息,方便聊天页展示:
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "group-ann-msg-1",
|
||
"topic": "grpNew001",
|
||
"head": {
|
||
"mime": "application/json",
|
||
"x-im-type": "group-announcement"
|
||
},
|
||
"content": {
|
||
"text": "为帮助新用户熟悉群规则,请勿发布违规内容。"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 设置管理员
|
||
|
||
原型页面:设置群管理员、添加管理员、删除管理员。
|
||
|
||
当前后端 `{set.sub.mode}` 使用绝对权限字符串,不支持 `+A`、`-A` 这类增量写法。前端需要按目标最终权限发送完整 mode;用户有效权限为 `want & given`,因此授予管理员时通常需要管理员设置目标用户的 `given`,目标用户再通过自己的 `{set.sub}` 提升 `want`。
|
||
|
||
授予管理员权限:
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "admin-add-1",
|
||
"topic": "grpNew001",
|
||
"sub": {
|
||
"user": "usrAdmin001",
|
||
"mode": "JRWPASD"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
移除管理员权限:
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "admin-del-1",
|
||
"topic": "grpNew001",
|
||
"sub": {
|
||
"user": "usrAdmin001",
|
||
"mode": "JRWPS"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 群禁言
|
||
|
||
禁言单个成员建议同时更新 ACL 与 `aux.x-im-group.mutedUsers`。第一阶段服务端会在发送消息时检查 `mutedUsers`,普通成员在列表中会被拒绝;管理员和群主不受该业务禁言影响。
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "mute-user-1",
|
||
"topic": "grpNew001",
|
||
"sub": {
|
||
"user": "usrMuted001",
|
||
"mode": "JRP"
|
||
},
|
||
"aux": {
|
||
"x-im-group": {
|
||
"mutedUsers": ["usrMuted001"]
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
解除禁言:
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "unmute-user-1",
|
||
"topic": "grpNew001",
|
||
"sub": {
|
||
"user": "usrMuted001",
|
||
"mode": "JRWP"
|
||
},
|
||
"aux": {
|
||
"x-im-group": {
|
||
"mutedUsers": []
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
全员禁言:
|
||
|
||
第一阶段服务端会拒绝普通成员发送消息,管理员和群主仍可发送。
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "mute-all-1",
|
||
"topic": "grpNew001",
|
||
"aux": {
|
||
"x-im-group": {
|
||
"muteAll": true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 群文件上传权限
|
||
|
||
当 `aux.x-im-group.allowMemberFileUpload=false` 时,服务端会拒绝普通成员发送 `x-im-type` 为 `image`、`audio`、`video`、`file` 的消息,或带 `extra.attachments` 的消息。管理员和群主不受限制。
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "group-file-policy-1",
|
||
"topic": "grpNew001",
|
||
"aux": {
|
||
"x-im-group": {
|
||
"allowMemberFileUpload": false
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 群主转让
|
||
|
||
原型页面:选择新群主、选择新群主提示。
|
||
|
||
第一步,当前群主发起转让,把新群主的 `given` 权限设置为包含 `O` 的完整权限:
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "owner-transfer-1",
|
||
"topic": "grpNew001",
|
||
"sub": {
|
||
"user": "usrNewOwner001",
|
||
"mode": "JRWPASDO"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
第二步,新群主接受转让,把自己的 `want` 权限设置为包含 `O` 的完整权限:
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "owner-accept-1",
|
||
"topic": "grpNew001",
|
||
"sub": {
|
||
"mode": "JRWPASDO"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
后端必须保证同一 topic 只有一个 `O` 权限用户。当前 Tinode 实际行为是:新群主接受后,服务端自动移除旧群主的 `O`,并向双方推送 `pres what="acs"`。
|
||
|
||
### 踢出群成员
|
||
|
||
```json
|
||
{
|
||
"del": {
|
||
"id": "kick-1",
|
||
"topic": "grpNew001",
|
||
"what": "sub",
|
||
"user": "usrTarget001",
|
||
"hard": false
|
||
}
|
||
}
|
||
```
|
||
|
||
### 退出群聊
|
||
|
||
```json
|
||
{
|
||
"leave": {
|
||
"id": "leave-group-1",
|
||
"topic": "grpNew001",
|
||
"unsub": true
|
||
}
|
||
}
|
||
```
|
||
|
||
## 文件、收藏与聊天记录
|
||
|
||
### 上传并发送图片
|
||
|
||
原型页面:单张图片状态、图片和语音发送中。
|
||
|
||
1. 客户端通过 `/v0/file/u/` 上传图片,获得 `ctrl.params.url`。
|
||
2. 通过 WebSocket `{pub}` 发送 Drafty 图片消息。
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "pub-img-1",
|
||
"topic": "usrPeer001",
|
||
"head": {
|
||
"mime": "text/x-drafty",
|
||
"attachments": ["/v0/file/s/img001.jpg"],
|
||
"x-im-type": "image",
|
||
"x-im-client-mid": "local-img-1"
|
||
},
|
||
"content": {
|
||
"txt": " ",
|
||
"fmt": [{"at": -1, "len": 1, "key": 0}],
|
||
"ent": [
|
||
{
|
||
"tp": "IM",
|
||
"data": {
|
||
"mime": "image/jpeg",
|
||
"ref": "/v0/file/s/img001.jpg",
|
||
"width": 1080,
|
||
"height": 1920,
|
||
"size": 245760
|
||
}
|
||
}
|
||
]
|
||
}
|
||
},
|
||
"extra": {
|
||
"attachments": ["/v0/file/s/img001.jpg"]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 发送普通文件
|
||
|
||
原型页面:文件发送状态、文件下载、文件打开。
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "pub-file-1",
|
||
"topic": "usrPeer001",
|
||
"head": {
|
||
"mime": "text/x-drafty",
|
||
"attachments": ["/v0/file/s/doc001.pdf"],
|
||
"x-im-type": "file"
|
||
},
|
||
"content": {
|
||
"txt": "365天天高清晰记忆曲线复习_pdf",
|
||
"fmt": [{"at": -1, "len": 0, "key": 0}],
|
||
"ent": [
|
||
{
|
||
"tp": "EX",
|
||
"data": {
|
||
"mime": "application/pdf",
|
||
"ref": "/v0/file/s/doc001.pdf",
|
||
"name": "365天天高清晰记忆曲线复习_pdf",
|
||
"size": 115000
|
||
}
|
||
}
|
||
]
|
||
}
|
||
},
|
||
"extra": {
|
||
"attachments": ["/v0/file/s/doc001.pdf"]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 发送语音消息
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "pub-audio-1",
|
||
"topic": "usrPeer001",
|
||
"head": {
|
||
"mime": "text/x-drafty",
|
||
"attachments": ["/v0/file/s/audio001.m4a"],
|
||
"x-im-type": "audio"
|
||
},
|
||
"content": {
|
||
"txt": " ",
|
||
"fmt": [{"at": -1, "len": 1, "key": 0}],
|
||
"ent": [
|
||
{
|
||
"tp": "AU",
|
||
"data": {
|
||
"mime": "audio/mp4",
|
||
"ref": "/v0/file/s/audio001.m4a",
|
||
"duration": 6000,
|
||
"name": "voice.m4a",
|
||
"size": 48000
|
||
}
|
||
}
|
||
]
|
||
}
|
||
},
|
||
"extra": {
|
||
"attachments": ["/v0/file/s/audio001.m4a"]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 群文件列表
|
||
|
||
群文件是群消息附件的业务视图。二期 MySQL 后端已建立 `im_message_index`,带 `x-im-filter` 或 `x-im-search` 的 `{get data}` 会优先走索引查询;其它未实现索引的数据库后端仍回退到一阶段内存过滤。
|
||
|
||
扩展查询:
|
||
|
||
```json
|
||
{
|
||
"get": {
|
||
"id": "group-files-1",
|
||
"topic": "grpNew001",
|
||
"what": "data",
|
||
"data": {
|
||
"limit": 50
|
||
},
|
||
"x-im-filter": {
|
||
"types": ["file"],
|
||
"keyword": "复习"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 收藏消息
|
||
|
||
原型页面:我的收藏、收藏文本、收藏文件、收藏语音、收藏链接。
|
||
|
||
建议收藏使用 `slf` topic 存储,`head.x-im-type="favorite"`,原始消息定位放在内容里。
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "fav-1",
|
||
"topic": "slf",
|
||
"head": {
|
||
"mime": "application/json",
|
||
"x-im-type": "favorite"
|
||
},
|
||
"content": {
|
||
"sourceTopic": "grpNew001",
|
||
"sourceSeq": 19,
|
||
"kind": "file",
|
||
"title": "365天天高清晰记忆曲线复习_pdf",
|
||
"summary": "PDF 115KB",
|
||
"collectedAt": "2026-07-31T08:00:00.000Z"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
获取收藏:
|
||
|
||
```json
|
||
{
|
||
"sub": {
|
||
"id": "fav-list-1",
|
||
"topic": "slf",
|
||
"get": {
|
||
"what": "data",
|
||
"data": {"limit": 50}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 查找聊天记录
|
||
|
||
原型页面:查看聊天记录-全部、图片、视频、文件、链接、搜索关键词。
|
||
|
||
Tinode 原生 `{get what="data"}` 支持按 `seq` 分页,不直接支持关键词搜索。关键词、类型搜索需要后端扩展索引。
|
||
二期以后 MySQL 后端会优先查询 `im_message_index`;当前 schema `119` 对 `search_text` 增加 `FULLTEXT` 索引,同时保留 `LIKE` fallback,避免中文短词或未命中全文分词时漏查。
|
||
|
||
```json
|
||
{
|
||
"get": {
|
||
"id": "search-msg-1",
|
||
"topic": "usrPeer001",
|
||
"what": "data",
|
||
"data": {"limit": 50},
|
||
"x-im-search": {
|
||
"keyword": "装修",
|
||
"types": ["text", "image", "file", "link"],
|
||
"before": 0
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 语音、视频与通话
|
||
|
||
### 发起语音通话
|
||
|
||
原型页面:语音通话-对方待接受状态、双方语音沟通中。
|
||
|
||
Tinode 原生通话流程为:先用 `{pub}` 发送 `head.webrtc="started"` 的通话邀请并持久化一条通话消息;后续信令用 `{note what="call"}`。本产品在通话消息内容与 `note.payload` 中约定 `callId`、`callType`。
|
||
|
||
后端校验规则:显式发送 `head.x-im-type="call"` 且 `head.webrtc="started"` 的产品通话邀请,`content.callId` 必填,否则返回 `400 malformed`。`ringing`、`accept`、`hang-up` 控制事件的 `payload.callId` 必填;`offer`、`answer`、`ice-candidate` 仍按 Tinode 原生 WebRTC 信令透传,避免破坏 SDP/ICE 结构。
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "call-audio-1",
|
||
"topic": "usrPeer001",
|
||
"head": {
|
||
"mime": "application/json",
|
||
"webrtc": "started",
|
||
"x-im-type": "call"
|
||
},
|
||
"content": {
|
||
"callId": "call-001",
|
||
"callType": "audio",
|
||
"audio": true,
|
||
"video": false
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 发起视频通话
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "call-video-1",
|
||
"topic": "usrPeer001",
|
||
"head": {
|
||
"mime": "application/json",
|
||
"webrtc": "started",
|
||
"x-im-type": "call"
|
||
},
|
||
"content": {
|
||
"callId": "call-002",
|
||
"callType": "video",
|
||
"audio": true,
|
||
"video": true,
|
||
"cameraOn": true
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 接受、拒绝、挂断
|
||
|
||
接受:
|
||
|
||
```json
|
||
{
|
||
"note": {
|
||
"topic": "usrPeer001",
|
||
"what": "call",
|
||
"event": "accept",
|
||
"seq": 102,
|
||
"payload": {
|
||
"callId": "call-002"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
拒绝或挂断使用 Tinode 原生 `hang-up` 事件;通话尚未接通时服务端会把最终消息标记为 declined/missed。
|
||
|
||
```json
|
||
{
|
||
"note": {
|
||
"topic": "usrPeer001",
|
||
"what": "call",
|
||
"event": "hang-up",
|
||
"seq": 102,
|
||
"payload": {
|
||
"callId": "call-002",
|
||
"reason": "user_declined"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
挂断:
|
||
|
||
```json
|
||
{
|
||
"note": {
|
||
"topic": "usrPeer001",
|
||
"what": "call",
|
||
"event": "hang-up",
|
||
"seq": 102,
|
||
"payload": {
|
||
"callId": "call-002",
|
||
"duration": 65000
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 通话状态消息
|
||
|
||
通话结束后发送一条持久消息用于聊天记录展示。
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "call-record-1",
|
||
"topic": "usrPeer001",
|
||
"head": {
|
||
"mime": "application/json",
|
||
"webrtc": "finished",
|
||
"webrtc-duration": 65000,
|
||
"x-im-type": "call"
|
||
},
|
||
"content": {
|
||
"callId": "call-002",
|
||
"callType": "video",
|
||
"status": "finished",
|
||
"duration": 65000
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### SDP 与 ICE 信令
|
||
|
||
通话被接受后,WebRTC 元数据使用 Tinode 原生 `offer`、`answer`、`ice-candidate` 事件转发。麦克风、扬声器、摄像头开关属于本地 UI 状态;如后续需要跨端同步,应在 `server/calls.go` 明确扩展事件白名单。
|
||
|
||
```json
|
||
{
|
||
"note": {
|
||
"topic": "usrPeer001",
|
||
"what": "call",
|
||
"event": "ice-candidate",
|
||
"seq": 102,
|
||
"payload": {
|
||
"callId": "call-002",
|
||
"candidate": "candidate:..."
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 话题功能
|
||
|
||
话题不是 Tinode 原生 topic 的“群聊 topic”,而是产品信息流/活动报名类业务。建议实现为频道 `chn`,每个话题详情页对应一个频道 topic;话题列表由 `fnd` 搜索或专用系统频道承载。
|
||
|
||
### 发布话题
|
||
|
||
原型页面:发布话题、话题类型选择、话题人数、积分不足。
|
||
|
||
二期 MySQL 后端已接入话题发布积分扣减:创建 `nch` 频道时,如果 `public.x-im-topic.pointsCost` 大于 `0`,服务端会在同一事务中创建话题、创建作者订阅并扣减 `im_user_points.balance`。余额不足返回 `422 policy violation`,话题不会创建;普通群聊 `{sub topic="new"}` 不受该规则影响。
|
||
|
||
```json
|
||
{
|
||
"sub": {
|
||
"id": "topic-create-1",
|
||
"topic": "nch",
|
||
"set": {
|
||
"desc": {
|
||
"defacs": {"auth": "R", "anon": "N"},
|
||
"public": {
|
||
"fn": "国防办举行高质量完成十四五规划系列主题新闻发布会",
|
||
"photo": {"ref": "/v0/file/s/topic-cover.jpg", "type": "image/jpeg"},
|
||
"note": "话题摘要内容",
|
||
"x-im-topic": {
|
||
"type": "news",
|
||
"labels": ["美食", "天气"],
|
||
"durationHours": 3,
|
||
"participantLimit": 20,
|
||
"pointsCost": 234,
|
||
"joinEnabled": true
|
||
}
|
||
}
|
||
},
|
||
"tags": ["topic", "topic:news", "美食", "天气"],
|
||
"aux": {
|
||
"x-im-topic": {
|
||
"participants": [],
|
||
"status": "open"
|
||
}
|
||
}
|
||
},
|
||
"get": {"what": "desc aux"}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 获取话题列表
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "topic-search-1",
|
||
"topic": "fnd",
|
||
"desc": {
|
||
"public": "topic news"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
```json
|
||
{"get":{"id":"topic-search-2","topic":"fnd","what":"sub","sub":{"limit":20}}}
|
||
```
|
||
|
||
### 推荐话题列表
|
||
|
||
推荐排序仍复用 Tinode `fnd` topic,通过 `{get what="sub"}` 附带 `x-im-recommend` 传入推荐条件。MySQL 后端当前按关键词、话题类型、标签与订阅数做轻量排序;频道型话题会按 Tinode 规范以 `chnXXX` 返回。
|
||
|
||
```json
|
||
{
|
||
"get": {
|
||
"id": "topic-recommend-1",
|
||
"topic": "fnd",
|
||
"what": "sub",
|
||
"sub": {"limit": 20},
|
||
"x-im-recommend": {
|
||
"keyword": "发布会",
|
||
"types": ["news"],
|
||
"labels": ["美食"],
|
||
"limit": 10
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
响应示例:
|
||
|
||
```json
|
||
{
|
||
"meta": {
|
||
"id": "topic-recommend-1",
|
||
"topic": "fnd",
|
||
"sub": [
|
||
{
|
||
"topic": "chnTopic001",
|
||
"public": {
|
||
"fn": "本周美食发布会",
|
||
"x-im-topic": {
|
||
"type": "news",
|
||
"labels": ["美食", "活动"]
|
||
}
|
||
},
|
||
"private": ["topic", "topic:news", "美食"],
|
||
"subcnt": 128
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 参与话题
|
||
|
||
话题详情页点击“参与话题”时订阅频道。
|
||
|
||
```json
|
||
{
|
||
"sub": {
|
||
"id": "topic-join-1",
|
||
"topic": "chnTopic001",
|
||
"set": {
|
||
"sub": {"mode": "R"}
|
||
},
|
||
"get": {"what": "desc data aux"}
|
||
}
|
||
}
|
||
```
|
||
|
||
若要发送评论,需要后端根据业务授予 `W` 权限。
|
||
|
||
### 转发帖子
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "topic-forward-1",
|
||
"topic": "usrPeer001",
|
||
"head": {
|
||
"mime": "application/json",
|
||
"x-im-type": "topic-share"
|
||
},
|
||
"content": {
|
||
"topic": "chnTopic001",
|
||
"title": "国防办举行高质量完成十四五规划系列主题新闻发布会",
|
||
"cover": "/v0/file/s/topic-cover.jpg",
|
||
"summary": "话题摘要内容"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 系统消息、举报与安全提示
|
||
|
||
### 系统消息
|
||
|
||
系统消息建议使用 `sys` topic 或专用系统账号向用户 P2P 发送。客户端消息列表可将系统账号展示为“系统消息”。
|
||
|
||
```json
|
||
{
|
||
"data": {
|
||
"topic": "usrSystem001",
|
||
"from": "usrSystem001",
|
||
"seq": 1,
|
||
"head": {
|
||
"mime": "application/json",
|
||
"x-im-type": "system-notice"
|
||
},
|
||
"content": {
|
||
"title": "平台整治净化治理公告",
|
||
"summary": "国防办举行高质量完成十四五规划系列...",
|
||
"url": "/notice/20260731"
|
||
},
|
||
"ts": "2026-07-31T08:00:00.000Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
### 举报
|
||
|
||
原型页面:举报、举报原因列表。
|
||
|
||
普通用户可向 `sys` 发布举报内容。
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "report-1",
|
||
"topic": "sys",
|
||
"head": {
|
||
"mime": "application/json",
|
||
"x-im-type": "report"
|
||
},
|
||
"content": {
|
||
"targetType": "message",
|
||
"targetUser": "usrBad001",
|
||
"targetTopic": "grpNew001",
|
||
"targetSeq": 19,
|
||
"reason": "发布违法违规内容",
|
||
"description": "群成员发布诈骗信息",
|
||
"evidence": ["/v0/file/s/evidence001.jpg"]
|
||
}
|
||
},
|
||
"extra": {
|
||
"attachments": ["/v0/file/s/evidence001.jpg"]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 反诈与安全提醒
|
||
|
||
反诈弹窗可由服务端在消息 `head` 上标记,前端收到后根据策略展示一次或二次确认。当前后端已实现轻量规则 MVP:疑似转账、验证码、外链、违禁词等命中后不阻断消息,只追加风险和审核标记,便于前端提示与后台后续处理。
|
||
|
||
```json
|
||
{
|
||
"data": {
|
||
"topic": "usrPeer001",
|
||
"from": "usrPeer001",
|
||
"seq": 20,
|
||
"head": {
|
||
"mime": "text/x-drafty",
|
||
"x-im-type": "text",
|
||
"x-im-anti-fraud": true,
|
||
"x-im-risk-level": "high",
|
||
"x-im-moderation": {
|
||
"status": "flagged",
|
||
"reasons": ["payment-scam", "credential-risk", "external-link"],
|
||
"reviewedAt": "2026-07-31T08:00:00.000Z"
|
||
}
|
||
},
|
||
"content": {"txt": "请点击链接完成转账"},
|
||
"ts": "2026-07-31T08:00:00.000Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
客户端确认提醒后可发送临时回执:
|
||
|
||
```json
|
||
{
|
||
"note": {
|
||
"topic": "usrPeer001",
|
||
"what": "data",
|
||
"payload": {
|
||
"x-im-event": "anti-fraud-confirmed",
|
||
"seq": 20
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 个人中心与设置
|
||
|
||
### 获取个人资料
|
||
|
||
```json
|
||
{"get":{"id":"profile-1","topic":"me","what":"desc tags cred"}}
|
||
```
|
||
|
||
### 修改昵称、头像、生日、地区、个性签名
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "profile-update-1",
|
||
"topic": "me",
|
||
"desc": {
|
||
"public": {
|
||
"fn": "林南",
|
||
"photo": {"ref": "/v0/file/s/avatar.jpg", "type": "image/jpeg"},
|
||
"note": "成人最懂什么都没留下",
|
||
"x-im-gender": "female",
|
||
"x-im-birthday": "2008-02-07",
|
||
"x-im-area": "广东省广州市"
|
||
}
|
||
}
|
||
},
|
||
"extra": {
|
||
"attachments": ["/v0/file/s/avatar.jpg"]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 置顶、免打扰、清空聊天记录
|
||
|
||
置顶写入 `me.private.tpins` 或当前 topic `private.x-im-pinned`。
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "pin-1",
|
||
"topic": "usrPeer001",
|
||
"desc": {
|
||
"private": {
|
||
"x-im-pinned": true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
免打扰:
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "mute-topic-1",
|
||
"topic": "usrPeer001",
|
||
"desc": {
|
||
"private": {
|
||
"x-im-muted": true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
清空聊天记录:
|
||
|
||
```json
|
||
{
|
||
"del": {
|
||
"id": "clear-local-1",
|
||
"topic": "usrPeer001",
|
||
"what": "msg",
|
||
"hard": false,
|
||
"delseq": [{"low": 1, "hi": 2147483647}]
|
||
}
|
||
}
|
||
```
|
||
|
||
### 隐私设置
|
||
|
||
原型页面:隐私设置-添加我的方式选项。
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "privacy-1",
|
||
"topic": "me",
|
||
"desc": {
|
||
"private": {
|
||
"x-im-settings": {
|
||
"allowSearchByCard": true,
|
||
"allowSearchByQr": true,
|
||
"allowSearchByGroup": true,
|
||
"allowSearchByPhone": false,
|
||
"joinGroupNeedsApproval": true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 青少年模式
|
||
|
||
```json
|
||
{
|
||
"set": {
|
||
"id": "teen-1",
|
||
"topic": "me",
|
||
"desc": {
|
||
"private": {
|
||
"x-im-settings": {
|
||
"teenMode": true,
|
||
"teenModeEnabledAt": "2026-07-31T08:00:00.000Z"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 签到任务
|
||
|
||
签到属于业务扩展,使用 `slf` topic 记录用户行为,同时后端更新积分系统。
|
||
|
||
二期 MySQL 后端已接入 `head.x-im-type="checkin"` 的 `{pub}` 处理:同一用户、同一自然日、同一 `event_type` 只记录一次,首次签到成功后固定增加 `5` 积分并保存一条 `slf` 消息;重复签到返回 `304 no action`,不重复加分,也不保存重复消息。前后端通信仍保持 WebSocket `{pub}` 形态。
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "checkin-1",
|
||
"topic": "slf",
|
||
"head": {
|
||
"mime": "application/json",
|
||
"x-im-type": "checkin"
|
||
},
|
||
"content": {
|
||
"date": "2026-07-31",
|
||
"points": 5
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
重复签到响应示例:
|
||
|
||
```json
|
||
{
|
||
"ctrl": {
|
||
"id": "checkin-1",
|
||
"topic": "slf",
|
||
"code": 304,
|
||
"text": "no action",
|
||
"params": {
|
||
"x-im-checkin": {
|
||
"already": true,
|
||
"date": "2026-07-31",
|
||
"points": 5,
|
||
"balance": 35
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
### 意见反馈
|
||
|
||
```json
|
||
{
|
||
"pub": {
|
||
"id": "feedback-1",
|
||
"topic": "sys",
|
||
"head": {
|
||
"mime": "application/json",
|
||
"x-im-type": "feedback"
|
||
},
|
||
"content": {
|
||
"type": "功能建议",
|
||
"text": "希望优化消息搜索",
|
||
"images": ["/v0/file/s/feedback001.jpg"]
|
||
}
|
||
},
|
||
"extra": {
|
||
"attachments": ["/v0/file/s/feedback001.jpg"]
|
||
}
|
||
}
|
||
```
|
||
|
||
## 错误码与状态码
|
||
|
||
Tinode `{ctrl.code}` 遵循 HTTP 状态码模型。
|
||
|
||
| code | 含义 | 前端处理 |
|
||
|---|---|---|
|
||
| `200` | 成功 | 更新本地状态 |
|
||
| `201` | 已创建 | 使用返回的 `topic` 或 `params` |
|
||
| `300` | 需要额外验证 | 展示验证码/身份验证页面 |
|
||
| `400` | 请求格式错误 | 检查 JSON 和字段 |
|
||
| `401` | 未登录或 Token 失效 | 重新登录 |
|
||
| `403` | 无权限 | 展示“无权限”或禁言/拉黑提示 |
|
||
| `404` | 资源不存在 | 提示用户不存在、群不存在 |
|
||
| `409` | 状态冲突 | 已申请、已加入、重复操作 |
|
||
| `413` | 消息或文件过大 | 提示压缩或重新选择 |
|
||
| `423` | Topic 锁定/处理中 | 稍后重试 |
|
||
| `500` | 服务端错误 | 展示通用错误并上报 |
|
||
| `503` | 队列满或服务不可用 | 自动重试或稍后重试 |
|
||
|
||
### 业务状态枚举
|
||
|
||
| 字段 | 值 | 含义 |
|
||
|---|---|---|
|
||
| `x-im-type` | `text` | 文本 |
|
||
| `x-im-type` | `image` | 图片 |
|
||
| `x-im-type` | `audio` | 语音 |
|
||
| `x-im-type` | `video` | 视频 |
|
||
| `x-im-type` | `file` | 文件 |
|
||
| `x-im-type` | `card` | 名片 |
|
||
| `x-im-type` | `favorite` | 收藏 |
|
||
| `x-im-type` | `forward-bundle` | 合并转发 |
|
||
| `x-im-type` | `group-join-apply` | 加群申请 |
|
||
| `x-im-type` | `group-join-approve` | 加群审批通过 |
|
||
| `x-im-type` | `group-join-reject` | 加群审批拒绝 |
|
||
| `x-im-type` | `group-announcement` | 群公告 |
|
||
| `x-im-type` | `call` | 通话记录 |
|
||
| `x-im-type` | `topic-share` | 话题分享 |
|
||
| `x-im-type` | `system-notice` | 系统通知 |
|
||
| `x-im-type` | `report` | 举报 |
|
||
| `x-im-type` | `feedback` | 意见反馈 |
|
||
| `x-im-type` | `checkin` | 签到 |
|
||
|
||
### 前端发送状态建议
|
||
|
||
| UI 状态 | 判定方式 |
|
||
|---|---|
|
||
| 发送中 | 本地消息已入队,尚未收到 `{ctrl}` 或 `{data}` echo |
|
||
| 发送成功 | 收到同 `id` 的 `{ctrl.code=200}` 或同 `x-im-client-mid` 的 `{data}` |
|
||
| 发送失败 | 收到 `{ctrl.code>=400}` 或超时 |
|
||
| 已送达 | 收到对端 `{info.what="recv"}` |
|
||
| 已读 | 收到对端 `{info.what="read"}` |
|
||
| 已撤回 | 收到 `{pres.what="del"}` 或 `{meta.del}` 包含对应 `seq` |
|
||
|
||
## 后端实现提示
|
||
|
||
1. 原生 Tinode 已支持的字段优先复用:`public`、`private`、`aux`、`head.mentions`、`head.reply`、`head.replace`、`head.forwarded`、`extra.attachments`。
|
||
2. 业务扩展统一从 `x-im-*` 读取和写入,避免污染 Tinode 原生字段。
|
||
3. 需要服务端索引的功能包括:聊天记录关键词搜索、按附件类型搜索、收藏分类、话题列表、积分扣减、反诈风险标记。
|
||
4. 所有 WebSocket 消息必须是严格 JSON,示例中的字段名和字符串在真实请求中都需要双引号。
|
||
5. 文件上传仍使用 Tinode 大文件接口;上传后的文件引用必须写入 `extra.attachments`,否则服务端 GC 可能清理未引用文件。
|
||
6. Phase 3 当前为后端内置 MVP:反诈和内容审核只做规则标记,推荐只做 MySQL 轻量排序,复杂模型、人工审核工作台、异步搜索服务仍可后续独立演进。
|