spark-server/internal/module/user/model/memory.go
zhr ae9a71034d feat: implement Spark IM V3 modular monolith
Add HTTP /v1 MVC, TCP send path, groups with server-side member-avatar collage, object-storage upload, and friends/favorites around the architecture-v3 layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-17 19:52:11 +08:00

220 lines
4.5 KiB
Go

package model
import (
"context"
"sync"
"time"
"spark-server/internal/module/user/entity"
"spark-server/internal/pkg/errs"
)
type MemoryRepo struct {
mu sync.Mutex
byID map[int64]*entity.User
byEmail map[string]int64
bySpark map[string]int64
devices map[deviceKey]*entity.Device
logs []*entity.LoginLog
deviceSeq int64
logSeq int64
}
type deviceKey struct {
UserID int64
DeviceID uint64
}
func NewMemoryRepo() *MemoryRepo {
return &MemoryRepo{
byID: make(map[int64]*entity.User),
byEmail: make(map[string]int64),
bySpark: make(map[string]int64),
devices: make(map[deviceKey]*entity.Device),
logs: make([]*entity.LoginLog, 0),
}
}
func (m *MemoryRepo) GetByID(_ context.Context, id int64) (*entity.User, error) {
m.mu.Lock()
defer m.mu.Unlock()
u, ok := m.byID[id]
if !ok {
return nil, errs.ErrNotFound
}
cp := *u
return &cp, nil
}
func (m *MemoryRepo) GetByEmail(_ context.Context, email string) (*entity.User, error) {
m.mu.Lock()
defer m.mu.Unlock()
id, ok := m.byEmail[email]
if !ok {
return nil, errs.ErrNotFound
}
cp := *m.byID[id]
return &cp, nil
}
func (m *MemoryRepo) GetBySparkID(_ context.Context, sparkID string) (*entity.User, error) {
m.mu.Lock()
defer m.mu.Unlock()
id, ok := m.bySpark[sparkID]
if !ok {
return nil, errs.ErrNotFound
}
cp := *m.byID[id]
return &cp, nil
}
func (m *MemoryRepo) GetByQRCode(_ context.Context, qrcode string) (*entity.User, error) {
m.mu.Lock()
defer m.mu.Unlock()
if qrcode == "" {
return nil, errs.ErrNotFound
}
for _, u := range m.byID {
if u.QRCode == qrcode {
cp := *u
return &cp, nil
}
}
return nil, errs.ErrNotFound
}
func (m *MemoryRepo) CreateWithEmail(_ context.Context, u *entity.User, email string) error {
m.mu.Lock()
defer m.mu.Unlock()
if _, ok := m.byEmail[email]; ok {
return errs.ErrDuplicate
}
if _, ok := m.bySpark[u.SparkID]; ok {
return errs.ErrDuplicate
}
if _, ok := m.byID[u.ID]; ok {
return errs.ErrDuplicate
}
cp := *u
m.byID[u.ID] = &cp
m.byEmail[email] = u.ID
m.bySpark[u.SparkID] = u.ID
return nil
}
func (m *MemoryRepo) UpdateSparkID(_ context.Context, userID int64, sparkID string) error {
m.mu.Lock()
defer m.mu.Unlock()
u, ok := m.byID[userID]
if !ok {
return errs.ErrNotFound
}
if other, ok := m.bySpark[sparkID]; ok && other != userID {
return errs.ErrDuplicate
}
delete(m.bySpark, u.SparkID)
u.SparkID = sparkID
u.UpdatedAt = time.Now()
m.bySpark[sparkID] = userID
return nil
}
func (m *MemoryRepo) TouchLogin(_ context.Context, userID int64, at time.Time) error {
m.mu.Lock()
defer m.mu.Unlock()
u, ok := m.byID[userID]
if !ok {
return errs.ErrNotFound
}
u.LastLoginAt = at
u.LastActiveAt = at
return nil
}
func (m *MemoryRepo) UpsertDevice(_ context.Context, d *entity.Device) error {
m.mu.Lock()
defer m.mu.Unlock()
key := deviceKey{UserID: d.UserID, DeviceID: d.DeviceID}
if old, ok := m.devices[key]; ok {
d.ID = old.ID
d.CreatedAt = old.CreatedAt
} else if d.ID == 0 {
m.deviceSeq++
d.ID = m.deviceSeq
}
cp := *d
m.devices[key] = &cp
return nil
}
func (m *MemoryRepo) GetDevice(_ context.Context, userID int64, deviceID uint64) (*entity.Device, error) {
m.mu.Lock()
defer m.mu.Unlock()
d, ok := m.devices[deviceKey{UserID: userID, DeviceID: deviceID}]
if !ok {
return nil, errs.ErrNotFound
}
cp := *d
return &cp, nil
}
func (m *MemoryRepo) UpdateQRCode(_ context.Context, userID int64, qrcode string) error {
m.mu.Lock()
defer m.mu.Unlock()
u, ok := m.byID[userID]
if !ok {
return errs.ErrNotFound
}
u.QRCode = qrcode
return nil
}
func (m *MemoryRepo) UpdateProfile(_ context.Context, u *entity.User) error {
if u == nil || u.ID <= 0 {
return errs.ErrInvalidArgument
}
m.mu.Lock()
defer m.mu.Unlock()
cur, ok := m.byID[u.ID]
if !ok {
return errs.ErrNotFound
}
if other, ok := m.bySpark[u.SparkID]; ok && other != u.ID {
return errs.ErrDuplicate
}
delete(m.bySpark, cur.SparkID)
cur.SparkID = u.SparkID
cur.Nickname = u.Nickname
cur.Avatar = u.Avatar
cur.Gender = u.Gender
cur.Birthday = u.Birthday
cur.UpdatedAt = u.UpdatedAt
m.bySpark[cur.SparkID] = u.ID
return nil
}
func (m *MemoryRepo) InsertLoginLog(_ context.Context, log *entity.LoginLog) error {
if log == nil {
return nil
}
m.mu.Lock()
defer m.mu.Unlock()
m.logSeq++
cp := *log
cp.ID = m.logSeq
log.ID = cp.ID
m.logs = append(m.logs, &cp)
return nil
}
func (m *MemoryRepo) LoginLogs() []*entity.LoginLog {
m.mu.Lock()
defer m.mu.Unlock()
out := make([]*entity.LoginLog, len(m.logs))
for i, row := range m.logs {
cp := *row
out[i] = &cp
}
return out
}