| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import type { RoomListItem } from '@music-together/shared' |
| import { EVENTS } from '@music-together/shared' |
| import { config } from '../config.js' |
| import type { TypedServer } from '../middleware/types.js' |
| import { chatRepo } from '../repositories/chatRepository.js' |
| import { roomRepo } from '../repositories/roomRepository.js' |
| import { logger } from '../utils/logger.js' |
| import { cleanupRoom as cleanupAuthRoom } from './authService.js' |
| import { cleanupRoom as cleanupPlayerRoom } from './playerService.js' |
| import { cleanupRoom as cleanupVoteRoom } from './voteService.js' |
| import { cleanupRoomRejoinTickets } from './rejoinTicketService.js' |
|
|
| |
| |
| |
|
|
| |
| const roomDeletionTimers = new Map<string, ReturnType<typeof setTimeout>>() |
|
|
| |
| let broadcastTimer: ReturnType<typeof setTimeout> | null = null |
| let pendingIO: TypedServer | null = null |
|
|
| |
| |
| |
|
|
| export function scheduleDeletion(roomId: string, io?: TypedServer): void { |
| |
| cancelDeletionTimer(roomId) |
|
|
| const room = roomRepo.get(roomId) |
| if (room?.permanent) { |
| logger.info(`永久房间 ${roomId} 已空置,将保留等待用户加入`, { |
| event: 'room.permanent_retained', |
| roomId, |
| }) |
| return |
| } |
|
|
| logger.info(`房间 ${roomId} 已空置,将在 ${config.room.gracePeriodMs / 1000} 秒后删除`, { |
| event: 'room.deletion_scheduled', |
| roomId, |
| gracePeriodMs: config.room.gracePeriodMs, |
| }) |
| const timer = setTimeout(() => { |
| const r = roomRepo.get(roomId) |
| if (r && r.users.length === 0 && !r.permanent) { |
| roomRepo.delete(roomId) |
| chatRepo.deleteRoom(roomId) |
| cleanupPlayerRoom(roomId) |
| cleanupVoteRoom(roomId) |
| cleanupAuthRoom(roomId) |
| cleanupRoomRejoinTickets(roomId) |
| roomDeletionTimers.delete(roomId) |
| logger.info(`空置房间 ${roomId} 已删除`, { event: 'room.deleted', roomId }) |
| |
| if (io) broadcastRoomList(io) |
| } |
| }, config.room.gracePeriodMs) |
| roomDeletionTimers.set(roomId, timer) |
| } |
|
|
| export function cancelDeletionTimer(roomId: string): void { |
| const timer = roomDeletionTimers.get(roomId) |
| if (timer) { |
| clearTimeout(timer) |
| roomDeletionTimers.delete(roomId) |
| logger.info(`用户重新加入,已取消删除房间 ${roomId}`, { event: 'room.deletion_cancelled', roomId }) |
| } |
| } |
|
|
| |
| export function destroyRoom(roomId: string, io: TypedServer): boolean { |
| const room = roomRepo.get(roomId) |
| if (!room) return false |
|
|
| cancelDeletionTimer(roomId) |
| for (const socket of io.getSocketsInRoom(roomId)) { |
| roomRepo.deleteSocketMapping(socket.id) |
| socket.leave(roomId) |
| socket.join('lobby') |
| socket.emit(EVENTS.ROOM_ERROR, { code: 'ROOM_NOT_FOUND', message: '房间已被服务器管理员解散' }) |
| } |
|
|
| roomRepo.delete(roomId) |
| chatRepo.deleteRoom(roomId) |
| cleanupPlayerRoom(roomId) |
| cleanupVoteRoom(roomId) |
| cleanupAuthRoom(roomId) |
| cleanupRoomRejoinTickets(roomId) |
| broadcastRoomList(io) |
| logger.info(`房间 ${roomId} 已被服务器管理员解散`, { event: 'room.dissolved', roomId }) |
| return true |
| } |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| export function broadcastRoomList(io: TypedServer): void { |
| pendingIO = io |
| if (broadcastTimer) return |
| broadcastTimer = setTimeout(() => { |
| broadcastTimer = null |
| if (pendingIO) { |
| const rooms: RoomListItem[] = roomRepo.getPublicLobbyList() |
| pendingIO.to('lobby').emit(EVENTS.ROOM_LIST_UPDATE, rooms) |
| } |
| }, 100) |
| } |
|
|
| |
| |
| |
|
|
| |
| export function clearAllTimers(): void { |
| |
| for (const timer of roomDeletionTimers.values()) clearTimeout(timer) |
| roomDeletionTimers.clear() |
|
|
| |
| if (broadcastTimer) { |
| clearTimeout(broadcastTimer) |
| broadcastTimer = null |
| } |
| pendingIO = null |
|
|
| logger.debug('房间生命周期定时器已全部清理') |
| } |
|
|