/** * 完整的协议实现示例 * 展示如何基于规范实现 Gateway 核心功能 */ import { MessageFactory, MessageValidator, MessageType, ClientType, Priority, CommandType, isCommandMessage, isRegisterMessage, CommandMessage, RegisterMessage, GatewayUtils, GATEWAY_SITE_ID, DeviceInfo, CommandChainItem } from '../src'; /** * Gateway 核心实现 */ export class GatewayCore { // 客户端连接管理 private clients = new Map; }>(); // 命令路由表 private commandRoutes = new Map(); // 待处理命令队列(按优先级) private commandQueues = { critical: [] as CommandMessage[], high: [] as CommandMessage[], normal: [] as CommandMessage[], low: [] as CommandMessage[] }; /** * 处理客户端消息 */ async handleMessage(clientSiteId: string, rawMessage: string) { try { const message = JSON.parse(rawMessage); // 验证基本消息格式 if (!MessageValidator.validateMessage(message)) { this.sendError(clientSiteId, 'PROTOCOL_INVALID', 'Invalid message format'); return; } // 根据消息类型分发处理 switch (message.type) { case 'register': await this.handleRegister(clientSiteId, message); break; case 'command': await this.handleCommand(clientSiteId, message); break; case 'command_response': await this.handleCommandResponse(clientSiteId, message); break; case 'heartbeat': await this.handleHeartbeat(clientSiteId, message); break; case 'batch': await this.handleBatch(clientSiteId, message); break; default: this.sendError(clientSiteId, 'PROTOCOL_INVALID', `Unknown message type: ${message.type}`); } } catch (error) { console.error('Error handling message:', error); this.sendError(clientSiteId, 'SYSTEM_ERROR', 'Internal error processing message'); } } /** * 处理注册 */ private async handleRegister(clientSiteId: string, message: RegisterMessage) { const client = this.clients.get(clientSiteId); if (!client) return; // 更新客户端信息 client.info = { clientId: message.clientId, clientType: message.clientType || 'device', status: 'online', connectedAt: new Date().toISOString(), lastHeartbeat: new Date().toISOString(), clientInfo: message.clientInfo }; // 记录客户端能力 if (message.clientInfo?.capabilities) { client.capabilities = new Set(message.clientInfo.capabilities); } // 如果是新协议,记录支持的特性 const extendedMsg = message as any; if (extendedMsg.features) { extendedMsg.features.forEach((feature: string) => { client.capabilities.add(feature); }); } // 更新路由表 this.updateRoutes(message.clientId, clientSiteId); // 发送确认 const ack = { type: 'register_ack', clientId: message.clientId, success: true, sessionId: this.generateSessionId(), timestamp: new Date().toISOString(), version: '1.0', // 告知客户端 Gateway 支持的特性 features: ['batch', 'chain', 'filter', 'compression'] }; this.sendToClient(clientSiteId, ack); console.log(`Client registered: ${message.clientId} (${message.clientType || 'device'})`); } /** * 处理命令 */ private async handleCommand(clientSiteId: string, message: CommandMessage) { // 验证命令消息 if (!MessageValidator.validateCommandMessage(message)) { this.sendError(clientSiteId, 'COMMAND_INVALID', 'Invalid command format'); return; } // 检查是否为 Gateway 命令 if (GatewayUtils.isGatewayCommand(message)) { await this.handleGatewayCommand(clientSiteId, message); return; } // 检查是否为广播命令 if (GatewayUtils.isBroadcastCommand(message)) { await this.handleBroadcastCommand(clientSiteId, message as ExtendedCommandMessage); return; } // 检查是否为命令链 const extendedCmd = message as ExtendedCommandMessage; if (extendedCmd.commandChain) { await this.handleCommandChain(clientSiteId, extendedCmd); return; } // 普通点对点命令 await this.routeCommand(clientSiteId, message); } /** * 处理 Gateway 命令 */ private async handleGatewayCommand(clientSiteId: string, message: CommandMessage) { const { command } = message; switch (command.commandCode) { case 'GET_DEVICE_STATUS': const targetSiteId = command.parameters.targetSiteId; const deviceInfo = this.getDeviceInfo(targetSiteId); this.sendCommandResponse(clientSiteId, message.requestRef, { success: !!deviceInfo, data: deviceInfo || { error: 'Device not found' } }); break; case 'LIST_DEVICES': const filter = command.parameters.filter as DeviceFilter; const devices = this.listDevices(filter); this.sendCommandResponse(clientSiteId, message.requestRef, { success: true, data: { devices, count: devices.length } }); break; case 'GET_STATS': const stats = this.getGatewayStats(); this.sendCommandResponse(clientSiteId, message.requestRef, { success: true, data: stats }); break; default: this.sendCommandResponse(clientSiteId, message.requestRef, { success: false, message: `Unknown Gateway command: ${command.commandCode}` }); } } /** * 处理广播命令 */ private async handleBroadcastCommand(clientSiteId: string, message: ExtendedCommandMessage) { const filter = message.filter || {}; const targets = this.findDevicesByFilter(filter); if (targets.length === 0) { this.sendCommandResponse(clientSiteId, message.requestRef, { success: false, message: 'No devices match the filter criteria' }); return; } console.log(`Broadcasting command to ${targets.length} devices`); // 为每个目标创建单独的命令 const results: any[] = []; for (const target of targets) { const targetCommand = { ...message, clientId: target.clientId, requestRef: `${message.requestRef}-${target.clientId}` }; // 发送命令并收集结果 try { await this.routeCommand(clientSiteId, targetCommand); results.push({ clientId: target.clientId, status: 'sent' }); } catch (error) { results.push({ clientId: target.clientId, status: 'failed', error: error.message }); } } // 返回批量执行结果 this.sendCommandResponse(clientSiteId, message.requestRef, { success: true, data: { totalTargets: targets.length, results } }); } /** * 处理命令链 */ private async handleCommandChain(clientSiteId: string, message: ExtendedCommandMessage) { const { clientId, commandChain, requestRef } = message; if (!commandChain || commandChain.length === 0) { this.sendCommandResponse(clientSiteId, requestRef, { success: false, message: 'Command chain is empty' }); return; } console.log(`Executing command chain with ${commandChain.length} commands for ${clientId}`); // 顺序执行命令链 const results: any[] = []; let chainSuccess = true; for (let i = 0; i < commandChain.length; i++) { const chainItem = commandChain[i]; // 如果有延迟,等待 if (chainItem.delay && chainItem.delay > 0) { await this.delay(chainItem.delay); } // 创建单个命令 const command = MessageFactory.createCommandMessage( `${requestRef}-step-${i}`, clientId, { commandCode: chainItem.commandCode, deviceType: message.command.deviceType, operationType: 'write', parameters: chainItem.parameters }, { priority: message.priority as Priority, timeout: 10000 } ); try { // 发送命令 await this.routeCommand(clientSiteId, command); results.push({ step: i, commandCode: chainItem.commandCode, status: 'completed' }); } catch (error) { results.push({ step: i, commandCode: chainItem.commandCode, status: 'failed', error: error.message }); chainSuccess = false; break; // 链中某个命令失败,停止执行 } } // 返回链执行结果 this.sendCommandResponse(clientSiteId, requestRef, { success: chainSuccess, data: { totalSteps: commandChain.length, executedSteps: results.length, results } }); } /** * 路由命令到目标设备 */ private async routeCommand(fromSiteId: string, message: CommandMessage) { const targetClient = this.findClientBySiteId(message.clientId); if (!targetClient) { this.sendCommandResponse(fromSiteId, message.requestRef, { success: false, message: `Device ${message.clientId} is offline or not found` }); return; } // 检查权限 if (!this.checkCommandPermission(fromSiteId, message.clientId)) { this.sendCommandResponse(fromSiteId, message.requestRef, { success: false, message: 'Permission denied' }); return; } // 根据优先级加入队列 const priority = message.priority || 'normal'; this.commandQueues[priority].push(message); // 立即处理高优先级命令 if (priority === 'critical' || priority === 'high') { await this.processCommandQueue(priority); } } /** * 处理命令队列 */ private async processCommandQueue(priority: string) { const queue = this.commandQueues[priority as keyof typeof this.commandQueues]; while (queue.length > 0) { const command = queue.shift(); if (!command) continue; const targetClient = this.findClientBySiteId(command.clientId); if (!targetClient) continue; // 直接转发命令 this.sendToClient(targetClient.info.clientId, command); // 设置超时处理 setTimeout(() => { // 检查是否收到响应 // 如果没有,发送超时错误 }, command.timeout || 10000); } } /** * 处理心跳 */ private async handleHeartbeat(clientSiteId: string, message: any) { const client = this.clients.get(clientSiteId); if (!client) return; // 更新最后心跳时间 client.info.lastHeartbeat = new Date().toISOString(); // 发送心跳确认 const ack = { type: 'heartbeat_ack', clientId: message.clientId, sequence: message.sequence, timestamp: new Date().toISOString() }; this.sendToClient(clientSiteId, ack); } /** * 处理批量消息 */ private async handleBatch(clientSiteId: string, message: any) { if (!message.messages || !Array.isArray(message.messages)) { this.sendError(clientSiteId, 'PROTOCOL_INVALID', 'Invalid batch message'); return; } console.log(`Processing batch of ${message.messages.length} messages`); // 并行处理批量消息 const results = await Promise.allSettled( message.messages.map(msg => this.handleMessage(clientSiteId, JSON.stringify(msg))) ); // 返回批量处理结果 const response = { type: 'batch_response', results: results.map((result, index) => ({ index, success: result.status === 'fulfilled', error: result.status === 'rejected' ? result.reason : undefined })) }; this.sendToClient(clientSiteId, response); } // === 辅助方法 === /** * 获取设备信息 */ private getDeviceInfo(clientId: string): DeviceInfo | null { for (const [_, client] of this.clients) { if (client.info.clientId === clientId) { return { ...client.info, statistics: { commandsSent: 0, commandsReceived: 0, errors: 0, uptime: Date.now() - new Date(client.info.connectedAt).getTime() } }; } } return null; } /** * 列出设备 */ private listDevices(filter?: DeviceFilter): DeviceInfo[] { const devices: DeviceInfo[] = []; for (const [_, client] of this.clients) { const info = client.info; // 应用过滤器 if (filter) { if (filter.status && info.status !== filter.status) continue; if (filter.clientType && info.clientType !== filter.clientType) continue; if (filter.deviceType && info.clientInfo?.deviceType !== filter.deviceType) continue; if (filter.tags && filter.tags.length > 0) { const clientTags = info.clientInfo?.tags || []; if (!filter.tags.every(tag => clientTags.includes(tag))) continue; } } devices.push(info); } return devices; } /** * 根据过滤器查找设备 */ private findDevicesByFilter(filter: DeviceFilter): DeviceInfo[] { return this.listDevices(filter); } /** * 根据 clientId 查找客户端 */ private findClientBySiteId(clientId: string) { for (const [_, client] of this.clients) { if (client.info.clientId === clientId) { return client; } } return null; } /** * 检查命令权限 */ private checkCommandPermission(fromSiteId: string, toSiteId: string): boolean { const fromClient = this.findClientBySiteId(fromSiteId); if (!fromClient) return false; // 基于客户端类型的权限控制 switch (fromClient.info.clientType) { case 'backend': case 'backend': // 后端可以发送给任何 Edge 或其管理的设备 return true; case 'edge': // Edge 可以: // 1. 回复来自 Backend 的命令 // 2. 上报其管理的设备状态 // 3. 不能主动向其他 Edge 或 Backend 发送命令 const toClient = this.findClientBySiteId(toSiteId); return toClient?.info.clientType === 'backend'; case 'device': // 设备不直接连接 Gateway return false; default: return false; } } /** * 发送命令响应 */ private sendCommandResponse(clientSiteId: string, requestRef: string, result: any) { const response = { type: 'command_response', requestRef, status: result.success ? 'completed' : 'failed', result, timestamp: new Date().toISOString() }; this.sendToClient(clientSiteId, response); } /** * 发送错误消息 */ private sendError(clientSiteId: string, code: string, message: string) { const error = { type: 'error', code, message, severity: 'medium', timestamp: new Date().toISOString() }; this.sendToClient(clientSiteId, error); } /** * 发送消息到客户端 */ private sendToClient(clientSiteId: string, message: any) { const client = this.clients.get(clientSiteId); if (!client || !client.ws) return; try { client.ws.send(JSON.stringify(message)); } catch (error) { console.error(`Error sending to client ${clientSiteId}:`, error); } } /** * 获取 Gateway 统计信息 */ private getGatewayStats() { const clientsByType: Record = {}; const clientsByStatus: Record = {}; for (const [_, client] of this.clients) { const type = client.info.clientType; const status = client.info.status; clientsByType[type] = (clientsByType[type] || 0) + 1; clientsByStatus[status] = (clientsByStatus[status] || 0) + 1; } return { uptime: process.uptime() * 1000, connections: { total: this.clients.size, byType: clientsByType, byStatus: clientsByStatus }, messages: { received: 0, // TODO: 实现计数器 sent: 0, errors: 0, byType: {} }, performance: { averageLatency: 0, messageRate: 0, errorRate: 0 } }; } /** * 更新路由表 */ private updateRoutes(clientId: string, clientSiteId: string) { this.commandRoutes.set(clientId, [clientSiteId]); } /** * 生成会话 ID */ private generateSessionId(): string { return `sess-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } /** * 延迟执行 */ private delay(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)); } /** * 处理命令响应 */ private async handleCommandResponse(clientSiteId: string, message: any) { // TODO: 路由响应回原始请求者 console.log(`Received command response from ${clientSiteId}:`, message.requestRef); } } // 使用示例 const gateway = new GatewayCore(); // 模拟客户端连接和消息处理 async function simulateGateway() { // 1. 设备注册 await gateway.handleMessage('conn-1', JSON.stringify({ type: 'register', clientId: 'device001' })); // 2. 后端注册(完整格式) await gateway.handleMessage('conn-2', JSON.stringify({ type: 'register', clientId: 'backend-server', clientType: 'backend', clientInfo: { version: '1.0.0', platform: 'nodejs', capabilities: ['command', 'program', 'batch'] }, features: ['batch', 'chain'], version: '1.0' })); // 3. 查询设备状态 await gateway.handleMessage('conn-2', JSON.stringify( GatewayUtils.createDeviceStatusQuery('device001', 'query-001') )); // 4. 批量命令 await gateway.handleMessage('conn-2', JSON.stringify( GatewayUtils.createBatchCommand( { deviceType: 'controller', status: 'online' }, { commandCode: 'RESET', deviceType: 'controller', operationType: 'write', parameters: {} }, { priority: Priority.HIGH } ) )); // 5. Complex类型命令(持续响应) await gateway.handleMessage('conn-2', JSON.stringify( GatewayUtils.createComplexCommand( 'device001', { commandType: CommandType.COMPLEX, commandCode: 'HealthCheck', parameters: { switchStatus: true, switchConfigInformation: true, synchronizerStatus: true, pillarStatus: true } }, { priority: Priority.NORMAL } ) )); } export { simulateGateway };