spark-server/internal/module/group/logic/service.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

106 lines
3.2 KiB
Go

package logic
import (
"context"
"sync"
"time"
deliverentity "spark-server/internal/module/deliver/entity"
"spark-server/internal/module/group/entity"
msgentity "spark-server/internal/module/message/entity"
userentity "spark-server/internal/module/user/entity"
)
const memberCacheTTL = 3 * time.Second
const cmdMessage uint16 = 6
// Repository is group persistence. Model implements it; do not split this by table.
type Repository interface {
CreateGroup(ctx context.Context, g *entity.Group, members []*entity.Member) error
Get(ctx context.Context, id int64) (*entity.Group, error)
GetByConversation(ctx context.Context, conversationID int64) (*entity.Group, error)
ListByUser(ctx context.Context, userID int64, limit int) ([]*entity.Group, error)
UpdateProfile(ctx context.Context, g *entity.Group) error
GetMember(ctx context.Context, groupID, userID int64) (*entity.Member, error)
ListMembers(ctx context.Context, groupID int64, limit int) ([]*entity.Member, error)
ListMemberIDs(ctx context.Context, groupID int64) ([]int64, error)
AddMembers(ctx context.Context, groupID int64, members []*entity.Member) error
RemoveMember(ctx context.Context, groupID, userID int64) error
UpdateMember(ctx context.Context, mem *entity.Member) error
TransferOwner(ctx context.Context, groupID, fromID, toID int64) error
Dissolve(ctx context.Context, groupID int64) error
UpdateLastMsg(ctx context.Context, conversationID, msgID, atMS int64) error
}
type Users interface {
Get(ctx context.Context, id int64) (*userentity.User, error)
}
type Friendships interface {
AreFriends(ctx context.Context, a, b int64) (bool, error)
}
type IDGen interface {
NextID() (int64, error)
}
type Messages interface {
Create(ctx context.Context, m *msgentity.Message) error
}
type DeliverQueue interface {
Push(ctx context.Context, job *deliverentity.Job) error
}
type Objects interface {
Get(ctx context.Context, key string) ([]byte, error)
Put(ctx context.Context, key string, body []byte, contentType string) error
}
type memberCacheEntry struct {
ids []int64
at time.Time
}
// Service is the group use-case layer (HTTP and TCP share it).
type Service struct {
repo Repository
users Users
friends Friendships
ids IDGen
msgs Messages
queue DeliverQueue
objects Objects
cache sync.Map
}
func NewService(repo Repository, users Users, friends Friendships, ids IDGen, msgs Messages, queue DeliverQueue, objects Objects) *Service {
return &Service{repo: repo, users: users, friends: friends, ids: ids, msgs: msgs, queue: queue, objects: objects}
}
func (s *Service) ready() bool {
return s != nil && s.repo != nil && s.users != nil && s.friends != nil && s.ids != nil
}
func (s *Service) cachedMemberIDs(groupID int64) ([]int64, bool) {
v, ok := s.cache.Load(groupID)
if !ok {
return nil, false
}
e, ok := v.(memberCacheEntry)
if !ok || time.Since(e.at) > memberCacheTTL {
s.cache.Delete(groupID)
return nil, false
}
return append([]int64(nil), e.ids...), true
}
func (s *Service) putMemberIDs(groupID int64, ids []int64) {
s.cache.Store(groupID, memberCacheEntry{ids: append([]int64(nil), ids...), at: time.Now()})
}
func (s *Service) invalidateMembers(groupID int64) {
s.cache.Delete(groupID)
}