// 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) }