SYNC_INBOX 补 conversation_type、peer_id、group_id 和 last_read_msg_id。历史消息在 sender 为 0 时用 peer_id 定位对方。已读清未读时保留会话 ZSET 分数,避免被毫秒时间戳冲到游标后面。 Co-authored-by: Cursor <cursoragent@cursor.com>
186 lines
4.0 KiB
Go
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
|
|
}
|