2026-04-25 01:18:30 +08:00

169 lines
6.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package store 提供 im-service 领域层当前使用的内存消息存储和顺序号分配。
package store
import (
"fmt"
"sync"
)
// Message 是 im-service 内部统一使用的消息实体。
type Message struct {
// MessageID 是 im-service 生成的消息唯一标识ACK 和幂等响应都依赖它。
MessageID string `json:"message_id"`
// MessageType 只能使用项目约定的 room_chat_message、room_system_message、peer_message、system_notice。
MessageType string `json:"message_type"`
// FromUserID 是发送方或房间系统事件 actor系统通知可以为空。
FromUserID int64 `json:"from_user_id,omitempty"`
// ToUserID 是单聊接收方、系统通知接收方或房间系统事件目标。
ToUserID int64 `json:"to_user_id,omitempty"`
// RoomID 标识消息所属房间;单聊和全局系统通知不需要填写。
RoomID string `json:"room_id,omitempty"`
// RoomSeq 是 im-service 分配的房间统一时间线序号room-service 不生成该字段。
RoomSeq int64 `json:"room_seq,omitempty"`
// EventID 是 room-service 房间系统事件的幂等键,公屏消息不需要填写。
EventID string `json:"event_id,omitempty"`
// Content 是当前 v1 的文本载荷,富媒体和礼物表现通过 Attributes 扩展。
Content string `json:"content,omitempty"`
// Attributes 保存业务扩展字段,避免在 v1 消息实体里提前展开过多类型。
Attributes map[string]string `json:"attributes,omitempty"`
// RequiresACK 只对 peer_message 和 system_notice 生效,房间消息不做逐用户 ACK。
RequiresACK bool `json:"requires_ack"`
// CreatedAtMS 使用毫秒时间戳,便于 WS JSON 和历史拉取直接消费。
CreatedAtMS int64 `json:"created_at_ms"`
}
// MemoryStore 负责消息顺序号、历史窗口和 ACK 待确认集合。
type MemoryStore struct {
// mu 保护所有 map 和切片;当前 store 是进程内同步实现,不跨节点共享。
mu sync.Mutex
// roomSeq 保存每个房间的下一条时间线基础值NextRoomSeq 在锁内递增。
roomSeq map[string]int64
// roomHistory 保存房间最近窗口消息,用于重进房补拉,不承担永久归档。
roomHistory map[string][]Message
// peerHistory 保存 v1 单聊基础历史key 为稳定的双人 pairKey。
peerHistory map[string][]Message
// systemHistory 保存用户系统通知历史,当前主要服务 ACK 和后续补拉扩展。
systemHistory map[int64][]Message
// pendingACK 保存 user_id -> message_id -> message只有需要 ACK 的消息进入这里。
pendingACK map[int64]map[string]Message
}
// NewMemoryStore 初始化内存消息存储。
func NewMemoryStore() *MemoryStore {
return &MemoryStore{
roomSeq: make(map[string]int64),
roomHistory: make(map[string][]Message),
peerHistory: make(map[string][]Message),
systemHistory: make(map[int64][]Message),
pendingACK: make(map[int64]map[string]Message),
}
}
// NextRoomSeq 为指定房间分配新的统一时间线顺序号。
func (s *MemoryStore) NextRoomSeq(roomID string) int64 {
s.mu.Lock()
defer s.mu.Unlock()
// 序号从 1 开始递增,保证房间系统消息和公屏消息共享同一条时间线。
s.roomSeq[roomID]++
return s.roomSeq[roomID]
}
// AppendRoomMessage 把房间消息写入最近窗口。
func (s *MemoryStore) AppendRoomMessage(roomID string, message Message, limit int) {
s.mu.Lock()
defer s.mu.Unlock()
s.roomHistory[roomID] = append(s.roomHistory[roomID], message)
if limit > 0 && len(s.roomHistory[roomID]) > limit {
// 只保留最近窗口,防止热门房长时间运行导致内存无限增长。
s.roomHistory[roomID] = append([]Message(nil), s.roomHistory[roomID][len(s.roomHistory[roomID])-limit:]...)
}
}
// RoomHistory 返回房间最近 N 条消息。
func (s *MemoryStore) RoomHistory(roomID string, limit int) []Message {
s.mu.Lock()
defer s.mu.Unlock()
history := append([]Message(nil), s.roomHistory[roomID]...)
if limit > 0 && len(history) > limit {
// 拉取侧 limit 不能扩大 store 保存窗口,只能在当前窗口内裁剪。
history = history[len(history)-limit:]
}
return history
}
// SavePeerMessage 保存单聊消息历史。
func (s *MemoryStore) SavePeerMessage(a int64, b int64, message Message) {
s.mu.Lock()
defer s.mu.Unlock()
// 双人会话使用无方向 key发送方和接收方读取时都落在同一个历史列表。
key := pairKey(a, b)
s.peerHistory[key] = append(s.peerHistory[key], message)
}
// SaveSystemNotice 保存系统通知历史。
func (s *MemoryStore) SaveSystemNotice(userID int64, message Message) {
s.mu.Lock()
defer s.mu.Unlock()
// 系统通知按目标用户聚合,后续可直接扩展用户维度分页拉取。
s.systemHistory[userID] = append(s.systemHistory[userID], message)
}
// AddPendingACK 把需要 ACK 的消息记到目标用户的待确认集合里。
func (s *MemoryStore) AddPendingACK(userID int64, message Message) {
s.mu.Lock()
defer s.mu.Unlock()
if s.pendingACK[userID] == nil {
// ACK 集合按用户懒创建,避免只有房间消息的用户占用额外 map。
s.pendingACK[userID] = make(map[string]Message)
}
s.pendingACK[userID][message.MessageID] = message
}
// Ack 标记指定消息已被目标用户确认。
func (s *MemoryStore) Ack(userID int64, messageID string) bool {
s.mu.Lock()
defer s.mu.Unlock()
if s.pendingACK[userID] == nil {
// 没有待确认集合说明该用户没有需要 ACK 的在线投递。
return false
}
if _, exists := s.pendingACK[userID][messageID]; !exists {
// message_id 不属于该用户的待确认集合时,拒绝跨用户 ACK。
return false
}
// ACK 成功后立即删除,当前版本不保留已读回执复杂状态。
delete(s.pendingACK[userID], messageID)
return true
}
// PendingACK 返回指定用户当前待确认消息数量。
func (s *MemoryStore) PendingACK(userID int64) int {
s.mu.Lock()
defer s.mu.Unlock()
return len(s.pendingACK[userID])
}
func pairKey(a int64, b int64) string {
if a > b {
// 规范化用户顺序,保证 A->B 和 B->A 写入同一条单聊历史。
a, b = b, a
}
return fmt.Sprintf("%d:%d", a, b)
}