feat: v1.1.0 迭代更新
Server: - 添加房间密码验证 POST /rooms/:id/join - 添加玩家踢出 POST /rooms/:id/kick/:playerId - 添加房间过期自动清理(30分钟无活动) - 添加流量统计 GET /traffic - 添加token认证中间件保护写操作API - 房间详情返回玩家列表 Client: - 添加设置持久化(electron-store) - 添加设置页面(玩家名、本地端口、自动重连、托盘最小化) - 添加系统托盘支持(最小化到托盘、右键菜单) - 添加最近连接服务器记录 - 连接成功自动保存服务器地址 - 加入房间自动填充默认端口
This commit is contained in:
@@ -37,6 +37,9 @@ export class Room {
|
||||
public password?: string;
|
||||
public nodeId: string;
|
||||
public createdAt: Date;
|
||||
public lastActivity: Date;
|
||||
public bytesIn: number = 0;
|
||||
public bytesOut: number = 0;
|
||||
public players: Map<string, Player> = new Map();
|
||||
private playerSockets: Map<string, net.Socket> = new Map();
|
||||
|
||||
@@ -61,6 +64,37 @@ export class Room {
|
||||
this.nodeId = options.nodeId;
|
||||
this.password = options.password;
|
||||
this.createdAt = new Date();
|
||||
this.lastActivity = new Date();
|
||||
}
|
||||
|
||||
checkPassword(pwd?: string): boolean {
|
||||
if (!this.password) return true;
|
||||
return this.password === pwd;
|
||||
}
|
||||
|
||||
touch(): void {
|
||||
this.lastActivity = new Date();
|
||||
}
|
||||
|
||||
addTraffic(bytesIn: number, bytesOut: number): void {
|
||||
this.bytesIn += bytesIn;
|
||||
this.bytesOut += bytesOut;
|
||||
this.touch();
|
||||
}
|
||||
|
||||
kickPlayer(playerId: string, reason?: string): boolean {
|
||||
const player = this.players.get(playerId);
|
||||
if (!player) return false;
|
||||
logger.info(`Player ${player.name} kicked from room ${this.name}: ${reason || 'no reason'}`);
|
||||
try { player.socket.destroy(); } catch (e) { /* ignore */ }
|
||||
this.players.delete(playerId);
|
||||
this.playerSockets.delete(playerId);
|
||||
return true;
|
||||
}
|
||||
|
||||
isExpired(maxIdleMs: number): boolean {
|
||||
if (this.currentPlayers > 0) return false;
|
||||
return Date.now() - this.lastActivity.getTime() > maxIdleMs;
|
||||
}
|
||||
|
||||
get currentPlayers(): number {
|
||||
@@ -93,6 +127,14 @@ export class Room {
|
||||
}
|
||||
}
|
||||
|
||||
getPlayerList(): { id: string; name: string; joinedAt: Date }[] {
|
||||
return Array.from(this.players.values()).map(p => ({
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
joinedAt: p.joinedAt,
|
||||
}));
|
||||
}
|
||||
|
||||
toInfo(): RoomInfo {
|
||||
return {
|
||||
id: this.id,
|
||||
@@ -184,4 +226,36 @@ export class RoomManager {
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
getTrafficStats(): { totalBytesIn: number; totalBytesOut: number } {
|
||||
let totalBytesIn = 0;
|
||||
let totalBytesOut = 0;
|
||||
for (const [, room] of this.rooms) {
|
||||
totalBytesIn += room.bytesIn;
|
||||
totalBytesOut += room.bytesOut;
|
||||
}
|
||||
return { totalBytesIn, totalBytesOut };
|
||||
}
|
||||
|
||||
cleanupExpiredRooms(maxIdleMs: number = 30 * 60 * 1000): number {
|
||||
let cleaned = 0;
|
||||
for (const [id, room] of this.rooms) {
|
||||
if (room.isExpired(maxIdleMs)) {
|
||||
logger.info(`Room ${room.name} (${id}) expired, cleaning up`);
|
||||
room.destroy();
|
||||
this.rooms.delete(id);
|
||||
cleaned++;
|
||||
}
|
||||
}
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
startCleanupTimer(intervalMs: number = 5 * 60 * 1000, maxIdleMs: number = 30 * 60 * 1000): void {
|
||||
setInterval(() => {
|
||||
const cleaned = this.cleanupExpiredRooms(maxIdleMs);
|
||||
if (cleaned > 0) {
|
||||
logger.info(`Cleaned up ${cleaned} expired rooms`);
|
||||
}
|
||||
}, intervalMs);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user