RoomService
房间业务入口,负责 Room 的创建、查询、加入、离开、关闭、开始。
概述
RoomService 是 Server Application 层的服务,承担"大厅"概念的全部业务:
- 创建房间(带
gameType/maxPlayers/minPlayers/meta) - 加入 / 离开房间(含房主离开自动继承)
- 查询 / 列表 / 关闭房间
- 开始游戏(委托
GameService.startGame)
不负责:
- 游戏规则(Plugin 已封装)
- 直接操作 GameEngine
RoomService 通过 RoomRepository 持久化 Room,通过 GameService 触发对局创建。
CreateRoomOptions
ts
interface CreateRoomOptions {
gameType: string
maxPlayers: number
ownerId?: string
minPlayers?: number
meta?: Record<string, unknown>
}| 字段 | 类型 | 说明 |
|---|---|---|
gameType | string | 游戏类型,需在 PluginRegistry 中已注册 |
maxPlayers | number | 最大玩家数(必须 >= 2) |
ownerId? | string | 房主 ID(实际由 createRoom(options, playerId) 第二参数传入) |
minPlayers? | number | 最少开始人数;缺省取 2;必须 >= 2 |
meta? | Record<string, unknown> | 房间元数据(如房间名、是否允许观战) |
ListRoomsFilter
ts
interface ListRoomsFilter {
gameType?: string
status?: RoomStatus
}| 字段 | 类型 | 说明 |
|---|---|---|
gameType? | string | 按游戏类型过滤 |
status? | RoomStatus | 按房间状态过滤 |
类
RoomService
ts
class RoomService {
constructor(
roomRepository: RoomRepository,
gameService: GameService
)
}方法
createRoom()
ts
async createRoom(
options: CreateRoomOptions,
playerId: string
): Promise<Room>创建房间。
参数
| 名称 | 类型 | 说明 |
|---|---|---|
options | CreateRoomOptions | 房间配置 |
playerId | string | 创建者玩家 ID(自动成为房主并加入房间) |
返回值
Promise<Room>:创建好的房间实例。
行为
- 校验
maxPlayers >= 2,否则抛ServerError('BAD_REQUEST', 'maxPlayers must be >= 2', 400)。 - 校验
minPlayers >= 2(若提供),否则抛ServerError('BAD_REQUEST', 'minPlayers must be >= 2', 400)。 - 构造
Room(id 由newRoomId()生成,ownerId = playerId,status = 'WAITING')。 room.addPlayer(playerId)将创建者加入房间。roomRepository.save(room)。- 日志记录并返回
room。
getRoom()
ts
async getRoom(roomId: string): Promise<Room>查询房间。
参数
| 名称 | 类型 | 说明 |
|---|---|---|
roomId | string | 房间 ID |
返回值
Promise<Room>:房间实例。
行为
- 未找到抛
ServerError('ROOM_NOT_FOUND', 'Room not found: ${roomId}', 404)。
listRooms()
ts
async listRooms(filter?: ListRoomsFilter): Promise<Room[]>列出房间。
参数
| 名称 | 类型 | 说明 |
|---|---|---|
filter? | ListRoomsFilter | 可选过滤条件 |
返回值
Promise<Room[]>:房间数组(未传 filter 时返回全部)。
行为
- 取出
roomRepository.findAll(),按filter.gameType/filter.status过滤。
joinRoom()
ts
async joinRoom(roomId: string, playerId: string): Promise<Room>加入房间。
参数
| 名称 | 类型 | 说明 |
|---|---|---|
roomId | string | 房间 ID |
playerId | string | 玩家 ID |
返回值
Promise<Room>:加入后的房间实例。
行为
- 取出房间;若
status不是WAITING或READY抛PLAYER_NOT_ALLOWED(403)。 - 若
room.isFull抛ServerError('ROOM_FULL', 'Room ${roomId} is full', 409)。 - 若
room.hasPlayer(playerId)直接返回(重复加入幂等)。 room.addPlayer(playerId)+roomRepository.save(room)。
leaveRoom()
ts
async leaveRoom(roomId: string, playerId: string): Promise<Room>离开房间。
参数
| 名称 | 类型 | 说明 |
|---|---|---|
roomId | string | 房间 ID |
playerId | string | 玩家 ID |
返回值
Promise<Room>:离开后的房间实例。
行为
- 取出房间;若玩家不在房间内抛
ServerError('PLAYER_NOT_IN_ROOM', '...', 403)。 room.removePlayer(playerId)。- 房主离开处理:
- 若
room.playerIds.length === 0:room.status = 'CLOSED'。 - 否则若
room.ownerId === playerId:room.setOwner(room.playerIds[0])自动转给剩余首位玩家。
- 若
roomRepository.save(room)。
closeRoom()
ts
async closeRoom(roomId: string): Promise<void>关闭房间。
参数
| 名称 | 类型 | 说明 |
|---|---|---|
roomId | string | 房间 ID |
返回值
Promise<void>
行为
- 取出房间,
room.status = 'CLOSED'+roomRepository.save(room)。
startRoom()
ts
async startRoom(roomId: string): Promise<{ room: Room; gameId: string }>开始游戏:委托 GameService.startGame。
参数
| 名称 | 类型 | 说明 |
|---|---|---|
roomId | string | 房间 ID |
返回值
Promise<{ room: Room; gameId: string }>:房间与新建的会话 ID。
行为
- 取出房间;若
!room.canStart抛ServerError('GAME_NOT_STARTED', '...', 400)。 - 调用
gameService.startGame(room)创建并启动 GameSession。 - 返回
{ room, gameId: session.id }。
权限校验
startRoom 本身不校验调用者是否为房主——这一权限检查由 Controller / Gateway 层完成。例如 RoomGateway.handleStart 会显式检查 room.ownerId === playerId。
示例
创建并加入房间
ts
import { createServer } from 'decklet/server'
const runtime = createServer()
const { roomService } = runtime
// 创建房间
const room = await roomService.createRoom(
{ gameType: 'uno', maxPlayers: 4, minPlayers: 2 },
'p1' // 创建者 = 房主
)
console.log(room.id, room.gameType, room.ownerId, room.status) // '...', 'uno', 'p1', 'WAITING'
// 其他玩家加入
await roomService.joinRoom(room.id, 'p2')
await roomService.joinRoom(room.id, 'p3')
console.log(room.currentPlayers) // 3
// 查询
const found = await roomService.getRoom(room.id)
const waiting = await roomService.listRooms({ status: 'WAITING' })
// 离开(p2 离开)
await roomService.leaveRoom(room.id, 'p2')开始游戏
ts
// 满足 minPlayers 后即可开始(RoomService 不校验房主身份)
const { room: started, gameId } = await roomService.startRoom(room.id)
console.log(started.status) // 'PLAYING'
console.log(started.gameSessionId) // 与 gameId 一致
console.log(gameId) // 'game_xxx'房主自动继承
ts
const room = await roomService.createRoom({ gameType: 'uno', maxPlayers: 4 }, 'p1')
await roomService.joinRoom(room.id, 'p2')
// p1 离开 → 房主自动转给 p2
const after = await roomService.leaveRoom(room.id, 'p1')
console.log(after.ownerId) // 'p2'
// 最后一人离开 → 房间关闭
const empty = await roomService.leaveRoom(after.id, 'p2')
console.log(empty.status) // 'CLOSED'注意事项
Room不等于GameSession:Room 是大厅概念,GameSession 是对局概念;Room 通过gameSessionId与 GameSession 弱关联。RoomService不直接持有 GameEngine / Plugin / Socket 引用,仅通过GameService间接创建对局。- 房主离开的继承策略在
leaveRoom中实现:剩余首位玩家继承;空房间自动CLOSED。 createRoom的ownerId字段在CreateRoomOptions中存在但实际由createRoom(options, playerId)第二参数传入并覆盖。- 房间状态枚举见
enums.ts(详见 session.md)。