package livechat import ( "context" "errors" "time" "gorm.io/gorm" biz "kra/internal/biz/livechat" ) type Provider interface{ DB() *gorm.DB } type repo struct{ data Provider } func NewRepo(data Provider) biz.Repo { return &repo{data: data} } 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 } var rows []messagePO if err := r.data.DB().WithContext(ctx).Where("room_id = ?", roomID).Order("id DESC").Limit(limit).Find(&rows).Error; err != nil { return nil, err } out := make([]*biz.Message, 0, len(rows)) for i := len(rows) - 1; i >= 0; i-- { out = append(out, toBiz(&rows[i])) } 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 } row := &messagePO{RoomID: roomID, Type: biz.TypeChat, UserID: user.ID, Username: user.Username, Nickname: user.Nickname, Avatar: user.Avatar, Content: content} if err = r.createAndPrune(ctx, row); err != nil { return nil, err } return toBiz(row), 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 } row := &messagePO{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} if err = r.createAndPrune(ctx, row); err != nil { return nil, err } return toBiz(row), nil } func (r *repo) createAndPrune(ctx context.Context, row *messagePO) error { return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error { if err := tx.Create(row).Error; err != nil { return err } var boundary messagePO err := tx.Select("id").Where("room_id = ?", row.RoomID).Order("id DESC").Offset(99).Limit(1).Take(&boundary).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil } if err != nil { return err } return tx.Where("room_id = ? AND id < ?", row.RoomID, boundary.ID).Delete(&messagePO{}).Error }) } 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} }