Go SDK 消息
本页介绍 Go SDK 中消息的发送与接收、撤回、删除、表情回应、置顶消息、已读与“正在输入”。会话列表与未读数见会话,文件上传的细节见文件与图片,事件与 Store 的运作见事件与错误处理。
发送目标统一用 Target 描述,用 im.ToUser / im.ToGroup 构造,恰选其一:
im.ToUser("bob") // 发给用户 bob
im.ToGroup(groupID) // 发到一个群下文的 c 指初始化、登录与连接后得到的客户端,ctx 为 context.Context。
发送消息
| 方法 | 用途 | 关键参数 |
|---|---|---|
SendText | 发送文本消息 | text 正文;ext 扩展字段 |
Send | 发送任意类型的消息 | msgType 类型;body 完整消息体 JSON;onlineOnly 只推在线 |
SendMedia | 发送媒体消息 | msgType 为 image / voice / video / file;info 为上传返回的 *FileInfo |
SendReply | 引用回复 | replySeq 被引用消息的 seq |
SendWithMentions | 发送带 @ 提醒的消息 | usernames 被 @ 的人;all @ 全体;两者恰选其一 |
全部返回 (*im.Message, error)。类型与消息体结构见下文消息类型。
发送文本
m, err := c.SendText(ctx, im.ToUser("bob"), "你好", nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(m.ConversationID, m.Seq)ext是你自定义的扩展字段(map[string]any),原样保存、原样返回,适合放业务 ID 之类的数据;不需要时传nil。- 没有会话也能发:给一个用户发出第一条消息时,服务端自动建立单聊会话。
发送自定义消息
Send 的 body 是完整的消息体 JSON([]byte),按 msgType 解释。SendText 就等价于以 "text" 类型和 {"text": ...} 消息体调用它:
body, _ := json.Marshal(map[string]any{"card": "order", "order_id": "8812"})
m, err := c.Send(ctx, im.ToGroup(groupID), "custom", body, nil, false)msgType为空串时按text处理;body为nil时发送空对象。clientMsgID由 SDK 自动生成(随机十六进制串),发送方法不接受自定义值,重复调用由服务端按它去重。- 最后一个参数
onlineOnly为true时消息不落库、只推给在线设备(仅custom可用)。
onlineOnly 与长连接
长连接在线时消息优先经 WS 发送,此时不携带 online_only 标记;只有回退到 REST 发送时它才会生效。需要确保“只推在线”的实时信令,请确认发送时长连接可用,或自行改用其他通道。
@ 提及
群消息可以 @ 成员或全体成员。正文中的“@张三”由你自己拼进消息体,SDK 只提交 mentions(usernames 与 all 恰选其一):
// @ 指定成员
m, err := c.SendWithMentions(ctx, im.ToGroup(groupID), "text",
[]byte(`{"text":"@李四 请确认"}`),
[]string{"lisi"}, false, nil)
// @ 全体成员
c.SendWithMentions(ctx, im.ToGroup(groupID), "text",
[]byte(`{"text":"@所有人 明天放假"}`),
nil, true, nil)被 @ 的人在会话状态的 MentionSeq 中看到“有人 @ 我”,见会话。
引用回复
// 回复该群第 120 条消息
m, err := c.SendReply(ctx, im.ToGroup(groupID), 120, "text", []byte(`{"text":"同意"}`), nil)发出后消息的 ReplyTo 为 {seq, sender};接收方按 seq 在会话中定位被引用的消息,不在本地时用 MessageLookup 取回,见下文。
发送路径与本地兜底
- 长连接在线时优先走 WS(
message.send),延迟低;连接刚好断开(unavailable、timeout)时自动回退 REST,业务错误则直接返回。 - 发送成功返回服务端的消息(带
Seq、MessageID、CreatedAt),并写入 Store。 - 服务端对发送者本人不回显
message.received,新会话也不一定向发送者推会话事件;SDK 在发送成功后于本地兜底建立/推进会话状态,保证你自己发出的消息出现在会话列表里、你自己的已读回执有处落地。因此不要在收到message.received时才把消息加进界面——发送方法的返回值就是这条消息。
接收消息
别人发来的消息通过 message.received 事件推送。事件先应用到 Store、再回调你的处理函数:
c.OnEvent(func(e im.Event) {
if e.Name != im.EventMessageReceived {
return
}
var m im.Message
if err := json.Unmarshal(e.Data, &m); err != nil {
return
}
if m.Type == "text" {
fmt.Println(m.Sender, m.TextBody())
}
})- 通常你不需要自己维护消息列表:回调里直接读 Store(
c.Store().Messages(conversationID),seq 升序),事件到达后重建界面即可。 - 乱序到达时 SDK 先收下、再异步补齐缺口;
cleared_seq变大时自动清除本地不超过它的消息。规则详见事件与错误处理的 Store 小节。 - 撤回、编辑、表情回应、置顶变化通过
message.changed实时更新到 Store 中的消息上。
消息管理
| 方法 | 用途 | 关键参数 |
|---|---|---|
Recall | 撤回消息 | conversationID、seq;时限内、有权限才成功 |
DeleteMessages | 删除消息(本人视角) | seqs:要删除的序号列表 |
AddReaction / RemoveReaction | 添加 / 取消表情回应 | seq、key:表情标识 |
PinMessage / UnpinMessage | 置顶 / 取消置顶消息 | conversationID、seq |
MessageLookup | 按 seq 批量取消息 | seqs:一次最多 100 个 |
Read | 标记会话已读 | seq:读到哪条,0 表示推进到最新 |
Typing | 发送“正在输入” | conversationID |
已读与正在输入
// 用户正在看这个会话:已读位置推进到最新一条
_ = c.Read(ctx, convID, 0)
// 只标记到某一条(如用户只看到了第 120 条)
_ = c.Read(ctx, convID, 120)
// 输入框每次变化时报告;长连接未连上时静默忽略
c.Typing(ctx, convID)Read同样 WS 优先、REST 回退,成功后同步推进 Store 中该会话的ReadSeq与UnreadCount。- 单聊中对方的已读位置在
ConversationState.PeerReadSeq,由message.peer_read事件实时更新,见会话的“已读回执”。 - 收到的“正在输入”经
message.typing事件进入 Store,c.Store().Typing(convID)返回 10 秒内最近的一次,超时自动失效。
表情回应
convID := m.ConversationID
seq := m.Seq
_ = c.AddReaction(ctx, convID, seq, "thumbs_up")
_ = c.RemoveReaction(ctx, convID, seq, "thumbs_up")- 每种表情的聚合(人数、前 3 个回应者、本人是否回应过)在消息的
Reactions中,别人加减回应时经message.changed实时更新:
for _, r := range m.Reactions {
fmt.Println(r.Key, r.Count, r.Users, r.Reacted)
}- 回应某种表情的全部人用
ReactionUsers翻页获取,见会话的“回应名单”。 - 表情的
key由客户端定义(如thumbs_up、heart),各端保持一致。
撤回、删除、置顶与查找
_ = c.Recall(ctx, convID, 120) // 撤回
_ = c.DeleteMessages(ctx, convID, []int64{101, 102}) // 只删本人视角
_ = c.PinMessage(ctx, convID, 120) // 置顶,UnpinMessage 取消
msgs, _ := c.MessageLookup(ctx, convID, []int64{3, 57}) // 按 seq 取回- 撤回后双方的消息都变为已撤回:
Recalled为{by, role, at},正文与扩展字段被清空,经message.changed更新。 DeleteMessages只影响本人,其他设备由message.deleted事件同步删除。- 引用、置顶、通知里提到的消息不在本地时,用
MessageLookup从服务端取回,结果同时写入 Store。
消息类型
Message.Body(json.RawMessage)按 Type 解释:
| 类型 | body 结构 | 读取方式 |
|---|---|---|
text | {"text": "..."} | m.TextBody(),非文本消息返回空串 |
image | 上传返回的 FileInfo JSON:url、thumbnail_url、width、height 等 | json.Unmarshal(m.Body, &info) 到 im.FileInfo |
voice | FileInfo,另有 duration_ms | 同上 |
video | FileInfo | 同上 |
file | FileInfo | 同上 |
custom / tip | 自定义 JSON 对象 | m.BodyMap(),解析失败返回 nil |
图片、语音、视频、文件消息的 url 不能直接请求,要先换取下载直链或用 Download,见文件与图片。
数据结构
Message
| 字段 | 类型 | 说明 |
|---|---|---|
ConversationID | string | 会话 ID |
ConversationType | string | single 或 group |
Seq | int64 | 在会话中的序号 |
MessageID | string | 全应用唯一的消息 ID |
ClientMsgID | string | 发送方生成的去重 ID |
Sender | string | 发送者用户名 |
SenderType | string | user 或 system |
Recipient | string | 单聊的接收者 |
Type | string | 消息类型,见上表 |
Body | json.RawMessage | 消息体,按类型解释 |
Ext | map[string]any | 发送时的扩展字段 |
Mentions | *Mentions | @ 的人:Usernames、All |
ReplyTo | *ReplyTo | 引用的消息:Seq、Sender |
Reactions | []Reaction | 表情回应聚合:Key、Count、Users、Reacted |
Pinned | *Pinned | 置顶信息:By、At |
Edited | *Edited | 编辑信息:At、Count |
Recalled | *Recalled | 撤回信息:By、Role、At |
Erased | bool | 内容是否已被擦除 |
Via | string | 写入方式:client、openapi、console、system |
CreatedAt | string | 服务端的发送时间(RFC3339) |
MessageVersion | int64 | 变更版本号,判断新旧用 |
辅助方法:TextBody() 提取文本正文;BodyMap() 把 body 解析为通用对象。错误对象 *im.APIError 与 im.IsCode 的用法见事件与错误处理。
