Go SDK 会话
会话是两个用户之间(单聊)或一个群中(群聊)的消息流。本页介绍 Go SDK 中会话的拉取与本地读取、未读数、已读回执,以及置顶、草稿、标记未读、删除和清空。会话里的消息见消息,事件订阅与 Store 的运作见事件与错误处理。
下文的 c 指初始化、登录与连接后得到的客户端:
import im "github.com/deeprespond/im/sdk/go"
c, _ := im.New(im.Config{
AppKey: "1575529652#demo", // 组织#应用
APIURL: "https://im.example.com", // IM 服务端 REST 地址
})
_, _ = c.Login(ctx, im.LoginPassword, "alice", "pass-123")会话从哪里来
你不需要“创建”会话。给一个用户发出第一条消息、在群里发出第一条消息时,服务端自动建立会话;对方先发来消息也一样。之后所有会话操作都针对服务端分配的会话 ID 进行。会话状态由服务端维护,SDK 通过登录后同步和长连接事件把它保持在内存 Store 中,你不需要自己合并。
会话的标识
一个会话在 SDK 中是 ConversationState,单聊和群聊用 ConversationType 区分:
- 单聊(
single):ConversationID是服务端生成的 ID,对端用户名在Peer中。PeerReadSeq是对方已读到的位置。 - 群聊(
group):ConversationID就是群 ID,同时出现在GroupID字段中。本人是否还在群里看Membership(member、left、removed、dismissed)。
群会话 ID 即群 ID,所以从群聊页进入会话时直接用 im.ToGroup(groupID) 发消息即可;单聊用 im.ToUser(peer),见消息。
方法一览
会话方法都在 Client 上,全部走 REST,第一个参数是 context.Context,失败时返回 *im.APIError:
| 方法 | 用途 | 关键参数 |
|---|---|---|
RecentConversations | 拉取最近会话,游标翻页 | cursor:首页传 "",之后传上页的 NextCursor;limit:每页条数,≤0 时按 100 |
Conversation | 获取单个会话状态 | id:会话 ID;结果同时写入本地 Store |
SingleConversation | 取与某用户的单聊会话 | peer:对方用户名;两人还没有会话时返回 not_found |
DeleteConversation | 删除会话 | id;服务端隐藏会话,本地缓存清空 |
ClearConversation | 清空会话历史 | id;服务端推进 ClearedSeq |
SetConversationPinned | 置顶 / 取消置顶 | id;pinned |
MarkConversationUnread | 标记 / 取消“未读” | id;marked |
SetConversationDraft | 保存会话草稿 | id;draft 草稿文本,空串清除 |
ReadAllConversations | 全部会话标记已读 | 无 |
UnreadCount | 全部会话的未读总数 | 无 |
GroupReceipts | 上报群聊已读回执 | conversationID;seqs:已读到的消息序号 |
ReactionUsers | 消息某种回应的名单 | conversationID、seq、key:表情标识 |
标记某个会话已读的方法 Read 在消息页介绍。
拉取最近会话
RecentConversations 返回 Page[ConversationState]:Items 为这一页的会话,NextCursor 非空表示还有下一页:
cursor := ""
for {
page, err := c.RecentConversations(ctx, cursor, 100)
if err != nil {
log.Fatal(err)
}
for _, st := range page.Items {
fmt.Printf("%s %s 未读 %d\n", st.ConversationID, st.ConversationType, st.UnreadCount)
}
if page.NextCursor == "" {
break
}
cursor = page.NextCursor
}用 Store 读会话列表
服务端拉取之外,更常用的做法是直接读内存 Store:长连接开启后,Store 随事件自动维护(新消息、已读、置顶、其他设备上的删除都会更新),直接重建界面即可:
store := c.Store()
for _, st := range store.Conversations() { // 按最近消息时间倒序
title := st.GroupID
if st.ConversationType == "single" {
title = st.Peer
}
// 未读数为 0 但被标记为未读时,显示一个小圆点
dot := st.UnreadCount == 0 && st.MarkedUnread
// 有 @ 本人还没读到的消息时,可以显示“[有人@我]”
mentioned := st.MentionSeq > 0
fmt.Println(title, st.UnreadCount, dot, mentioned)
}- Store 是进程内的内存缓存:进程重启后为空,先调用
SyncAll(或等 SDK 的同步流程)填充,见事件与错误处理的 Store 小节。 store.Conversation(id)读取单个会话,没有时返回nil;REST 的Conversation、SingleConversation等取到的状态也可以用store.PutConversation写进去。store.Typing(conversationID)返回 10 秒内的“正在输入”,见消息。- 界面刷新的时机由事件驱动(
message.received、message.conversation_changed等),在OnEvent回调中重新读取 Store 即可。
未读数
- 单个会话的未读数在
ConversationState.UnreadCount。Store 收到别人发来的消息时自动累加;本人发的消息和exclude_from_unread的消息不计入。 - 全部会话的未读总数:
total, err := c.UnreadCount(ctx)- 用户想稍后再处理某个会话时,把它标记为未读,不影响已读位置:
_ = c.MarkConversationUnread(ctx, convID, true)
_ = c.MarkConversationUnread(ctx, convID, false)- 一键全部已读:
_ = c.ReadAllConversations(ctx)用户正在看某个会话时,应调用 Read 标记已读,见消息的“已读与正在输入”。
已读回执
- 单聊:不需要单独查询。
ConversationState.PeerReadSeq是对方已读到的序号,本人发出的、Seq不大于它的消息都可以显示“已读”。对方标记已读时服务端推送message.peer_read事件,Store 中的会话状态随之更新。 - 群聊:群成员读到要求回执的消息后,用
GroupReceipts上报已读的序号(单聊没有这个接口)。发送方通过message.read_by事件得知谁读到了哪些消息,事件载荷见事件与错误处理。
// 用户把群会话看到第 120 条时
_ = c.GroupReceipts(ctx, groupConvID, []int64{120})置顶与草稿
// 置顶、取消置顶
_ = c.SetConversationPinned(ctx, convID, true)
_ = c.SetConversationPinned(ctx, convID, false)
// 离开会话时保存草稿,发出后清除
_ = c.SetConversationDraft(ctx, convID, "明天上午十点")
_ = c.SetConversationDraft(ctx, convID, "")- 置顶后
ConversationState.PinnedAt为置顶时间,会话列表可以把置顶的排前面。 - 草稿是一段任意文本,由服务端透传存储,在本人登录的设备间共享。Go SDK 的
ConversationState不包含草稿字段,需要恢复草稿时请在应用侧自行记录草稿与会话的对应关系。
删除与清空
// 从会话列表中删除:服务端隐藏会话,Store 中的本地缓存一并清空
_ = c.DeleteConversation(ctx, convID)
// 只清空聊天记录,会话仍在列表中:服务端推进 ClearedSeq,
// Store 中不大于它的消息随后被自动清除
_ = c.ClearConversation(ctx, convID)- 两者都只影响本人,对方和群里的其他成员不受影响。
- 删除后的会话在对方再发来消息时会随事件重新同步回来。
- 清空后本人看不到此前的消息;
StartSeq(可见范围的起点)随之变大。
回应名单
某条消息上回应了某个表情的全部用户,用 ReactionUsers 翻页获取(添加、取消回应的方法 AddReaction / RemoveReaction 见消息):
users, err := c.ReactionUsers(ctx, convID, 120, "thumbs_up")
if err != nil {
log.Fatal(err)
}
for _, u := range users.Items { // 用户名列表
fmt.Println(u)
}
if users.NextCursor != "" {
// 下一页
}单聊会话的定位
从联系人页等入口进入聊天时,用对方用户名取单聊会话:
st, err := c.SingleConversation(ctx, "bob")
if im.IsCode(err, im.CodeNotFound) {
// 两人还没有会话:直接发第一条消息即可,服务端会自动建立
}会话已存在时返回它的状态;Conversation(ctx, id) 则按会话 ID 精确获取,取到后同时写入 Store。
数据结构
ConversationState
本人在一个会话中的状态,REST 与 Store 读到的是同一结构。
| 字段 | 类型 | 说明 |
|---|---|---|
ConversationID | string | 会话 ID;群会话的 ID 就是群 ID |
ConversationType | string | single 或 group |
Peer | string | 单聊的对方用户名,群聊为空 |
GroupID | string | 群聊的群 ID,单聊为空 |
Membership | *string | 群聊中本人的身份:member、left、removed、dismissed;单聊为 nil |
StartSeq | int64 | 本人能看到的第一条消息的序号(入群、清空都会让它变大) |
MaxSeq | int64 | 本人能看到的最后一条消息的序号 |
ReadSeq | int64 | 本人的已读位置 |
UnreadCount | int64 | 未读消息数 |
MarkedUnread | bool | 是否被标记为未读 |
PeerReadSeq | int64 | 单聊中对方已读到的序号 |
MentionSeq | int64 | @ 本人或全体、本人还没读到的消息的序号,0 表示没有 |
ClearedSeq | int64 | 清空聊天记录的位置,0 表示没有清空过 |
HiddenSeq | int64 | 删除会话时的位置,0 表示没有删除过 |
Hidden | bool | 会话是否处于删除(隐藏)状态 |
PinnedAt | string | 置顶的时间,未置顶为空 |
JoinedAt / LeftAt | string | 群聊中本次入群 / 离开的时间 |
LastMessageAt | string | 最后一条消息的时间 |
MessageVersion | int64 | 会话中消息的变更版本号 |
Version | int64 | 状态版本号,同步与去重用 |
Page[T]
泛型分页返回,RecentConversations 与 ReactionUsers 都返回它。
| 字段 | 类型 | 说明 |
|---|---|---|
Items | []T | 这一页的条目 |
NextCursor | string | 下一页的游标,为空表示没有下一页 |
Total | int64 | 总数(接口给出时才有) |
