Go SDK 事件、同步与 Store
Go SDK 用一条 WebSocket 长连接接收服务端事件,用一个内存 Store(c.Store())维护会话与消息的本地缓存,登录后用 SyncAll 做一次全量同步。本页介绍事件模型、全部事件、同步流程、Store 的读取方法与错误处理。
安装、固定版本与本地源码替换统一见快速集成。
事件模型
长连接建立后(见初始化与登录的 ConnectWait),注册回调即可收到全部事件:
c.OnEvent(func(e im.Event) {
switch e.Name {
case im.EventMessageReceived:
var m im.Message
if json.Unmarshal(e.Data, &m) == nil {
fmt.Println(m.Sender, m.TextBody())
}
}
})im.Event的字段:Name(事件名,如message.received)、ID(事件 ID,用于去重)、At(事件时间,RFC3339,用e.Time()解析)、Data(事件载荷,json.RawMessage,按事件自行解析)。- 可以注册多个回调,按注册顺序依次调用;回调运行在独立 goroutine 中,回调里的 panic 会被 SDK 恢复,不影响连接。
- 事件先由 SDK 应用到 Store(顺序化、去重),再回调给你,所以回调里读
c.Store()已经是这条事件之后的状态。 - 服务端可能新增事件类型,SDK 不认识的事件名不会报错、原样回调,按需解析
e.Data或忽略。
连接状态变化另有 c.OnState(fn func(im.ConnectionState)),状态为 StateDisconnected、StateConnecting、StateConnected(auth 完成),用法见初始化与登录。
全部事件
事件名常量都在 im 包里,按前缀分组。载荷一列为 SDK 已定义的结构;标注“未建模”的事件 SDK 没有定义载荷结构,直接解析 e.Data。
消息与会话
| 常量 | 事件名 | 含义 | 载荷 |
|---|---|---|---|
im.EventMessageReceived | message.received | 收到新消息(不回显给发送者本人) | im.Message(内嵌于 im.MessageEventData) |
im.EventMessageChanged | message.changed | 消息变化:撤回、编辑、回应、置顶等,只带变化字段 | im.MessageChangedData |
im.EventMessageDeleted | message.deleted | 消息被删除(seq 列表) | im.MessageDeletedData |
im.EventMessageOnline | message.online | 只推在线、不落库的消息 | im.Message |
im.EventConversationChanged | message.conversation_changed | 会话状态变化;data 也可能是 {"resync":true},SDK 会自动重新同步 | im.ConversationState 或 resync 指令 |
im.EventPeerRead | message.peer_read | 单聊对方已读位置前进 | im.PeerReadData |
im.EventReadBy | message.read_by | 群消息已读明细 | im.ReadByData |
im.EventTyping | message.typing | 正在输入 | im.TypingData |
im.EventSyncHint | message.sync_hint | 服务端提示有新数据,SDK 收到后自动做增量同步 | im.SyncHintData |
im.MessageChangedData 的 Change.Kind 为 recalled、edited、erased、reaction_added、reaction_removed、pinned、unpinned,变化字段按 kind 出现在对应字段里(Recalled、Edited、Reactions、Pinned 等)。
好友与黑名单
| 常量 | 事件名 | 含义 | 载荷 |
|---|---|---|---|
im.EventFriendAdded | friend.added | 新增好友 | im.FriendEventData |
im.EventFriendUpdated | friend.updated | 好友资料或备注变化 | im.FriendEventData |
im.EventFriendRemoved | friend.removed | 删除好友(Reason 给出原因) | im.FriendEventData |
im.EventFriendRequestReceived | friend_request.received | 收到好友申请 | im.FriendRequestEventData |
im.EventFriendRequestDeclined | friend_request.declined | 发出的申请被拒绝 | im.FriendRequestEventData |
im.EventFriendRequestRead | friend_request.read | 申请列表已读位置前进(其他设备操作) | im.FriendRequestReadData |
im.EventBlacklistChanged | blacklist.changed | 黑名单变化,Blocked 为 false 表示移出 | im.BlacklistChangedData |
群组
| 常量 | 事件名 | 含义 | 载荷 |
|---|---|---|---|
im.EventGroupCreated | group.created | 有新群(建群、被拉入群) | im.GroupEventData |
im.EventGroupInfoChanged | group.info_changed | 群资料变化(Info 为完整群信息,ChangedFields 列出变化字段) | im.GroupEventData |
im.EventGroupMembersAdded | group.members_added | 成员加入 | im.GroupEventData |
im.EventGroupMembersRemoved | group.members_removed | 成员移出(Reason) | im.GroupEventData |
im.EventGroupMembersUpdated | group.members_updated | 成员角色等变化(Role) | im.GroupEventData |
im.EventGroupDismissed | group.dismissed | 群解散 | im.GroupEventData |
im.EventGroupRequestCreated | group.request_created | 有新的入群申请或邀请 | im.GroupEventData |
im.EventGroupRequestHandled | group.request_handled | 入群申请或邀请被处理 | im.GroupEventData |
用户、在线状态与系统
| 常量 | 事件名 | 含义 | 载荷 |
|---|---|---|---|
im.EventUserProfileChanged | user.profile_changed | 本人资料变化 | im.UserProfileChangedData |
im.EventUserSessionsRevoked | user.sessions_revoked | 本人的登录会话被吊销 | 未建模 |
im.EventUserReloginRequired | user.relogin_required | 需要重新登录 | 未建模 |
im.EventUserStatusChanged | user.status_changed | 用户状态变化 | 未建模 |
im.EventUserMuteChanged | user.mute_changed | 本人禁言状态变化 | 未建模 |
im.EventPresenceChanged | presence.changed | 订阅的人在线状态变化(见用户、好友与在线状态) | 结构同 im.PresenceItem |
im.EventPresenceReset | presence.reset | 订阅被服务端取消,清空本地状态后重新订阅 | 未建模 |
im.EventDevicesChanged | devices.changed | 本人设备列表变化 | 未建模 |
im.EventConfigChanged | config.changed | 应用配置变化 | 未建模 |
im.EventAuthExpired | auth.expired | 凭证过期 | 未建模 |
im.EventSyncRequired | sync.required | 服务端要求重新同步,SDK 收到后自动重新同步 | 未建模 |
im.EventPolicyChanged | policy.changed | 应用运行策略变化 | 未建模 |
登录后同步
c.SyncAll(ctx) 做一次全量同步,返回同步到的会话数,结果全部写入 Store:
- recent:拉取最近的会话列表(含置顶),连同每条的最后一条消息写入 Store;首页给出的版本号作为下一步增量的起点。
- group-digest:按游标翻页取全部群会话的状态摘要。
- 增量 sync:从 recent 给出的版本号起增量拉取会话状态和被删除的消息;服务端返回“需要重跑全量”时,SDK 会整体重跑一遍(只重试一次,防止循环)。
- last-messages:对 recent 没有覆盖到最后一条消息的会话,逐个补拉最后一条。
ConnectWait 之后不会自动同步
SDK 建立长连接后不会自己调用 SyncAll:登录并 ConnectWait 成功后,先调一次 c.SyncAll(ctx) 把会话列表和消息拉齐,再读 Store 渲染界面。此后 SDK 会自动维持增量:收到 sync_hint、sync.required 或要求重同步的会话事件时,SDK 在后台重跑同步(30 秒超时),不需要你干预。
补充拉取历史用 c.FetchMessages(conversationID, afterSeq, beforeSeq, limit):afterSeq > 0 从该 seq 之后补缺口,beforeSeq > 0 往前翻历史,limit <= 0 用默认 50;c.History(conversationID, beforeSeq, limit) 是往前翻页的简写。两者都会把结果写入 Store。
Store
c.Store() 返回线程安全的内存缓存(内部读写锁)。读取方法返回的都是快照拷贝,可以放心遍历,不会与事件应用互相干扰:
| 方法 | 用途 |
|---|---|
s.Self() | 当前用户名 |
s.Conversation(id) | 指定会话状态(*im.ConversationState),没有时为 nil |
s.Conversations() | 全部会话,按最近消息时间倒序 |
s.Messages(conversationID) | 会话内已缓存的消息,按 seq 升序 |
s.LastMessages(conversationID, limit) | 会话内最近 limit 条消息(升序) |
s.Typing(conversationID) | 最近的“正在输入”(10 秒内有效,否则 nil) |
写方法 PutConversation、PutMessage、DeleteMessages 供同步流程和 REST 拉取写入,一般不需要直接调用。
Store 按序应用事件:message.received 的 seq 等于本地最大 seq 加一时直接追加;更大说明有缺口,先收下这条再异步补拉缺口;更小的忽略。会话的 cleared_seq 变大时自动删除本地不超过它的消息。相同事件 ID 不会重复应用。发送者本人收不到自己的 message.received,SDK 在发送成功后会在本地兜底建立或推进对应会话的状态。
内存 Store,进程重启即清空
Store 只在内存里:进程重启后为空,重新登录并调用 SyncAll 即可恢复。令牌持久化(Config.TokenStorage)只让重启后免于重新登录,不会保留 Store 数据。好友、群和成员不在 Store 缓存范围内,需要本地保留的自行持久化。
错误处理
REST 和长连接的业务错误统一是 *im.APIError:
| 字段 | 说明 |
|---|---|
Code | 机器可读的错误码,如 unauthenticated、not_found |
Message | 人类可读描述 |
Details | 附加细节(json.RawMessage),如 {"reason":"token_expired"} |
RequestID | 服务端排障 ID |
HTTPStatus | REST 时的 HTTP 状态码;长连接错误为 0 |
用 im.IsCode 判断错误码;Reason() 取 details.reason。长连接未就绪、WS 请求超时等本地失败也以 *im.APIError 返回(码为 unavailable、timeout)。
_, err := c.UpdateGroup(ctx, id, &name, nil, nil)
if err != nil {
switch {
case im.IsCode(err, im.CodePermission):
fmt.Println("没有权限")
case im.IsCode(err, im.CodeNotFound):
fmt.Println("群不存在")
default:
fmt.Println(err) // 形如 im: forbidden: xxx [request_id ...]
}
}im.IsCode 可判断的错误码常量:
| 常量 | 错误码 | 含义 |
|---|---|---|
im.CodeUnauthenticated | unauthenticated | 未登录或凭证失效(SDK 会先用 refresh token 自动续期一次) |
im.CodeForbidden | forbidden | 禁止访问 |
im.CodeNotFound | not_found | 资源不存在或不可见 |
im.CodeInvalidArgument | invalid_argument | 参数不合法 |
im.CodeVersionConflict | version_conflict | 版本冲突 |
im.CodeRateLimited | rate_limited | 请求过于频繁 |
im.CodePermission | permission_denied | 没有权限或功能未开启 |
im.CodeInternal | internal | 服务端内部错误 |
im.CodeUnavailable | unavailable | 服务暂时不可用(含长连接断开时在途请求的失败) |
此外 c.SessionValid() 报告当前是否持有未过期的访问令牌。令牌失效的自动续期、长连接按关闭码的重连策略见初始化与登录。
