未讀數與已讀
從你的服務端查詢使用者的未讀數,或代使用者標記已讀。
| 要做什麼 | 介面 |
|---|---|
| 使用者很久沒登入但有未讀,用簡訊或郵件提醒他 | 查詢使用者的未讀總數 |
| 使用者已在你的網頁、郵件裡看過了這條通知 | 標記會話已讀 |
| 一鍵清除使用者的全部未讀 | 全部標記已讀 |
例如,查 bob 有多少未讀:
curl "$IM_API/$ORG/$APP/users/bob/unread-count" \
-H "Authorization: Bearer $APP_TOKEN"
# {"total":3,"conversation_count":1,"marked_unread_count":1,"truncated":false}服務端為每個使用者在每個會話中記一個已讀位置(使用者會話物件的 read_seq),據此計算未讀數。客戶端沒拉取過的訊息也計入,所以服務端的未讀數總是準確的。
代使用者標記已讀就是真正的已讀,和使用者在客戶端標記一樣:他的所有裝置隨之更新,單聊對方看到新的已讀位置,群裡要求了回執的訊息計入已讀。
未讀數的計算
未讀數 = 已讀位置之後、使用者能看到的、計入未讀的訊息條數。已讀位置只增不減:讀到後面的訊息,就表示讀過了前面的。
不計入未讀的訊息:
- 群提示;
- 傳送時
exclude_from_unread為true的訊息; - 使用者自己在客戶端傳送的訊息;
- 超過保留期已刪除的訊息。
各種操作對未讀的影響:
| 操作 | 已讀位置 | 未讀數 |
|---|---|---|
| 服務端以使用者的身份發訊息,他原本沒有未讀 | 前進到這條訊息 | 仍為 0 |
| 服務端以使用者的身份發訊息,他原本有未讀 | 不變 | 這條也計入他自己的未讀,見傳送者的未讀數 |
| 使用者刪除了還沒讀的訊息 | 不變 | 仍計入,直到已讀位置越過它。代使用者刪除時請同時代他標記已讀 |
| 刪除會話、清空聊天記錄 | 前進到當時的最後一條 | 清零 |
| 使用者在客戶端“標記為未讀” | 不變 | 不變,只是 marked_unread 變為 true;再次標記已讀時取消 |
群裡 @ 了使用者或 @ 全體成員的訊息,使用者讀到之前 mention_seq 是它的序號,讀到之後變為 null。
已讀的同步效果
標記已讀,以及同樣會推進已讀位置的刪除會話、清空聊天記錄,會產生以下效果:
| 物件 | 標記已讀、刪除會話、清空聊天記錄 | 全部標記已讀 |
|---|---|---|
| 使用者自己的線上裝置 | 實時收到這個會話的新狀態,未讀數、@ 提醒隨之更新 | 收到需要重新同步的通知,隨後同步會話列表 |
| 使用者的離線裝置 | 下次登入或同步時更新 | 同左 |
| 單聊的對方 | 對方會話中的 peer_read_seq 前進到新的已讀位置,線上裝置實時收到已讀通知 | peer_read_seq 同樣前進,但不實時通知,對方在下次同步時看到 |
| 群裡要求已讀回執的訊息 | 計入已讀人數(已離開群的使用者不計入);群不超過 500 人時,訊息的傳送者實時收到已讀通知 | 同左 |
- 開關:單聊已讀由執行策略的
single_read_ack_enabled控制(預設開啟),關閉後不更新、不返回peer_read_seq,也不通知對方。群已讀回執由group_read_ack_enabled控制(預設關閉),只對傳送時要求了回執的訊息生效。 - 重複標記:已讀位置沒有前進、對方看到的也已是最新時,不通知對方,也不影響回執。
- 以使用者身份發訊息不算已讀:不會推進單聊對方看到的已讀位置,也不計入群回執。要表示他讀過了,另外呼叫標記會話已讀。
查詢使用者的未讀總數
查詢一個使用者全部會話的未讀數之和與有未讀的會話數,可用於 App 圖示上的角標或未讀提醒。
/{org_name}/{app_name}/users/{username}/unread-count統計範圍:
- 使用者當前所在的全部群;
- 最近的 1000 個單聊和已離開的群(按最後一條訊息的時間)。更多時只統計這 1000 個,
truncated為true; - 使用者刪除了的會話(
hidden為true)不計入。
路徑引數
org_name、app_name 見接入概述。
| 引數 | 型別 | 說明 |
|---|---|---|
username | String | 使用者名稱,不區分大小寫 |
請求示例
curl "$IM_API/$ORG/$APP/users/bob/unread-count" \
-H "Authorization: Bearer $APP_TOKEN"響應
成功返回 200 OK:
| 欄位 | 型別 | 說明 |
|---|---|---|
total | Number | 全部會話的未讀數之和 |
conversation_count | Number | 有未讀訊息的會話數 |
marked_unread_count | Number | 沒有未讀訊息、但被使用者標記為未讀的會話數 |
truncated | Boolean | 單聊和已離開的群超過 1000 個,只統計了最近的 1000 個 |
{
"total": 3,
"conversation_count": 1,
"marked_unread_count": 1,
"truncated": false
}錯誤
| HTTP 狀態碼 | code | 說明 |
|---|---|---|
| 404 | not_found | 使用者不存在或已刪除 |
標記會話已讀
代使用者把一個會話標記為已讀:把他的已讀位置推進到指定的訊息,同時取消“標記為未讀”。效果見已讀的同步效果。
/{org_name}/{app_name}/users/{username}/conversations/{conversation_id}/read已離開的群也可以標記已讀,只改變這個使用者自己的狀態。
路徑引數
org_name、app_name 見接入概述。
| 引數 | 型別 | 說明 |
|---|---|---|
username | String | 使用者名稱,不區分大小寫 |
conversation_id | String | 會話 ID;群會話的 ID 就是群 ID |
請求體
請求體可以省略,省略時標記到最新一條。
| 欄位 | 型別 | 必填 | 說明 |
|---|---|---|---|
seq | Number | 否 | 已讀到的訊息序號。不傳或為 null 時為使用者能看到的最新一條;大於最新一條時按最新一條處理;不大於當前已讀位置時已讀位置不變 |
請求示例
curl -X POST "$IM_API/$ORG/$APP/users/bob/conversations/99582660229201920/read" \
-H "Authorization: Bearer $APP_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"seq": 5
}'響應
成功返回 200 OK:
| 欄位 | 型別 | 說明 |
|---|---|---|
conversation | Object | 操作之後的使用者會話物件 |
changed | Boolean | 是否有變化。已讀位置沒有前進、沒有取消“標記為未讀”、單聊對方看到的已讀位置也不需要更新時為 false,可以放心重試 |
{
"conversation": {
"conversation_id": "99582660229201920",
"conversation_type": "single",
"peer": "alice",
"group_id": null,
"membership": null,
"start_seq": 1,
"max_seq": 10,
"read_seq": 5,
"unread_count": 5,
"marked_unread": false,
"peer_read_seq": 0,
"mention_seq": null,
"cleared_seq": 0,
"hidden_seq": 0,
"hidden": false,
"pinned_at": null,
"joined_at": null,
"left_at": null,
"message_version": 0,
"last_message_at": "2026-10-02T19:07:06.211Z",
"version": 12
},
"changed": true
}呼叫之後,對方 alice 的會話中 peer_read_seq 變為 5,她的線上裝置實時收到 bob 已讀到第 5 條的通知。
錯誤
| HTTP 狀態碼 | code | 說明 |
|---|---|---|
| 400 | invalid_argument | 請求體不是 JSON 物件,seq 不是數字,或包含其他欄位 |
| 404 | not_found | 使用者不存在或已刪除;會話不存在;單聊中使用者不是會話的一方;群不存在,或是私有群而使用者從沒入過群 |
| 403 | not_group_member | 公開群,使用者不是成員,也沒有離開之前的記錄 |
| 403 | not_group_member | details.reason 為 leave_pending:使用者剛離開群,處理還沒完成,稍後重試 |
全部標記已讀
代使用者把全部有未讀的會話標記為已讀,範圍與未讀總數相同:當前所在的群,以及最近的 1000 個單聊和已離開的群中,有未讀訊息或被標記為未讀的會話。
/{org_name}/{app_name}/users/{username}/conversations/read-all使用者的線上裝置收到需要重新同步的通知;單聊的對方不會逐個收到已讀通知,在下次同步時看到新的已讀位置,見已讀的同步效果。
路徑引數
org_name、app_name 見接入概述。
| 引數 | 型別 | 說明 |
|---|---|---|
username | String | 使用者名稱,不區分大小寫 |
請求示例
curl -X POST "$IM_API/$ORG/$APP/users/bob/conversations/read-all" \
-H "Authorization: Bearer $APP_TOKEN"響應
成功返回 200 OK:
| 欄位 | 型別 | 說明 |
|---|---|---|
count | Number | 這次標記為已讀的會話數。沒有未讀的會話時為 0 |
truncated | Boolean | 單聊和已離開的群超過 1000 個,更早的會話沒有處理。這些會話可以用標記會話已讀逐個處理 |
{
"count": 2,
"truncated": false
}錯誤
| HTTP 狀態碼 | code | 說明 |
|---|---|---|
| 404 | not_found | 使用者不存在或已刪除 |
