fix(chat): 撤回消息时回退未读角标,避免当成新消息

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-17 04:55:17 +08:00
parent c9d27b2b0c
commit d1155cbd64
4 changed files with 58 additions and 16 deletions

View File

@@ -401,12 +401,12 @@ func (h *Handlers) RecallChatMessage(c *gin.Context) {
return
}
oversee := h.chatOversee(claims.ID)
msg, err := h.Chat.RecallMessage(claims.ID, roomID, uint(mid), oversee)
msg, newly, err := h.Chat.RecallMessage(claims.ID, roomID, uint(mid), oversee)
if err != nil {
c.JSON(chatErrToStatus(err), gin.H{"error": err.Error()})
return
}
h.broadcastChatRecalled(roomID, msg)
h.broadcastChatRecalled(roomID, msg, newly)
// 监管者保留正文供界面展示;撤回者本人保留正文供「重新编辑」;其余不回传
if !oversee && msg.SenderID != claims.ID {
msg.Content = ""
@@ -504,19 +504,19 @@ func (h *Handlers) AdminRecallChatMessage(c *gin.Context) {
}
oversee := h.chatOversee(claims.ID)
// 非监管 staff:仅允许撤回自己发送的(与前台一致)
msg, err := h.Chat.RecallMessage(claims.ID, uint(roomID), uint(mid), oversee)
msg, newly, err := h.Chat.RecallMessage(claims.ID, uint(roomID), uint(mid), oversee)
if err != nil {
c.JSON(chatErrToStatus(err), gin.H{"error": err.Error()})
return
}
h.broadcastChatRecalled(uint(roomID), msg)
h.broadcastChatRecalled(uint(roomID), msg, newly)
c.JSON(http.StatusOK, gin.H{
"ok": true, "id": msg.ID, "room_id": roomID, "recalled_at": msg.RecalledAt,
})
}
// broadcastChatRecalled 会话内撤回与管理端撤回共用:先落库再推 chat:{roomID}
func (h *Handlers) broadcastChatRecalled(roomID uint, msg *model.ChatMessage) {
func (h *Handlers) broadcastChatRecalled(roomID uint, msg *model.ChatMessage, newly bool) {
if h.Hub == nil || msg == nil {
return
}
@@ -547,4 +547,35 @@ func (h *Handlers) broadcastChatRecalled(roomID uint, msg *model.ChatMessage) {
h.Hub.BroadcastUser(uid, env)
}
}
// 发送时已给未读者 +1;撤回后必须回退,否则角标会停在一条已撤回的「新消息」上
if newly {
h.broadcastChatUnreadDecrement(roomID, msg)
}
}
// broadcastChatUnreadDecrement 与发信对称:给除发送者外的全体成员推 delta:-1。
// 不能按 last_read 过滤——发信时 +1 也是全员推的;另一标签页已读后首页角标仍需回退。
func (h *Handlers) broadcastChatUnreadDecrement(roomID uint, msg *model.ChatMessage) {
if h.Hub == nil || h.Chat == nil || msg == nil {
return
}
ids, err := h.Chat.ListMemberIDs(roomID)
if err != nil {
return
}
env := realtime.Envelope{
Type: realtime.EventChatUnread,
Data: gin.H{
"room_id": roomID,
"message_id": msg.ID,
"sender_id": msg.SenderID,
"delta": -1,
},
}
for _, uid := range ids {
if uid == msg.SenderID {
continue
}
h.Hub.BroadcastUser(uid, env)
}
}