package livechat import ( "context" "sync" "time" "gorm.io/gorm" biz "kra/internal/biz/livechat" ) type Provider interface{ DB() *gorm.DB } type repo struct { data Provider mu sync.RWMutex nextID uint rooms map[uint][]*biz.Message } func NewRepo(data Provider) biz.Repo { return &repo{data: data, rooms: make(map[uint][]*biz.Message)} } type messagePO struct { ID uint `gorm:"primaryKey"` RoomID uint `gorm:"index"` Type string `gorm:"size:20;index"` UserID uint `gorm:"index"` Username string `gorm:"size:120"` Nickname string `gorm:"size:120"` Avatar string `gorm:"size:1000"` Content string `gorm:"size:255"` GiftID uint GiftName string `gorm:"size:60"` GiftIcon string `gorm:"size:1000"` Quantity int DiamondTotal int64 CreatedAt time.Time `gorm:"index"` } func (messagePO) TableName() string { return "live_chat_messages" } type userSnapshot struct { ID uint Username string Nickname string Avatar string } func (r *repo) ListRecent(ctx context.Context, roomID uint, limit int) ([]*biz.Message, error) { if err := r.ensureRoom(ctx, roomID); err != nil { return nil, err } r.mu.RLock() items := r.rooms[roomID] if len(items) > limit { items = items[len(items)-limit:] } out := make([]*biz.Message, 0, len(items)) for _, item := range items { out = append(out, cloneMessage(item)) } r.mu.RUnlock() return out, nil } func (r *repo) CreateChat(ctx context.Context, roomID, userID uint, content string) (*biz.Message, error) { if err := r.ensureRoom(ctx, roomID); err != nil { return nil, err } user, err := r.user(ctx, userID) if err != nil { return nil, err } return r.append(&biz.Message{RoomID: roomID, Type: biz.TypeChat, UserID: user.ID, Username: user.Username, Nickname: user.Nickname, Avatar: user.Avatar, Content: content}), nil } func (r *repo) CreateGift(ctx context.Context, gift *biz.GiftSnapshot) (*biz.Message, error) { if err := r.ensureRoom(ctx, gift.RoomID); err != nil { return nil, err } user, err := r.user(ctx, gift.UserID) if err != nil { return nil, err } username, nickname := gift.Username, gift.Nickname if username == "" { username = user.Username } if nickname == "" { nickname = user.Nickname } return r.append(&biz.Message{RoomID: gift.RoomID, Type: biz.TypeGift, UserID: gift.UserID, Username: username, Nickname: nickname, Avatar: user.Avatar, GiftID: gift.GiftID, GiftName: gift.GiftName, GiftIcon: gift.GiftIcon, Quantity: gift.Quantity, DiamondTotal: gift.DiamondTotal}), nil } func (r *repo) append(item *biz.Message) *biz.Message { r.mu.Lock() defer r.mu.Unlock() r.nextID++ item.ID = r.nextID item.CreatedAt = time.Now() items := append(r.rooms[item.RoomID], cloneMessage(item)) if len(items) > 100 { items = append([]*biz.Message(nil), items[len(items)-100:]...) } r.rooms[item.RoomID] = items return cloneMessage(item) } func (r *repo) Clear(_ context.Context, roomID uint) error { r.mu.Lock() delete(r.rooms, roomID) r.mu.Unlock() return nil } func (r *repo) ensureRoom(ctx context.Context, roomID uint) error { var count int64 if err := r.data.DB().WithContext(ctx).Table("live_rooms").Where("id = ? AND deleted_at IS NULL AND enabled = ?", roomID, true).Count(&count).Error; err != nil { return err } if count == 0 { return biz.ErrRoomNotFound } return nil } func (r *repo) user(ctx context.Context, userID uint) (*userSnapshot, error) { var row userSnapshot if err := r.data.DB().WithContext(ctx).Table("sys_users").Select("id, username, nick_name AS nickname, header_img AS avatar").Where("id = ? AND deleted_at IS NULL AND enable = ?", userID, 1).Scan(&row).Error; err != nil { return nil, err } if row.ID == 0 { return nil, biz.ErrUserNotFound } return &row, nil } func toBiz(row *messagePO) *biz.Message { return &biz.Message{ID: row.ID, RoomID: row.RoomID, Type: row.Type, UserID: row.UserID, Username: row.Username, Nickname: row.Nickname, Avatar: row.Avatar, Content: row.Content, GiftID: row.GiftID, GiftName: row.GiftName, GiftIcon: row.GiftIcon, Quantity: row.Quantity, DiamondTotal: row.DiamondTotal, CreatedAt: row.CreatedAt} } func cloneMessage(item *biz.Message) *biz.Message { if item == nil { return nil } copy := *item return © }