feat:聊天页面相关功能优化,提供用户体验感
This commit is contained in:
@@ -96,9 +96,17 @@ type HistoryMessageRequest struct {
|
||||
}
|
||||
|
||||
// HistoryMessageResponse 历史消息响应
|
||||
//
|
||||
// 已读状态回填设计(2026-04-20):
|
||||
// - PeerLastReadMsgID:单聊时填"对方 last_read_msg_id",用于前端在页面刷新后恢复 readStatusMap,
|
||||
// 避免已读标签变未读的体验退化;群聊时为 0
|
||||
// - ReadCountMap:群聊时批量返回"自己发送消息的 read_count",前端合并到 groupReadCountMap;
|
||||
// 单聊时为 nil(JSON omitempty 省略,节省带宽)
|
||||
type HistoryMessageResponse struct {
|
||||
List []MessageDTO `json:"list"` // 消息列表(ID 降序)
|
||||
HasMore bool `json:"has_more"` // 是否还有更多历史消息
|
||||
List []MessageDTO `json:"list"` // 消息列表(ID 降序)
|
||||
HasMore bool `json:"has_more"` // 是否还有更多历史消息
|
||||
PeerLastReadMsgID int64 `json:"peer_last_read_msg_id"` // 单聊:对方已读位置(群聊=0)
|
||||
ReadCountMap map[int64]int `json:"read_count_map,omitempty"` // 群聊:{messageID: readCount}(仅自己发的消息)
|
||||
}
|
||||
|
||||
// ====== 已读标记 DTO ======
|
||||
|
||||
@@ -29,7 +29,7 @@ var (
|
||||
ErrUploadFailed = errors.New("文件上传失败")
|
||||
ErrFileTooLarge = errors.New("文件大小超出限制")
|
||||
ErrInvalidImage = errors.New("无效的图片文件")
|
||||
ErrInvalidVoice = errors.New("不支持的语音格式,仅支持 mp3/wav/aac/m4a")
|
||||
ErrInvalidVoice = errors.New("不支持的语音格式,仅支持 mp3/wav/aac/m4a/webm/ogg")
|
||||
ErrVoiceTooLong = errors.New("语音时长不能超过 60 秒")
|
||||
ErrThumbnailGen = errors.New("缩略图生成失败")
|
||||
)
|
||||
@@ -76,11 +76,14 @@ type VoiceUploadResult struct {
|
||||
}
|
||||
|
||||
// allowedVoiceExts 允许的语音文件扩展名
|
||||
// .webm / .ogg 用于兼容 H5 端浏览器 MediaRecorder 输出(uni-app H5 端不支持 getRecorderManager)
|
||||
var allowedVoiceExts = map[string]bool{
|
||||
".mp3": true,
|
||||
".wav": true,
|
||||
".aac": true,
|
||||
".m4a": true,
|
||||
".mp3": true,
|
||||
".wav": true,
|
||||
".aac": true,
|
||||
".m4a": true,
|
||||
".webm": true,
|
||||
".ogg": true,
|
||||
}
|
||||
|
||||
// Upload 上传文件到 MinIO,返回文件访问 URL
|
||||
|
||||
@@ -537,7 +537,44 @@ func (s *IMService) GetHistoryMessages(ctx context.Context, userID int64, req *d
|
||||
list = append(list, *s.toMessageDTO(&m))
|
||||
}
|
||||
|
||||
return &dto.HistoryMessageResponse{List: list, HasMore: hasMore}, nil
|
||||
resp := &dto.HistoryMessageResponse{List: list, HasMore: hasMore}
|
||||
|
||||
// ========== 已读状态回填(修复页面刷新后已读标签变未读的 Bug) ==========
|
||||
// 获取会话类型以决定回填策略
|
||||
conv, convErr := s.convDAO.GetByID(ctx, req.ConversationID)
|
||||
if convErr != nil || conv == nil {
|
||||
// 拉不到会话元信息时静默跳过已读回填,消息仍正常返回(向前兼容)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
switch conv.Type {
|
||||
case constants.ConversationTypePrivate:
|
||||
// 单聊:查询对方成员的 last_read_msg_id
|
||||
peerID, pErr := s.convDAO.GetPeerUserID(ctx, req.ConversationID, userID)
|
||||
if pErr == nil && peerID > 0 {
|
||||
peerMember, pmErr := s.convDAO.GetMember(ctx, req.ConversationID, peerID)
|
||||
if pmErr == nil && peerMember != nil {
|
||||
resp.PeerLastReadMsgID = peerMember.LastReadMsgID
|
||||
}
|
||||
}
|
||||
case constants.ConversationTypeGroup:
|
||||
// 群聊:批量查询"本次返回消息中自己发送的那些"的 read_count
|
||||
// 仅查自己发的,因为前端仅在 isSelf=true 时展示 "N人已读"
|
||||
selfMsgIDs := make([]int64, 0, len(messages))
|
||||
for i := range messages {
|
||||
if messages[i].SenderID == userID && messages[i].ID > 0 {
|
||||
selfMsgIDs = append(selfMsgIDs, messages[i].ID)
|
||||
}
|
||||
}
|
||||
if len(selfMsgIDs) > 0 && s.readRecorder != nil {
|
||||
countMap, rcErr := s.readRecorder.GetReadCountBatch(ctx, selfMsgIDs)
|
||||
if rcErr == nil && len(countMap) > 0 {
|
||||
resp.ReadCountMap = countMap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// MarkRead 标记会话已读(清零未读 + 更新 Redis 总未读数 + 推送已读 ACK 给对方)
|
||||
|
||||
Reference in New Issue
Block a user