Go SDK 事件、同步與 Store
Go SDK 用一條 WebSocket 長連線接收服務端事件,用一個記憶體 Store(c.Store())維護會話與訊息的本地快取,登入後用 SyncAll 做一次全量同步。本頁介紹事件模型、全部事件、同步流程、Store 的讀取方法與錯誤處理。
安裝、固定版本與本地原始碼替換統一見快速整合。
| 要做什麼 | 看哪裡 |
|---|---|
| 收事件、瞭解回撥在哪個 goroutine 中執行 | 事件模型 |
| 查某個事件的名字和載荷 | 全部事件 |
| 登入後拉齊會話和訊息、翻歷史 | 登入後同步 |
| 從記憶體快取讀會話和訊息 | 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,按事件自行解析)。- 可以註冊多個回撥,按註冊順序依次呼叫;回撥裡的 panic 會被 SDK 恢復,不影響連線。
- 回撥在接收長連線資料的 goroutine 中同步執行:回撥返回之前,後續事件和 WS 請求的回覆都在等待。回撥裡不要做耗時操作,也不要直接呼叫
SendText等走長連線的方法(它們要等的回覆要在回撥返回後才能讀到,只能超時後改走 REST);需要時自己開 goroutine。 - 事件先由 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() 報告當前是否持有訪問令牌(不檢查是否過期,過期的在下次請求時自動續期)。令牌失效的自動續期、長連線按關閉碼的重連策略見初始化與登入。
