spark-server/internal/module/conversation/model/memory.go
DESKTOP-L8OUL9F\gfl0034 e27ae8723a fix: 单聊系统消息带回当前用户视角的对方
SYNC_INBOX 补 conversation_type、peer_id、group_id 和 last_read_msg_id。历史消息在 sender 为 0 时用 peer_id 定位对方。已读清未读时保留会话 ZSET 分数,避免被毫秒时间戳冲到游标后面。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 15:04:03 +08:00

186 lines
4.0 KiB
Go

package model
import (
"context"
"sync"
"time"
"spark-server/internal/module/conversation/entity"
"spark-server/internal/pkg/errs"
)
type p2pKey struct {
Small int64
Big int64
}
// MemoryP2P is an in-process conversation store for tests and empty-DSN bring-up.
type MemoryP2P struct {
mu sync.Mutex
byID map[int64]*entity.Conversation
byP2P map[p2pKey]int64
members map[int64]map[int64]*entity.Member
}
func NewMemoryP2P() *MemoryP2P {
return &MemoryP2P{
byID: make(map[int64]*entity.Conversation),
byP2P: make(map[p2pKey]int64),
members: make(map[int64]map[int64]*entity.Member),
}
}
func (m *MemoryP2P) GetP2P(_ context.Context, userA, userB int64) (*entity.Conversation, error) {
m.mu.Lock()
defer m.mu.Unlock()
small, big := entity.OrderUsers(userA, userB)
id, ok := m.byP2P[p2pKey{Small: small, Big: big}]
if !ok {
return nil, errs.ErrNotFound
}
cp := *m.byID[id]
return &cp, nil
}
func (m *MemoryP2P) CreateP2P(_ context.Context, c *entity.Conversation, p2p *entity.P2P, members []*entity.Member) error {
if c == nil || p2p == nil {
return errs.ErrInvalidArgument
}
m.mu.Lock()
defer m.mu.Unlock()
key := p2pKey{Small: p2p.UserSmall, Big: p2p.UserBig}
if _, ok := m.byP2P[key]; ok {
return errs.ErrDuplicate
}
if _, ok := m.byID[c.ID]; ok {
return errs.ErrDuplicate
}
cp := *c
m.byID[c.ID] = &cp
m.byP2P[key] = c.ID
byUser := make(map[int64]*entity.Member, len(members))
for _, mem := range members {
if mem == nil {
continue
}
row := *mem
byUser[mem.UserID] = &row
}
m.members[c.ID] = byUser
return nil
}
func (m *MemoryP2P) Get(_ context.Context, id int64) (*entity.Conversation, error) {
m.mu.Lock()
defer m.mu.Unlock()
row, ok := m.byID[id]
if !ok {
return nil, errs.ErrNotFound
}
cp := *row
return &cp, nil
}
func (m *MemoryP2P) ListMemberIDs(_ context.Context, conversationID int64) ([]int64, error) {
m.mu.Lock()
defer m.mu.Unlock()
byUser, ok := m.members[conversationID]
if !ok {
return nil, errs.ErrNotFound
}
ids := make([]int64, 0, len(byUser))
for uid := range byUser {
ids = append(ids, uid)
}
return ids, nil
}
func (m *MemoryP2P) DialogFaces(_ context.Context, viewerID int64, conversationIDs []int64) (map[int64]entity.DialogFace, error) {
m.mu.Lock()
defer m.mu.Unlock()
out := make(map[int64]entity.DialogFace, len(conversationIDs))
for _, id := range conversationIDs {
conv, ok := m.byID[id]
if !ok {
continue
}
face := entity.DialogFace{ConversationID: id, Type: conv.Type}
if mem := m.members[id][viewerID]; mem != nil {
face.LastReadMsgID = mem.LastReadMsgID
}
if conv.Type == entity.TypeP2P {
for uid := range m.members[id] {
if uid > 0 && uid != viewerID {
face.PeerID = uid
break
}
}
}
out[id] = face
}
return out, nil
}
func (m *MemoryP2P) GetMember(_ context.Context, conversationID, userID int64) (*entity.Member, error) {
m.mu.Lock()
defer m.mu.Unlock()
byUser, ok := m.members[conversationID]
if !ok {
return nil, errs.ErrNotFound
}
mem, ok := byUser[userID]
if !ok {
return nil, errs.ErrNotFound
}
cp := *mem
return &cp, nil
}
func (m *MemoryP2P) UpdateLastRead(_ context.Context, conversationID, userID, msgID int64) error {
if conversationID <= 0 || userID <= 0 || msgID <= 0 {
return errs.ErrInvalidArgument
}
m.mu.Lock()
defer m.mu.Unlock()
byUser, ok := m.members[conversationID]
if !ok {
return errs.ErrNotFound
}
mem, ok := byUser[userID]
if !ok {
return errs.ErrNotFound
}
if msgID > mem.LastReadMsgID {
mem.LastReadMsgID = msgID
}
return nil
}
func (m *MemoryP2P) UpdateLastMsg(_ context.Context, id, msgID, atMS int64) error {
m.mu.Lock()
defer m.mu.Unlock()
c, ok := m.byID[id]
if !ok {
return errs.ErrNotFound
}
c.LastMsgID = msgID
if atMS > 0 {
c.LastMsgAt = atMS
} else {
c.LastMsgAt = time.Now().UnixMilli()
}
return nil
}
func (m *MemoryP2P) PurgeUser(_ context.Context, userID int64) error {
if userID <= 0 {
return errs.ErrInvalidArgument
}
m.mu.Lock()
defer m.mu.Unlock()
for _, byUser := range m.members {
delete(byUser, userID)
}
return nil
}