diff --git a/internal/domain/room/error.go b/internal/domain/room/error.go index eb880b2..3cde5d1 100644 --- a/internal/domain/room/error.go +++ b/internal/domain/room/error.go @@ -21,11 +21,12 @@ type Error struct { func (e *Error) Error() string { return e.Message } var ( - ErrRateLimited = &Error{RateLimited, "操作过于频繁,请稍后再试。"} - errLinkForbidden = &Error{Forbidden, "临时房间只能点哔哩哔哩的链接。"} - errQueueFull = &Error{QueueFull, fmt.Sprintf("临时房间的播放列表最多 %d 首歌,等播放几首之后再点。", ephemeralQueueLimit)} - errDuplicatePick = &Error{Conflict, "这首歌已经在播放列表里了。"} - errNotInQueue = &Error{NotFound, "这首歌已经不在播放列表里了。"} - errNothingPlaying = &Error{NothingPlaying, "现在没有在放的歌。"} - errDuplicateVote = &Error{Conflict, "你已经投过票了。"} + ErrRateLimited = &Error{RateLimited, "操作过于频繁,请稍后再试。"} + errLinkForbidden = &Error{Forbidden, "临时房间只能点哔哩哔哩的链接。"} + errEphemeralQueueFull = &Error{QueueFull, fmt.Sprintf("临时房间的播放列表最多 %d 首歌,等播放几首之后再点。", ephemeralQueueLimit)} + errPersistentQueueFull = &Error{QueueFull, fmt.Sprintf("播放列表最多 %d 首歌,等播放几首之后再点。", persistentQueueLimit)} + errDuplicatePick = &Error{Conflict, "这首歌已经在播放列表里了。"} + errNotInQueue = &Error{NotFound, "这首歌已经不在播放列表里了。"} + errNothingPlaying = &Error{NothingPlaying, "现在没有在放的歌。"} + errDuplicateVote = &Error{Conflict, "你已经投过票了。"} ) diff --git a/internal/domain/room/queue.go b/internal/domain/room/queue.go index a24d5cc..adc15c3 100644 --- a/internal/domain/room/queue.go +++ b/internal/domain/room/queue.go @@ -40,6 +40,13 @@ func (r *Room) burst() int { return EphemeralBurst } +func (r *Room) queueLimit() (int, *Error) { + if r.info.Kind == Persistent { + return persistentQueueLimit, errPersistentQueueFull + } + return ephemeralQueueLimit, errEphemeralQueueFull +} + func (r *Room) enqueue(b *batch, by member.Member, a Enqueue) (Result, *Error) { if !r.pickLimit.allow(b.now, r.burst(), RefillInterval) { return nil, ErrRateLimited @@ -47,8 +54,8 @@ func (r *Room) enqueue(b *batch, by member.Member, a Enqueue) (Result, *Error) { if a.Track.Source == track.Link && r.info.Kind == Ephemeral { return nil, errLinkForbidden } - if r.info.Kind == Ephemeral && len(r.waiting) >= ephemeralQueueLimit { - return nil, errQueueFull + if limit, full := r.queueLimit(); len(r.waiting) >= limit { + return nil, full } if r.queued(a.Track) { return nil, errDuplicatePick diff --git a/internal/domain/room/queue_test.go b/internal/domain/room/queue_test.go index 8a42dba..398419b 100644 --- a/internal/domain/room/queue_test.go +++ b/internal/domain/room/queue_test.go @@ -34,6 +34,19 @@ func TestEphemeralRoomLimits(t *testing.T) { wantError(t, r.Handle(at(13*time.Minute), enqueue(alice, netease("z"))), QueueFull) } +func TestPersistentRoomQueueLimit(t *testing.T) { + r := newRoom(t, Persistent) + // 每分钟点一首,额度不会用完;第一首成为待播,之后 200 首进入等待队列。 + for i := range 201 { + if res := reply(t, r.Handle(at(time.Duration(i+1)*time.Minute), enqueue(alice, netease(fmt.Sprint(i))))); res.Err != nil { + t.Fatalf("第 %d 首点歌失败:%#v", i+1, res) + } + } + if res := reply(t, r.Handle(at(203*time.Minute), enqueue(alice, netease("z")))); res.Err != errPersistentQueueFull { + t.Errorf("等待队列满 200 首后再点歌 = %#v,期望 errPersistentQueueFull", res.Err) + } +} + func TestRateLimitsByRoomKind(t *testing.T) { cases := []struct { name string diff --git a/internal/domain/room/room.go b/internal/domain/room/room.go index f4f0886..4be5be5 100644 --- a/internal/domain/room/room.go +++ b/internal/domain/room/room.go @@ -16,7 +16,9 @@ const ( EphemeralBurst = 5 persistentBurst = 30 ephemeralQueueLimit = 10 - chatLimit = 500 + // 等待队列每次变化都要整体落盘,并向所有会话广播完整的队列,所以常驻房间也要有上限。 + persistentQueueLimit = 200 + chatLimit = 500 ) // Room 内部没有锁,同一个 Room 的 Handle 必须由调用方串行调用。