重构network库(mvp版本)搭建基础设施和核心接口

定义ITransport/ISerializer/INetworkMessage接口
NetworkIdentity组件
基础事件定义
This commit is contained in:
YHH
2025-08-13 13:07:40 +08:00
parent 25136349ff
commit 62f250b43c
97 changed files with 1877 additions and 16607 deletions

View File

@@ -1,622 +0,0 @@
/**
* 身份验证管理器
*
* 处理客户端身份验证、令牌验证等功能
*/
import { EventEmitter } from 'events';
import { createHash, randomBytes } from 'crypto';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
import { ClientConnection } from '../core/ClientConnection';
/**
* 认证配置
*/
export interface AuthConfig {
/** 令牌过期时间(毫秒) */
tokenExpirationTime?: number;
/** 最大登录尝试次数 */
maxLoginAttempts?: number;
/** 登录尝试重置时间(毫秒) */
loginAttemptResetTime?: number;
/** 是否启用令牌刷新 */
enableTokenRefresh?: boolean;
/** 令牌刷新阈值(毫秒) */
tokenRefreshThreshold?: number;
/** 是否启用IP限制 */
enableIpRestriction?: boolean;
/** 密码哈希算法 */
passwordHashAlgorithm?: 'sha256' | 'sha512';
}
/**
* 用户信息
*/
export interface UserInfo {
/** 用户ID */
id: string;
/** 用户名 */
username: string;
/** 密码哈希 */
passwordHash: string;
/** 用户角色 */
roles: string[];
/** 用户元数据 */
metadata: Record<string, NetworkValue>;
/** 创建时间 */
createdAt: Date;
/** 最后登录时间 */
lastLoginAt?: Date;
/** 是否激活 */
isActive: boolean;
/** 允许的IP地址列表 */
allowedIps?: string[];
}
/**
* 认证令牌
*/
export interface AuthToken {
/** 令牌ID */
id: string;
/** 用户ID */
userId: string;
/** 令牌值 */
token: string;
/** 创建时间 */
createdAt: Date;
/** 过期时间 */
expiresAt: Date;
/** 是否已撤销 */
isRevoked: boolean;
/** 令牌元数据 */
metadata: Record<string, NetworkValue>;
}
/**
* 登录尝试记录
*/
interface LoginAttempt {
/** IP地址 */
ip: string;
/** 用户名 */
username: string;
/** 尝试次数 */
attempts: number;
/** 最后尝试时间 */
lastAttempt: Date;
}
/**
* 认证结果
*/
export interface AuthResult {
/** 是否成功 */
success: boolean;
/** 用户信息 */
user?: UserInfo;
/** 认证令牌 */
token?: AuthToken;
/** 错误信息 */
error?: string;
/** 错误代码 */
errorCode?: string;
}
/**
* 认证管理器事件
*/
export interface AuthManagerEvents {
/** 用户登录成功 */
'login-success': (user: UserInfo, token: AuthToken, clientId: string) => void;
/** 用户登录失败 */
'login-failed': (username: string, reason: string, clientId: string) => void;
/** 用户注销 */
'logout': (userId: string, clientId: string) => void;
/** 令牌过期 */
'token-expired': (userId: string, tokenId: string) => void;
/** 令牌刷新 */
'token-refreshed': (userId: string, oldTokenId: string, newTokenId: string) => void;
/** 认证错误 */
'auth-error': (error: Error, clientId?: string) => void;
}
/**
* 身份验证管理器
*/
export class AuthenticationManager extends EventEmitter {
private config: AuthConfig;
private users = new Map<string, UserInfo>();
private tokens = new Map<string, AuthToken>();
private loginAttempts = new Map<string, LoginAttempt>();
private cleanupTimer: NodeJS.Timeout | null = null;
constructor(config: AuthConfig = {}) {
super();
this.config = {
tokenExpirationTime: 24 * 60 * 60 * 1000, // 24小时
maxLoginAttempts: 5,
loginAttemptResetTime: 15 * 60 * 1000, // 15分钟
enableTokenRefresh: true,
tokenRefreshThreshold: 60 * 60 * 1000, // 1小时
enableIpRestriction: false,
passwordHashAlgorithm: 'sha256',
...config
};
this.initialize();
}
/**
* 注册用户
*/
async registerUser(userData: {
username: string;
password: string;
roles?: string[];
metadata?: Record<string, NetworkValue>;
allowedIps?: string[];
}): Promise<UserInfo> {
const { username, password, roles = ['user'], metadata = {}, allowedIps } = userData;
// 检查用户名是否已存在
if (this.findUserByUsername(username)) {
throw new Error('Username already exists');
}
const userId = this.generateId();
const passwordHash = this.hashPassword(password);
const user: UserInfo = {
id: userId,
username,
passwordHash,
roles,
metadata,
createdAt: new Date(),
isActive: true,
allowedIps
};
this.users.set(userId, user);
console.log(`User registered: ${username} (${userId})`);
return user;
}
/**
* 用户登录
*/
async login(
username: string,
password: string,
client: ClientConnection
): Promise<AuthResult> {
try {
const clientIp = client.remoteAddress;
const attemptKey = `${clientIp}-${username}`;
// 检查登录尝试次数
if (this.isLoginBlocked(attemptKey)) {
const result: AuthResult = {
success: false,
error: 'Too many login attempts. Please try again later.',
errorCode: 'LOGIN_BLOCKED'
};
this.emit('login-failed', username, result.error!, client.id);
return result;
}
// 查找用户
const user = this.findUserByUsername(username);
if (!user || !user.isActive) {
this.recordLoginAttempt(attemptKey);
const result: AuthResult = {
success: false,
error: 'Invalid username or password',
errorCode: 'INVALID_CREDENTIALS'
};
this.emit('login-failed', username, result.error!, client.id);
return result;
}
// 验证密码
const passwordHash = this.hashPassword(password);
if (user.passwordHash !== passwordHash) {
this.recordLoginAttempt(attemptKey);
const result: AuthResult = {
success: false,
error: 'Invalid username or password',
errorCode: 'INVALID_CREDENTIALS'
};
this.emit('login-failed', username, result.error!, client.id);
return result;
}
// IP限制检查
if (this.config.enableIpRestriction && user.allowedIps && user.allowedIps.length > 0) {
if (!user.allowedIps.includes(clientIp)) {
const result: AuthResult = {
success: false,
error: 'Access denied from this IP address',
errorCode: 'IP_RESTRICTED'
};
this.emit('login-failed', username, result.error!, client.id);
return result;
}
}
// 创建认证令牌
const token = this.createToken(user.id);
// 更新用户最后登录时间
user.lastLoginAt = new Date();
// 清除登录尝试记录
this.loginAttempts.delete(attemptKey);
const result: AuthResult = {
success: true,
user,
token
};
console.log(`User logged in: ${username} (${user.id}) from ${clientIp}`);
this.emit('login-success', user, token, client.id);
return result;
} catch (error) {
const result: AuthResult = {
success: false,
error: (error as Error).message,
errorCode: 'INTERNAL_ERROR'
};
this.emit('auth-error', error as Error, client.id);
return result;
}
}
/**
* 用户注销
*/
async logout(tokenValue: string, client: ClientConnection): Promise<boolean> {
try {
const token = this.findTokenByValue(tokenValue);
if (!token) {
return false;
}
// 撤销令牌
token.isRevoked = true;
console.log(`User logged out: ${token.userId} from ${client.remoteAddress}`);
this.emit('logout', token.userId, client.id);
return true;
} catch (error) {
this.emit('auth-error', error as Error, client.id);
return false;
}
}
/**
* 验证令牌
*/
async validateToken(tokenValue: string): Promise<AuthResult> {
try {
const token = this.findTokenByValue(tokenValue);
if (!token || token.isRevoked) {
return {
success: false,
error: 'Invalid token',
errorCode: 'INVALID_TOKEN'
};
}
if (token.expiresAt < new Date()) {
token.isRevoked = true;
this.emit('token-expired', token.userId, token.id);
return {
success: false,
error: 'Token expired',
errorCode: 'TOKEN_EXPIRED'
};
}
const user = this.users.get(token.userId);
if (!user || !user.isActive) {
return {
success: false,
error: 'User not found or inactive',
errorCode: 'USER_NOT_FOUND'
};
}
return {
success: true,
user,
token
};
} catch (error) {
this.emit('auth-error', error as Error);
return {
success: false,
error: (error as Error).message,
errorCode: 'INTERNAL_ERROR'
};
}
}
/**
* 刷新令牌
*/
async refreshToken(tokenValue: string): Promise<AuthResult> {
try {
const validationResult = await this.validateToken(tokenValue);
if (!validationResult.success || !validationResult.user || !validationResult.token) {
return validationResult;
}
const token = validationResult.token;
const timeUntilExpiration = token.expiresAt.getTime() - Date.now();
// 检查是否需要刷新
if (timeUntilExpiration > this.config.tokenRefreshThreshold!) {
return validationResult; // 不需要刷新
}
// 创建新令牌
const newToken = this.createToken(token.userId, token.metadata);
// 撤销旧令牌
token.isRevoked = true;
console.log(`Token refreshed for user: ${token.userId}`);
this.emit('token-refreshed', token.userId, token.id, newToken.id);
return {
success: true,
user: validationResult.user,
token: newToken
};
} catch (error) {
this.emit('auth-error', error as Error);
return {
success: false,
error: (error as Error).message,
errorCode: 'INTERNAL_ERROR'
};
}
}
/**
* 获取用户信息
*/
getUserById(userId: string): UserInfo | undefined {
return this.users.get(userId);
}
/**
* 获取用户信息(通过用户名)
*/
getUserByUsername(username: string): UserInfo | undefined {
return this.findUserByUsername(username);
}
/**
* 更新用户信息
*/
async updateUser(userId: string, updates: Partial<UserInfo>): Promise<boolean> {
const user = this.users.get(userId);
if (!user) {
return false;
}
// 不允许更新某些字段
const { id, createdAt, ...allowedUpdates } = updates as any;
Object.assign(user, allowedUpdates);
return true;
}
/**
* 撤销所有用户令牌
*/
async revokeAllUserTokens(userId: string): Promise<number> {
let revokedCount = 0;
for (const token of this.tokens.values()) {
if (token.userId === userId && !token.isRevoked) {
token.isRevoked = true;
revokedCount++;
}
}
return revokedCount;
}
/**
* 获取活跃令牌数量
*/
getActiveTokenCount(): number {
return Array.from(this.tokens.values())
.filter(token => !token.isRevoked && token.expiresAt > new Date()).length;
}
/**
* 清理过期令牌和登录尝试记录
*/
cleanup(): void {
const now = new Date();
let cleanedTokens = 0;
let cleanedAttempts = 0;
// 清理过期令牌
for (const [tokenId, token] of this.tokens.entries()) {
if (token.expiresAt < now || token.isRevoked) {
this.tokens.delete(tokenId);
cleanedTokens++;
}
}
// 清理过期的登录尝试记录
const resetTime = this.config.loginAttemptResetTime!;
for (const [attemptKey, attempt] of this.loginAttempts.entries()) {
if (now.getTime() - attempt.lastAttempt.getTime() > resetTime) {
this.loginAttempts.delete(attemptKey);
cleanedAttempts++;
}
}
if (cleanedTokens > 0 || cleanedAttempts > 0) {
console.log(`Auth cleanup: ${cleanedTokens} tokens, ${cleanedAttempts} login attempts`);
}
}
/**
* 销毁认证管理器
*/
destroy(): void {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
this.users.clear();
this.tokens.clear();
this.loginAttempts.clear();
this.removeAllListeners();
}
/**
* 初始化
*/
private initialize(): void {
// 启动清理定时器(每小时清理一次)
this.cleanupTimer = setInterval(() => {
this.cleanup();
}, 60 * 60 * 1000);
}
/**
* 查找用户(通过用户名)
*/
private findUserByUsername(username: string): UserInfo | undefined {
return Array.from(this.users.values())
.find(user => user.username === username);
}
/**
* 查找令牌(通过令牌值)
*/
private findTokenByValue(tokenValue: string): AuthToken | undefined {
return Array.from(this.tokens.values())
.find(token => token.token === tokenValue);
}
/**
* 生成ID
*/
private generateId(): string {
return randomBytes(16).toString('hex');
}
/**
* 哈希密码
*/
private hashPassword(password: string): string {
return createHash(this.config.passwordHashAlgorithm!)
.update(password)
.digest('hex');
}
/**
* 创建认证令牌
*/
private createToken(userId: string, metadata: Record<string, NetworkValue> = {}): AuthToken {
const tokenId = this.generateId();
const tokenValue = randomBytes(32).toString('hex');
const now = new Date();
const expiresAt = new Date(now.getTime() + this.config.tokenExpirationTime!);
const token: AuthToken = {
id: tokenId,
userId,
token: tokenValue,
createdAt: now,
expiresAt,
isRevoked: false,
metadata
};
this.tokens.set(tokenId, token);
return token;
}
/**
* 检查登录是否被阻止
*/
private isLoginBlocked(attemptKey: string): boolean {
const attempt = this.loginAttempts.get(attemptKey);
if (!attempt) {
return false;
}
const now = new Date();
const resetTime = this.config.loginAttemptResetTime!;
// 检查重置时间
if (now.getTime() - attempt.lastAttempt.getTime() > resetTime) {
this.loginAttempts.delete(attemptKey);
return false;
}
return attempt.attempts >= this.config.maxLoginAttempts!;
}
/**
* 记录登录尝试
*/
private recordLoginAttempt(attemptKey: string): void {
const now = new Date();
const [ip, username] = attemptKey.split('-', 2);
const existingAttempt = this.loginAttempts.get(attemptKey);
if (existingAttempt) {
// 检查是否需要重置
if (now.getTime() - existingAttempt.lastAttempt.getTime() > this.config.loginAttemptResetTime!) {
existingAttempt.attempts = 1;
} else {
existingAttempt.attempts++;
}
existingAttempt.lastAttempt = now;
} else {
this.loginAttempts.set(attemptKey, {
ip,
username,
attempts: 1,
lastAttempt: now
});
}
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof AuthManagerEvents>(event: K, listener: AuthManagerEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof AuthManagerEvents>(event: K, ...args: Parameters<AuthManagerEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -1,684 +0,0 @@
/**
* 权限管理器
*
* 处理用户权限、角色管理、访问控制等功能
*/
import { EventEmitter } from 'events';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
import { UserInfo } from './AuthenticationManager';
/**
* 权限类型
*/
export type Permission = string;
/**
* 角色定义
*/
export interface Role {
/** 角色ID */
id: string;
/** 角色名称 */
name: string;
/** 角色描述 */
description?: string;
/** 权限列表 */
permissions: Permission[];
/** 父角色ID */
parentRoleId?: string;
/** 是否系统角色 */
isSystemRole: boolean;
/** 角色元数据 */
metadata: Record<string, NetworkValue>;
/** 创建时间 */
createdAt: Date;
}
/**
* 权限检查上下文
*/
export interface PermissionContext {
/** 用户ID */
userId: string;
/** 用户角色 */
userRoles: string[];
/** 请求的权限 */
permission: Permission;
/** 资源ID可选 */
resourceId?: string;
/** 附加上下文数据 */
context?: Record<string, NetworkValue>;
}
/**
* 权限检查结果
*/
export interface PermissionResult {
/** 是否允许 */
granted: boolean;
/** 原因 */
reason?: string;
/** 匹配的角色 */
matchingRole?: string;
/** 使用的权限 */
usedPermission?: Permission;
}
/**
* 权限管理器配置
*/
export interface AuthorizationConfig {
/** 是否启用权限继承 */
enableInheritance?: boolean;
/** 是否启用权限缓存 */
enableCache?: boolean;
/** 缓存过期时间(毫秒) */
cacheExpirationTime?: number;
/** 默认权限策略 */
defaultPolicy?: 'deny' | 'allow';
}
/**
* 权限管理器事件
*/
export interface AuthorizationEvents {
/** 权限被授予 */
'permission-granted': (context: PermissionContext, result: PermissionResult) => void;
/** 权限被拒绝 */
'permission-denied': (context: PermissionContext, result: PermissionResult) => void;
/** 角色创建 */
'role-created': (role: Role) => void;
/** 角色更新 */
'role-updated': (roleId: string, updates: Partial<Role>) => void;
/** 角色删除 */
'role-deleted': (roleId: string) => void;
/** 权限错误 */
'authorization-error': (error: Error, context?: PermissionContext) => void;
}
/**
* 权限缓存项
*/
interface CacheItem {
result: PermissionResult;
expiresAt: Date;
}
/**
* 预定义权限
*/
export const Permissions = {
// 系统权限
SYSTEM_ADMIN: 'system:admin',
SYSTEM_CONFIG: 'system:config',
// 用户管理权限
USER_CREATE: 'user:create',
USER_READ: 'user:read',
USER_UPDATE: 'user:update',
USER_DELETE: 'user:delete',
USER_MANAGE_ROLES: 'user:manage-roles',
// 房间权限
ROOM_CREATE: 'room:create',
ROOM_JOIN: 'room:join',
ROOM_LEAVE: 'room:leave',
ROOM_MANAGE: 'room:manage',
ROOM_KICK_PLAYERS: 'room:kick-players',
// 网络权限
NETWORK_SEND_RPC: 'network:send-rpc',
NETWORK_SYNC_VARS: 'network:sync-vars',
NETWORK_BROADCAST: 'network:broadcast',
// 聊天权限
CHAT_SEND: 'chat:send',
CHAT_MODERATE: 'chat:moderate',
CHAT_PRIVATE: 'chat:private',
// 文件权限
FILE_UPLOAD: 'file:upload',
FILE_DOWNLOAD: 'file:download',
FILE_DELETE: 'file:delete'
} as const;
/**
* 预定义角色
*/
export const SystemRoles = {
ADMIN: 'admin',
MODERATOR: 'moderator',
USER: 'user',
GUEST: 'guest'
} as const;
/**
* 权限管理器
*/
export class AuthorizationManager extends EventEmitter {
private config: AuthorizationConfig;
private roles = new Map<string, Role>();
private permissionCache = new Map<string, CacheItem>();
private cleanupTimer: NodeJS.Timeout | null = null;
constructor(config: AuthorizationConfig = {}) {
super();
this.config = {
enableInheritance: true,
enableCache: true,
cacheExpirationTime: 5 * 60 * 1000, // 5分钟
defaultPolicy: 'deny',
...config
};
this.initialize();
}
/**
* 创建角色
*/
async createRole(roleData: {
id: string;
name: string;
description?: string;
permissions: Permission[];
parentRoleId?: string;
metadata?: Record<string, NetworkValue>;
}): Promise<Role> {
const { id, name, description, permissions, parentRoleId, metadata = {} } = roleData;
if (this.roles.has(id)) {
throw new Error(`Role with id "${id}" already exists`);
}
// 验证父角色是否存在
if (parentRoleId && !this.roles.has(parentRoleId)) {
throw new Error(`Parent role "${parentRoleId}" not found`);
}
const role: Role = {
id,
name,
description,
permissions: [...permissions],
parentRoleId,
isSystemRole: false,
metadata,
createdAt: new Date()
};
this.roles.set(id, role);
this.clearPermissionCache(); // 清除缓存
console.log(`Role created: ${name} (${id})`);
this.emit('role-created', role);
return role;
}
/**
* 获取角色
*/
getRole(roleId: string): Role | undefined {
return this.roles.get(roleId);
}
/**
* 获取所有角色
*/
getAllRoles(): Role[] {
return Array.from(this.roles.values());
}
/**
* 更新角色
*/
async updateRole(roleId: string, updates: Partial<Role>): Promise<boolean> {
const role = this.roles.get(roleId);
if (!role) {
return false;
}
// 系统角色不允许修改某些字段
if (role.isSystemRole) {
const { permissions, parentRoleId, ...allowedUpdates } = updates;
Object.assign(role, allowedUpdates);
} else {
// 不允许更新某些字段
const { id, createdAt, isSystemRole, ...allowedUpdates } = updates as any;
Object.assign(role, allowedUpdates);
}
this.clearPermissionCache(); // 清除缓存
console.log(`Role updated: ${role.name} (${roleId})`);
this.emit('role-updated', roleId, updates);
return true;
}
/**
* 删除角色
*/
async deleteRole(roleId: string): Promise<boolean> {
const role = this.roles.get(roleId);
if (!role) {
return false;
}
if (role.isSystemRole) {
throw new Error('Cannot delete system role');
}
// 检查是否有子角色依赖此角色
const childRoles = Array.from(this.roles.values())
.filter(r => r.parentRoleId === roleId);
if (childRoles.length > 0) {
throw new Error(`Cannot delete role "${roleId}": ${childRoles.length} child roles depend on it`);
}
this.roles.delete(roleId);
this.clearPermissionCache(); // 清除缓存
console.log(`Role deleted: ${role.name} (${roleId})`);
this.emit('role-deleted', roleId);
return true;
}
/**
* 检查权限
*/
async checkPermission(context: PermissionContext): Promise<PermissionResult> {
try {
// 检查缓存
const cacheKey = this.getCacheKey(context);
if (this.config.enableCache) {
const cached = this.permissionCache.get(cacheKey);
if (cached && cached.expiresAt > new Date()) {
return cached.result;
}
}
const result = await this.performPermissionCheck(context);
// 缓存结果
if (this.config.enableCache) {
const expiresAt = new Date(Date.now() + this.config.cacheExpirationTime!);
this.permissionCache.set(cacheKey, { result, expiresAt });
}
// 触发事件
if (result.granted) {
this.emit('permission-granted', context, result);
} else {
this.emit('permission-denied', context, result);
}
return result;
} catch (error) {
this.emit('authorization-error', error as Error, context);
return {
granted: this.config.defaultPolicy === 'allow',
reason: `Authorization error: ${(error as Error).message}`
};
}
}
/**
* 检查用户是否有权限
*/
async hasPermission(user: UserInfo, permission: Permission, resourceId?: string): Promise<boolean> {
const context: PermissionContext = {
userId: user.id,
userRoles: user.roles,
permission,
resourceId
};
const result = await this.checkPermission(context);
return result.granted;
}
/**
* 获取用户的所有权限
*/
async getUserPermissions(user: UserInfo): Promise<Permission[]> {
const permissions = new Set<Permission>();
for (const roleId of user.roles) {
const rolePermissions = await this.getRolePermissions(roleId);
rolePermissions.forEach(p => permissions.add(p));
}
return Array.from(permissions);
}
/**
* 获取角色的所有权限(包括继承的权限)
*/
async getRolePermissions(roleId: string): Promise<Permission[]> {
const permissions = new Set<Permission>();
const visited = new Set<string>();
const collectPermissions = (currentRoleId: string) => {
if (visited.has(currentRoleId)) {
return; // 防止循环引用
}
visited.add(currentRoleId);
const role = this.roles.get(currentRoleId);
if (!role) {
return;
}
// 添加当前角色的权限
role.permissions.forEach(p => permissions.add(p));
// 递归添加父角色的权限
if (this.config.enableInheritance && role.parentRoleId) {
collectPermissions(role.parentRoleId);
}
};
collectPermissions(roleId);
return Array.from(permissions);
}
/**
* 为角色添加权限
*/
async addPermissionToRole(roleId: string, permission: Permission): Promise<boolean> {
const role = this.roles.get(roleId);
if (!role) {
return false;
}
if (!role.permissions.includes(permission)) {
role.permissions.push(permission);
this.clearPermissionCache();
console.log(`Permission "${permission}" added to role "${roleId}"`);
}
return true;
}
/**
* 从角色移除权限
*/
async removePermissionFromRole(roleId: string, permission: Permission): Promise<boolean> {
const role = this.roles.get(roleId);
if (!role) {
return false;
}
const index = role.permissions.indexOf(permission);
if (index !== -1) {
role.permissions.splice(index, 1);
this.clearPermissionCache();
console.log(`Permission "${permission}" removed from role "${roleId}"`);
}
return true;
}
/**
* 检查用户是否有指定角色
*/
hasRole(user: UserInfo, roleId: string): boolean {
return user.roles.includes(roleId);
}
/**
* 为用户添加角色
*/
async addRoleToUser(user: UserInfo, roleId: string): Promise<boolean> {
if (!this.roles.has(roleId)) {
return false;
}
if (!user.roles.includes(roleId)) {
user.roles.push(roleId);
this.clearUserPermissionCache(user.id);
console.log(`Role "${roleId}" added to user "${user.id}"`);
}
return true;
}
/**
* 从用户移除角色
*/
async removeRoleFromUser(user: UserInfo, roleId: string): Promise<boolean> {
const index = user.roles.indexOf(roleId);
if (index !== -1) {
user.roles.splice(index, 1);
this.clearUserPermissionCache(user.id);
console.log(`Role "${roleId}" removed from user "${user.id}"`);
return true;
}
return false;
}
/**
* 清除权限缓存
*/
clearPermissionCache(): void {
this.permissionCache.clear();
}
/**
* 清除指定用户的权限缓存
*/
clearUserPermissionCache(userId: string): void {
const keysToDelete: string[] = [];
for (const [key] of this.permissionCache) {
if (key.startsWith(`${userId}:`)) {
keysToDelete.push(key);
}
}
keysToDelete.forEach(key => this.permissionCache.delete(key));
}
/**
* 销毁权限管理器
*/
destroy(): void {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
this.roles.clear();
this.permissionCache.clear();
this.removeAllListeners();
}
/**
* 初始化
*/
private initialize(): void {
// 创建系统角色
this.createSystemRoles();
// 启动缓存清理定时器每30分钟清理一次
if (this.config.enableCache) {
this.cleanupTimer = setInterval(() => {
this.cleanupCache();
}, 30 * 60 * 1000);
}
}
/**
* 创建系统角色
*/
private createSystemRoles(): void {
// 管理员角色
const adminRole: Role = {
id: SystemRoles.ADMIN,
name: 'Administrator',
description: 'Full system access',
permissions: Object.values(Permissions),
isSystemRole: true,
metadata: {},
createdAt: new Date()
};
// 版主角色
const moderatorRole: Role = {
id: SystemRoles.MODERATOR,
name: 'Moderator',
description: 'Room and user management',
permissions: [
Permissions.USER_READ,
Permissions.ROOM_CREATE,
Permissions.ROOM_JOIN,
Permissions.ROOM_MANAGE,
Permissions.ROOM_KICK_PLAYERS,
Permissions.NETWORK_SEND_RPC,
Permissions.NETWORK_SYNC_VARS,
Permissions.CHAT_SEND,
Permissions.CHAT_MODERATE,
Permissions.CHAT_PRIVATE
],
parentRoleId: SystemRoles.USER,
isSystemRole: true,
metadata: {},
createdAt: new Date()
};
// 普通用户角色
const userRole: Role = {
id: SystemRoles.USER,
name: 'User',
description: 'Basic user permissions',
permissions: [
Permissions.ROOM_JOIN,
Permissions.ROOM_LEAVE,
Permissions.NETWORK_SEND_RPC,
Permissions.NETWORK_SYNC_VARS,
Permissions.CHAT_SEND,
Permissions.FILE_DOWNLOAD
],
parentRoleId: SystemRoles.GUEST,
isSystemRole: true,
metadata: {},
createdAt: new Date()
};
// 访客角色
const guestRole: Role = {
id: SystemRoles.GUEST,
name: 'Guest',
description: 'Limited access for guests',
permissions: [
Permissions.ROOM_JOIN
],
isSystemRole: true,
metadata: {},
createdAt: new Date()
};
this.roles.set(adminRole.id, adminRole);
this.roles.set(moderatorRole.id, moderatorRole);
this.roles.set(userRole.id, userRole);
this.roles.set(guestRole.id, guestRole);
console.log('System roles created');
}
/**
* 执行权限检查
*/
private async performPermissionCheck(context: PermissionContext): Promise<PermissionResult> {
// 获取用户的所有角色权限
const userPermissions = new Set<Permission>();
for (const roleId of context.userRoles) {
const rolePermissions = await this.getRolePermissions(roleId);
rolePermissions.forEach(p => userPermissions.add(p));
}
// 直接权限匹配
if (userPermissions.has(context.permission)) {
return {
granted: true,
reason: 'Direct permission match',
usedPermission: context.permission
};
}
// 通配符权限匹配
const wildcardPermissions = Array.from(userPermissions)
.filter(p => p.endsWith('*'));
for (const wildcardPerm of wildcardPermissions) {
const prefix = wildcardPerm.slice(0, -1);
if (context.permission.startsWith(prefix)) {
return {
granted: true,
reason: 'Wildcard permission match',
usedPermission: wildcardPerm
};
}
}
// 如果没有匹配的权限
return {
granted: this.config.defaultPolicy === 'allow',
reason: this.config.defaultPolicy === 'allow'
? 'Default allow policy'
: 'No matching permissions found'
};
}
/**
* 获取缓存键
*/
private getCacheKey(context: PermissionContext): string {
const roleString = context.userRoles.sort().join(',');
const resourcePart = context.resourceId ? `:${context.resourceId}` : '';
return `${context.userId}:${roleString}:${context.permission}${resourcePart}`;
}
/**
* 清理过期缓存
*/
private cleanupCache(): void {
const now = new Date();
let cleanedCount = 0;
for (const [key, item] of this.permissionCache.entries()) {
if (item.expiresAt < now) {
this.permissionCache.delete(key);
cleanedCount++;
}
}
if (cleanedCount > 0) {
console.log(`Permission cache cleanup: ${cleanedCount} entries removed`);
}
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof AuthorizationEvents>(event: K, listener: AuthorizationEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof AuthorizationEvents>(event: K, ...args: Parameters<AuthorizationEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -1,6 +0,0 @@
/**
* 认证系统导出
*/
export * from './AuthenticationManager';
export * from './AuthorizationManager';

View File

@@ -1,478 +0,0 @@
/**
* 客户端连接管理
*/
import { EventEmitter } from 'events';
import { NetworkValue, NetworkMessage } from '@esengine/ecs-framework-network-shared';
import { TransportMessage } from './Transport';
/**
* 客户端连接状态
*/
export enum ClientConnectionState {
/** 连接中 */
CONNECTING = 'connecting',
/** 已连接 */
CONNECTED = 'connected',
/** 认证中 */
AUTHENTICATING = 'authenticating',
/** 已认证 */
AUTHENTICATED = 'authenticated',
/** 断开连接中 */
DISCONNECTING = 'disconnecting',
/** 已断开 */
DISCONNECTED = 'disconnected',
/** 错误状态 */
ERROR = 'error'
}
/**
* 客户端权限
*/
export interface ClientPermissions {
/** 是否可以加入房间 */
canJoinRooms?: boolean;
/** 是否可以创建房间 */
canCreateRooms?: boolean;
/** 是否可以发送RPC */
canSendRpc?: boolean;
/** 是否可以同步变量 */
canSyncVars?: boolean;
/** 自定义权限 */
customPermissions?: Record<string, boolean>;
}
/**
* 客户端连接事件
*/
export interface ClientConnectionEvents {
/** 状态变化 */
'state-changed': (oldState: ClientConnectionState, newState: ClientConnectionState) => void;
/** 收到消息 */
'message': (message: TransportMessage) => void;
/** 连接错误 */
'error': (error: Error) => void;
/** 连接超时 */
'timeout': () => void;
/** 身份验证成功 */
'authenticated': (userData: Record<string, NetworkValue>) => void;
/** 身份验证失败 */
'authentication-failed': (reason: string) => void;
}
/**
* 客户端统计信息
*/
export interface ClientStats {
/** 消息发送数 */
messagesSent: number;
/** 消息接收数 */
messagesReceived: number;
/** 字节发送数 */
bytesSent: number;
/** 字节接收数 */
bytesReceived: number;
/** 最后活跃时间 */
lastActivity: Date;
/** 连接时长(毫秒) */
connectionDuration: number;
}
/**
* 客户端连接管理类
*/
export class ClientConnection extends EventEmitter {
/** 连接ID */
public readonly id: string;
/** 客户端IP地址 */
public readonly remoteAddress: string;
/** 连接创建时间 */
public readonly connectedAt: Date;
/** 当前状态 */
private _state: ClientConnectionState = ClientConnectionState.CONNECTING;
/** 用户数据 */
private _userData: Record<string, NetworkValue> = {};
/** 权限信息 */
private _permissions: ClientPermissions = {};
/** 所在房间ID */
private _currentRoomId: string | null = null;
/** 统计信息 */
private _stats: ClientStats;
/** 最后活跃时间 */
private _lastActivity: Date;
/** 超时定时器 */
private _timeoutTimer: NodeJS.Timeout | null = null;
/** 连接超时时间(毫秒) */
private _connectionTimeout: number;
/** 发送消息回调 */
private _sendMessageCallback: (message: TransportMessage) => Promise<boolean>;
constructor(
id: string,
remoteAddress: string,
sendMessageCallback: (message: TransportMessage) => Promise<boolean>,
options: {
connectionTimeout?: number;
userData?: Record<string, NetworkValue>;
permissions?: ClientPermissions;
} = {}
) {
super();
this.id = id;
this.remoteAddress = remoteAddress;
this.connectedAt = new Date();
this._lastActivity = new Date();
this._connectionTimeout = options.connectionTimeout || 60000; // 1分钟
this._sendMessageCallback = sendMessageCallback;
if (options.userData) {
this._userData = { ...options.userData };
}
if (options.permissions) {
this._permissions = { ...options.permissions };
}
this._stats = {
messagesSent: 0,
messagesReceived: 0,
bytesSent: 0,
bytesReceived: 0,
lastActivity: this._lastActivity,
connectionDuration: 0
};
this.setState(ClientConnectionState.CONNECTED);
this.startTimeout();
}
/**
* 获取当前状态
*/
get state(): ClientConnectionState {
return this._state;
}
/**
* 获取用户数据
*/
get userData(): Readonly<Record<string, NetworkValue>> {
return this._userData;
}
/**
* 获取权限信息
*/
get permissions(): Readonly<ClientPermissions> {
return this._permissions;
}
/**
* 获取当前房间ID
*/
get currentRoomId(): string | null {
return this._currentRoomId;
}
/**
* 获取统计信息
*/
get stats(): Readonly<ClientStats> {
this._stats.connectionDuration = Date.now() - this.connectedAt.getTime();
this._stats.lastActivity = this._lastActivity;
return this._stats;
}
/**
* 获取最后活跃时间
*/
get lastActivity(): Date {
return this._lastActivity;
}
/**
* 是否已连接
*/
get isConnected(): boolean {
return this._state === ClientConnectionState.CONNECTED ||
this._state === ClientConnectionState.AUTHENTICATED;
}
/**
* 是否已认证
*/
get isAuthenticated(): boolean {
return this._state === ClientConnectionState.AUTHENTICATED;
}
/**
* 发送消息
*/
async sendMessage(message: TransportMessage): Promise<boolean> {
if (!this.isConnected) {
return false;
}
try {
const success = await this._sendMessageCallback(message);
if (success) {
this._stats.messagesSent++;
const messageSize = JSON.stringify(message).length;
this._stats.bytesSent += messageSize;
this.updateActivity();
}
return success;
} catch (error) {
this.handleError(error as Error);
return false;
}
}
/**
* 处理接收到的消息
*/
handleMessage(message: TransportMessage): void {
if (!this.isConnected) {
return;
}
this._stats.messagesReceived++;
const messageSize = JSON.stringify(message).length;
this._stats.bytesReceived += messageSize;
this.updateActivity();
this.emit('message', message);
}
/**
* 设置用户数据
*/
setUserData(key: string, value: NetworkValue): void {
this._userData[key] = value;
}
/**
* 获取用户数据
*/
getUserData<T extends NetworkValue = NetworkValue>(key: string): T | undefined {
return this._userData[key] as T;
}
/**
* 批量设置用户数据
*/
setUserDataBatch(data: Record<string, NetworkValue>): void {
Object.assign(this._userData, data);
}
/**
* 设置权限
*/
setPermission(permission: keyof ClientPermissions, value: boolean): void {
(this._permissions as any)[permission] = value;
}
/**
* 检查权限
*/
hasPermission(permission: keyof ClientPermissions): boolean {
return (this._permissions as any)[permission] || false;
}
/**
* 设置自定义权限
*/
setCustomPermission(permission: string, value: boolean): void {
if (!this._permissions.customPermissions) {
this._permissions.customPermissions = {};
}
this._permissions.customPermissions[permission] = value;
}
/**
* 检查自定义权限
*/
hasCustomPermission(permission: string): boolean {
return this._permissions.customPermissions?.[permission] || false;
}
/**
* 进行身份认证
*/
async authenticate(credentials: Record<string, NetworkValue>): Promise<boolean> {
if (this._state !== ClientConnectionState.CONNECTED) {
return false;
}
this.setState(ClientConnectionState.AUTHENTICATING);
try {
// 这里可以添加实际的认证逻辑
// 目前简单地认为所有认证都成功
this.setUserDataBatch(credentials);
this.setState(ClientConnectionState.AUTHENTICATED);
this.emit('authenticated', credentials);
return true;
} catch (error) {
this.setState(ClientConnectionState.CONNECTED);
this.emit('authentication-failed', (error as Error).message);
return false;
}
}
/**
* 加入房间
*/
joinRoom(roomId: string): void {
this._currentRoomId = roomId;
}
/**
* 离开房间
*/
leaveRoom(): void {
this._currentRoomId = null;
}
/**
* 断开连接
*/
disconnect(reason?: string): void {
if (this._state === ClientConnectionState.DISCONNECTED) {
return;
}
this.setState(ClientConnectionState.DISCONNECTING);
this.stopTimeout();
// 发送断开连接消息
this.sendMessage({
type: 'system',
data: {
action: 'disconnect',
reason: reason || 'server-disconnect'
}
}).finally(() => {
this.setState(ClientConnectionState.DISCONNECTED);
});
}
/**
* 更新活跃时间
*/
updateActivity(): void {
this._lastActivity = new Date();
this.resetTimeout();
}
/**
* 设置连接状态
*/
private setState(newState: ClientConnectionState): void {
const oldState = this._state;
if (oldState !== newState) {
this._state = newState;
this.emit('state-changed', oldState, newState);
}
}
/**
* 处理错误
*/
private handleError(error: Error): void {
this.setState(ClientConnectionState.ERROR);
this.emit('error', error);
}
/**
* 启动超时检测
*/
private startTimeout(): void {
this.resetTimeout();
}
/**
* 重置超时定时器
*/
private resetTimeout(): void {
this.stopTimeout();
if (this._connectionTimeout > 0) {
this._timeoutTimer = setTimeout(() => {
this.handleTimeout();
}, this._connectionTimeout);
}
}
/**
* 停止超时检测
*/
private stopTimeout(): void {
if (this._timeoutTimer) {
clearTimeout(this._timeoutTimer);
this._timeoutTimer = null;
}
}
/**
* 处理超时
*/
private handleTimeout(): void {
this.emit('timeout');
this.disconnect('timeout');
}
/**
* 销毁连接
*/
destroy(): void {
this.stopTimeout();
this.removeAllListeners();
this.setState(ClientConnectionState.DISCONNECTED);
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof ClientConnectionEvents>(event: K, listener: ClientConnectionEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof ClientConnectionEvents>(event: K, ...args: Parameters<ClientConnectionEvents[K]>): boolean {
return super.emit(event, ...args);
}
/**
* 序列化连接信息
*/
toJSON(): object {
return {
id: this.id,
remoteAddress: this.remoteAddress,
state: this._state,
connectedAt: this.connectedAt.toISOString(),
lastActivity: this._lastActivity.toISOString(),
currentRoomId: this._currentRoomId,
userData: this._userData,
permissions: this._permissions,
stats: this.stats
};
}
}

View File

@@ -1,602 +0,0 @@
/**
* HTTP 传输层实现
*
* 用于处理 REST API 请求和长轮询连接
*/
import { createServer, IncomingMessage, ServerResponse, Server as HttpServer } from 'http';
import { parse as parseUrl } from 'url';
import { v4 as uuidv4 } from 'uuid';
import { Transport, TransportConfig, ClientConnectionInfo, TransportMessage } from './Transport';
/**
* HTTP 传输配置
*/
export interface HttpTransportConfig extends TransportConfig {
/** API 路径前缀 */
apiPrefix?: string;
/** 最大请求大小(字节) */
maxRequestSize?: number;
/** 长轮询超时(毫秒) */
longPollTimeout?: number;
/** 是否启用 CORS */
enableCors?: boolean;
/** 允许的域名 */
corsOrigins?: string[];
}
/**
* HTTP 请求上下文
*/
interface HttpRequestContext {
/** 请求ID */
id: string;
/** HTTP 请求 */
request: IncomingMessage;
/** HTTP 响应 */
response: ServerResponse;
/** 解析后的URL */
parsedUrl: any;
/** 请求体数据 */
body?: string;
/** 查询参数 */
query: Record<string, string>;
}
/**
* HTTP 客户端连接信息(用于长轮询)
*/
interface HttpConnectionInfo extends ClientConnectionInfo {
/** 长轮询响应对象 */
longPollResponse?: ServerResponse;
/** 消息队列 */
messageQueue: TransportMessage[];
/** 长轮询超时定时器 */
longPollTimer?: NodeJS.Timeout;
}
/**
* HTTP 传输层实现
*/
export class HttpTransport extends Transport {
private httpServer: HttpServer | null = null;
private httpConnections = new Map<string, HttpConnectionInfo>();
protected override config: HttpTransportConfig;
constructor(config: HttpTransportConfig) {
super(config);
this.config = {
apiPrefix: '/api',
maxRequestSize: 1024 * 1024, // 1MB
longPollTimeout: 30000, // 30秒
enableCors: true,
corsOrigins: ['*'],
heartbeatInterval: 60000,
connectionTimeout: 120000,
maxConnections: 1000,
...config
};
}
/**
* 启动 HTTP 服务器
*/
async start(): Promise<void> {
if (this.isRunning) {
throw new Error('HTTP transport is already running');
}
try {
this.httpServer = createServer((req, res) => {
this.handleHttpRequest(req, res);
});
this.httpServer.on('error', (error: Error) => {
this.handleError(error);
});
await new Promise<void>((resolve, reject) => {
this.httpServer!.listen(this.config.port, this.config.host, (error?: Error) => {
if (error) {
reject(error);
} else {
this.isRunning = true;
resolve();
}
});
});
this.emit('server-started', this.config);
} catch (error) {
await this.cleanup();
throw error;
}
}
/**
* 停止 HTTP 服务器
*/
async stop(): Promise<void> {
if (!this.isRunning) {
return;
}
this.isRunning = false;
// 断开所有长轮询连接
for (const [connectionId] of this.httpConnections) {
this.disconnectClient(connectionId, 'server-shutdown');
}
await this.cleanup();
this.emit('server-stopped');
}
/**
* 发送消息给指定客户端
*/
async sendToClient(connectionId: string, message: TransportMessage): Promise<boolean> {
const connection = this.httpConnections.get(connectionId);
if (!connection) {
return false;
}
// 如果有长轮询连接,直接发送
if (connection.longPollResponse && !connection.longPollResponse.headersSent) {
this.sendLongPollResponse(connection, [message]);
return true;
}
// 否则加入消息队列
connection.messageQueue.push(message);
return true;
}
/**
* 广播消息给所有客户端
*/
async broadcast(message: TransportMessage, excludeId?: string): Promise<number> {
let sentCount = 0;
for (const [connectionId, connection] of this.httpConnections) {
if (excludeId && connectionId === excludeId) {
continue;
}
if (await this.sendToClient(connectionId, message)) {
sentCount++;
}
}
return sentCount;
}
/**
* 发送消息给指定客户端列表
*/
async sendToClients(connectionIds: string[], message: TransportMessage): Promise<number> {
let sentCount = 0;
for (const connectionId of connectionIds) {
if (await this.sendToClient(connectionId, message)) {
sentCount++;
}
}
return sentCount;
}
/**
* 断开指定客户端连接
*/
async disconnectClient(connectionId: string, reason?: string): Promise<void> {
const connection = this.httpConnections.get(connectionId);
if (connection) {
this.cleanupConnection(connectionId);
this.removeConnection(connectionId, reason);
}
}
/**
* 处理 HTTP 请求
*/
private async handleHttpRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
try {
// 设置 CORS 头
if (this.config.enableCors) {
this.setCorsHeaders(res);
}
// 处理 OPTIONS 请求
if (req.method === 'OPTIONS') {
res.writeHead(200);
res.end();
return;
}
const parsedUrl = parseUrl(req.url || '', true);
const pathname = parsedUrl.pathname || '';
// 检查是否为 API 请求
if (!pathname.startsWith(this.config.apiPrefix!)) {
this.sendErrorResponse(res, 404, 'Not Found');
return;
}
const context: HttpRequestContext = {
id: uuidv4(),
request: req,
response: res,
parsedUrl,
query: parsedUrl.query as Record<string, string>,
};
// 读取请求体
if (req.method === 'POST' || req.method === 'PUT') {
context.body = await this.readRequestBody(req);
}
// 路由处理
const apiPath = pathname.substring(this.config.apiPrefix!.length);
await this.routeApiRequest(context, apiPath);
} catch (error) {
this.handleError(error as Error);
this.sendErrorResponse(res, 500, 'Internal Server Error');
}
}
/**
* API 路由处理
*/
private async routeApiRequest(context: HttpRequestContext, apiPath: string): Promise<void> {
const { request, response } = context;
switch (apiPath) {
case '/connect':
if (request.method === 'POST') {
await this.handleConnect(context);
} else {
this.sendErrorResponse(response, 405, 'Method Not Allowed');
}
break;
case '/disconnect':
if (request.method === 'POST') {
await this.handleDisconnect(context);
} else {
this.sendErrorResponse(response, 405, 'Method Not Allowed');
}
break;
case '/poll':
if (request.method === 'GET') {
await this.handleLongPoll(context);
} else {
this.sendErrorResponse(response, 405, 'Method Not Allowed');
}
break;
case '/send':
if (request.method === 'POST') {
await this.handleSendMessage(context);
} else {
this.sendErrorResponse(response, 405, 'Method Not Allowed');
}
break;
case '/status':
if (request.method === 'GET') {
await this.handleStatus(context);
} else {
this.sendErrorResponse(response, 405, 'Method Not Allowed');
}
break;
default:
this.sendErrorResponse(response, 404, 'API endpoint not found');
break;
}
}
/**
* 处理连接请求
*/
private async handleConnect(context: HttpRequestContext): Promise<void> {
const { request, response } = context;
try {
// 检查连接数限制
if (this.config.maxConnections && this.httpConnections.size >= this.config.maxConnections) {
this.sendErrorResponse(response, 429, 'Too many connections');
return;
}
const connectionId = uuidv4();
const remoteAddress = request.socket.remoteAddress || request.headers['x-forwarded-for'] || 'unknown';
const connectionInfo: HttpConnectionInfo = {
id: connectionId,
remoteAddress: Array.isArray(remoteAddress) ? remoteAddress[0] : remoteAddress,
connectedAt: new Date(),
lastActivity: new Date(),
userData: {},
messageQueue: []
};
this.httpConnections.set(connectionId, connectionInfo);
this.addConnection(connectionInfo);
this.sendJsonResponse(response, 200, {
success: true,
connectionId,
serverTime: Date.now()
});
} catch (error) {
this.handleError(error as Error);
this.sendErrorResponse(response, 500, 'Failed to create connection');
}
}
/**
* 处理断开连接请求
*/
private async handleDisconnect(context: HttpRequestContext): Promise<void> {
const { response, query } = context;
const connectionId = query.connectionId;
if (!connectionId) {
this.sendErrorResponse(response, 400, 'Missing connectionId');
return;
}
await this.disconnectClient(connectionId, 'client-disconnect');
this.sendJsonResponse(response, 200, {
success: true,
message: 'Disconnected successfully'
});
}
/**
* 处理长轮询请求
*/
private async handleLongPoll(context: HttpRequestContext): Promise<void> {
const { response, query } = context;
const connectionId = query.connectionId;
if (!connectionId) {
this.sendErrorResponse(response, 400, 'Missing connectionId');
return;
}
const connection = this.httpConnections.get(connectionId);
if (!connection) {
this.sendErrorResponse(response, 404, 'Connection not found');
return;
}
this.updateClientActivity(connectionId);
// 如果有排队的消息,立即返回
if (connection.messageQueue.length > 0) {
const messages = connection.messageQueue.splice(0);
this.sendLongPollResponse(connection, messages);
return;
}
// 设置长轮询
connection.longPollResponse = response;
// 设置超时
connection.longPollTimer = setTimeout(() => {
this.sendLongPollResponse(connection, []);
}, this.config.longPollTimeout);
}
/**
* 处理发送消息请求
*/
private async handleSendMessage(context: HttpRequestContext): Promise<void> {
const { response, query, body } = context;
const connectionId = query.connectionId;
if (!connectionId) {
this.sendErrorResponse(response, 400, 'Missing connectionId');
return;
}
const connection = this.httpConnections.get(connectionId);
if (!connection) {
this.sendErrorResponse(response, 404, 'Connection not found');
return;
}
if (!body) {
this.sendErrorResponse(response, 400, 'Missing message body');
return;
}
try {
const message = JSON.parse(body) as TransportMessage;
message.senderId = connectionId;
this.handleMessage(connectionId, message);
this.sendJsonResponse(response, 200, {
success: true,
message: 'Message sent successfully'
});
} catch (error) {
this.sendErrorResponse(response, 400, 'Invalid message format');
}
}
/**
* 处理状态请求
*/
private async handleStatus(context: HttpRequestContext): Promise<void> {
const { response } = context;
this.sendJsonResponse(response, 200, {
success: true,
status: 'running',
connections: this.httpConnections.size,
uptime: process.uptime(),
serverTime: Date.now()
});
}
/**
* 读取请求体
*/
private readRequestBody(req: IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
let body = '';
let totalSize = 0;
req.on('data', (chunk: Buffer) => {
totalSize += chunk.length;
if (totalSize > this.config.maxRequestSize!) {
reject(new Error('Request body too large'));
return;
}
body += chunk.toString();
});
req.on('end', () => {
resolve(body);
});
req.on('error', (error) => {
reject(error);
});
});
}
/**
* 发送长轮询响应
*/
private sendLongPollResponse(connection: HttpConnectionInfo, messages: TransportMessage[]): void {
if (!connection.longPollResponse || connection.longPollResponse.headersSent) {
return;
}
// 清理定时器
if (connection.longPollTimer) {
clearTimeout(connection.longPollTimer);
connection.longPollTimer = undefined;
}
this.sendJsonResponse(connection.longPollResponse, 200, {
success: true,
messages
});
connection.longPollResponse = undefined;
}
/**
* 设置 CORS 头
*/
private setCorsHeaders(res: ServerResponse): void {
const origins = this.config.corsOrigins!;
const origin = origins.includes('*') ? '*' : origins[0];
res.setHeader('Access-Control-Allow-Origin', origin);
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Max-Age', '86400');
}
/**
* 发送 JSON 响应
*/
private sendJsonResponse(res: ServerResponse, statusCode: number, data: any): void {
if (res.headersSent) return;
res.setHeader('Content-Type', 'application/json');
res.writeHead(statusCode);
res.end(JSON.stringify(data));
}
/**
* 发送错误响应
*/
private sendErrorResponse(res: ServerResponse, statusCode: number, message: string): void {
if (res.headersSent) return;
this.sendJsonResponse(res, statusCode, {
success: false,
error: message,
code: statusCode
});
}
/**
* 清理连接资源
*/
private cleanupConnection(connectionId: string): void {
const connection = this.httpConnections.get(connectionId);
if (connection) {
if (connection.longPollTimer) {
clearTimeout(connection.longPollTimer);
}
if (connection.longPollResponse && !connection.longPollResponse.headersSent) {
this.sendJsonResponse(connection.longPollResponse, 200, {
success: true,
messages: [],
disconnected: true
});
}
this.httpConnections.delete(connectionId);
}
}
/**
* 清理所有资源
*/
private async cleanup(): Promise<void> {
// 清理所有连接
for (const connectionId of this.httpConnections.keys()) {
this.cleanupConnection(connectionId);
}
this.clearConnections();
// 关闭 HTTP 服务器
if (this.httpServer) {
await new Promise<void>((resolve) => {
this.httpServer!.close(() => resolve());
});
this.httpServer = null;
}
}
/**
* 获取 HTTP 连接统计信息
*/
getHttpStats(): {
totalConnections: number;
activeLongPolls: number;
queuedMessages: number;
} {
let activeLongPolls = 0;
let queuedMessages = 0;
for (const connection of this.httpConnections.values()) {
if (connection.longPollResponse && !connection.longPollResponse.headersSent) {
activeLongPolls++;
}
queuedMessages += connection.messageQueue.length;
}
return {
totalConnections: this.httpConnections.size,
activeLongPolls,
queuedMessages
};
}
}

View File

@@ -1,452 +0,0 @@
/**
* 网络服务器主类
*
* 整合 WebSocket 和 HTTP 传输,提供统一的网络服务接口
*/
import { EventEmitter } from 'events';
import { Transport, TransportConfig, TransportMessage } from './Transport';
import { WebSocketTransport, WebSocketTransportConfig } from './WebSocketTransport';
import { HttpTransport, HttpTransportConfig } from './HttpTransport';
import { ClientConnection, ClientConnectionState, ClientPermissions } from './ClientConnection';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
/**
* 网络服务器配置
*/
export interface NetworkServerConfig {
/** 服务器名称 */
name?: string;
/** WebSocket 配置 */
websocket?: WebSocketTransportConfig;
/** HTTP 配置 */
http?: HttpTransportConfig;
/** 默认客户端权限 */
defaultPermissions?: ClientPermissions;
/** 最大客户端连接数 */
maxConnections?: number;
/** 客户端认证超时(毫秒) */
authenticationTimeout?: number;
/** 是否启用统计 */
enableStats?: boolean;
}
/**
* 服务器统计信息
*/
export interface ServerStats {
/** 总连接数 */
totalConnections: number;
/** 当前活跃连接数 */
activeConnections: number;
/** 已认证连接数 */
authenticatedConnections: number;
/** 消息总数 */
totalMessages: number;
/** 错误总数 */
totalErrors: number;
/** 服务器启动时间 */
startTime: Date;
/** 服务器运行时间(毫秒) */
uptime: number;
}
/**
* 网络服务器事件
*/
export interface NetworkServerEvents {
/** 服务器启动 */
'server-started': () => void;
/** 服务器停止 */
'server-stopped': () => void;
/** 客户端连接 */
'client-connected': (client: ClientConnection) => void;
/** 客户端断开连接 */
'client-disconnected': (clientId: string, reason?: string) => void;
/** 客户端认证成功 */
'client-authenticated': (client: ClientConnection) => void;
/** 收到消息 */
'message': (client: ClientConnection, message: TransportMessage) => void;
/** 服务器错误 */
'error': (error: Error, clientId?: string) => void;
}
/**
* 网络服务器主类
*/
export class NetworkServer extends EventEmitter {
private config: NetworkServerConfig;
private wsTransport: WebSocketTransport | null = null;
private httpTransport: HttpTransport | null = null;
private clients = new Map<string, ClientConnection>();
private isRunning = false;
private stats: ServerStats;
constructor(config: NetworkServerConfig) {
super();
this.config = {
name: 'NetworkServer',
maxConnections: 1000,
authenticationTimeout: 30000, // 30秒
enableStats: true,
defaultPermissions: {
canJoinRooms: true,
canCreateRooms: false,
canSendRpc: true,
canSyncVars: true
},
...config
};
this.stats = {
totalConnections: 0,
activeConnections: 0,
authenticatedConnections: 0,
totalMessages: 0,
totalErrors: 0,
startTime: new Date(),
uptime: 0
};
this.initialize();
}
/**
* 启动服务器
*/
async start(): Promise<void> {
if (this.isRunning) {
throw new Error('Server is already running');
}
try {
const promises: Promise<void>[] = [];
// 启动 WebSocket 传输
if (this.config.websocket && this.wsTransport) {
promises.push(this.wsTransport.start());
}
// 启动 HTTP 传输
if (this.config.http && this.httpTransport) {
promises.push(this.httpTransport.start());
}
if (promises.length === 0) {
throw new Error('No transport configured. Please configure at least one transport (WebSocket or HTTP)');
}
await Promise.all(promises);
this.isRunning = true;
this.stats.startTime = new Date();
console.log(`Network Server "${this.config.name}" started successfully`);
if (this.config.websocket) {
console.log(`- WebSocket: ws://${this.config.websocket.host || 'localhost'}:${this.config.websocket.port}${this.config.websocket.path || '/ws'}`);
}
if (this.config.http) {
console.log(`- HTTP: http://${this.config.http.host || 'localhost'}:${this.config.http.port}${this.config.http.apiPrefix || '/api'}`);
}
this.emit('server-started');
} catch (error) {
await this.stop();
throw error;
}
}
/**
* 停止服务器
*/
async stop(): Promise<void> {
if (!this.isRunning) {
return;
}
this.isRunning = false;
// 断开所有客户端
const clients = Array.from(this.clients.values());
for (const client of clients) {
client.disconnect('server-shutdown');
}
// 停止传输层
const promises: Promise<void>[] = [];
if (this.wsTransport) {
promises.push(this.wsTransport.stop());
}
if (this.httpTransport) {
promises.push(this.httpTransport.stop());
}
await Promise.all(promises);
console.log(`Network Server "${this.config.name}" stopped`);
this.emit('server-stopped');
}
/**
* 获取服务器配置
*/
getConfig(): Readonly<NetworkServerConfig> {
return this.config;
}
/**
* 获取服务器统计信息
*/
getStats(): ServerStats {
this.stats.uptime = Date.now() - this.stats.startTime.getTime();
this.stats.activeConnections = this.clients.size;
this.stats.authenticatedConnections = Array.from(this.clients.values())
.filter(client => client.isAuthenticated).length;
return { ...this.stats };
}
/**
* 获取所有客户端连接
*/
getClients(): ClientConnection[] {
return Array.from(this.clients.values());
}
/**
* 获取指定客户端连接
*/
getClient(clientId: string): ClientConnection | undefined {
return this.clients.get(clientId);
}
/**
* 检查客户端是否存在
*/
hasClient(clientId: string): boolean {
return this.clients.has(clientId);
}
/**
* 获取客户端数量
*/
getClientCount(): number {
return this.clients.size;
}
/**
* 发送消息给指定客户端
*/
async sendToClient(clientId: string, message: TransportMessage): Promise<boolean> {
const client = this.clients.get(clientId);
if (!client) {
return false;
}
return await client.sendMessage(message);
}
/**
* 广播消息给所有客户端
*/
async broadcast(message: TransportMessage, excludeId?: string): Promise<number> {
const promises = Array.from(this.clients.entries())
.filter(([clientId]) => clientId !== excludeId)
.map(([, client]) => client.sendMessage(message));
const results = await Promise.allSettled(promises);
return results.filter(result => result.status === 'fulfilled' && result.value).length;
}
/**
* 发送消息给指定房间的所有客户端
*/
async broadcastToRoom(roomId: string, message: TransportMessage, excludeId?: string): Promise<number> {
const roomClients = Array.from(this.clients.values())
.filter(client => client.currentRoomId === roomId && client.id !== excludeId);
const promises = roomClients.map(client => client.sendMessage(message));
const results = await Promise.allSettled(promises);
return results.filter(result => result.status === 'fulfilled' && result.value).length;
}
/**
* 断开指定客户端连接
*/
async disconnectClient(clientId: string, reason?: string): Promise<void> {
const client = this.clients.get(clientId);
if (client) {
client.disconnect(reason);
}
}
/**
* 获取在指定房间的客户端列表
*/
getClientsInRoom(roomId: string): ClientConnection[] {
return Array.from(this.clients.values())
.filter(client => client.currentRoomId === roomId);
}
/**
* 检查服务器是否正在运行
*/
isServerRunning(): boolean {
return this.isRunning;
}
/**
* 初始化服务器
*/
private initialize(): void {
// 初始化 WebSocket 传输
if (this.config.websocket) {
this.wsTransport = new WebSocketTransport(this.config.websocket);
this.setupTransportEvents(this.wsTransport);
}
// 初始化 HTTP 传输
if (this.config.http) {
this.httpTransport = new HttpTransport(this.config.http);
this.setupTransportEvents(this.httpTransport);
}
}
/**
* 设置传输层事件监听
*/
private setupTransportEvents(transport: Transport): void {
transport.on('client-connected', (connectionInfo) => {
this.handleClientConnected(connectionInfo.id, connectionInfo.remoteAddress || 'unknown', transport);
});
transport.on('client-disconnected', (connectionId, reason) => {
this.handleClientDisconnected(connectionId, reason);
});
transport.on('message', (connectionId, message) => {
this.handleTransportMessage(connectionId, message);
});
transport.on('error', (error, connectionId) => {
this.handleTransportError(error, connectionId);
});
}
/**
* 处理客户端连接
*/
private handleClientConnected(connectionId: string, remoteAddress: string, transport: Transport): void {
// 检查连接数限制
if (this.config.maxConnections && this.clients.size >= this.config.maxConnections) {
transport.disconnectClient(connectionId, 'Max connections reached');
return;
}
const client = new ClientConnection(
connectionId,
remoteAddress,
(message) => transport.sendToClient(connectionId, message),
{
connectionTimeout: this.config.authenticationTimeout,
permissions: this.config.defaultPermissions
}
);
// 设置客户端事件监听
this.setupClientEvents(client);
this.clients.set(connectionId, client);
this.stats.totalConnections++;
console.log(`Client connected: ${connectionId} from ${remoteAddress}`);
this.emit('client-connected', client);
}
/**
* 处理客户端断开连接
*/
private handleClientDisconnected(connectionId: string, reason?: string): void {
const client = this.clients.get(connectionId);
if (client) {
client.destroy();
this.clients.delete(connectionId);
console.log(`Client disconnected: ${connectionId}, reason: ${reason || 'unknown'}`);
this.emit('client-disconnected', connectionId, reason);
}
}
/**
* 处理传输层消息
*/
private handleTransportMessage(connectionId: string, message: TransportMessage): void {
const client = this.clients.get(connectionId);
if (!client) {
return;
}
client.handleMessage(message);
this.stats.totalMessages++;
this.emit('message', client, message);
}
/**
* 处理传输层错误
*/
private handleTransportError(error: Error, connectionId?: string): void {
this.stats.totalErrors++;
console.error(`Transport error${connectionId ? ` (client: ${connectionId})` : ''}:`, error.message);
this.emit('error', error, connectionId);
// 如果是特定客户端的错误,断开该客户端
if (connectionId) {
this.disconnectClient(connectionId, 'transport-error');
}
}
/**
* 设置客户端事件监听
*/
private setupClientEvents(client: ClientConnection): void {
client.on('authenticated', (userData) => {
console.log(`Client authenticated: ${client.id}`, userData);
this.emit('client-authenticated', client);
});
client.on('error', (error) => {
console.error(`Client error (${client.id}):`, error.message);
this.emit('error', error, client.id);
});
client.on('timeout', () => {
console.log(`Client timeout: ${client.id}`);
this.disconnectClient(client.id, 'timeout');
});
client.on('state-changed', (oldState, newState) => {
console.log(`Client ${client.id} state changed: ${oldState} -> ${newState}`);
});
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof NetworkServerEvents>(event: K, listener: NetworkServerEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof NetworkServerEvents>(event: K, ...args: Parameters<NetworkServerEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -1,224 +0,0 @@
/**
* 网络传输层抽象接口
*/
import { EventEmitter } from 'events';
import { NetworkMessage, NetworkValue } from '@esengine/ecs-framework-network-shared';
/**
* 传输层配置
*/
export interface TransportConfig {
/** 服务器端口 */
port: number;
/** 主机地址 */
host?: string;
/** 最大连接数 */
maxConnections?: number;
/** 心跳间隔(毫秒) */
heartbeatInterval?: number;
/** 连接超时(毫秒) */
connectionTimeout?: number;
}
/**
* 客户端连接信息
*/
export interface ClientConnectionInfo {
/** 连接ID */
id: string;
/** 客户端IP */
remoteAddress?: string;
/** 连接时间 */
connectedAt: Date;
/** 最后活跃时间 */
lastActivity: Date;
/** 用户数据 */
userData?: Record<string, NetworkValue>;
}
/**
* 网络消息包装
*/
export interface TransportMessage {
/** 消息类型 */
type: 'rpc' | 'syncvar' | 'system' | 'custom';
/** 消息数据 */
data: NetworkValue;
/** 发送者ID */
senderId?: string;
/** 目标客户端ID(可选,用于单播) */
targetId?: string;
/** 是否可靠传输 */
reliable?: boolean;
}
/**
* 网络传输层事件
*/
export interface TransportEvents {
/** 客户端连接 */
'client-connected': (connectionInfo: ClientConnectionInfo) => void;
/** 客户端断开连接 */
'client-disconnected': (connectionId: string, reason?: string) => void;
/** 收到消息 */
'message': (connectionId: string, message: TransportMessage) => void;
/** 传输错误 */
'error': (error: Error, connectionId?: string) => void;
/** 服务器启动 */
'server-started': (config: TransportConfig) => void;
/** 服务器关闭 */
'server-stopped': () => void;
}
/**
* 网络传输层抽象类
*/
export abstract class Transport extends EventEmitter {
protected config: TransportConfig;
protected isRunning = false;
protected connections = new Map<string, ClientConnectionInfo>();
constructor(config: TransportConfig) {
super();
this.config = config;
}
/**
* 启动传输层服务
*/
abstract start(): Promise<void>;
/**
* 停止传输层服务
*/
abstract stop(): Promise<void>;
/**
* 发送消息给指定客户端
*/
abstract sendToClient(connectionId: string, message: TransportMessage): Promise<boolean>;
/**
* 广播消息给所有客户端
*/
abstract broadcast(message: TransportMessage, excludeId?: string): Promise<number>;
/**
* 广播消息给指定客户端列表
*/
abstract sendToClients(connectionIds: string[], message: TransportMessage): Promise<number>;
/**
* 断开指定客户端连接
*/
abstract disconnectClient(connectionId: string, reason?: string): Promise<void>;
/**
* 获取在线客户端数量
*/
getConnectionCount(): number {
return this.connections.size;
}
/**
* 获取所有连接信息
*/
getConnections(): ClientConnectionInfo[] {
return Array.from(this.connections.values());
}
/**
* 获取指定连接信息
*/
getConnection(connectionId: string): ClientConnectionInfo | undefined {
return this.connections.get(connectionId);
}
/**
* 检查连接是否存在
*/
hasConnection(connectionId: string): boolean {
return this.connections.has(connectionId);
}
/**
* 服务器是否正在运行
*/
isServerRunning(): boolean {
return this.isRunning;
}
/**
* 获取传输层配置
*/
getConfig(): TransportConfig {
return { ...this.config };
}
/**
* 更新客户端最后活跃时间
*/
protected updateClientActivity(connectionId: string): void {
const connection = this.connections.get(connectionId);
if (connection) {
connection.lastActivity = new Date();
}
}
/**
* 添加客户端连接
*/
protected addConnection(connectionInfo: ClientConnectionInfo): void {
this.connections.set(connectionInfo.id, connectionInfo);
this.emit('client-connected', connectionInfo);
}
/**
* 移除客户端连接
*/
protected removeConnection(connectionId: string, reason?: string): void {
if (this.connections.delete(connectionId)) {
this.emit('client-disconnected', connectionId, reason);
}
}
/**
* 处理接收到的消息
*/
protected handleMessage(connectionId: string, message: TransportMessage): void {
this.updateClientActivity(connectionId);
this.emit('message', connectionId, message);
}
/**
* 处理传输错误
*/
protected handleError(error: Error, connectionId?: string): void {
this.emit('error', error, connectionId);
}
/**
* 清理所有连接
*/
protected clearConnections(): void {
const connectionIds = Array.from(this.connections.keys());
for (const id of connectionIds) {
this.removeConnection(id, 'server-shutdown');
}
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof TransportEvents>(event: K, listener: TransportEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof TransportEvents>(event: K, ...args: Parameters<TransportEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -1,406 +0,0 @@
/**
* WebSocket 传输层实现
*/
import { WebSocketServer, WebSocket } from 'ws';
import { createServer, Server as HttpServer } from 'http';
import { v4 as uuidv4 } from 'uuid';
import { Transport, TransportConfig, ClientConnectionInfo, TransportMessage } from './Transport';
/**
* WebSocket 传输配置
*/
export interface WebSocketTransportConfig extends TransportConfig {
/** WebSocket 路径 */
path?: string;
/** 是否启用压缩 */
compression?: boolean;
/** 最大消息大小(字节) */
maxMessageSize?: number;
/** ping 间隔(毫秒) */
pingInterval?: number;
/** pong 超时(毫秒) */
pongTimeout?: number;
}
/**
* WebSocket 客户端连接扩展信息
*/
interface WebSocketConnectionInfo extends ClientConnectionInfo {
/** WebSocket 实例 */
socket: WebSocket;
/** ping 定时器 */
pingTimer?: NodeJS.Timeout;
/** pong 超时定时器 */
pongTimer?: NodeJS.Timeout;
}
/**
* WebSocket 传输层实现
*/
export class WebSocketTransport extends Transport {
private httpServer: HttpServer | null = null;
private wsServer: WebSocketServer | null = null;
private wsConnections = new Map<string, WebSocketConnectionInfo>();
protected override config: WebSocketTransportConfig;
constructor(config: WebSocketTransportConfig) {
super(config);
this.config = {
path: '/ws',
compression: true,
maxMessageSize: 1024 * 1024, // 1MB
pingInterval: 30000, // 30秒
pongTimeout: 5000, // 5秒
heartbeatInterval: 30000,
connectionTimeout: 60000,
maxConnections: 1000,
...config
};
}
/**
* 启动 WebSocket 服务器
*/
async start(): Promise<void> {
if (this.isRunning) {
throw new Error('WebSocket transport is already running');
}
try {
// 创建 HTTP 服务器
this.httpServer = createServer();
// 创建 WebSocket 服务器
this.wsServer = new WebSocketServer({
server: this.httpServer,
path: this.config.path,
maxPayload: this.config.maxMessageSize,
perMessageDeflate: this.config.compression
});
// 设置事件监听
this.setupEventListeners();
// 启动服务器
await new Promise<void>((resolve, reject) => {
this.httpServer!.listen(this.config.port, this.config.host, (error?: Error) => {
if (error) {
reject(error);
} else {
this.isRunning = true;
resolve();
}
});
});
this.emit('server-started', this.config);
} catch (error) {
await this.cleanup();
throw error;
}
}
/**
* 停止 WebSocket 服务器
*/
async stop(): Promise<void> {
if (!this.isRunning) {
return;
}
this.isRunning = false;
// 断开所有客户端连接
for (const [connectionId, connection] of this.wsConnections) {
this.disconnectClient(connectionId, 'server-shutdown');
}
await this.cleanup();
this.emit('server-stopped');
}
/**
* 发送消息给指定客户端
*/
async sendToClient(connectionId: string, message: TransportMessage): Promise<boolean> {
const connection = this.wsConnections.get(connectionId);
if (!connection || connection.socket.readyState !== WebSocket.OPEN) {
return false;
}
try {
const data = JSON.stringify(message);
connection.socket.send(data);
this.updateClientActivity(connectionId);
return true;
} catch (error) {
this.handleError(error as Error, connectionId);
return false;
}
}
/**
* 广播消息给所有客户端
*/
async broadcast(message: TransportMessage, excludeId?: string): Promise<number> {
const data = JSON.stringify(message);
let sentCount = 0;
for (const [connectionId, connection] of this.wsConnections) {
if (excludeId && connectionId === excludeId) {
continue;
}
if (connection.socket.readyState === WebSocket.OPEN) {
try {
connection.socket.send(data);
sentCount++;
} catch (error) {
this.handleError(error as Error, connectionId);
}
}
}
return sentCount;
}
/**
* 发送消息给指定客户端列表
*/
async sendToClients(connectionIds: string[], message: TransportMessage): Promise<number> {
const data = JSON.stringify(message);
let sentCount = 0;
for (const connectionId of connectionIds) {
const connection = this.wsConnections.get(connectionId);
if (connection && connection.socket.readyState === WebSocket.OPEN) {
try {
connection.socket.send(data);
sentCount++;
} catch (error) {
this.handleError(error as Error, connectionId);
}
}
}
return sentCount;
}
/**
* 断开指定客户端连接
*/
async disconnectClient(connectionId: string, reason?: string): Promise<void> {
const connection = this.wsConnections.get(connectionId);
if (connection) {
this.cleanupConnection(connectionId);
connection.socket.close(1000, reason);
}
}
/**
* 设置事件监听器
*/
private setupEventListeners(): void {
if (!this.wsServer) return;
this.wsServer.on('connection', (socket: WebSocket, request) => {
this.handleNewConnection(socket, request);
});
this.wsServer.on('error', (error: Error) => {
this.handleError(error);
});
if (this.httpServer) {
this.httpServer.on('error', (error: Error) => {
this.handleError(error);
});
}
}
/**
* 处理新连接
*/
private handleNewConnection(socket: WebSocket, request: any): void {
// 检查连接数限制
if (this.config.maxConnections && this.wsConnections.size >= this.config.maxConnections) {
socket.close(1013, 'Too many connections');
return;
}
const connectionId = uuidv4();
const remoteAddress = request.socket.remoteAddress || request.headers['x-forwarded-for'] || 'unknown';
const connectionInfo: WebSocketConnectionInfo = {
id: connectionId,
socket,
remoteAddress: Array.isArray(remoteAddress) ? remoteAddress[0] : remoteAddress,
connectedAt: new Date(),
lastActivity: new Date(),
userData: {}
};
this.wsConnections.set(connectionId, connectionInfo);
this.addConnection(connectionInfo);
// 设置 socket 事件监听
socket.on('message', (data: Buffer) => {
this.handleClientMessage(connectionId, data);
});
socket.on('close', (code: number, reason: Buffer) => {
this.handleClientDisconnect(connectionId, code, reason.toString());
});
socket.on('error', (error: Error) => {
this.handleError(error, connectionId);
this.handleClientDisconnect(connectionId, 1006, 'Socket error');
});
socket.on('pong', () => {
this.handlePong(connectionId);
});
// 启动心跳检测
this.startHeartbeat(connectionId);
}
/**
* 处理客户端消息
*/
private handleClientMessage(connectionId: string, data: Buffer): void {
try {
const message = JSON.parse(data.toString()) as TransportMessage;
message.senderId = connectionId;
this.handleMessage(connectionId, message);
} catch (error) {
this.handleError(new Error(`Invalid message format from client ${connectionId}`), connectionId);
}
}
/**
* 处理客户端断开连接
*/
private handleClientDisconnect(connectionId: string, code: number, reason: string): void {
this.cleanupConnection(connectionId);
this.removeConnection(connectionId, `${code}: ${reason}`);
}
/**
* 启动心跳检测
*/
private startHeartbeat(connectionId: string): void {
const connection = this.wsConnections.get(connectionId);
if (!connection) return;
if (this.config.pingInterval && this.config.pingInterval > 0) {
connection.pingTimer = setInterval(() => {
this.sendPing(connectionId);
}, this.config.pingInterval);
}
}
/**
* 发送 ping
*/
private sendPing(connectionId: string): void {
const connection = this.wsConnections.get(connectionId);
if (!connection || connection.socket.readyState !== WebSocket.OPEN) {
return;
}
connection.socket.ping();
// 设置 pong 超时
if (this.config.pongTimeout && this.config.pongTimeout > 0) {
if (connection.pongTimer) {
clearTimeout(connection.pongTimer);
}
connection.pongTimer = setTimeout(() => {
this.disconnectClient(connectionId, 'Pong timeout');
}, this.config.pongTimeout);
}
}
/**
* 处理 pong 响应
*/
private handlePong(connectionId: string): void {
const connection = this.wsConnections.get(connectionId);
if (connection && connection.pongTimer) {
clearTimeout(connection.pongTimer);
connection.pongTimer = undefined;
}
this.updateClientActivity(connectionId);
}
/**
* 清理连接资源
*/
private cleanupConnection(connectionId: string): void {
const connection = this.wsConnections.get(connectionId);
if (connection) {
if (connection.pingTimer) {
clearInterval(connection.pingTimer);
}
if (connection.pongTimer) {
clearTimeout(connection.pongTimer);
}
this.wsConnections.delete(connectionId);
}
}
/**
* 清理所有资源
*/
private async cleanup(): Promise<void> {
// 清理所有连接
for (const connectionId of this.wsConnections.keys()) {
this.cleanupConnection(connectionId);
}
this.clearConnections();
// 关闭 WebSocket 服务器
if (this.wsServer) {
this.wsServer.close();
this.wsServer = null;
}
// 关闭 HTTP 服务器
if (this.httpServer) {
await new Promise<void>((resolve) => {
this.httpServer!.close(() => resolve());
});
this.httpServer = null;
}
}
/**
* 获取 WebSocket 连接统计信息
*/
getWebSocketStats(): {
totalConnections: number;
activeConnections: number;
inactiveConnections: number;
} {
let activeConnections = 0;
let inactiveConnections = 0;
for (const connection of this.wsConnections.values()) {
if (connection.socket.readyState === WebSocket.OPEN) {
activeConnections++;
} else {
inactiveConnections++;
}
}
return {
totalConnections: this.wsConnections.size,
activeConnections,
inactiveConnections
};
}
}

View File

@@ -1,9 +0,0 @@
/**
* 核心模块导出
*/
export * from './Transport';
export * from './WebSocketTransport';
export * from './HttpTransport';
export * from './ClientConnection';
export * from './NetworkServer';

View File

@@ -1,79 +1,26 @@
/**
* ECS Framework Network Server
*
* 提供完整的网络服务端功能,包括:
* - WebSocket 和 HTTP 传输层
* - 客户端连接管理
* - 房间系统
* - 身份验证和权限管理
* - SyncVar 和 RPC 系统
* - 消息验证
* @esengine/network-server
* ECS Framework网络层 - 服务端实现
*/
// 核心模块
export * from './core';
// 核心服务器 (待实现)
// export * from './core/NetworkServer';
// export * from './core/ClientConnection';
// 房间系统
export * from './rooms';
// 传输层 (待实现)
// export * from './transport/WebSocketTransport';
// export * from './transport/HttpTransport';
// 认证系统
export * from './auth';
// 系统层 (待实现)
// export * from './systems/SyncVarSystem';
// export * from './systems/RpcSystem';
// 网络系统
export * from './systems';
// 房间管理 (待实现)
// export * from './rooms/Room';
// export * from './rooms/RoomManager';
// 验证系统
export * from './validation';
// 认证授权 (待实现)
// export * from './auth/AuthManager';
// 版本信息
export const VERSION = '1.0.0';
// 导出常用组合配置
export interface ServerConfigPreset {
/** 服务器名称 */
name: string;
/** WebSocket 端口 */
wsPort: number;
/** HTTP 端口(可选) */
httpPort?: number;
/** 最大连接数 */
maxConnections: number;
/** 是否启用认证 */
enableAuth: boolean;
/** 是否启用房间系统 */
enableRooms: boolean;
}
/**
* 预定义服务器配置
*/
export const ServerPresets = {
/** 开发环境配置 */
Development: {
name: 'Development Server',
wsPort: 8080,
httpPort: 3000,
maxConnections: 100,
enableAuth: false,
enableRooms: true
} as ServerConfigPreset,
/** 生产环境配置 */
Production: {
name: 'Production Server',
wsPort: 443,
httpPort: 80,
maxConnections: 10000,
enableAuth: true,
enableRooms: true
} as ServerConfigPreset,
/** 测试环境配置 */
Testing: {
name: 'Test Server',
wsPort: 9090,
maxConnections: 10,
enableAuth: false,
enableRooms: false
} as ServerConfigPreset
};
// 重新导出shared包的类型
export * from '@esengine/network-shared';

View File

@@ -1,637 +0,0 @@
/**
* 房间管理
*
* 类似于 Unity Mirror 的 Scene 概念,管理一组客户端和网络对象
*/
import { EventEmitter } from 'events';
import { Entity, Scene } from '@esengine/ecs-framework';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
import { ClientConnection } from '../core/ClientConnection';
import { TransportMessage } from '../core/Transport';
/**
* 房间状态
*/
export enum RoomState {
/** 创建中 */
CREATING = 'creating',
/** 活跃状态 */
ACTIVE = 'active',
/** 暂停状态 */
PAUSED = 'paused',
/** 关闭中 */
CLOSING = 'closing',
/** 已关闭 */
CLOSED = 'closed'
}
/**
* 房间配置
*/
export interface RoomConfig {
/** 房间ID */
id: string;
/** 房间名称 */
name: string;
/** 房间描述 */
description?: string;
/** 最大玩家数 */
maxPlayers: number;
/** 是否私有房间 */
isPrivate?: boolean;
/** 房间密码 */
password?: string;
/** 房间元数据 */
metadata?: Record<string, NetworkValue>;
/** 是否持久化 */
persistent?: boolean;
/** 房间过期时间(毫秒) */
expirationTime?: number;
}
/**
* 玩家数据
*/
export interface PlayerData {
/** 客户端连接 */
client: ClientConnection;
/** 加入时间 */
joinedAt: Date;
/** 是否为房主 */
isOwner: boolean;
/** 玩家自定义数据 */
customData: Record<string, NetworkValue>;
}
/**
* 房间统计信息
*/
export interface RoomStats {
/** 当前玩家数 */
currentPlayers: number;
/** 最大玩家数 */
maxPlayers: number;
/** 总加入过的玩家数 */
totalPlayersJoined: number;
/** 消息总数 */
totalMessages: number;
/** 创建时间 */
createdAt: Date;
/** 房间存活时间(毫秒) */
lifetime: number;
}
/**
* 房间事件
*/
export interface RoomEvents {
/** 玩家加入 */
'player-joined': (player: PlayerData) => void;
/** 玩家离开 */
'player-left': (clientId: string, reason?: string) => void;
/** 房主变更 */
'owner-changed': (newOwnerId: string, oldOwnerId?: string) => void;
/** 房间状态变化 */
'state-changed': (oldState: RoomState, newState: RoomState) => void;
/** 收到消息 */
'message': (clientId: string, message: TransportMessage) => void;
/** 房间更新 */
'room-updated': (updatedFields: Partial<RoomConfig>) => void;
/** 房间错误 */
'error': (error: Error, clientId?: string) => void;
/** 房间即将关闭 */
'closing': (reason: string) => void;
/** 房间已关闭 */
'closed': (reason: string) => void;
}
/**
* 房间类
*/
export class Room extends EventEmitter {
private config: RoomConfig;
private state: RoomState = RoomState.CREATING;
private players = new Map<string, PlayerData>();
private ownerId: string | null = null;
private ecsScene: Scene | null = null;
private stats: RoomStats;
private expirationTimer: NodeJS.Timeout | null = null;
constructor(config: RoomConfig) {
super();
this.config = { ...config };
this.stats = {
currentPlayers: 0,
maxPlayers: config.maxPlayers,
totalPlayersJoined: 0,
totalMessages: 0,
createdAt: new Date(),
lifetime: 0
};
this.initialize();
}
/**
* 获取房间ID
*/
get id(): string {
return this.config.id;
}
/**
* 获取房间名称
*/
get name(): string {
return this.config.name;
}
/**
* 获取房间状态
*/
get currentState(): RoomState {
return this.state;
}
/**
* 获取房间配置
*/
getConfig(): Readonly<RoomConfig> {
return this.config;
}
/**
* 获取房间统计信息
*/
getStats(): RoomStats {
this.stats.lifetime = Date.now() - this.stats.createdAt.getTime();
this.stats.currentPlayers = this.players.size;
return { ...this.stats };
}
/**
* 获取所有玩家
*/
getPlayers(): PlayerData[] {
return Array.from(this.players.values());
}
/**
* 获取指定玩家
*/
getPlayer(clientId: string): PlayerData | undefined {
return this.players.get(clientId);
}
/**
* 检查玩家是否在房间中
*/
hasPlayer(clientId: string): boolean {
return this.players.has(clientId);
}
/**
* 获取当前玩家数量
*/
getPlayerCount(): number {
return this.players.size;
}
/**
* 检查房间是否已满
*/
isFull(): boolean {
return this.players.size >= this.config.maxPlayers;
}
/**
* 检查房间是否为空
*/
isEmpty(): boolean {
return this.players.size === 0;
}
/**
* 获取房主
*/
getOwner(): PlayerData | undefined {
return this.ownerId ? this.players.get(this.ownerId) : undefined;
}
/**
* 获取 ECS 场景
*/
getEcsScene(): Scene | null {
return this.ecsScene;
}
/**
* 玩家加入房间
*/
async addPlayer(client: ClientConnection, customData: Record<string, NetworkValue> = {}): Promise<boolean> {
if (this.state !== RoomState.ACTIVE) {
throw new Error(`Cannot join room in state: ${this.state}`);
}
if (this.hasPlayer(client.id)) {
throw new Error(`Player ${client.id} is already in the room`);
}
if (this.isFull()) {
throw new Error('Room is full');
}
// 检查房间密码
if (this.config.isPrivate && this.config.password) {
const providedPassword = customData.password as string;
if (providedPassword !== this.config.password) {
throw new Error('Invalid room password');
}
}
const isFirstPlayer = this.isEmpty();
const playerData: PlayerData = {
client,
joinedAt: new Date(),
isOwner: isFirstPlayer,
customData: { ...customData }
};
this.players.set(client.id, playerData);
client.joinRoom(this.id);
// 设置房主
if (isFirstPlayer) {
this.ownerId = client.id;
}
this.stats.totalPlayersJoined++;
// 通知其他玩家
await this.broadcast({
type: 'system',
data: {
action: 'player-joined',
playerId: client.id,
playerData: {
id: client.id,
joinedAt: playerData.joinedAt.toISOString(),
isOwner: playerData.isOwner,
customData: playerData.customData
}
}
}, client.id);
console.log(`Player ${client.id} joined room ${this.id}`);
this.emit('player-joined', playerData);
return true;
}
/**
* 玩家离开房间
*/
async removePlayer(clientId: string, reason?: string): Promise<boolean> {
const player = this.players.get(clientId);
if (!player) {
return false;
}
this.players.delete(clientId);
player.client.leaveRoom();
// 如果离开的是房主,转移房主权限
if (this.ownerId === clientId) {
await this.transferOwnership();
}
// 通知其他玩家
await this.broadcast({
type: 'system',
data: {
action: 'player-left',
playerId: clientId,
reason: reason || 'unknown'
}
});
console.log(`Player ${clientId} left room ${this.id}, reason: ${reason || 'unknown'}`);
this.emit('player-left', clientId, reason);
// 如果房间为空,考虑关闭
if (this.isEmpty() && !this.config.persistent) {
await this.close('empty-room');
}
return true;
}
/**
* 转移房主权限
*/
async transferOwnership(newOwnerId?: string): Promise<boolean> {
const oldOwnerId = this.ownerId;
if (newOwnerId) {
const newOwner = this.players.get(newOwnerId);
if (!newOwner) {
return false;
}
this.ownerId = newOwnerId;
newOwner.isOwner = true;
} else {
// 自动选择下一个玩家作为房主
const players = Array.from(this.players.values());
if (players.length > 0) {
const newOwner = players[0];
this.ownerId = newOwner.client.id;
newOwner.isOwner = true;
} else {
this.ownerId = null;
}
}
// 更新旧房主状态
if (oldOwnerId) {
const oldOwner = this.players.get(oldOwnerId);
if (oldOwner) {
oldOwner.isOwner = false;
}
}
// 通知所有玩家房主变更
if (this.ownerId) {
await this.broadcast({
type: 'system',
data: {
action: 'owner-changed',
newOwnerId: this.ownerId,
oldOwnerId: oldOwnerId || ''
}
});
console.log(`Room ${this.id} ownership transferred from ${oldOwnerId || 'none'} to ${this.ownerId}`);
this.emit('owner-changed', this.ownerId, oldOwnerId || undefined);
}
return true;
}
/**
* 广播消息给房间内所有玩家
*/
async broadcast(message: TransportMessage, excludeClientId?: string): Promise<number> {
const players = Array.from(this.players.values())
.filter(player => player.client.id !== excludeClientId);
const promises = players.map(player => player.client.sendMessage(message));
const results = await Promise.allSettled(promises);
return results.filter(result => result.status === 'fulfilled' && result.value).length;
}
/**
* 发送消息给指定玩家
*/
async sendToPlayer(clientId: string, message: TransportMessage): Promise<boolean> {
const player = this.players.get(clientId);
if (!player) {
return false;
}
return await player.client.sendMessage(message);
}
/**
* 处理玩家消息
*/
async handleMessage(clientId: string, message: TransportMessage): Promise<void> {
if (!this.hasPlayer(clientId)) {
return;
}
this.stats.totalMessages++;
this.emit('message', clientId, message);
// 根据消息类型进行处理
switch (message.type) {
case 'rpc':
await this.handleRpcMessage(clientId, message);
break;
case 'syncvar':
await this.handleSyncVarMessage(clientId, message);
break;
case 'system':
await this.handleSystemMessage(clientId, message);
break;
default:
// 转发自定义消息
await this.broadcast(message, clientId);
break;
}
}
/**
* 更新房间配置
*/
async updateConfig(updates: Partial<RoomConfig>): Promise<void> {
// 验证更新
if (updates.maxPlayers !== undefined && updates.maxPlayers < this.players.size) {
throw new Error('Cannot reduce maxPlayers below current player count');
}
const oldConfig = { ...this.config };
Object.assign(this.config, updates);
// 通知所有玩家房间更新
await this.broadcast({
type: 'system',
data: {
action: 'room-updated',
updates
}
});
this.emit('room-updated', updates);
}
/**
* 暂停房间
*/
async pause(): Promise<void> {
if (this.state === RoomState.ACTIVE) {
this.setState(RoomState.PAUSED);
await this.broadcast({
type: 'system',
data: {
action: 'room-paused'
}
});
}
}
/**
* 恢复房间
*/
async resume(): Promise<void> {
if (this.state === RoomState.PAUSED) {
this.setState(RoomState.ACTIVE);
await this.broadcast({
type: 'system',
data: {
action: 'room-resumed'
}
});
}
}
/**
* 关闭房间
*/
async close(reason: string = 'server-shutdown'): Promise<void> {
if (this.state === RoomState.CLOSED || this.state === RoomState.CLOSING) {
return;
}
this.setState(RoomState.CLOSING);
this.emit('closing', reason);
// 通知所有玩家房间即将关闭
await this.broadcast({
type: 'system',
data: {
action: 'room-closing',
reason
}
});
// 移除所有玩家
const playerIds = Array.from(this.players.keys());
for (const clientId of playerIds) {
await this.removePlayer(clientId, 'room-closed');
}
this.cleanup();
this.setState(RoomState.CLOSED);
console.log(`Room ${this.id} closed, reason: ${reason}`);
this.emit('closed', reason);
}
/**
* 初始化房间
*/
private initialize(): void {
// 创建 ECS 场景
this.ecsScene = new Scene();
// 设置过期定时器
if (this.config.expirationTime && this.config.expirationTime > 0) {
this.expirationTimer = setTimeout(() => {
this.close('expired');
}, this.config.expirationTime);
}
this.setState(RoomState.ACTIVE);
}
/**
* 处理 RPC 消息
*/
private async handleRpcMessage(clientId: string, message: TransportMessage): Promise<void> {
// RPC 消息处理逻辑
// 这里可以添加权限检查、速率限制等
await this.broadcast(message, clientId);
}
/**
* 处理 SyncVar 消息
*/
private async handleSyncVarMessage(clientId: string, message: TransportMessage): Promise<void> {
// SyncVar 消息处理逻辑
// 这里可以添加权限检查、数据验证等
await this.broadcast(message, clientId);
}
/**
* 处理系统消息
*/
private async handleSystemMessage(clientId: string, message: TransportMessage): Promise<void> {
const data = message.data as any;
switch (data.action) {
case 'request-ownership':
// 处理房主权限转移请求
if (this.ownerId === clientId) {
await this.transferOwnership(data.newOwnerId);
}
break;
// 其他系统消息处理...
}
}
/**
* 设置房间状态
*/
private setState(newState: RoomState): void {
const oldState = this.state;
if (oldState !== newState) {
this.state = newState;
this.emit('state-changed', oldState, newState);
}
}
/**
* 清理资源
*/
private cleanup(): void {
if (this.expirationTimer) {
clearTimeout(this.expirationTimer);
this.expirationTimer = null;
}
this.removeAllListeners();
if (this.ecsScene) {
this.ecsScene = null;
}
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof RoomEvents>(event: K, listener: RoomEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof RoomEvents>(event: K, ...args: Parameters<RoomEvents[K]>): boolean {
return super.emit(event, ...args);
}
/**
* 序列化房间信息
*/
toJSON(): object {
return {
id: this.id,
name: this.name,
state: this.state,
config: this.config,
stats: this.getStats(),
players: this.getPlayers().map(player => ({
id: player.client.id,
joinedAt: player.joinedAt.toISOString(),
isOwner: player.isOwner,
customData: player.customData
})),
ownerId: this.ownerId
};
}
}

View File

@@ -1,499 +0,0 @@
/**
* 房间管理器
*
* 管理所有房间的创建、销毁、查找等操作
*/
import { EventEmitter } from 'events';
import { Room, RoomConfig, RoomState, PlayerData } from './Room';
import { ClientConnection } from '../core/ClientConnection';
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
/**
* 房间管理器配置
*/
export interface RoomManagerConfig {
/** 最大房间数量 */
maxRooms?: number;
/** 默认房间过期时间(毫秒) */
defaultExpirationTime?: number;
/** 是否启用房间统计 */
enableStats?: boolean;
/** 房间清理间隔(毫秒) */
cleanupInterval?: number;
}
/**
* 房间查询选项
*/
export interface RoomQueryOptions {
/** 房间名称模糊搜索 */
namePattern?: string;
/** 房间状态过滤 */
state?: RoomState;
/** 是否私有房间 */
isPrivate?: boolean;
/** 最小空位数 */
minAvailableSlots?: number;
/** 最大空位数 */
maxAvailableSlots?: number;
/** 元数据过滤 */
metadata?: Record<string, NetworkValue>;
/** 限制结果数量 */
limit?: number;
/** 跳过条数 */
offset?: number;
}
/**
* 房间管理器统计信息
*/
export interface RoomManagerStats {
/** 总房间数 */
totalRooms: number;
/** 活跃房间数 */
activeRooms: number;
/** 总玩家数 */
totalPlayers: number;
/** 私有房间数 */
privateRooms: number;
/** 持久化房间数 */
persistentRooms: number;
/** 创建的房间总数 */
roomsCreated: number;
/** 关闭的房间总数 */
roomsClosed: number;
}
/**
* 房间管理器事件
*/
export interface RoomManagerEvents {
/** 房间创建 */
'room-created': (room: Room) => void;
/** 房间关闭 */
'room-closed': (roomId: string, reason: string) => void;
/** 玩家加入房间 */
'player-joined-room': (roomId: string, player: PlayerData) => void;
/** 玩家离开房间 */
'player-left-room': (roomId: string, clientId: string, reason?: string) => void;
/** 房间管理器错误 */
'error': (error: Error, roomId?: string) => void;
}
/**
* 房间管理器
*/
export class RoomManager extends EventEmitter {
private config: RoomManagerConfig;
private rooms = new Map<string, Room>();
private stats: RoomManagerStats;
private cleanupTimer: NodeJS.Timeout | null = null;
constructor(config: RoomManagerConfig = {}) {
super();
this.config = {
maxRooms: 1000,
defaultExpirationTime: 0, // 0 = 不过期
enableStats: true,
cleanupInterval: 60000, // 1分钟
...config
};
this.stats = {
totalRooms: 0,
activeRooms: 0,
totalPlayers: 0,
privateRooms: 0,
persistentRooms: 0,
roomsCreated: 0,
roomsClosed: 0
};
this.initialize();
}
/**
* 获取房间管理器配置
*/
getConfig(): Readonly<RoomManagerConfig> {
return this.config;
}
/**
* 获取房间管理器统计信息
*/
getStats(): RoomManagerStats {
this.updateStats();
return { ...this.stats };
}
/**
* 创建房间
*/
async createRoom(config: RoomConfig, creatorClient?: ClientConnection): Promise<Room> {
// 检查房间数量限制
if (this.config.maxRooms && this.rooms.size >= this.config.maxRooms) {
throw new Error('Maximum number of rooms reached');
}
// 检查房间ID是否已存在
if (this.rooms.has(config.id)) {
throw new Error(`Room with id "${config.id}" already exists`);
}
// 应用默认过期时间
const roomConfig: RoomConfig = {
expirationTime: this.config.defaultExpirationTime,
...config
};
const room = new Room(roomConfig);
// 设置房间事件监听
this.setupRoomEvents(room);
this.rooms.set(room.id, room);
this.stats.roomsCreated++;
console.log(`Room created: ${room.id} by ${creatorClient?.id || 'system'}`);
this.emit('room-created', room);
// 如果有创建者,自动加入房间
if (creatorClient) {
try {
await room.addPlayer(creatorClient);
} catch (error) {
console.error(`Failed to add creator to room ${room.id}:`, error);
}
}
return room;
}
/**
* 获取房间
*/
getRoom(roomId: string): Room | undefined {
return this.rooms.get(roomId);
}
/**
* 检查房间是否存在
*/
hasRoom(roomId: string): boolean {
return this.rooms.has(roomId);
}
/**
* 获取所有房间
*/
getAllRooms(): Room[] {
return Array.from(this.rooms.values());
}
/**
* 查询房间
*/
findRooms(options: RoomQueryOptions = {}): Room[] {
let rooms = Array.from(this.rooms.values());
// 状态过滤
if (options.state !== undefined) {
rooms = rooms.filter(room => room.currentState === options.state);
}
// 私有房间过滤
if (options.isPrivate !== undefined) {
rooms = rooms.filter(room => room.getConfig().isPrivate === options.isPrivate);
}
// 名称模糊搜索
if (options.namePattern) {
const pattern = options.namePattern.toLowerCase();
rooms = rooms.filter(room =>
room.getConfig().name.toLowerCase().includes(pattern)
);
}
// 空位数过滤
if (options.minAvailableSlots !== undefined) {
rooms = rooms.filter(room => {
const available = room.getConfig().maxPlayers - room.getPlayerCount();
return available >= options.minAvailableSlots!;
});
}
if (options.maxAvailableSlots !== undefined) {
rooms = rooms.filter(room => {
const available = room.getConfig().maxPlayers - room.getPlayerCount();
return available <= options.maxAvailableSlots!;
});
}
// 元数据过滤
if (options.metadata) {
rooms = rooms.filter(room => {
const roomMetadata = room.getConfig().metadata || {};
return Object.entries(options.metadata!).every(([key, value]) =>
roomMetadata[key] === value
);
});
}
// 排序(按创建时间,最新的在前)
rooms.sort((a, b) =>
b.getStats().createdAt.getTime() - a.getStats().createdAt.getTime()
);
// 分页
const offset = options.offset || 0;
const limit = options.limit || rooms.length;
return rooms.slice(offset, offset + limit);
}
/**
* 关闭房间
*/
async closeRoom(roomId: string, reason: string = 'manual'): Promise<boolean> {
const room = this.rooms.get(roomId);
if (!room) {
return false;
}
try {
await room.close(reason);
return true;
} catch (error) {
this.emit('error', error as Error, roomId);
return false;
}
}
/**
* 玩家加入房间
*/
async joinRoom(
roomId: string,
client: ClientConnection,
customData: Record<string, NetworkValue> = {}
): Promise<boolean> {
const room = this.rooms.get(roomId);
if (!room) {
throw new Error(`Room "${roomId}" not found`);
}
try {
return await room.addPlayer(client, customData);
} catch (error) {
this.emit('error', error as Error, roomId);
throw error;
}
}
/**
* 玩家离开房间
*/
async leaveRoom(roomId: string, clientId: string, reason?: string): Promise<boolean> {
const room = this.rooms.get(roomId);
if (!room) {
return false;
}
try {
return await room.removePlayer(clientId, reason);
} catch (error) {
this.emit('error', error as Error, roomId);
return false;
}
}
/**
* 玩家离开所有房间
*/
async leaveAllRooms(clientId: string, reason?: string): Promise<number> {
let leftCount = 0;
for (const room of this.rooms.values()) {
if (room.hasPlayer(clientId)) {
try {
await room.removePlayer(clientId, reason);
leftCount++;
} catch (error) {
console.error(`Error removing player ${clientId} from room ${room.id}:`, error);
}
}
}
return leftCount;
}
/**
* 获取玩家所在的房间
*/
getPlayerRooms(clientId: string): Room[] {
return Array.from(this.rooms.values())
.filter(room => room.hasPlayer(clientId));
}
/**
* 获取房间数量
*/
getRoomCount(): number {
return this.rooms.size;
}
/**
* 获取总玩家数量
*/
getTotalPlayerCount(): number {
return Array.from(this.rooms.values())
.reduce((total, room) => total + room.getPlayerCount(), 0);
}
/**
* 清理空闲房间
*/
async cleanupRooms(): Promise<number> {
let cleanedCount = 0;
const now = Date.now();
for (const room of this.rooms.values()) {
const config = room.getConfig();
const stats = room.getStats();
// 清理条件:
// 1. 非持久化的空房间
// 2. 已过期的房间
// 3. 已关闭的房间
let shouldClean = false;
let reason = '';
if (room.currentState === RoomState.CLOSED) {
shouldClean = true;
reason = 'room-closed';
} else if (!config.persistent && room.isEmpty()) {
shouldClean = true;
reason = 'empty-room';
} else if (config.expirationTime && config.expirationTime > 0) {
const expireTime = stats.createdAt.getTime() + config.expirationTime;
if (now >= expireTime) {
shouldClean = true;
reason = 'expired';
}
}
if (shouldClean) {
try {
if (room.currentState !== RoomState.CLOSED) {
await room.close(reason);
}
this.rooms.delete(room.id);
cleanedCount++;
console.log(`Cleaned up room: ${room.id}, reason: ${reason}`);
} catch (error) {
console.error(`Error cleaning up room ${room.id}:`, error);
}
}
}
return cleanedCount;
}
/**
* 关闭所有房间
*/
async closeAllRooms(reason: string = 'shutdown'): Promise<void> {
const rooms = Array.from(this.rooms.values());
const promises = rooms.map(room => room.close(reason));
await Promise.allSettled(promises);
this.rooms.clear();
console.log(`Closed ${rooms.length} rooms, reason: ${reason}`);
}
/**
* 销毁房间管理器
*/
async destroy(): Promise<void> {
// 停止清理定时器
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
// 关闭所有房间
await this.closeAllRooms('manager-destroyed');
// 移除所有事件监听器
this.removeAllListeners();
}
/**
* 初始化房间管理器
*/
private initialize(): void {
// 启动清理定时器
if (this.config.cleanupInterval && this.config.cleanupInterval > 0) {
this.cleanupTimer = setInterval(() => {
this.cleanupRooms().catch(error => {
console.error('Error during room cleanup:', error);
});
}, this.config.cleanupInterval);
}
}
/**
* 设置房间事件监听
*/
private setupRoomEvents(room: Room): void {
room.on('player-joined', (player) => {
this.emit('player-joined-room', room.id, player);
});
room.on('player-left', (clientId, reason) => {
this.emit('player-left-room', room.id, clientId, reason);
});
room.on('closed', (reason) => {
this.rooms.delete(room.id);
this.stats.roomsClosed++;
console.log(`Room ${room.id} removed from manager, reason: ${reason}`);
this.emit('room-closed', room.id, reason);
});
room.on('error', (error) => {
this.emit('error', error, room.id);
});
}
/**
* 更新统计信息
*/
private updateStats(): void {
this.stats.totalRooms = this.rooms.size;
this.stats.activeRooms = Array.from(this.rooms.values())
.filter(room => room.currentState === RoomState.ACTIVE).length;
this.stats.totalPlayers = this.getTotalPlayerCount();
this.stats.privateRooms = Array.from(this.rooms.values())
.filter(room => room.getConfig().isPrivate).length;
this.stats.persistentRooms = Array.from(this.rooms.values())
.filter(room => room.getConfig().persistent).length;
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof RoomManagerEvents>(event: K, listener: RoomManagerEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof RoomManagerEvents>(event: K, ...args: Parameters<RoomManagerEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -1,6 +0,0 @@
/**
* 房间系统导出
*/
export * from './Room';
export * from './RoomManager';

View File

@@ -1,762 +0,0 @@
/**
* RPC 系统
*
* 处理服务端的 RPC 调用、权限验证、参数验证等
*/
import { EventEmitter } from 'events';
import { v4 as uuidv4 } from 'uuid';
import {
NetworkValue,
RpcMetadata
} from '@esengine/ecs-framework-network-shared';
import { ClientConnection } from '../core/ClientConnection';
import { Room } from '../rooms/Room';
import { TransportMessage } from '../core/Transport';
/**
* RPC 调用记录
*/
export interface RpcCall {
/** 调用ID */
id: string;
/** 网络对象ID */
networkId: number;
/** 组件类型 */
componentType: string;
/** 方法名 */
methodName: string;
/** 参数 */
parameters: NetworkValue[];
/** 元数据 */
metadata: RpcMetadata;
/** 发送者客户端ID */
senderId: string;
/** 目标客户端IDs用于 ClientRpc */
targetClientIds?: string[];
/** 是否需要响应 */
requiresResponse: boolean;
/** 时间戳 */
timestamp: Date;
/** 过期时间 */
expiresAt?: Date;
}
/**
* RPC 响应
*/
export interface RpcResponse {
/** 调用ID */
callId: string;
/** 是否成功 */
success: boolean;
/** 返回值 */
result?: NetworkValue;
/** 错误信息 */
error?: string;
/** 错误代码 */
errorCode?: string;
/** 时间戳 */
timestamp: Date;
}
/**
* RPC 系统配置
*/
export interface RpcSystemConfig {
/** RPC 调用超时时间(毫秒) */
callTimeout?: number;
/** 最大并发 RPC 调用数 */
maxConcurrentCalls?: number;
/** 是否启用权限检查 */
enablePermissionCheck?: boolean;
/** 是否启用参数验证 */
enableParameterValidation?: boolean;
/** 是否启用频率限制 */
enableRateLimit?: boolean;
/** 最大 RPC 频率(调用/秒) */
maxRpcRate?: number;
/** 单个参数最大大小(字节) */
maxParameterSize?: number;
}
/**
* RPC 系统事件
*/
export interface RpcSystemEvents {
/** ClientRpc 调用 */
'client-rpc-called': (call: RpcCall) => void;
/** ServerRpc 调用 */
'server-rpc-called': (call: RpcCall) => void;
/** RPC 调用完成 */
'rpc-completed': (call: RpcCall, response?: RpcResponse) => void;
/** RPC 调用超时 */
'rpc-timeout': (callId: string) => void;
/** 权限验证失败 */
'permission-denied': (clientId: string, call: RpcCall) => void;
/** 参数验证失败 */
'parameter-validation-failed': (clientId: string, call: RpcCall, reason: string) => void;
/** 频率限制触发 */
'rate-limit-exceeded': (clientId: string) => void;
/** RPC 错误 */
'rpc-error': (error: Error, callId?: string, clientId?: string) => void;
}
/**
* 客户端 RPC 状态
*/
interface ClientRpcState {
/** 客户端ID */
clientId: string;
/** 活跃的调用 */
activeCalls: Map<string, RpcCall>;
/** RPC 调用计数 */
rpcCount: number;
/** 频率重置时间 */
rateResetTime: Date;
}
/**
* 待处理的 RPC 响应
*/
interface PendingRpcResponse {
/** 调用信息 */
call: RpcCall;
/** 超时定时器 */
timeoutTimer: NodeJS.Timeout;
/** 响应回调 */
responseCallback: (response: RpcResponse) => void;
}
/**
* RPC 系统
*/
export class RpcSystem extends EventEmitter {
private config: RpcSystemConfig;
private clientStates = new Map<string, ClientRpcState>();
private pendingCalls = new Map<string, PendingRpcResponse>();
private cleanupTimer: NodeJS.Timeout | null = null;
constructor(config: RpcSystemConfig = {}) {
super();
this.config = {
callTimeout: 30000, // 30秒
maxConcurrentCalls: 10,
enablePermissionCheck: true,
enableParameterValidation: true,
enableRateLimit: true,
maxRpcRate: 30, // 30次/秒
maxParameterSize: 65536, // 64KB
...config
};
this.initialize();
}
/**
* 处理 ClientRpc 调用
*/
async handleClientRpcCall(
client: ClientConnection,
message: TransportMessage,
room: Room
): Promise<void> {
try {
const data = message.data as any;
const {
networkId,
componentType,
methodName,
parameters = [],
metadata,
targetFilter = 'all'
} = data;
// 创建 RPC 调用记录
const rpcCall: RpcCall = {
id: uuidv4(),
networkId,
componentType,
methodName,
parameters,
metadata,
senderId: client.id,
requiresResponse: metadata?.requiresResponse || false,
timestamp: new Date()
};
// 权限检查
if (this.config.enablePermissionCheck) {
if (!this.checkRpcPermission(client, rpcCall, 'client-rpc')) {
this.emit('permission-denied', client.id, rpcCall);
return;
}
}
// 频率限制检查
if (this.config.enableRateLimit && !this.checkRpcRate(client.id)) {
this.emit('rate-limit-exceeded', client.id);
return;
}
// 参数验证
if (this.config.enableParameterValidation) {
const validationResult = this.validateRpcParameters(rpcCall);
if (!validationResult.valid) {
this.emit('parameter-validation-failed', client.id, rpcCall, validationResult.reason!);
return;
}
}
// 确定目标客户端
const targetClientIds = this.getClientRpcTargets(room, client.id, targetFilter);
rpcCall.targetClientIds = targetClientIds;
// 记录活跃调用
this.recordActiveCall(client.id, rpcCall);
// 触发事件
this.emit('client-rpc-called', rpcCall);
// 发送到目标客户端
await this.sendClientRpc(room, rpcCall, targetClientIds);
// 如果不需要响应,立即标记完成
if (!rpcCall.requiresResponse) {
this.completeRpcCall(rpcCall);
}
} catch (error) {
this.emit('rpc-error', error as Error, undefined, client.id);
}
}
/**
* 处理 ServerRpc 调用
*/
async handleServerRpcCall(
client: ClientConnection,
message: TransportMessage,
room: Room
): Promise<void> {
try {
const data = message.data as any;
const {
networkId,
componentType,
methodName,
parameters = [],
metadata
} = data;
// 创建 RPC 调用记录
const rpcCall: RpcCall = {
id: uuidv4(),
networkId,
componentType,
methodName,
parameters,
metadata,
senderId: client.id,
requiresResponse: metadata?.requiresResponse || false,
timestamp: new Date()
};
// 权限检查
if (this.config.enablePermissionCheck) {
if (!this.checkRpcPermission(client, rpcCall, 'server-rpc')) {
this.emit('permission-denied', client.id, rpcCall);
return;
}
}
// 频率限制检查
if (this.config.enableRateLimit && !this.checkRpcRate(client.id)) {
this.emit('rate-limit-exceeded', client.id);
return;
}
// 参数验证
if (this.config.enableParameterValidation) {
const validationResult = this.validateRpcParameters(rpcCall);
if (!validationResult.valid) {
this.emit('parameter-validation-failed', client.id, rpcCall, validationResult.reason!);
return;
}
}
// 记录活跃调用
this.recordActiveCall(client.id, rpcCall);
// 触发事件
this.emit('server-rpc-called', rpcCall);
// ServerRpc 在服务端执行,这里需要实际的执行逻辑
// 在实际使用中,应该通过事件或回调来执行具体的方法
const response = await this.executeServerRpc(rpcCall);
// 发送响应(如果需要)
if (rpcCall.requiresResponse && response) {
await this.sendRpcResponse(client, response);
}
this.completeRpcCall(rpcCall, response || undefined);
} catch (error) {
this.emit('rpc-error', error as Error, undefined, client.id);
// 发送错误响应
if (message.data && (message.data as any).requiresResponse) {
const errorResponse: RpcResponse = {
callId: (message.data as any).callId || uuidv4(),
success: false,
error: (error as Error).message,
errorCode: 'EXECUTION_ERROR',
timestamp: new Date()
};
await this.sendRpcResponse(client, errorResponse);
}
}
}
/**
* 处理 RPC 响应
*/
async handleRpcResponse(
client: ClientConnection,
message: TransportMessage
): Promise<void> {
try {
const response = message.data as any as RpcResponse;
const pendingCall = this.pendingCalls.get(response.callId);
if (pendingCall) {
// 清除超时定时器
clearTimeout(pendingCall.timeoutTimer);
this.pendingCalls.delete(response.callId);
// 调用响应回调
pendingCall.responseCallback(response);
// 完成调用
this.completeRpcCall(pendingCall.call, response);
}
} catch (error) {
this.emit('rpc-error', error as Error, undefined, client.id);
}
}
/**
* 调用 ClientRpc从服务端向客户端发送
*/
async callClientRpc(
room: Room,
networkId: number,
componentType: string,
methodName: string,
parameters: NetworkValue[] = [],
options: {
targetFilter?: 'all' | 'others' | 'owner' | string[];
requiresResponse?: boolean;
timeout?: number;
} = {}
): Promise<RpcResponse[]> {
const rpcCall: RpcCall = {
id: uuidv4(),
networkId,
componentType,
methodName,
parameters,
metadata: {
methodName,
rpcType: 'client-rpc',
requiresAuth: false,
reliable: true,
requiresResponse: options.requiresResponse || false
},
senderId: 'server',
requiresResponse: options.requiresResponse || false,
timestamp: new Date()
};
// 确定目标客户端
const targetClientIds = typeof options.targetFilter === 'string'
? this.getClientRpcTargets(room, 'server', options.targetFilter)
: options.targetFilter || [];
rpcCall.targetClientIds = targetClientIds;
// 发送到目标客户端
await this.sendClientRpc(room, rpcCall, targetClientIds);
// 如果需要响应,等待响应
if (options.requiresResponse) {
return await this.waitForRpcResponses(rpcCall, targetClientIds, options.timeout);
}
this.completeRpcCall(rpcCall);
return [];
}
/**
* 获取客户端统计信息
*/
getClientRpcStats(clientId: string): {
activeCalls: number;
totalCalls: number;
} {
const state = this.clientStates.get(clientId);
return {
activeCalls: state?.activeCalls.size || 0,
totalCalls: state?.rpcCount || 0
};
}
/**
* 取消所有客户端的 RPC 调用
*/
cancelClientRpcs(clientId: string): number {
const state = this.clientStates.get(clientId);
if (!state) {
return 0;
}
const cancelledCount = state.activeCalls.size;
// 取消所有活跃调用
for (const call of state.activeCalls.values()) {
this.completeRpcCall(call);
}
state.activeCalls.clear();
return cancelledCount;
}
/**
* 销毁 RPC 系统
*/
destroy(): void {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
// 清除所有待处理的调用
for (const pending of this.pendingCalls.values()) {
clearTimeout(pending.timeoutTimer);
}
this.clientStates.clear();
this.pendingCalls.clear();
this.removeAllListeners();
}
/**
* 初始化系统
*/
private initialize(): void {
// 启动清理定时器(每分钟清理一次)
this.cleanupTimer = setInterval(() => {
this.cleanup();
}, 60000);
}
/**
* 检查 RPC 权限
*/
private checkRpcPermission(
client: ClientConnection,
call: RpcCall,
rpcType: 'client-rpc' | 'server-rpc'
): boolean {
// 基本权限检查
if (!client.hasPermission('canSendRpc')) {
return false;
}
// ServerRpc 额外权限检查
if (rpcType === 'server-rpc' && call.metadata.requiresAuth) {
if (!client.isAuthenticated) {
return false;
}
}
// 可以添加更多特定的权限检查逻辑
return true;
}
/**
* 检查 RPC 频率
*/
private checkRpcRate(clientId: string): boolean {
if (!this.config.maxRpcRate || this.config.maxRpcRate <= 0) {
return true;
}
const now = new Date();
let state = this.clientStates.get(clientId);
if (!state) {
state = {
clientId,
activeCalls: new Map(),
rpcCount: 1,
rateResetTime: new Date(now.getTime() + 1000)
};
this.clientStates.set(clientId, state);
return true;
}
// 检查是否需要重置计数
if (now >= state.rateResetTime) {
state.rpcCount = 1;
state.rateResetTime = new Date(now.getTime() + 1000);
return true;
}
// 检查频率限制
if (state.rpcCount >= this.config.maxRpcRate) {
return false;
}
state.rpcCount++;
return true;
}
/**
* 验证 RPC 参数
*/
private validateRpcParameters(call: RpcCall): { valid: boolean; reason?: string } {
// 检查参数数量
if (call.parameters.length > 10) {
return { valid: false, reason: 'Too many parameters' };
}
// 检查每个参数的大小
for (let i = 0; i < call.parameters.length; i++) {
const param = call.parameters[i];
try {
const serialized = JSON.stringify(param);
if (serialized.length > this.config.maxParameterSize!) {
return { valid: false, reason: `Parameter ${i} is too large` };
}
} catch (error) {
return { valid: false, reason: `Parameter ${i} is not serializable` };
}
}
return { valid: true };
}
/**
* 获取 ClientRpc 目标客户端
*/
private getClientRpcTargets(
room: Room,
senderId: string,
targetFilter: string
): string[] {
const players = room.getPlayers();
switch (targetFilter) {
case 'all':
return players.map(p => p.client.id);
case 'others':
return players
.filter(p => p.client.id !== senderId)
.map(p => p.client.id);
case 'owner':
const owner = room.getOwner();
return owner ? [owner.client.id] : [];
default:
return [];
}
}
/**
* 发送 ClientRpc
*/
private async sendClientRpc(
room: Room,
call: RpcCall,
targetClientIds: string[]
): Promise<void> {
const message: TransportMessage = {
type: 'rpc',
data: {
action: 'client-rpc',
callId: call.id,
networkId: call.networkId,
componentType: call.componentType,
methodName: call.methodName,
parameters: call.parameters,
metadata: call.metadata as any,
requiresResponse: call.requiresResponse,
timestamp: call.timestamp.getTime()
} as any
};
// 发送给目标客户端
const promises = targetClientIds.map(clientId =>
room.sendToPlayer(clientId, message)
);
await Promise.allSettled(promises);
}
/**
* 执行 ServerRpc
*/
private async executeServerRpc(call: RpcCall): Promise<RpcResponse | null> {
// 这里应该是实际的服务端方法执行逻辑
// 在实际实现中,可能需要通过事件或回调来执行具体的方法
// 示例响应
const response: RpcResponse = {
callId: call.id,
success: true,
result: undefined, // 实际执行结果
timestamp: new Date()
};
return response;
}
/**
* 发送 RPC 响应
*/
private async sendRpcResponse(
client: ClientConnection,
response: RpcResponse
): Promise<void> {
const message: TransportMessage = {
type: 'rpc',
data: {
action: 'rpc-response',
...response
} as any
};
await client.sendMessage(message);
}
/**
* 等待 RPC 响应
*/
private async waitForRpcResponses(
call: RpcCall,
targetClientIds: string[],
timeout?: number
): Promise<RpcResponse[]> {
return new Promise((resolve) => {
const responses: RpcResponse[] = [];
const responseTimeout = timeout || this.config.callTimeout!;
let responseCount = 0;
const responseCallback = (response: RpcResponse) => {
responses.push(response);
responseCount++;
// 如果收到所有响应立即resolve
if (responseCount >= targetClientIds.length) {
resolve(responses);
}
};
// 设置超时
const timeoutTimer = setTimeout(() => {
resolve(responses); // 返回已收到的响应
this.emit('rpc-timeout', call.id);
}, responseTimeout);
// 注册待处理的响应
this.pendingCalls.set(call.id, {
call,
timeoutTimer,
responseCallback
});
});
}
/**
* 记录活跃调用
*/
private recordActiveCall(clientId: string, call: RpcCall): void {
let state = this.clientStates.get(clientId);
if (!state) {
state = {
clientId,
activeCalls: new Map(),
rpcCount: 0,
rateResetTime: new Date()
};
this.clientStates.set(clientId, state);
}
state.activeCalls.set(call.id, call);
}
/**
* 完成 RPC 调用
*/
private completeRpcCall(call: RpcCall, response?: RpcResponse): void {
// 从活跃调用中移除
const state = this.clientStates.get(call.senderId);
if (state) {
state.activeCalls.delete(call.id);
}
// 触发完成事件
this.emit('rpc-completed', call, response);
}
/**
* 清理过期的调用和状态
*/
private cleanup(): void {
const now = new Date();
let cleanedCalls = 0;
let cleanedStates = 0;
// 清理过期的待处理调用
for (const [callId, pending] of this.pendingCalls.entries()) {
if (pending.call.expiresAt && pending.call.expiresAt < now) {
clearTimeout(pending.timeoutTimer);
this.pendingCalls.delete(callId);
cleanedCalls++;
}
}
// 清理空的客户端状态
for (const [clientId, state] of this.clientStates.entries()) {
if (state.activeCalls.size === 0 &&
now.getTime() - state.rateResetTime.getTime() > 60000) {
this.clientStates.delete(clientId);
cleanedStates++;
}
}
if (cleanedCalls > 0 || cleanedStates > 0) {
console.log(`RPC cleanup: ${cleanedCalls} calls, ${cleanedStates} states`);
}
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof RpcSystemEvents>(event: K, listener: RpcSystemEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof RpcSystemEvents>(event: K, ...args: Parameters<RpcSystemEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -1,587 +0,0 @@
/**
* SyncVar 同步系统
*
* 处理服务端的 SyncVar 同步逻辑、权限验证、数据传播等
*/
import { EventEmitter } from 'events';
import {
NetworkValue,
SyncVarMetadata,
NetworkSerializer
} from '@esengine/ecs-framework-network-shared';
import { ClientConnection } from '../core/ClientConnection';
import { Room } from '../rooms/Room';
import { TransportMessage } from '../core/Transport';
/**
* SyncVar 更改记录
*/
export interface SyncVarChange {
/** 网络对象ID */
networkId: number;
/** 组件类型 */
componentType: string;
/** 属性名 */
propertyName: string;
/** 旧值 */
oldValue: NetworkValue;
/** 新值 */
newValue: NetworkValue;
/** 元数据 */
metadata: SyncVarMetadata;
/** 发送者客户端ID */
senderId: string;
/** 时间戳 */
timestamp: Date;
}
/**
* SyncVar 同步配置
*/
export interface SyncVarSystemConfig {
/** 批量同步间隔(毫秒) */
batchInterval?: number;
/** 单次批量最大数量 */
maxBatchSize?: number;
/** 是否启用增量同步 */
enableDeltaSync?: boolean;
/** 是否启用权限检查 */
enablePermissionCheck?: boolean;
/** 是否启用数据验证 */
enableDataValidation?: boolean;
/** 最大同步频率(次/秒) */
maxSyncRate?: number;
}
/**
* 网络对象状态
*/
export interface NetworkObjectState {
/** 网络对象ID */
networkId: number;
/** 拥有者客户端ID */
ownerId: string;
/** 组件状态 */
components: Map<string, Map<string, NetworkValue>>;
/** 最后更新时间 */
lastUpdateTime: Date;
/** 权威状态 */
hasAuthority: boolean;
}
/**
* SyncVar 系统事件
*/
export interface SyncVarSystemEvents {
/** SyncVar 值变化 */
'syncvar-changed': (change: SyncVarChange) => void;
/** 同步批次完成 */
'batch-synced': (changes: SyncVarChange[], targetClients: string[]) => void;
/** 权限验证失败 */
'permission-denied': (clientId: string, change: SyncVarChange) => void;
/** 数据验证失败 */
'validation-failed': (clientId: string, change: SyncVarChange, reason: string) => void;
/** 同步错误 */
'sync-error': (error: Error, clientId?: string) => void;
}
/**
* 客户端同步状态
*/
interface ClientSyncState {
/** 客户端ID */
clientId: string;
/** 待同步的变化列表 */
pendingChanges: SyncVarChange[];
/** 最后同步时间 */
lastSyncTime: Date;
/** 同步频率限制 */
syncCount: number;
/** 频率重置时间 */
rateResetTime: Date;
}
/**
* SyncVar 同步系统
*/
export class SyncVarSystem extends EventEmitter {
private config: SyncVarSystemConfig;
private networkObjects = new Map<number, NetworkObjectState>();
private clientSyncStates = new Map<string, ClientSyncState>();
private serializer: NetworkSerializer;
private batchTimer: NodeJS.Timeout | null = null;
constructor(config: SyncVarSystemConfig = {}) {
super();
this.config = {
batchInterval: 50, // 50ms批量间隔
maxBatchSize: 100,
enableDeltaSync: true,
enablePermissionCheck: true,
enableDataValidation: true,
maxSyncRate: 60, // 60次/秒
...config
};
this.serializer = new NetworkSerializer();
this.initialize();
}
/**
* 注册网络对象
*/
registerNetworkObject(
networkId: number,
ownerId: string,
hasAuthority: boolean = true
): void {
if (this.networkObjects.has(networkId)) {
console.warn(`Network object ${networkId} is already registered`);
return;
}
const networkObject: NetworkObjectState = {
networkId,
ownerId,
components: new Map(),
lastUpdateTime: new Date(),
hasAuthority
};
this.networkObjects.set(networkId, networkObject);
console.log(`Network object registered: ${networkId} owned by ${ownerId}`);
}
/**
* 注销网络对象
*/
unregisterNetworkObject(networkId: number): boolean {
const removed = this.networkObjects.delete(networkId);
if (removed) {
console.log(`Network object unregistered: ${networkId}`);
}
return removed;
}
/**
* 获取网络对象
*/
getNetworkObject(networkId: number): NetworkObjectState | undefined {
return this.networkObjects.get(networkId);
}
/**
* 处理 SyncVar 变化消息
*/
async handleSyncVarChange(
client: ClientConnection,
message: TransportMessage,
room?: Room
): Promise<void> {
try {
const data = message.data as any;
const {
networkId,
componentType,
propertyName,
oldValue,
newValue,
metadata
} = data;
// 创建变化记录
const change: SyncVarChange = {
networkId,
componentType,
propertyName,
oldValue,
newValue,
metadata,
senderId: client.id,
timestamp: new Date()
};
// 权限检查
if (this.config.enablePermissionCheck) {
if (!this.checkSyncVarPermission(client, change)) {
this.emit('permission-denied', client.id, change);
return;
}
}
// 频率限制检查
if (!this.checkSyncRate(client.id)) {
console.warn(`SyncVar rate limit exceeded for client ${client.id}`);
return;
}
// 数据验证
if (this.config.enableDataValidation) {
const validationResult = this.validateSyncVarData(change);
if (!validationResult.valid) {
this.emit('validation-failed', client.id, change, validationResult.reason!);
return;
}
}
// 更新网络对象状态
this.updateNetworkObjectState(change);
// 触发变化事件
this.emit('syncvar-changed', change);
// 添加到待同步列表
if (room) {
this.addToBatchSync(change, room);
}
} catch (error) {
this.emit('sync-error', error as Error, client.id);
}
}
/**
* 获取网络对象的完整状态
*/
getNetworkObjectSnapshot(networkId: number): Record<string, any> | null {
const networkObject = this.networkObjects.get(networkId);
if (!networkObject) {
return null;
}
const snapshot: Record<string, any> = {};
for (const [componentType, componentData] of networkObject.components) {
snapshot[componentType] = {};
for (const [propertyName, value] of componentData) {
snapshot[componentType][propertyName] = value;
}
}
return snapshot;
}
/**
* 向客户端发送网络对象快照
*/
async sendNetworkObjectSnapshot(
client: ClientConnection,
networkId: number
): Promise<boolean> {
const snapshot = this.getNetworkObjectSnapshot(networkId);
if (!snapshot) {
return false;
}
const message: TransportMessage = {
type: 'syncvar',
data: {
action: 'snapshot',
networkId,
snapshot
}
};
return await client.sendMessage(message);
}
/**
* 同步所有网络对象给新客户端
*/
async syncAllNetworkObjects(client: ClientConnection, room: Room): Promise<number> {
let syncedCount = 0;
for (const networkObject of this.networkObjects.values()) {
// 检查客户端是否有权限看到这个网络对象
if (this.canClientSeeNetworkObject(client.id, networkObject)) {
const success = await this.sendNetworkObjectSnapshot(client, networkObject.networkId);
if (success) {
syncedCount++;
}
}
}
console.log(`Synced ${syncedCount} network objects to client ${client.id}`);
return syncedCount;
}
/**
* 设置网络对象拥有者
*/
setNetworkObjectOwner(networkId: number, newOwnerId: string): boolean {
const networkObject = this.networkObjects.get(networkId);
if (!networkObject) {
return false;
}
const oldOwnerId = networkObject.ownerId;
networkObject.ownerId = newOwnerId;
networkObject.lastUpdateTime = new Date();
console.log(`Network object ${networkId} ownership changed: ${oldOwnerId} -> ${newOwnerId}`);
return true;
}
/**
* 获取网络对象拥有者
*/
getNetworkObjectOwner(networkId: number): string | undefined {
const networkObject = this.networkObjects.get(networkId);
return networkObject?.ownerId;
}
/**
* 销毁 SyncVar 系统
*/
destroy(): void {
if (this.batchTimer) {
clearInterval(this.batchTimer);
this.batchTimer = null;
}
this.networkObjects.clear();
this.clientSyncStates.clear();
this.removeAllListeners();
}
/**
* 初始化系统
*/
private initialize(): void {
// 启动批量同步定时器
if (this.config.batchInterval && this.config.batchInterval > 0) {
this.batchTimer = setInterval(() => {
this.processBatchSync();
}, this.config.batchInterval);
}
}
/**
* 检查 SyncVar 权限
*/
private checkSyncVarPermission(client: ClientConnection, change: SyncVarChange): boolean {
// 检查客户端是否有网络同步权限
if (!client.hasPermission('canSyncVars')) {
return false;
}
// 获取网络对象
const networkObject = this.networkObjects.get(change.networkId);
if (!networkObject) {
return false;
}
// 检查权威权限
if (change.metadata.authorityOnly) {
// 只有网络对象拥有者或有权威权限的客户端可以修改
return networkObject.ownerId === client.id || networkObject.hasAuthority;
}
return true;
}
/**
* 检查同步频率
*/
private checkSyncRate(clientId: string): boolean {
if (!this.config.maxSyncRate || this.config.maxSyncRate <= 0) {
return true;
}
const now = new Date();
let syncState = this.clientSyncStates.get(clientId);
if (!syncState) {
syncState = {
clientId,
pendingChanges: [],
lastSyncTime: now,
syncCount: 1,
rateResetTime: new Date(now.getTime() + 1000) // 1秒后重置
};
this.clientSyncStates.set(clientId, syncState);
return true;
}
// 检查是否需要重置计数
if (now >= syncState.rateResetTime) {
syncState.syncCount = 1;
syncState.rateResetTime = new Date(now.getTime() + 1000);
return true;
}
// 检查频率限制
if (syncState.syncCount >= this.config.maxSyncRate) {
return false;
}
syncState.syncCount++;
return true;
}
/**
* 验证 SyncVar 数据
*/
private validateSyncVarData(change: SyncVarChange): { valid: boolean; reason?: string } {
// 基本类型检查
if (change.newValue === null || change.newValue === undefined) {
return { valid: false, reason: 'Value cannot be null or undefined' };
}
// 检查数据大小(防止过大的数据)
try {
const serialized = JSON.stringify(change.newValue);
if (serialized.length > 65536) { // 64KB限制
return { valid: false, reason: 'Data too large' };
}
} catch (error) {
return { valid: false, reason: 'Data is not serializable' };
}
// 可以添加更多特定的验证逻辑
return { valid: true };
}
/**
* 更新网络对象状态
*/
private updateNetworkObjectState(change: SyncVarChange): void {
let networkObject = this.networkObjects.get(change.networkId);
if (!networkObject) {
// 如果网络对象不存在,创建一个新的(可能是客户端创建的)
networkObject = {
networkId: change.networkId,
ownerId: change.senderId,
components: new Map(),
lastUpdateTime: new Date(),
hasAuthority: true
};
this.networkObjects.set(change.networkId, networkObject);
}
// 获取或创建组件数据
let componentData = networkObject.components.get(change.componentType);
if (!componentData) {
componentData = new Map();
networkObject.components.set(change.componentType, componentData);
}
// 更新属性值
componentData.set(change.propertyName, change.newValue);
networkObject.lastUpdateTime = change.timestamp;
}
/**
* 添加到批量同步
*/
private addToBatchSync(change: SyncVarChange, room: Room): void {
// 获取房间内需要同步的客户端
const roomPlayers = room.getPlayers();
const targetClientIds = roomPlayers
.filter(player => player.client.id !== change.senderId) // 不发送给发送者
.map(player => player.client.id);
// 为每个目标客户端添加变化记录
for (const clientId of targetClientIds) {
let syncState = this.clientSyncStates.get(clientId);
if (!syncState) {
syncState = {
clientId,
pendingChanges: [],
lastSyncTime: new Date(),
syncCount: 0,
rateResetTime: new Date()
};
this.clientSyncStates.set(clientId, syncState);
}
syncState.pendingChanges.push(change);
}
}
/**
* 处理批量同步
*/
private async processBatchSync(): Promise<void> {
const syncPromises: Promise<void>[] = [];
for (const [clientId, syncState] of this.clientSyncStates.entries()) {
if (syncState.pendingChanges.length === 0) {
continue;
}
// 获取要同步的变化(限制批量大小)
const changesToSync = syncState.pendingChanges.splice(
0,
this.config.maxBatchSize
);
if (changesToSync.length > 0) {
syncPromises.push(this.sendBatchChanges(clientId, changesToSync));
}
}
if (syncPromises.length > 0) {
await Promise.allSettled(syncPromises);
}
}
/**
* 发送批量变化
*/
private async sendBatchChanges(clientId: string, changes: SyncVarChange[]): Promise<void> {
try {
// 这里需要获取客户端连接,实际实现中可能需要从外部传入
// 为了简化,这里假设有一个方法可以获取客户端连接
// 实际使用时,可能需要通过回调或事件来发送消息
const message: TransportMessage = {
type: 'syncvar',
data: {
action: 'batch-update',
changes: changes.map(change => ({
networkId: change.networkId,
componentType: change.componentType,
propertyName: change.propertyName,
newValue: change.newValue,
metadata: change.metadata as any,
timestamp: change.timestamp.getTime()
}))
} as any
};
// 这里需要实际的发送逻辑
// 在实际使用中,应该通过事件或回调来发送消息
this.emit('batch-synced', changes, [clientId]);
} catch (error) {
this.emit('sync-error', error as Error, clientId);
}
}
/**
* 检查客户端是否可以看到网络对象
*/
private canClientSeeNetworkObject(clientId: string, networkObject: NetworkObjectState): boolean {
// 基本实现:客户端可以看到自己拥有的对象和公共对象
// 实际实现中可能需要更复杂的可见性逻辑
return true;
}
/**
* 类型安全的事件监听
*/
override on<K extends keyof SyncVarSystemEvents>(event: K, listener: SyncVarSystemEvents[K]): this {
return super.on(event, listener);
}
/**
* 类型安全的事件触发
*/
override emit<K extends keyof SyncVarSystemEvents>(event: K, ...args: Parameters<SyncVarSystemEvents[K]>): boolean {
return super.emit(event, ...args);
}
}

View File

@@ -1,6 +0,0 @@
/**
* 系统模块导出
*/
export * from './SyncVarSystem';
export * from './RpcSystem';

View File

@@ -1,572 +0,0 @@
/**
* 消息验证器
*
* 验证网络消息的格式、大小、内容等
*/
import { NetworkValue } from '@esengine/ecs-framework-network-shared';
import { TransportMessage } from '../core/Transport';
/**
* 验证结果
*/
export interface ValidationResult {
/** 是否有效 */
valid: boolean;
/** 错误信息 */
error?: string;
/** 错误代码 */
errorCode?: string;
/** 详细信息 */
details?: Record<string, any>;
}
/**
* 验证配置
*/
export interface ValidationConfig {
/** 最大消息大小(字节) */
maxMessageSize?: number;
/** 最大数组长度 */
maxArrayLength?: number;
/** 最大对象深度 */
maxObjectDepth?: number;
/** 最大字符串长度 */
maxStringLength?: number;
/** 允许的消息类型 */
allowedMessageTypes?: string[];
/** 是否允许null值 */
allowNullValues?: boolean;
/** 是否允许undefined值 */
allowUndefinedValues?: boolean;
}
/**
* 验证规则
*/
export interface ValidationRule {
/** 规则名称 */
name: string;
/** 验证函数 */
validate: (value: any, context: ValidationContext) => ValidationResult;
/** 是否必需 */
required?: boolean;
}
/**
* 验证上下文
*/
export interface ValidationContext {
/** 当前路径 */
path: string[];
/** 当前深度 */
depth: number;
/** 配置 */
config: ValidationConfig;
/** 消息类型 */
messageType?: string;
}
/**
* 消息验证器
*/
export class MessageValidator {
private config: ValidationConfig;
private customRules = new Map<string, ValidationRule>();
constructor(config: ValidationConfig = {}) {
this.config = {
maxMessageSize: 1024 * 1024, // 1MB
maxArrayLength: 1000,
maxObjectDepth: 10,
maxStringLength: 10000,
allowedMessageTypes: ['rpc', 'syncvar', 'system', 'custom'],
allowNullValues: true,
allowUndefinedValues: false,
...config
};
}
/**
* 验证传输消息
*/
validateMessage(message: TransportMessage): ValidationResult {
try {
// 基本结构验证
const structureResult = this.validateMessageStructure(message);
if (!structureResult.valid) {
return structureResult;
}
// 消息大小验证
const sizeResult = this.validateMessageSize(message);
if (!sizeResult.valid) {
return sizeResult;
}
// 消息类型验证
const typeResult = this.validateMessageType(message);
if (!typeResult.valid) {
return typeResult;
}
// 数据内容验证
const dataResult = this.validateMessageData(message);
if (!dataResult.valid) {
return dataResult;
}
// 自定义规则验证
const customResult = this.validateCustomRules(message);
if (!customResult.valid) {
return customResult;
}
return { valid: true };
} catch (error) {
return {
valid: false,
error: (error as Error).message,
errorCode: 'VALIDATION_ERROR'
};
}
}
/**
* 验证网络值
*/
validateNetworkValue(value: NetworkValue, context?: Partial<ValidationContext>): ValidationResult {
const fullContext: ValidationContext = {
path: [],
depth: 0,
config: this.config,
...context
};
return this.validateValue(value, fullContext);
}
/**
* 添加自定义验证规则
*/
addValidationRule(rule: ValidationRule): void {
this.customRules.set(rule.name, rule);
}
/**
* 移除自定义验证规则
*/
removeValidationRule(ruleName: string): boolean {
return this.customRules.delete(ruleName);
}
/**
* 获取所有自定义规则
*/
getCustomRules(): ValidationRule[] {
return Array.from(this.customRules.values());
}
/**
* 验证消息结构
*/
private validateMessageStructure(message: TransportMessage): ValidationResult {
// 检查必需字段
if (!message.type) {
return {
valid: false,
error: 'Message type is required',
errorCode: 'MISSING_TYPE'
};
}
if (message.data === undefined) {
return {
valid: false,
error: 'Message data is required',
errorCode: 'MISSING_DATA'
};
}
// 检查字段类型
if (typeof message.type !== 'string') {
return {
valid: false,
error: 'Message type must be a string',
errorCode: 'INVALID_TYPE_FORMAT'
};
}
// 检查可选字段
if (message.senderId && typeof message.senderId !== 'string') {
return {
valid: false,
error: 'Sender ID must be a string',
errorCode: 'INVALID_SENDER_ID'
};
}
if (message.targetId && typeof message.targetId !== 'string') {
return {
valid: false,
error: 'Target ID must be a string',
errorCode: 'INVALID_TARGET_ID'
};
}
if (message.reliable !== undefined && typeof message.reliable !== 'boolean') {
return {
valid: false,
error: 'Reliable flag must be a boolean',
errorCode: 'INVALID_RELIABLE_FLAG'
};
}
return { valid: true };
}
/**
* 验证消息大小
*/
private validateMessageSize(message: TransportMessage): ValidationResult {
try {
const serialized = JSON.stringify(message);
const size = new TextEncoder().encode(serialized).length;
if (size > this.config.maxMessageSize!) {
return {
valid: false,
error: `Message size (${size} bytes) exceeds maximum (${this.config.maxMessageSize} bytes)`,
errorCode: 'MESSAGE_TOO_LARGE',
details: { actualSize: size, maxSize: this.config.maxMessageSize }
};
}
return { valid: true };
} catch (error) {
return {
valid: false,
error: 'Failed to serialize message for size validation',
errorCode: 'SERIALIZATION_ERROR'
};
}
}
/**
* 验证消息类型
*/
private validateMessageType(message: TransportMessage): ValidationResult {
if (!this.config.allowedMessageTypes!.includes(message.type)) {
return {
valid: false,
error: `Message type '${message.type}' is not allowed`,
errorCode: 'INVALID_MESSAGE_TYPE',
details: {
messageType: message.type,
allowedTypes: this.config.allowedMessageTypes
}
};
}
return { valid: true };
}
/**
* 验证消息数据
*/
private validateMessageData(message: TransportMessage): ValidationResult {
const context: ValidationContext = {
path: ['data'],
depth: 0,
config: this.config,
messageType: message.type
};
return this.validateValue(message.data, context);
}
/**
* 验证值
*/
private validateValue(value: any, context: ValidationContext): ValidationResult {
// 深度检查
if (context.depth > this.config.maxObjectDepth!) {
return {
valid: false,
error: `Object depth (${context.depth}) exceeds maximum (${this.config.maxObjectDepth})`,
errorCode: 'OBJECT_TOO_DEEP',
details: { path: context.path.join('.'), depth: context.depth }
};
}
// null/undefined 检查
if (value === null) {
if (!this.config.allowNullValues) {
return {
valid: false,
error: 'Null values are not allowed',
errorCode: 'NULL_NOT_ALLOWED',
details: { path: context.path.join('.') }
};
}
return { valid: true };
}
if (value === undefined) {
if (!this.config.allowUndefinedValues) {
return {
valid: false,
error: 'Undefined values are not allowed',
errorCode: 'UNDEFINED_NOT_ALLOWED',
details: { path: context.path.join('.') }
};
}
return { valid: true };
}
// 根据类型验证
switch (typeof value) {
case 'string':
return this.validateString(value, context);
case 'number':
return this.validateNumber(value, context);
case 'boolean':
return { valid: true };
case 'object':
if (Array.isArray(value)) {
return this.validateArray(value, context);
} else {
return this.validateObject(value, context);
}
default:
return {
valid: false,
error: `Unsupported value type: ${typeof value}`,
errorCode: 'UNSUPPORTED_TYPE',
details: { path: context.path.join('.'), type: typeof value }
};
}
}
/**
* 验证字符串
*/
private validateString(value: string, context: ValidationContext): ValidationResult {
if (value.length > this.config.maxStringLength!) {
return {
valid: false,
error: `String length (${value.length}) exceeds maximum (${this.config.maxStringLength})`,
errorCode: 'STRING_TOO_LONG',
details: {
path: context.path.join('.'),
actualLength: value.length,
maxLength: this.config.maxStringLength
}
};
}
return { valid: true };
}
/**
* 验证数字
*/
private validateNumber(value: number, context: ValidationContext): ValidationResult {
// 检查是否为有效数字
if (!Number.isFinite(value)) {
return {
valid: false,
error: 'Number must be finite',
errorCode: 'INVALID_NUMBER',
details: { path: context.path.join('.'), value }
};
}
return { valid: true };
}
/**
* 验证数组
*/
private validateArray(value: any[], context: ValidationContext): ValidationResult {
// 长度检查
if (value.length > this.config.maxArrayLength!) {
return {
valid: false,
error: `Array length (${value.length}) exceeds maximum (${this.config.maxArrayLength})`,
errorCode: 'ARRAY_TOO_LONG',
details: {
path: context.path.join('.'),
actualLength: value.length,
maxLength: this.config.maxArrayLength
}
};
}
// 验证每个元素
for (let i = 0; i < value.length; i++) {
const elementContext: ValidationContext = {
...context,
path: [...context.path, `[${i}]`],
depth: context.depth + 1
};
const result = this.validateValue(value[i], elementContext);
if (!result.valid) {
return result;
}
}
return { valid: true };
}
/**
* 验证对象
*/
private validateObject(value: Record<string, any>, context: ValidationContext): ValidationResult {
// 验证每个属性
for (const [key, propertyValue] of Object.entries(value)) {
const propertyContext: ValidationContext = {
...context,
path: [...context.path, key],
depth: context.depth + 1
};
const result = this.validateValue(propertyValue, propertyContext);
if (!result.valid) {
return result;
}
}
return { valid: true };
}
/**
* 验证自定义规则
*/
private validateCustomRules(message: TransportMessage): ValidationResult {
for (const rule of this.customRules.values()) {
const context: ValidationContext = {
path: [],
depth: 0,
config: this.config,
messageType: message.type
};
const result = rule.validate(message, context);
if (!result.valid) {
return {
...result,
details: {
...result.details,
rule: rule.name
}
};
}
}
return { valid: true };
}
}
/**
* 预定义验证规则
*/
export const DefaultValidationRules = {
/**
* RPC 消息验证规则
*/
RpcMessage: {
name: 'RpcMessage',
validate: (message: TransportMessage, context: ValidationContext): ValidationResult => {
if (message.type !== 'rpc') {
return { valid: true }; // 不是 RPC 消息,跳过验证
}
const data = message.data as any;
// 检查必需字段
if (!data.networkId || typeof data.networkId !== 'number') {
return {
valid: false,
error: 'RPC message must have a valid networkId',
errorCode: 'RPC_INVALID_NETWORK_ID'
};
}
if (!data.componentType || typeof data.componentType !== 'string') {
return {
valid: false,
error: 'RPC message must have a valid componentType',
errorCode: 'RPC_INVALID_COMPONENT_TYPE'
};
}
if (!data.methodName || typeof data.methodName !== 'string') {
return {
valid: false,
error: 'RPC message must have a valid methodName',
errorCode: 'RPC_INVALID_METHOD_NAME'
};
}
// 检查参数数组
if (data.parameters && !Array.isArray(data.parameters)) {
return {
valid: false,
error: 'RPC parameters must be an array',
errorCode: 'RPC_INVALID_PARAMETERS'
};
}
return { valid: true };
}
} as ValidationRule,
/**
* SyncVar 消息验证规则
*/
SyncVarMessage: {
name: 'SyncVarMessage',
validate: (message: TransportMessage, context: ValidationContext): ValidationResult => {
if (message.type !== 'syncvar') {
return { valid: true }; // 不是 SyncVar 消息,跳过验证
}
const data = message.data as any;
// 检查必需字段
if (!data.networkId || typeof data.networkId !== 'number') {
return {
valid: false,
error: 'SyncVar message must have a valid networkId',
errorCode: 'SYNCVAR_INVALID_NETWORK_ID'
};
}
if (!data.componentType || typeof data.componentType !== 'string') {
return {
valid: false,
error: 'SyncVar message must have a valid componentType',
errorCode: 'SYNCVAR_INVALID_COMPONENT_TYPE'
};
}
if (!data.propertyName || typeof data.propertyName !== 'string') {
return {
valid: false,
error: 'SyncVar message must have a valid propertyName',
errorCode: 'SYNCVAR_INVALID_PROPERTY_NAME'
};
}
return { valid: true };
}
} as ValidationRule
};

View File

@@ -1,776 +0,0 @@
/**
* RPC 验证器
*
* 专门用于验证 RPC 调用的参数、权限、频率等
*/
import { NetworkValue, RpcMetadata } from '@esengine/ecs-framework-network-shared';
import { ClientConnection } from '../core/ClientConnection';
import { ValidationResult } from './MessageValidator';
/**
* RPC 验证配置
*/
export interface RpcValidationConfig {
/** 最大参数数量 */
maxParameterCount?: number;
/** 单个参数最大大小(字节) */
maxParameterSize?: number;
/** 允许的参数类型 */
allowedParameterTypes?: string[];
/** 方法名黑名单 */
blacklistedMethods?: string[];
/** 方法名白名单 */
whitelistedMethods?: string[];
/** 是否启用参数类型检查 */
enableTypeCheck?: boolean;
/** 是否启用参数内容过滤 */
enableContentFilter?: boolean;
}
/**
* RPC 调用上下文
*/
export interface RpcCallContext {
/** 客户端连接 */
client: ClientConnection;
/** 网络对象ID */
networkId: number;
/** 组件类型 */
componentType: string;
/** 方法名 */
methodName: string;
/** 参数列表 */
parameters: NetworkValue[];
/** RPC 元数据 */
metadata: RpcMetadata;
/** RPC 类型 */
rpcType: 'client-rpc' | 'server-rpc';
}
/**
* 参数类型定义
*/
export interface ParameterTypeDefinition {
/** 参数名 */
name: string;
/** 参数类型 */
type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'any';
/** 是否必需 */
required?: boolean;
/** 最小值/长度 */
min?: number;
/** 最大值/长度 */
max?: number;
/** 允许的值列表 */
allowedValues?: NetworkValue[];
/** 正则表达式(仅用于字符串) */
pattern?: RegExp;
/** 自定义验证函数 */
customValidator?: (value: NetworkValue) => ValidationResult;
}
/**
* 方法签名定义
*/
export interface MethodSignature {
/** 方法名 */
methodName: string;
/** 组件类型 */
componentType: string;
/** 参数定义 */
parameters: ParameterTypeDefinition[];
/** 返回值类型 */
returnType?: string;
/** 是否需要权限验证 */
requiresAuth?: boolean;
/** 所需权限 */
requiredPermissions?: string[];
/** 频率限制(调用/分钟) */
rateLimit?: number;
/** 自定义验证函数 */
customValidator?: (context: RpcCallContext) => ValidationResult;
}
/**
* RPC 频率跟踪
*/
interface RpcRateTracker {
/** 客户端ID */
clientId: string;
/** 方法调用计数 */
methodCalls: Map<string, { count: number; resetTime: Date }>;
/** 最后更新时间 */
lastUpdate: Date;
}
/**
* RPC 验证器
*/
export class RpcValidator {
private config: RpcValidationConfig;
private methodSignatures = new Map<string, MethodSignature>();
private rateTrackers = new Map<string, RpcRateTracker>();
private cleanupTimer: NodeJS.Timeout | null = null;
constructor(config: RpcValidationConfig = {}) {
this.config = {
maxParameterCount: 10,
maxParameterSize: 65536, // 64KB
allowedParameterTypes: ['string', 'number', 'boolean', 'object', 'array'],
blacklistedMethods: [],
whitelistedMethods: [],
enableTypeCheck: true,
enableContentFilter: true,
...config
};
this.initialize();
}
/**
* 验证 RPC 调用
*/
validateRpcCall(context: RpcCallContext): ValidationResult {
try {
// 基本验证
const basicResult = this.validateBasicRpcCall(context);
if (!basicResult.valid) {
return basicResult;
}
// 方法名验证
const methodResult = this.validateMethodName(context);
if (!methodResult.valid) {
return methodResult;
}
// 权限验证
const permissionResult = this.validateRpcPermissions(context);
if (!permissionResult.valid) {
return permissionResult;
}
// 参数验证
const parameterResult = this.validateParameters(context);
if (!parameterResult.valid) {
return parameterResult;
}
// 频率限制验证
const rateResult = this.validateRateLimit(context);
if (!rateResult.valid) {
return rateResult;
}
// 签名验证(如果有定义)
const signatureResult = this.validateMethodSignature(context);
if (!signatureResult.valid) {
return signatureResult;
}
return { valid: true };
} catch (error) {
return {
valid: false,
error: (error as Error).message,
errorCode: 'RPC_VALIDATION_ERROR'
};
}
}
/**
* 注册方法签名
*/
registerMethodSignature(signature: MethodSignature): void {
const key = `${signature.componentType}.${signature.methodName}`;
this.methodSignatures.set(key, signature);
}
/**
* 移除方法签名
*/
removeMethodSignature(componentType: string, methodName: string): boolean {
const key = `${componentType}.${methodName}`;
return this.methodSignatures.delete(key);
}
/**
* 获取方法签名
*/
getMethodSignature(componentType: string, methodName: string): MethodSignature | undefined {
const key = `${componentType}.${methodName}`;
return this.methodSignatures.get(key);
}
/**
* 添加方法到黑名单
*/
addToBlacklist(methodName: string): void {
if (!this.config.blacklistedMethods!.includes(methodName)) {
this.config.blacklistedMethods!.push(methodName);
}
}
/**
* 从黑名单移除方法
*/
removeFromBlacklist(methodName: string): boolean {
const index = this.config.blacklistedMethods!.indexOf(methodName);
if (index !== -1) {
this.config.blacklistedMethods!.splice(index, 1);
return true;
}
return false;
}
/**
* 添加方法到白名单
*/
addToWhitelist(methodName: string): void {
if (!this.config.whitelistedMethods!.includes(methodName)) {
this.config.whitelistedMethods!.push(methodName);
}
}
/**
* 获取客户端的 RPC 统计
*/
getClientRpcStats(clientId: string): {
totalCalls: number;
methodStats: Record<string, number>;
} {
const tracker = this.rateTrackers.get(clientId);
if (!tracker) {
return { totalCalls: 0, methodStats: {} };
}
let totalCalls = 0;
const methodStats: Record<string, number> = {};
for (const [method, data] of tracker.methodCalls) {
totalCalls += data.count;
methodStats[method] = data.count;
}
return { totalCalls, methodStats };
}
/**
* 重置客户端的频率限制
*/
resetClientRateLimit(clientId: string): boolean {
const tracker = this.rateTrackers.get(clientId);
if (!tracker) {
return false;
}
tracker.methodCalls.clear();
tracker.lastUpdate = new Date();
return true;
}
/**
* 销毁验证器
*/
destroy(): void {
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
this.cleanupTimer = null;
}
this.methodSignatures.clear();
this.rateTrackers.clear();
}
/**
* 初始化验证器
*/
private initialize(): void {
// 启动清理定时器每5分钟清理一次
this.cleanupTimer = setInterval(() => {
this.cleanupRateTrackers();
}, 5 * 60 * 1000);
}
/**
* 基本 RPC 调用验证
*/
private validateBasicRpcCall(context: RpcCallContext): ValidationResult {
// 网络对象ID验证
if (!Number.isInteger(context.networkId) || context.networkId <= 0) {
return {
valid: false,
error: 'Invalid network object ID',
errorCode: 'INVALID_NETWORK_ID'
};
}
// 组件类型验证
if (!context.componentType || typeof context.componentType !== 'string') {
return {
valid: false,
error: 'Invalid component type',
errorCode: 'INVALID_COMPONENT_TYPE'
};
}
// 方法名验证
if (!context.methodName || typeof context.methodName !== 'string') {
return {
valid: false,
error: 'Invalid method name',
errorCode: 'INVALID_METHOD_NAME'
};
}
// 参数数组验证
if (!Array.isArray(context.parameters)) {
return {
valid: false,
error: 'Parameters must be an array',
errorCode: 'INVALID_PARAMETERS_FORMAT'
};
}
// 参数数量检查
if (context.parameters.length > this.config.maxParameterCount!) {
return {
valid: false,
error: `Too many parameters: ${context.parameters.length} (max: ${this.config.maxParameterCount})`,
errorCode: 'TOO_MANY_PARAMETERS'
};
}
return { valid: true };
}
/**
* 方法名验证
*/
private validateMethodName(context: RpcCallContext): ValidationResult {
const methodName = context.methodName;
// 黑名单检查
if (this.config.blacklistedMethods!.length > 0) {
if (this.config.blacklistedMethods!.includes(methodName)) {
return {
valid: false,
error: `Method '${methodName}' is blacklisted`,
errorCode: 'METHOD_BLACKLISTED'
};
}
}
// 白名单检查
if (this.config.whitelistedMethods!.length > 0) {
if (!this.config.whitelistedMethods!.includes(methodName)) {
return {
valid: false,
error: `Method '${methodName}' is not whitelisted`,
errorCode: 'METHOD_NOT_WHITELISTED'
};
}
}
// 危险方法名检查
const dangerousPatterns = [
/^__/, // 私有方法
/constructor/i,
/prototype/i,
/eval/i,
/function/i
];
for (const pattern of dangerousPatterns) {
if (pattern.test(methodName)) {
return {
valid: false,
error: `Potentially dangerous method name: '${methodName}'`,
errorCode: 'DANGEROUS_METHOD_NAME'
};
}
}
return { valid: true };
}
/**
* RPC 权限验证
*/
private validateRpcPermissions(context: RpcCallContext): ValidationResult {
// 基本 RPC 权限检查
if (!context.client.hasPermission('canSendRpc')) {
return {
valid: false,
error: 'Client does not have RPC permission',
errorCode: 'RPC_PERMISSION_DENIED'
};
}
// ServerRpc 特殊权限检查
if (context.rpcType === 'server-rpc') {
if (context.metadata.requiresAuth && !context.client.isAuthenticated) {
return {
valid: false,
error: 'Authentication required for this RPC',
errorCode: 'AUTHENTICATION_REQUIRED'
};
}
}
// 检查方法签名中的权限要求
const signature = this.getMethodSignature(context.componentType, context.methodName);
if (signature && signature.requiredPermissions) {
for (const permission of signature.requiredPermissions) {
if (!context.client.hasCustomPermission(permission)) {
return {
valid: false,
error: `Required permission '${permission}' not found`,
errorCode: 'INSUFFICIENT_PERMISSIONS'
};
}
}
}
return { valid: true };
}
/**
* 参数验证
*/
private validateParameters(context: RpcCallContext): ValidationResult {
// 参数大小检查
for (let i = 0; i < context.parameters.length; i++) {
const param = context.parameters[i];
try {
const serialized = JSON.stringify(param);
const size = new TextEncoder().encode(serialized).length;
if (size > this.config.maxParameterSize!) {
return {
valid: false,
error: `Parameter ${i} is too large: ${size} bytes (max: ${this.config.maxParameterSize})`,
errorCode: 'PARAMETER_TOO_LARGE'
};
}
} catch (error) {
return {
valid: false,
error: `Parameter ${i} is not serializable`,
errorCode: 'PARAMETER_NOT_SERIALIZABLE'
};
}
}
// 参数类型检查
if (this.config.enableTypeCheck) {
const typeResult = this.validateParameterTypes(context);
if (!typeResult.valid) {
return typeResult;
}
}
// 参数内容过滤
if (this.config.enableContentFilter) {
const contentResult = this.validateParameterContent(context);
if (!contentResult.valid) {
return contentResult;
}
}
return { valid: true };
}
/**
* 参数类型验证
*/
private validateParameterTypes(context: RpcCallContext): ValidationResult {
for (let i = 0; i < context.parameters.length; i++) {
const param = context.parameters[i];
const paramType = this.getParameterType(param);
if (!this.config.allowedParameterTypes!.includes(paramType)) {
return {
valid: false,
error: `Parameter ${i} type '${paramType}' is not allowed`,
errorCode: 'INVALID_PARAMETER_TYPE'
};
}
}
return { valid: true };
}
/**
* 参数内容验证
*/
private validateParameterContent(context: RpcCallContext): ValidationResult {
for (let i = 0; i < context.parameters.length; i++) {
const param = context.parameters[i];
// 检查危险内容
if (typeof param === 'string') {
const dangerousPatterns = [
/<script/i,
/javascript:/i,
/eval\(/i,
/function\(/i,
/__proto__/i
];
for (const pattern of dangerousPatterns) {
if (pattern.test(param)) {
return {
valid: false,
error: `Parameter ${i} contains potentially dangerous content`,
errorCode: 'DANGEROUS_PARAMETER_CONTENT'
};
}
}
}
}
return { valid: true };
}
/**
* 频率限制验证
*/
private validateRateLimit(context: RpcCallContext): ValidationResult {
const signature = this.getMethodSignature(context.componentType, context.methodName);
if (!signature || !signature.rateLimit) {
return { valid: true }; // 没有频率限制
}
const clientId = context.client.id;
const methodKey = `${context.componentType}.${context.methodName}`;
let tracker = this.rateTrackers.get(clientId);
if (!tracker) {
tracker = {
clientId,
methodCalls: new Map(),
lastUpdate: new Date()
};
this.rateTrackers.set(clientId, tracker);
}
const now = new Date();
let methodData = tracker.methodCalls.get(methodKey);
if (!methodData) {
methodData = {
count: 1,
resetTime: new Date(now.getTime() + 60000) // 1分钟后重置
};
tracker.methodCalls.set(methodKey, methodData);
return { valid: true };
}
// 检查是否需要重置
if (now >= methodData.resetTime) {
methodData.count = 1;
methodData.resetTime = new Date(now.getTime() + 60000);
return { valid: true };
}
// 检查频率限制
if (methodData.count >= signature.rateLimit) {
return {
valid: false,
error: `Rate limit exceeded for method '${methodKey}': ${methodData.count}/${signature.rateLimit} per minute`,
errorCode: 'RATE_LIMIT_EXCEEDED'
};
}
methodData.count++;
tracker.lastUpdate = now;
return { valid: true };
}
/**
* 方法签名验证
*/
private validateMethodSignature(context: RpcCallContext): ValidationResult {
const signature = this.getMethodSignature(context.componentType, context.methodName);
if (!signature) {
return { valid: true }; // 没有定义签名,跳过验证
}
// 参数数量检查
const requiredParams = signature.parameters.filter(p => p.required !== false);
if (context.parameters.length < requiredParams.length) {
return {
valid: false,
error: `Not enough parameters: expected at least ${requiredParams.length}, got ${context.parameters.length}`,
errorCode: 'INSUFFICIENT_PARAMETERS'
};
}
if (context.parameters.length > signature.parameters.length) {
return {
valid: false,
error: `Too many parameters: expected at most ${signature.parameters.length}, got ${context.parameters.length}`,
errorCode: 'EXCESS_PARAMETERS'
};
}
// 参数类型和值验证
for (let i = 0; i < Math.min(context.parameters.length, signature.parameters.length); i++) {
const param = context.parameters[i];
const paramDef = signature.parameters[i];
const paramResult = this.validateParameterDefinition(param, paramDef, i);
if (!paramResult.valid) {
return paramResult;
}
}
// 自定义验证
if (signature.customValidator) {
const customResult = signature.customValidator(context);
if (!customResult.valid) {
return customResult;
}
}
return { valid: true };
}
/**
* 验证参数定义
*/
private validateParameterDefinition(
value: NetworkValue,
definition: ParameterTypeDefinition,
index: number
): ValidationResult {
// 类型检查
const actualType = this.getParameterType(value);
if (definition.type !== 'any' && actualType !== definition.type) {
return {
valid: false,
error: `Parameter ${index} type mismatch: expected '${definition.type}', got '${actualType}'`,
errorCode: 'PARAMETER_TYPE_MISMATCH'
};
}
// 值范围检查
if (typeof value === 'number' && (definition.min !== undefined || definition.max !== undefined)) {
if (definition.min !== undefined && value < definition.min) {
return {
valid: false,
error: `Parameter ${index} value ${value} is less than minimum ${definition.min}`,
errorCode: 'PARAMETER_BELOW_MINIMUM'
};
}
if (definition.max !== undefined && value > definition.max) {
return {
valid: false,
error: `Parameter ${index} value ${value} is greater than maximum ${definition.max}`,
errorCode: 'PARAMETER_ABOVE_MAXIMUM'
};
}
}
// 字符串长度检查
if (typeof value === 'string' && (definition.min !== undefined || definition.max !== undefined)) {
if (definition.min !== undefined && value.length < definition.min) {
return {
valid: false,
error: `Parameter ${index} string length ${value.length} is less than minimum ${definition.min}`,
errorCode: 'STRING_TOO_SHORT'
};
}
if (definition.max !== undefined && value.length > definition.max) {
return {
valid: false,
error: `Parameter ${index} string length ${value.length} is greater than maximum ${definition.max}`,
errorCode: 'STRING_TOO_LONG'
};
}
}
// 允许值检查
if (definition.allowedValues && definition.allowedValues.length > 0) {
if (!definition.allowedValues.includes(value)) {
return {
valid: false,
error: `Parameter ${index} value '${value}' is not in allowed values: ${definition.allowedValues.join(', ')}`,
errorCode: 'VALUE_NOT_ALLOWED'
};
}
}
// 正则表达式检查(字符串)
if (typeof value === 'string' && definition.pattern) {
if (!definition.pattern.test(value)) {
return {
valid: false,
error: `Parameter ${index} string '${value}' does not match required pattern`,
errorCode: 'PATTERN_MISMATCH'
};
}
}
// 自定义验证
if (definition.customValidator) {
const customResult = definition.customValidator(value);
if (!customResult.valid) {
return {
...customResult,
error: `Parameter ${index} validation failed: ${customResult.error}`
};
}
}
return { valid: true };
}
/**
* 获取参数类型
*/
private getParameterType(value: any): string {
if (value === null || value === undefined) {
return 'null';
}
if (Array.isArray(value)) {
return 'array';
}
return typeof value;
}
/**
* 清理过期的频率跟踪器
*/
private cleanupRateTrackers(): void {
const now = new Date();
const expireTime = 10 * 60 * 1000; // 10分钟
let cleanedCount = 0;
for (const [clientId, tracker] of this.rateTrackers.entries()) {
if (now.getTime() - tracker.lastUpdate.getTime() > expireTime) {
this.rateTrackers.delete(clientId);
cleanedCount++;
} else {
// 清理过期的方法调用记录
for (const [methodKey, methodData] of tracker.methodCalls.entries()) {
if (now >= methodData.resetTime) {
tracker.methodCalls.delete(methodKey);
}
}
}
}
if (cleanedCount > 0) {
console.log(`RPC validator cleanup: ${cleanedCount} rate trackers removed`);
}
}
}

View File

@@ -1,6 +0,0 @@
/**
* 验证系统导出
*/
export * from './MessageValidator';
export * from './RpcValidator';