385 lines
15 KiB
JavaScript
385 lines
15 KiB
JavaScript
"use strict";
|
||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||
};
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
const Logger_1 = __importDefault(require("../utils/Logger"));
|
||
/**
|
||
* Web客户端管理器
|
||
*/
|
||
class WebClientManager {
|
||
constructor(databaseService) {
|
||
this.clients = new Map();
|
||
this.socketToClient = new Map();
|
||
this.deviceControllers = new Map(); // deviceId -> clientId
|
||
// 🔧 添加请求速率限制 - 防止频繁重复请求
|
||
this.requestTimestamps = new Map(); // "clientId:deviceId" -> timestamp
|
||
this.REQUEST_COOLDOWN = 2000; // 2秒内不允许重复请求(增加冷却时间)
|
||
this.logger = new Logger_1.default('WebClientManager');
|
||
this.databaseService = databaseService;
|
||
}
|
||
/**
|
||
* ✅ 清理所有客户端记录(服务器重启时调用)
|
||
*/
|
||
clearAllClients() {
|
||
const clientCount = this.clients.size;
|
||
this.clients.clear();
|
||
this.socketToClient.clear();
|
||
this.deviceControllers.clear();
|
||
this.requestTimestamps.clear();
|
||
this.logger.info(`🧹 已清理所有客户端记录: ${clientCount} 个客户端`);
|
||
}
|
||
/**
|
||
* 设置Socket.IO实例
|
||
*/
|
||
setSocketIO(io) {
|
||
this.io = io;
|
||
}
|
||
/**
|
||
* 添加Web客户端
|
||
*/
|
||
addClient(clientInfo) {
|
||
// 🔧 检查是否已有相同Socket ID的客户端记录
|
||
const existingClientId = this.socketToClient.get(clientInfo.socketId);
|
||
if (existingClientId) {
|
||
this.logger.warn(`⚠️ Socket ${clientInfo.socketId} 已有客户端记录 ${existingClientId},清理旧记录`);
|
||
this.removeClient(existingClientId);
|
||
}
|
||
this.clients.set(clientInfo.id, clientInfo);
|
||
this.socketToClient.set(clientInfo.socketId, clientInfo.id);
|
||
this.logger.info(`Web客户端已添加: ${clientInfo.id} from ${clientInfo.ip}`);
|
||
}
|
||
/**
|
||
* 移除Web客户端
|
||
*/
|
||
removeClient(clientId) {
|
||
const client = this.clients.get(clientId);
|
||
if (client) {
|
||
this.clients.delete(clientId);
|
||
this.socketToClient.delete(client.socketId);
|
||
// 如果客户端正在控制设备,释放控制权
|
||
if (client.controllingDeviceId) {
|
||
this.logger.info(`🔓 客户端断开连接,自动释放设备控制权: ${clientId} -> ${client.controllingDeviceId}`);
|
||
this.releaseDeviceControl(client.controllingDeviceId);
|
||
}
|
||
// 清理请求时间戳记录
|
||
const keysToDelete = Array.from(this.requestTimestamps.keys()).filter(key => key.startsWith(clientId + ':'));
|
||
keysToDelete.forEach(key => this.requestTimestamps.delete(key));
|
||
this.logger.info(`Web客户端已移除: ${clientId}`);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
/**
|
||
* 通过Socket ID移除客户端
|
||
*/
|
||
removeClientBySocketId(socketId) {
|
||
const clientId = this.socketToClient.get(socketId);
|
||
if (clientId) {
|
||
return this.removeClient(clientId);
|
||
}
|
||
return false;
|
||
}
|
||
/**
|
||
* 获取客户端信息
|
||
*/
|
||
getClient(clientId) {
|
||
return this.clients.get(clientId);
|
||
}
|
||
/**
|
||
* 通过Socket ID获取客户端
|
||
*/
|
||
getClientBySocketId(socketId) {
|
||
const clientId = this.socketToClient.get(socketId);
|
||
return clientId ? this.clients.get(clientId) : undefined;
|
||
}
|
||
/**
|
||
* 获取所有客户端
|
||
*/
|
||
getAllClients() {
|
||
return Array.from(this.clients.values());
|
||
}
|
||
/**
|
||
* 获取客户端数量
|
||
*/
|
||
getClientCount() {
|
||
return this.clients.size;
|
||
}
|
||
/**
|
||
* 获取客户端Socket
|
||
*/
|
||
getClientSocket(clientId) {
|
||
const client = this.clients.get(clientId);
|
||
if (client && this.io) {
|
||
return this.io.sockets.sockets.get(client.socketId);
|
||
}
|
||
return undefined;
|
||
}
|
||
/**
|
||
* 请求控制设备
|
||
*/
|
||
requestDeviceControl(clientId, deviceId) {
|
||
// 🔧 防止频繁重复请求
|
||
const requestKey = `${clientId}:${deviceId}`;
|
||
const now = Date.now();
|
||
const lastRequestTime = this.requestTimestamps.get(requestKey) || 0;
|
||
if (now - lastRequestTime < this.REQUEST_COOLDOWN) {
|
||
this.logger.debug(`🚫 请求过于频繁: ${clientId} -> ${deviceId} (间隔${now - lastRequestTime}ms < ${this.REQUEST_COOLDOWN}ms)`);
|
||
return {
|
||
success: false,
|
||
message: '请求过于频繁,请稍后再试'
|
||
};
|
||
}
|
||
// 获取客户端信息
|
||
const client = this.clients.get(clientId);
|
||
if (!client) {
|
||
this.logger.error(`❌ 客户端不存在: ${clientId}`);
|
||
return {
|
||
success: false,
|
||
message: '客户端不存在'
|
||
};
|
||
}
|
||
// ✅ 优化:先检查是否是重复请求(已经在控制此设备)
|
||
const currentController = this.deviceControllers.get(deviceId);
|
||
if (currentController === clientId) {
|
||
this.logger.debug(`🔄 客户端 ${clientId} 重复请求控制设备 ${deviceId},已在控制中`);
|
||
client.lastSeen = new Date();
|
||
// 更新请求时间戳,但返回成功(避免频繁日志)
|
||
this.requestTimestamps.set(requestKey, now);
|
||
return {
|
||
success: true,
|
||
message: '已在控制此设备'
|
||
};
|
||
}
|
||
// 记录请求时间戳(在检查重复控制后记录)
|
||
this.requestTimestamps.set(requestKey, now);
|
||
// 检查设备是否被其他客户端控制
|
||
if (currentController && currentController !== clientId) {
|
||
const controllerClient = this.clients.get(currentController);
|
||
this.logger.warn(`🚫 设备 ${deviceId} 控制权冲突: 当前控制者 ${currentController}, 请求者 ${clientId}`);
|
||
return {
|
||
success: false,
|
||
message: `设备正在被其他客户端控制 (${controllerClient?.ip || 'unknown'})`,
|
||
currentController
|
||
};
|
||
}
|
||
// 如果客户端已在控制其他设备,先释放
|
||
if (client.controllingDeviceId && client.controllingDeviceId !== deviceId) {
|
||
this.logger.info(`🔄 客户端 ${clientId} 切换控制设备: ${client.controllingDeviceId} -> ${deviceId}`);
|
||
this.releaseDeviceControl(client.controllingDeviceId);
|
||
}
|
||
// 建立控制关系
|
||
this.deviceControllers.set(deviceId, clientId);
|
||
client.controllingDeviceId = deviceId;
|
||
client.lastSeen = new Date();
|
||
// 🔐 如果客户端有用户ID,将权限持久化到数据库
|
||
if (client.userId && this.databaseService) {
|
||
this.databaseService.grantUserDevicePermission(client.userId, deviceId, 'control');
|
||
this.logger.info(`🔐 用户 ${client.userId} 的设备 ${deviceId} 控制权限已持久化`);
|
||
}
|
||
this.logger.info(`🎮 客户端 ${clientId} 开始控制设备 ${deviceId}`);
|
||
return {
|
||
success: true,
|
||
message: '控制权获取成功'
|
||
};
|
||
}
|
||
/**
|
||
* 释放设备控制权
|
||
*/
|
||
releaseDeviceControl(deviceId) {
|
||
const controllerId = this.deviceControllers.get(deviceId);
|
||
if (controllerId) {
|
||
const client = this.clients.get(controllerId);
|
||
if (client) {
|
||
const previousDevice = client.controllingDeviceId;
|
||
client.controllingDeviceId = undefined;
|
||
this.logger.debug(`🔓 客户端 ${controllerId} 释放设备控制权: ${previousDevice}`);
|
||
}
|
||
else {
|
||
this.logger.warn(`⚠️ 控制设备 ${deviceId} 的客户端 ${controllerId} 不存在,可能已断开`);
|
||
}
|
||
this.deviceControllers.delete(deviceId);
|
||
this.logger.info(`🔓 设备 ${deviceId} 的控制权已释放 (之前控制者: ${controllerId})`);
|
||
return true;
|
||
}
|
||
else {
|
||
this.logger.debug(`🤷 设备 ${deviceId} 没有被控制,无需释放`);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 获取设备控制者
|
||
*/
|
||
getDeviceController(deviceId) {
|
||
return this.deviceControllers.get(deviceId);
|
||
}
|
||
/**
|
||
* 检查客户端是否有设备控制权
|
||
*/
|
||
hasDeviceControl(clientId, deviceId) {
|
||
// 🛡️ 记录权限检查审计日志
|
||
this.logPermissionOperation(clientId, deviceId, '权限检查');
|
||
// 🔐 获取客户端信息
|
||
const client = this.clients.get(clientId);
|
||
// 🆕 超级管理员绕过权限检查
|
||
if (client?.username) {
|
||
const superAdminUsername = process.env.SUPERADMIN_USERNAME || 'superadmin';
|
||
if (client.username === superAdminUsername) {
|
||
this.logger.info(`🔐 超级管理员 ${client.username} 绕过设备控制权限检查`);
|
||
return true;
|
||
}
|
||
}
|
||
// 🔐 首先检查内存中的控制权
|
||
const memoryControl = this.deviceControllers.get(deviceId) === clientId;
|
||
if (memoryControl) {
|
||
return true;
|
||
}
|
||
// 🔐 如果内存中没有控制权,检查数据库中的用户权限
|
||
if (client?.userId && this.databaseService) {
|
||
const hasPermission = this.databaseService.hasUserDevicePermission(client.userId, deviceId, 'control');
|
||
if (hasPermission) {
|
||
// 🔐 如果用户有权限,自动建立控制关系(允许权限恢复)
|
||
this.deviceControllers.set(deviceId, clientId);
|
||
client.controllingDeviceId = deviceId;
|
||
this.logger.info(`🔐 用户 ${client.userId} 基于数据库权限获得设备 ${deviceId} 控制权`);
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
/**
|
||
* 向指定客户端发送消息
|
||
*/
|
||
sendToClient(clientId, event, data) {
|
||
const socket = this.getClientSocket(clientId);
|
||
if (socket) {
|
||
socket.emit(event, data);
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
/**
|
||
* 向所有客户端广播消息
|
||
*/
|
||
broadcastToAll(event, data) {
|
||
if (this.io) {
|
||
let activeClients = 0;
|
||
// 只向Web客户端广播,且过滤掉已断开的连接
|
||
for (const [socketId, clientId] of this.socketToClient.entries()) {
|
||
const socket = this.io.sockets.sockets.get(socketId);
|
||
if (socket && socket.connected) {
|
||
socket.emit(event, data);
|
||
activeClients++;
|
||
}
|
||
}
|
||
this.logger.debug(`📡 广播消息到 ${activeClients} 个活跃Web客户端: ${event}`);
|
||
}
|
||
}
|
||
/**
|
||
* 向控制指定设备的客户端发送消息
|
||
*/
|
||
sendToDeviceController(deviceId, event, data) {
|
||
const controllerId = this.deviceControllers.get(deviceId);
|
||
if (controllerId) {
|
||
return this.sendToClient(controllerId, event, data);
|
||
}
|
||
return false;
|
||
}
|
||
/**
|
||
* 更新客户端活跃时间
|
||
*/
|
||
updateClientActivity(socketId) {
|
||
const clientId = this.socketToClient.get(socketId);
|
||
if (clientId) {
|
||
const client = this.clients.get(clientId);
|
||
if (client) {
|
||
client.lastSeen = new Date();
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* 清理不活跃的客户端
|
||
*/
|
||
cleanupInactiveClients(timeoutMs = 600000) {
|
||
const now = Date.now();
|
||
const clientsToRemove = [];
|
||
for (const [clientId, client] of this.clients.entries()) {
|
||
if (now - client.lastSeen.getTime() > timeoutMs) {
|
||
clientsToRemove.push(clientId);
|
||
}
|
||
}
|
||
clientsToRemove.forEach(clientId => {
|
||
this.removeClient(clientId);
|
||
});
|
||
if (clientsToRemove.length > 0) {
|
||
this.logger.info(`已清理 ${clientsToRemove.length} 个不活跃的Web客户端`);
|
||
}
|
||
}
|
||
/**
|
||
* 获取客户端统计信息
|
||
*/
|
||
getClientStats() {
|
||
const clients = Array.from(this.clients.values());
|
||
return {
|
||
total: clients.length,
|
||
controlling: clients.filter(c => c.controllingDeviceId).length,
|
||
idle: clients.filter(c => !c.controllingDeviceId).length,
|
||
};
|
||
}
|
||
/**
|
||
* 🔐 恢复用户的设备权限
|
||
*/
|
||
restoreUserPermissions(userId, clientId) {
|
||
if (!this.databaseService) {
|
||
this.logger.warn('数据库服务未初始化,无法恢复用户权限');
|
||
return;
|
||
}
|
||
try {
|
||
// 获取用户的所有设备权限
|
||
const permissions = this.databaseService.getUserDevicePermissions(userId);
|
||
if (permissions.length > 0) {
|
||
this.logger.info(`🔐 为用户 ${userId} 恢复 ${permissions.length} 个设备权限`);
|
||
// 恢复第一个设备的控制权(优先恢复用户之前的权限)
|
||
for (const permission of permissions) {
|
||
if (permission.permissionType === 'control') {
|
||
// 直接恢复权限,不检查冲突(因为这是用户自己的权限恢复)
|
||
this.deviceControllers.set(permission.deviceId, clientId);
|
||
const client = this.clients.get(clientId);
|
||
if (client) {
|
||
client.controllingDeviceId = permission.deviceId;
|
||
this.logger.info(`🔐 用户 ${userId} 的设备 ${permission.deviceId} 控制权已恢复`);
|
||
break; // 只恢复第一个设备
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (error) {
|
||
this.logger.error('恢复用户权限失败:', error);
|
||
}
|
||
}
|
||
/**
|
||
* 🔐 设置客户端用户信息
|
||
*/
|
||
setClientUserInfo(clientId, userId, username) {
|
||
const client = this.clients.get(clientId);
|
||
if (client) {
|
||
client.userId = userId;
|
||
client.username = username;
|
||
this.logger.info(`🔐 客户端 ${clientId} 用户信息已设置: ${username} (${userId})`);
|
||
// 🛡️ 记录安全审计日志
|
||
this.logger.info(`🛡️ 安全审计: 客户端 ${clientId} (IP: ${client.ip}) 绑定用户 ${username} (${userId})`);
|
||
}
|
||
}
|
||
/**
|
||
* 🛡️ 记录权限操作审计日志
|
||
*/
|
||
logPermissionOperation(clientId, deviceId, operation) {
|
||
const client = this.clients.get(clientId);
|
||
if (client) {
|
||
this.logger.info(`🛡️ 权限审计: 客户端 ${clientId} (用户: ${client.username || 'unknown'}, IP: ${client.ip}) 执行 ${operation} 操作,目标设备: ${deviceId}`);
|
||
}
|
||
}
|
||
}
|
||
exports.default = WebClientManager;
|
||
//# sourceMappingURL=WebClientManager.js.map
|