用户端要怎么接 morsaim-im
这份文档给接入方看:你的业务后端 + 你的 App。IM 是独立服务,多个 App 共用,数据按 app_id 隔离。
当前服务地址:
从这里开始
- 在控制台用引导密钥建一个 App,抄走 app_secret(只亮一次)。
- 业务后端用 AppSecret 导入用户、签发 User Token。AppSecret 不要进客户端。
- 客户端下载对应 SDK,用 Token 连 WebSocket,收发消息。
三把钥匙
| 钥匙 | 谁拿 | 干什么 |
|---|---|---|
| Bootstrap Secret | 平台运维 | 进控制台、建/停 App。不要给接入方用户端。 |
| AppSecret | 你的业务后端 | 调 REST:导入用户、发 Token、代发消息、管好友/群。 |
| User Token | 终端 SDK | 连 /v1/ws。默认 90 天。 |
接入步骤
1. 建 App
控制台「应用 → 新建」,或后端:
响应里的 app_secret / webhook_secret 只返回这一次,立刻存进你的密钥系统。
2. 导入用户
user_id 由你定义,不能包含英文冒号 :。先导入再连线。
3. 签发 User Token
把 token 交给客户端。过期后由你的后端再签一张。
4. 客户端连上
SDK 会把 https 换成 wss,连:
连上后 SDK 会自动 list_conv,再按每个会话已见的 max_seq + 1 做 sync。发送必须带 client_msg_id(SDK 会自己生成),以服务端 ack 为准——ack 表示已经落库、有序号,不是“进了队列”。
下载 SDK
下面是当前服务打进镜像的源码包。每个 zip 里都有 Cursor Skill:解压进工程后打开对话,让 AI 读 AGENTS.md 和 .cursor/skills/morsaim-im/SKILL.md,按清单接入。不要用 OpenIM SDK。
Swift / iOS
安装
- 下载并解压 morsaim-im-swift.zip,放到你的工程旁,例如 Vendor/MorsaimIM。
- Xcode → File → Add Package Dependencies → Add Local → 选这个目录(里面有 Package.swift)。
- 把产品 MorsaimIM 加到 App target。
也可用 SPM 路径:
.package(path: "Vendor/MorsaimIM")
使用
- connect 成功时已经做完会话列表和补拉。
- onPush 同时覆盖实时推送和补拉到的历史;同一 seq 不会回调两次。
- 默认断线会重连。被踢(onKick)或你调用 close() 不会重连。
- 自己持久化 max_seq:实现 IMStore,把 MemoryStore() 换成你的实现,同一实例在重连时传入。
Android / Kotlin
安装
- 解压 morsaim-im-android.zip 到例如 sdk/morsaim-im。
- 在 settings.gradle.kts 里 include(":morsaim-im"),project(":morsaim-im").projectDir 指到该目录。
- App 模块:implementation(project(":morsaim-im"))。
- 网络权限:android.permission.INTERNET。
依赖已写在包内:OkHttp 4.12、kotlinx-coroutines。Android 工程用 JDK 17+ 即可。
使用
connect / send / syncAll 都是 suspend,在 viewModelScope 或 lifecycleScope 里调。回调默认在 WebSocket 线程,更新 UI 请切回主线程。
Web / TypeScript
安装
- 解压 morsaim-im-web.zip。
- 在该目录执行 npm install && npm run build。
- 业务项目:npm install /绝对路径/morsaim-im-web,或把 src/ 拷进工程直接 import。
使用
浏览器用原生 WebSocket。Node 18+ 同样可以。页面关闭前调用 client.close()。
Go
模块路径 github.com/morsaim-h/morsaim-im/sdk。也可以下载 zip,把 client.go / types.go / store.go 放进你的 module(不要 import 我们的 internal/)。
长连接协议
帧是 JSON 文本,字段是我们的,不是 OpenIM 的。
{
"id": "r_ab12…",
"op": "send | ack | push | sync | sync_result | list_conv | list_result | ping | pong | error | kick",
"payload": {}
}
| 方向 | op | 含义 |
|---|---|---|
| C→S | ping | 心跳。默认 20s 一次,90s 无 ping 服务端断开。 |
| C→S | send | 发消息。payload:client_msg_id, recv_id 或 group_id, type, body |
| S→C | ack | 已落库。带 server_msg_id, conversation_id, seq |
| S→C | push | 推给在线接收方(发送方自己的 ack 另外走) |
| C→S | list_conv | 会话列表 |
| C→S | sync | 按 conversation_id + start_seq 补拉 |
| S→C | kick | 被踢下线,SDK 触发 onKick,不要自动重连 |
会话 ID
- 单聊:p2p:{app_id}:{min_uid}:{max_uid}(两个 user_id 字符串升序)
- 群:grp:{app_id}:{group_id}
幂等
同一 (app_id, sender_id, client_msg_id) 只成一条。重试发送会得到同一条 ack,不会再 push 一遍。
REST 一览(AppSecret)
请求头 Authorization: Bearer {app_secret}。控制台的 Bootstrap 接口另算,客户端不要调。
| 方法 | 路径 | 用途 |
|---|---|---|
| PUT | /v1/users/{user_id} | 导入/更新用户 |
| GET | /v1/users/{user_id} | 查用户 |
| POST | /v1/auth/user-tokens | 签发 User Token。body: {"user_id":"..."} |
| POST | /v1/messages | 后端代发。ack 语义同 WS |
| GET | /v1/users/{id}/conversations | 会话列表 |
| GET | /v1/conversations/{id}/messages | 补拉历史 |
| POST | /v1/conversations/{id}/read | 已读 |
| POST | /v1/friends/applications | 申请好友 |
| POST | /v1/groups | 建群 |
建 App 时可配 webhook_url、push_url。Webhook 签名 HMAC-SHA256(timestamp.body)。接收方当时不在线才会 POST push.offline,失败不影响 ack。
注意
- user_id 不能含 :。
- 任一侧拉黑,单聊双向 403。
- App 打开 friend_required 时,单聊必须先是好友。
- 群消息读扩散:库里一份消息,每个成员一条 inbox。群成员上限 200。
- 不要依赖 OpenIM 的 Login / GetConversationList 等同名 API,包名和类型都是我们的。