fix: 空评论列表 500,并完善撤回审计与角色说明

空切片 JSON 统一为数组;撤回保留正文供站长阅读,成员侧居中提示与重新编辑;头像支持拖放;角色弹窗补全职责说明并汇入封禁。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-16 00:23:55 +08:00
parent 42c395a864
commit 8c5de13899
29 changed files with 835 additions and 405 deletions

View File

@@ -153,6 +153,10 @@ func (s *ChatService) toViews(rooms []model.ChatRoom, userID uint) []RoomView {
unread := s.unreadCounts(userID, ids)
views := make([]RoomView, 0, len(rooms))
for _, r := range rooms {
// 会话列表预览不向前台下发已撤回正文
if r.LastMessage != nil && r.LastMessage.RecalledAt != nil {
r.LastMessage.Content = ""
}
v := RoomView{ChatRoom: r, UnreadCount: unread[r.ID]}
if r.IsDefault {
v.Pinned = true
@@ -887,7 +891,7 @@ func (s *ChatService) ListMessages(userID, roomID, beforeID uint, size int, over
q = q.Where("id < ?", beforeID)
}
var msgs []model.ChatMessage
if err := q.Order("id DESC").Limit(size + 1).Preload("Sender").Find(&msgs).Error; err != nil {
if err := q.Order("id DESC").Limit(size + 1).Preload("Sender").Preload("Recaller").Find(&msgs).Error; err != nil {
return nil, false, err
}
hasMore := len(msgs) > size
@@ -897,7 +901,7 @@ func (s *ChatService) ListMessages(userID, roomID, beforeID uint, size int, over
for i, j := 0, len(msgs)-1; i < j; i, j = i+1, j-1 {
msgs[i], msgs[j] = msgs[j], msgs[i]
}
// 对非监管者清空已撤回正文
// 已撤回正文:仅监管者(站长/超管)保留,供聊天界面审计展示;普通成员清空
if !oversee {
for i := range msgs {
if msgs[i].RecalledAt != nil {
@@ -908,7 +912,7 @@ func (s *ChatService) ListMessages(userID, roomID, beforeID uint, size int, over
return msgs, hasMore, nil
}
// RecallMessage 撤回消息:群主可撤自己的;监管者可撤任意;标记 RecalledAt 不清行
// RecallMessage 撤回消息:群主可撤自己的;监管者可撤任意;标记 RecalledAt,正文保留供审计
func (s *ChatService) RecallMessage(operatorID, roomID, messageID uint, oversee bool) (*model.ChatMessage, error) {
if _, err := s.getRoom(roomID); err != nil {
return nil, err
@@ -935,17 +939,16 @@ func (s *ChatService) RecallMessage(operatorID, roomID, messageID uint, oversee
}
}
now := time.Now()
// 只打撤回标记,保留正文供站长审计;前台 ListMessages 对非监管者置空 content
if err := s.db.Model(&msg).Updates(map[string]interface{}{
"recalled_at": now,
"recalled_by": operatorID,
"content": "",
}).Error; err != nil {
return nil, err
}
msg.RecalledAt = &now
msg.RecalledBy = operatorID
msg.Content = ""
_ = s.db.Preload("Sender").First(&msg, msg.ID)
_ = s.db.Preload("Sender").Preload("Recaller").First(&msg, msg.ID)
return &msg, nil
}