@thejrsoft/subway-protocol 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ACK_MESSAGES_IMPLEMENTATION_SUMMARY.md +128 -0
- package/ACK_MESSAGE_DESIGN.md +457 -0
- package/CHANGELOG.md +58 -0
- package/COMMAND_VALIDATION_RULES.md +178 -0
- package/DOCUMENTATION_REORGANIZATION_SUMMARY.md +81 -0
- package/DOCUMENTATION_STRUCTURE.md +106 -0
- package/GATEWAY_MIGRATION_GUIDE.md +130 -0
- package/GATEWAY_PROTOCOL_COMPARISON.md +216 -0
- package/INTEGRATION_GUIDE.md +190 -0
- package/OPTIONAL_FIELDS_WITHOUT_DEFAULTS.md +97 -0
- package/PROTOCOL_UTILS_USAGE.md +278 -0
- package/README.md +237 -0
- package/TYPE_FIXES_SUMMARY.md +210 -0
- package/UPDATE_ENUM_VALUES.md +139 -0
- package/dist/asyncapi-sync.d.ts +47 -0
- package/dist/asyncapi-sync.d.ts.map +1 -0
- package/dist/asyncapi-sync.js +85 -0
- package/dist/asyncapi-sync.js.map +1 -0
- package/dist/command-factory.d.ts +62 -0
- package/dist/command-factory.d.ts.map +1 -0
- package/dist/command-factory.js +137 -0
- package/dist/command-factory.js.map +1 -0
- package/dist/command-types.d.ts +27 -0
- package/dist/command-types.d.ts.map +1 -0
- package/dist/command-types.js +31 -0
- package/dist/command-types.js.map +1 -0
- package/dist/index.d.ts +403 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +413 -0
- package/dist/index.js.map +1 -0
- package/dist/message-validator.d.ts +102 -0
- package/dist/message-validator.d.ts.map +1 -0
- package/dist/message-validator.js +640 -0
- package/dist/message-validator.js.map +1 -0
- package/dist/protocol-utils.d.ts +108 -0
- package/dist/protocol-utils.d.ts.map +1 -0
- package/dist/protocol-utils.js +293 -0
- package/dist/protocol-utils.js.map +1 -0
- package/docs/01-protocol/README.md +45 -0
- package/docs/01-protocol/design-rationale.md +198 -0
- package/docs/01-protocol/message-types.md +669 -0
- package/docs/01-protocol/specification.md +1466 -0
- package/docs/02-commands/README.md +56 -0
- package/docs/02-commands/batch-command.md +435 -0
- package/docs/02-commands/complex-command.md +537 -0
- package/docs/02-commands/simple-command.md +332 -0
- package/docs/02-commands/typed-commands.md +362 -0
- package/docs/03-architecture/README.md +66 -0
- package/docs/03-architecture/device-protocol.md +430 -0
- package/docs/03-architecture/edge-proxy.md +727 -0
- package/docs/03-architecture/routing-flow.md +893 -0
- package/docs/04-integration/README.md +144 -0
- package/docs/04-integration/backend-guide.md +551 -0
- package/docs/04-integration/edge-guide.md +684 -0
- package/docs/04-integration/gateway-guide.md +180 -0
- package/docs/04-integration/migration-guide.md +226 -0
- package/docs/05-examples/README.md +141 -0
- package/docs/05-examples/progress-update-examples.md +757 -0
- package/docs/06-reference/README.md +67 -0
- package/docs/06-reference/api.md +572 -0
- package/docs/06-reference/faq.md +302 -0
- package/docs/06-reference/glossary.md +232 -0
- package/examples/backend-upgrade.ts +279 -0
- package/examples/edge-multi-device.ts +513 -0
- package/examples/gateway-upgrade.ts +150 -0
- package/examples/protocol-implementation.ts +715 -0
- package/package.json +48 -0
- package/scripts/validate-asyncapi.ts +78 -0
- package/src/__tests__/protocol.test.ts +297 -0
- package/src/asyncapi-sync.ts +84 -0
- package/src/command-factory.ts +183 -0
- package/src/command-types.ts +72 -0
- package/src/edge-proxy.ts +494 -0
- package/src/gateway-extensions.ts +278 -0
- package/src/index.ts +792 -0
- package/src/message-validator.ts +726 -0
- package/src/protocol-utils.ts +355 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
# Edge 集成指南
|
|
2
|
+
|
|
3
|
+
本文档说明如何实现和集成 Edge 节点,作为设备的代理服务。
|
|
4
|
+
|
|
5
|
+
## 概述
|
|
6
|
+
|
|
7
|
+
Edge 节点是位于 Gateway 和设备之间的代理层,负责:
|
|
8
|
+
- 管理设备的 WebSocket 连接
|
|
9
|
+
- 转发 Gateway 和设备之间的消息
|
|
10
|
+
- 维护设备状态和连接信息
|
|
11
|
+
- 处理设备的注册和心跳
|
|
12
|
+
|
|
13
|
+
## 架构设计
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
Gateway (18081)
|
|
17
|
+
↓
|
|
18
|
+
Edge (Dynamic Port)
|
|
19
|
+
↓
|
|
20
|
+
Devices (Multiple Connections)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 实现步骤
|
|
24
|
+
|
|
25
|
+
### 1. Edge 服务器实现
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import WebSocket from 'ws';
|
|
29
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
30
|
+
import { EventEmitter } from 'events';
|
|
31
|
+
|
|
32
|
+
interface DeviceConnection {
|
|
33
|
+
id: string;
|
|
34
|
+
ws: WebSocket;
|
|
35
|
+
info: any;
|
|
36
|
+
lastHeartbeat: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export class EdgeServer extends EventEmitter {
|
|
40
|
+
private gatewayClient: WebSocket | null = null;
|
|
41
|
+
private deviceServer: WebSocket.Server;
|
|
42
|
+
private devices: Map<string, DeviceConnection> = new Map();
|
|
43
|
+
private edgeId: string;
|
|
44
|
+
private gatewayUrl: string;
|
|
45
|
+
|
|
46
|
+
constructor(edgeId: string, gatewayUrl: string, devicePort: number) {
|
|
47
|
+
super();
|
|
48
|
+
this.edgeId = edgeId;
|
|
49
|
+
this.gatewayUrl = gatewayUrl;
|
|
50
|
+
|
|
51
|
+
// 创建设备服务器
|
|
52
|
+
this.deviceServer = new WebSocket.Server({ port: devicePort });
|
|
53
|
+
this.setupDeviceServer();
|
|
54
|
+
|
|
55
|
+
// 连接到 Gateway
|
|
56
|
+
this.connectToGateway();
|
|
57
|
+
|
|
58
|
+
// 启动心跳检查
|
|
59
|
+
this.startHeartbeatCheck();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private connectToGateway(): void {
|
|
63
|
+
console.log(`Connecting to Gateway at ${this.gatewayUrl}`);
|
|
64
|
+
|
|
65
|
+
this.gatewayClient = new WebSocket(this.gatewayUrl);
|
|
66
|
+
|
|
67
|
+
this.gatewayClient.on('open', () => {
|
|
68
|
+
console.log('Connected to Gateway');
|
|
69
|
+
this.registerWithGateway();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
this.gatewayClient.on('message', (data: WebSocket.Data) => {
|
|
73
|
+
this.handleGatewayMessage(data.toString());
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
this.gatewayClient.on('close', () => {
|
|
77
|
+
console.log('Disconnected from Gateway, reconnecting...');
|
|
78
|
+
setTimeout(() => this.connectToGateway(), 5000);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
this.gatewayClient.on('error', (error) => {
|
|
82
|
+
console.error('Gateway connection error:', error);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private registerWithGateway(): void {
|
|
87
|
+
const registerMessage = {
|
|
88
|
+
type: 'register',
|
|
89
|
+
clientId: this.edgeId,
|
|
90
|
+
clientType: 'EDGE',
|
|
91
|
+
clientInfo: {
|
|
92
|
+
name: 'Edge Node',
|
|
93
|
+
version: '1.0.0',
|
|
94
|
+
capabilities: ['device-proxy', 'batch-command'],
|
|
95
|
+
metadata: {
|
|
96
|
+
maxDevices: 100,
|
|
97
|
+
location: 'tunnel-exit-1'
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
timestamp: new Date().toISOString(),
|
|
101
|
+
version: '1.0'
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
this.sendToGateway(registerMessage);
|
|
105
|
+
|
|
106
|
+
// 启动心跳
|
|
107
|
+
this.startGatewayHeartbeat();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private setupDeviceServer(): void {
|
|
111
|
+
this.deviceServer.on('connection', (ws: WebSocket, req) => {
|
|
112
|
+
console.log('New device connection');
|
|
113
|
+
|
|
114
|
+
// 临时存储连接,等待注册
|
|
115
|
+
const tempId = uuidv4();
|
|
116
|
+
let registered = false;
|
|
117
|
+
|
|
118
|
+
ws.on('message', (data: WebSocket.Data) => {
|
|
119
|
+
const message = JSON.parse(data.toString());
|
|
120
|
+
|
|
121
|
+
if (message.type === 'register' && !registered) {
|
|
122
|
+
// 处理设备注册
|
|
123
|
+
this.handleDeviceRegistration(ws, message);
|
|
124
|
+
registered = true;
|
|
125
|
+
} else if (registered) {
|
|
126
|
+
// 处理其他消息
|
|
127
|
+
this.handleDeviceMessage(message);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
ws.on('close', () => {
|
|
132
|
+
// 查找并移除设备
|
|
133
|
+
for (const [id, device] of this.devices) {
|
|
134
|
+
if (device.ws === ws) {
|
|
135
|
+
console.log(`Device ${id} disconnected`);
|
|
136
|
+
this.devices.delete(id);
|
|
137
|
+
this.notifyGatewayDeviceStatus(id, 'offline');
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
ws.on('error', (error) => {
|
|
144
|
+
console.error('Device connection error:', error);
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private handleDeviceRegistration(ws: WebSocket, message: any): void {
|
|
150
|
+
const deviceId = message.clientId;
|
|
151
|
+
|
|
152
|
+
// 检查是否已存在
|
|
153
|
+
if (this.devices.has(deviceId)) {
|
|
154
|
+
console.log(`Device ${deviceId} already registered, closing old connection`);
|
|
155
|
+
this.devices.get(deviceId)!.ws.close();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 存储设备连接
|
|
159
|
+
this.devices.set(deviceId, {
|
|
160
|
+
id: deviceId,
|
|
161
|
+
ws: ws,
|
|
162
|
+
info: message.clientInfo,
|
|
163
|
+
lastHeartbeat: Date.now()
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
console.log(`Device ${deviceId} registered`);
|
|
167
|
+
|
|
168
|
+
// 通知 Gateway 设备上线
|
|
169
|
+
this.notifyGatewayDeviceStatus(deviceId, 'online');
|
|
170
|
+
|
|
171
|
+
// 回复注册成功
|
|
172
|
+
ws.send(JSON.stringify({
|
|
173
|
+
type: 'register_response',
|
|
174
|
+
success: true,
|
|
175
|
+
timestamp: new Date().toISOString(),
|
|
176
|
+
version: '1.0'
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### 2. 消息路由实现
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
class MessageRouter {
|
|
186
|
+
|
|
187
|
+
// Gateway 消息处理
|
|
188
|
+
private handleGatewayMessage(data: string): void {
|
|
189
|
+
try {
|
|
190
|
+
const message = JSON.parse(data);
|
|
191
|
+
|
|
192
|
+
switch (message.type) {
|
|
193
|
+
case 'command':
|
|
194
|
+
this.routeCommandToDevice(message);
|
|
195
|
+
break;
|
|
196
|
+
|
|
197
|
+
case 'program':
|
|
198
|
+
this.routeProgramToDevice(message);
|
|
199
|
+
break;
|
|
200
|
+
|
|
201
|
+
case 'heartbeat':
|
|
202
|
+
// Gateway 心跳响应
|
|
203
|
+
break;
|
|
204
|
+
|
|
205
|
+
default:
|
|
206
|
+
console.log(`Unknown message type from Gateway: ${message.type}`);
|
|
207
|
+
}
|
|
208
|
+
} catch (error) {
|
|
209
|
+
console.error('Error handling Gateway message:', error);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 路由命令到设备
|
|
214
|
+
private routeCommandToDevice(message: any): void {
|
|
215
|
+
// targetClientId 直接就是设备ID
|
|
216
|
+
const deviceId = message.targetClientId;
|
|
217
|
+
|
|
218
|
+
// 查找设备
|
|
219
|
+
const device = this.devices.get(deviceId);
|
|
220
|
+
if (!device) {
|
|
221
|
+
this.sendErrorToGateway(message.requestRef, 'DEVICE_NOT_FOUND',
|
|
222
|
+
`Device ${deviceId} not connected`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 修改 targetClientId 为设备ID
|
|
227
|
+
message.targetClientId = deviceId;
|
|
228
|
+
|
|
229
|
+
// 转发到设备
|
|
230
|
+
device.ws.send(JSON.stringify(message));
|
|
231
|
+
console.log(`Forwarded command to device ${deviceId}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// 设备消息处理
|
|
235
|
+
private handleDeviceMessage(message: any): void {
|
|
236
|
+
switch (message.type) {
|
|
237
|
+
case 'command_response':
|
|
238
|
+
case 'progress_update':
|
|
239
|
+
case 'program_response':
|
|
240
|
+
// 转发响应到 Gateway
|
|
241
|
+
this.sendToGateway(message);
|
|
242
|
+
break;
|
|
243
|
+
|
|
244
|
+
case 'heartbeat':
|
|
245
|
+
// 更新设备心跳时间
|
|
246
|
+
const device = this.devices.get(message.clientId);
|
|
247
|
+
if (device) {
|
|
248
|
+
device.lastHeartbeat = Date.now();
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
|
|
252
|
+
case 'error':
|
|
253
|
+
// 转发错误到 Gateway
|
|
254
|
+
this.sendToGateway(message);
|
|
255
|
+
break;
|
|
256
|
+
|
|
257
|
+
default:
|
|
258
|
+
console.log(`Unknown message type from device: ${message.type}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// 发送消息到 Gateway
|
|
263
|
+
private sendToGateway(message: any): void {
|
|
264
|
+
if (this.gatewayClient && this.gatewayClient.readyState === WebSocket.OPEN) {
|
|
265
|
+
this.gatewayClient.send(JSON.stringify(message));
|
|
266
|
+
} else {
|
|
267
|
+
console.error('Gateway connection not available');
|
|
268
|
+
// 可以考虑缓存消息,等待重连
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// 发送错误到 Gateway
|
|
273
|
+
private sendErrorToGateway(requestRef: string, code: string, message: string): void {
|
|
274
|
+
const errorMessage = {
|
|
275
|
+
type: 'error',
|
|
276
|
+
requestRef,
|
|
277
|
+
code,
|
|
278
|
+
message,
|
|
279
|
+
timestamp: new Date().toISOString(),
|
|
280
|
+
version: '1.0'
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
this.sendToGateway(errorMessage);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### 3. 设备状态管理
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
class DeviceManager {
|
|
292
|
+
private devices: Map<string, DeviceConnection> = new Map();
|
|
293
|
+
private deviceStatus: Map<string, DeviceStatus> = new Map();
|
|
294
|
+
|
|
295
|
+
interface DeviceStatus {
|
|
296
|
+
online: boolean;
|
|
297
|
+
lastSeen: Date;
|
|
298
|
+
metrics: {
|
|
299
|
+
commandsReceived: number;
|
|
300
|
+
commandsSucceeded: number;
|
|
301
|
+
commandsFailed: number;
|
|
302
|
+
averageResponseTime: number;
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 通知 Gateway 设备状态变化
|
|
307
|
+
private notifyGatewayDeviceStatus(deviceId: string, status: 'online' | 'offline'): void {
|
|
308
|
+
const statusMessage = {
|
|
309
|
+
type: 'device_status',
|
|
310
|
+
edgeId: this.edgeId,
|
|
311
|
+
deviceId,
|
|
312
|
+
status,
|
|
313
|
+
timestamp: new Date().toISOString(),
|
|
314
|
+
version: '1.0'
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
this.sendToGateway(statusMessage);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// 获取所有设备状态
|
|
321
|
+
getDeviceStatuses(): DeviceStatus[] {
|
|
322
|
+
const statuses = [];
|
|
323
|
+
|
|
324
|
+
for (const [id, device] of this.devices) {
|
|
325
|
+
const status = this.deviceStatus.get(id) || this.createDefaultStatus();
|
|
326
|
+
statuses.push({
|
|
327
|
+
deviceId: id,
|
|
328
|
+
...status,
|
|
329
|
+
info: device.info
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return statuses;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// 更新设备指标
|
|
337
|
+
updateDeviceMetrics(deviceId: string, success: boolean, responseTime: number): void {
|
|
338
|
+
const status = this.deviceStatus.get(deviceId) || this.createDefaultStatus();
|
|
339
|
+
|
|
340
|
+
status.metrics.commandsReceived++;
|
|
341
|
+
if (success) {
|
|
342
|
+
status.metrics.commandsSucceeded++;
|
|
343
|
+
} else {
|
|
344
|
+
status.metrics.commandsFailed++;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// 计算平均响应时间
|
|
348
|
+
const total = status.metrics.commandsSucceeded;
|
|
349
|
+
status.metrics.averageResponseTime =
|
|
350
|
+
(status.metrics.averageResponseTime * (total - 1) + responseTime) / total;
|
|
351
|
+
|
|
352
|
+
this.deviceStatus.set(deviceId, status);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### 4. 心跳和健康检查
|
|
358
|
+
|
|
359
|
+
```typescript
|
|
360
|
+
class HealthChecker {
|
|
361
|
+
private heartbeatInterval = 30000; // 30秒
|
|
362
|
+
private heartbeatTimeout = 90000; // 90秒
|
|
363
|
+
|
|
364
|
+
// Gateway 心跳
|
|
365
|
+
private startGatewayHeartbeat(): void {
|
|
366
|
+
setInterval(() => {
|
|
367
|
+
if (this.gatewayClient && this.gatewayClient.readyState === WebSocket.OPEN) {
|
|
368
|
+
const heartbeat = {
|
|
369
|
+
type: 'heartbeat',
|
|
370
|
+
clientId: this.edgeId,
|
|
371
|
+
timestamp: new Date().toISOString(),
|
|
372
|
+
version: '1.0'
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
this.gatewayClient.send(JSON.stringify(heartbeat));
|
|
376
|
+
}
|
|
377
|
+
}, this.heartbeatInterval);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// 设备心跳检查
|
|
381
|
+
private startHeartbeatCheck(): void {
|
|
382
|
+
setInterval(() => {
|
|
383
|
+
const now = Date.now();
|
|
384
|
+
|
|
385
|
+
for (const [id, device] of this.devices) {
|
|
386
|
+
if (now - device.lastHeartbeat > this.heartbeatTimeout) {
|
|
387
|
+
console.log(`Device ${id} heartbeat timeout, removing`);
|
|
388
|
+
device.ws.close();
|
|
389
|
+
this.devices.delete(id);
|
|
390
|
+
this.notifyGatewayDeviceStatus(id, 'offline');
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}, this.heartbeatInterval);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// 健康检查端点
|
|
397
|
+
getHealthStatus(): HealthStatus {
|
|
398
|
+
return {
|
|
399
|
+
status: 'healthy',
|
|
400
|
+
edgeId: this.edgeId,
|
|
401
|
+
gatewayConnected: this.gatewayClient?.readyState === WebSocket.OPEN,
|
|
402
|
+
deviceCount: this.devices.size,
|
|
403
|
+
uptime: process.uptime(),
|
|
404
|
+
memory: process.memoryUsage(),
|
|
405
|
+
timestamp: new Date().toISOString()
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### 5. 批量命令处理
|
|
412
|
+
|
|
413
|
+
```typescript
|
|
414
|
+
class BatchCommandHandler {
|
|
415
|
+
|
|
416
|
+
// 处理批量命令
|
|
417
|
+
async handleBatchCommand(message: any): Promise<void> {
|
|
418
|
+
const { command } = message;
|
|
419
|
+
// targetClientId 直接就是设备ID
|
|
420
|
+
const targetDeviceId = message.targetClientId;
|
|
421
|
+
|
|
422
|
+
// 批量命令直接转发给设备
|
|
423
|
+
const device = this.devices.get(targetDeviceId);
|
|
424
|
+
if (!device) {
|
|
425
|
+
this.sendErrorToGateway(message.requestRef, 'DEVICE_NOT_FOUND',
|
|
426
|
+
`Device ${targetDeviceId} not connected`);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// 设备会处理批量逻辑并发送进度更新
|
|
431
|
+
device.ws.send(JSON.stringify(message));
|
|
432
|
+
|
|
433
|
+
console.log(`Forwarded batch command to device ${targetDeviceId}`);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// 聚合批量命令的进度更新
|
|
437
|
+
private aggregateBatchProgress(updates: ProgressUpdate[]): any {
|
|
438
|
+
const total = updates.length;
|
|
439
|
+
const successful = updates.filter(u => u.status === 'COMPLETED').length;
|
|
440
|
+
const failed = updates.filter(u => u.status === 'FAILED').length;
|
|
441
|
+
const inProgress = updates.filter(u => u.status === 'IN_PROGRESS').length;
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
total,
|
|
445
|
+
successful,
|
|
446
|
+
failed,
|
|
447
|
+
inProgress,
|
|
448
|
+
progress: (successful + failed) / total * 100
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### 6. 配置和启动
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
// config.ts
|
|
458
|
+
export interface EdgeConfig {
|
|
459
|
+
edgeId: string;
|
|
460
|
+
gatewayUrl: string;
|
|
461
|
+
devicePort: number;
|
|
462
|
+
maxDevices: number;
|
|
463
|
+
heartbeatInterval: number;
|
|
464
|
+
logLevel: 'debug' | 'info' | 'warn' | 'error';
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// index.ts
|
|
468
|
+
import { EdgeServer } from './edge-server';
|
|
469
|
+
import { EdgeConfig } from './config';
|
|
470
|
+
|
|
471
|
+
const config: EdgeConfig = {
|
|
472
|
+
edgeId: process.env.EDGE_ID || 'edge-001',
|
|
473
|
+
gatewayUrl: process.env.GATEWAY_URL || 'ws://localhost:18081',
|
|
474
|
+
devicePort: parseInt(process.env.DEVICE_PORT || '18090'),
|
|
475
|
+
maxDevices: 100,
|
|
476
|
+
heartbeatInterval: 30000,
|
|
477
|
+
logLevel: 'info'
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
// 启动 Edge 服务
|
|
481
|
+
const edge = new EdgeServer(config.edgeId, config.gatewayUrl, config.devicePort);
|
|
482
|
+
|
|
483
|
+
// 健康检查 HTTP 端点
|
|
484
|
+
import express from 'express';
|
|
485
|
+
const app = express();
|
|
486
|
+
|
|
487
|
+
app.get('/health', (req, res) => {
|
|
488
|
+
res.json(edge.getHealthStatus());
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
app.get('/devices', (req, res) => {
|
|
492
|
+
res.json(edge.getDeviceStatuses());
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
app.listen(18091, () => {
|
|
496
|
+
console.log('Edge HTTP server listening on port 18091');
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
// 优雅关闭
|
|
500
|
+
process.on('SIGTERM', () => {
|
|
501
|
+
console.log('Shutting down Edge server...');
|
|
502
|
+
edge.shutdown();
|
|
503
|
+
process.exit(0);
|
|
504
|
+
});
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
## 部署建议
|
|
508
|
+
|
|
509
|
+
### 1. Docker 部署
|
|
510
|
+
|
|
511
|
+
```dockerfile
|
|
512
|
+
FROM node:18-alpine
|
|
513
|
+
|
|
514
|
+
WORKDIR /app
|
|
515
|
+
|
|
516
|
+
COPY package*.json ./
|
|
517
|
+
RUN npm ci --only=production
|
|
518
|
+
|
|
519
|
+
COPY . .
|
|
520
|
+
|
|
521
|
+
EXPOSE 18090 18091
|
|
522
|
+
|
|
523
|
+
CMD ["node", "dist/index.js"]
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### 2. 环境变量
|
|
527
|
+
|
|
528
|
+
```bash
|
|
529
|
+
# .env
|
|
530
|
+
EDGE_ID=edge-001
|
|
531
|
+
GATEWAY_URL=ws://gateway:18081
|
|
532
|
+
DEVICE_PORT=18090
|
|
533
|
+
LOG_LEVEL=info
|
|
534
|
+
NODE_ENV=production
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
### 3. 监控和日志
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
import winston from 'winston';
|
|
541
|
+
|
|
542
|
+
const logger = winston.createLogger({
|
|
543
|
+
level: config.logLevel,
|
|
544
|
+
format: winston.format.combine(
|
|
545
|
+
winston.format.timestamp(),
|
|
546
|
+
winston.format.json()
|
|
547
|
+
),
|
|
548
|
+
transports: [
|
|
549
|
+
new winston.transports.File({ filename: 'edge.log' }),
|
|
550
|
+
new winston.transports.Console()
|
|
551
|
+
]
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// 使用结构化日志
|
|
555
|
+
logger.info('Device connected', {
|
|
556
|
+
deviceId: device.id,
|
|
557
|
+
edgeId: this.edgeId,
|
|
558
|
+
timestamp: new Date().toISOString()
|
|
559
|
+
});
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
## 高可用设计
|
|
563
|
+
|
|
564
|
+
### 1. 自动重连
|
|
565
|
+
|
|
566
|
+
```typescript
|
|
567
|
+
class ReconnectManager {
|
|
568
|
+
private reconnectDelay = 5000;
|
|
569
|
+
private maxReconnectDelay = 60000;
|
|
570
|
+
private reconnectAttempts = 0;
|
|
571
|
+
|
|
572
|
+
async reconnectToGateway(): Promise<void> {
|
|
573
|
+
const delay = Math.min(
|
|
574
|
+
this.reconnectDelay * Math.pow(2, this.reconnectAttempts),
|
|
575
|
+
this.maxReconnectDelay
|
|
576
|
+
);
|
|
577
|
+
|
|
578
|
+
this.reconnectAttempts++;
|
|
579
|
+
|
|
580
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
581
|
+
|
|
582
|
+
try {
|
|
583
|
+
await this.connectToGateway();
|
|
584
|
+
this.reconnectAttempts = 0;
|
|
585
|
+
} catch (error) {
|
|
586
|
+
console.error('Reconnection failed:', error);
|
|
587
|
+
this.reconnectToGateway();
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
### 2. 消息缓存
|
|
594
|
+
|
|
595
|
+
```typescript
|
|
596
|
+
class MessageBuffer {
|
|
597
|
+
private buffer: any[] = [];
|
|
598
|
+
private maxSize = 1000;
|
|
599
|
+
|
|
600
|
+
add(message: any): void {
|
|
601
|
+
if (this.buffer.length >= this.maxSize) {
|
|
602
|
+
this.buffer.shift(); // 移除最旧的消息
|
|
603
|
+
}
|
|
604
|
+
this.buffer.push(message);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
flush(): any[] {
|
|
608
|
+
const messages = [...this.buffer];
|
|
609
|
+
this.buffer = [];
|
|
610
|
+
return messages;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
## 性能优化
|
|
616
|
+
|
|
617
|
+
### 1. 连接池
|
|
618
|
+
|
|
619
|
+
```typescript
|
|
620
|
+
class ConnectionPool {
|
|
621
|
+
private pool: WebSocket[] = [];
|
|
622
|
+
private maxConnections = 5;
|
|
623
|
+
|
|
624
|
+
async getConnection(): Promise<WebSocket> {
|
|
625
|
+
// 实现连接池逻辑
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
### 2. 消息批处理
|
|
631
|
+
|
|
632
|
+
```typescript
|
|
633
|
+
class MessageBatcher {
|
|
634
|
+
private batch: any[] = [];
|
|
635
|
+
private batchSize = 100;
|
|
636
|
+
private batchInterval = 100; // ms
|
|
637
|
+
|
|
638
|
+
add(message: any): void {
|
|
639
|
+
this.batch.push(message);
|
|
640
|
+
|
|
641
|
+
if (this.batch.length >= this.batchSize) {
|
|
642
|
+
this.flush();
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
private flush(): void {
|
|
647
|
+
if (this.batch.length > 0) {
|
|
648
|
+
this.sendBatch(this.batch);
|
|
649
|
+
this.batch = [];
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
## 故障排查
|
|
656
|
+
|
|
657
|
+
### 常见问题
|
|
658
|
+
|
|
659
|
+
1. **设备连接不上**
|
|
660
|
+
- 检查防火墙设置
|
|
661
|
+
- 验证设备端口是否正确
|
|
662
|
+
- 查看 Edge 日志
|
|
663
|
+
|
|
664
|
+
2. **消息转发失败**
|
|
665
|
+
- 确认设备已注册
|
|
666
|
+
- 检查消息格式
|
|
667
|
+
- 验证 Gateway 连接状态
|
|
668
|
+
|
|
669
|
+
3. **性能问题**
|
|
670
|
+
- 监控设备数量
|
|
671
|
+
- 检查消息队列长度
|
|
672
|
+
- 优化消息处理逻辑
|
|
673
|
+
|
|
674
|
+
## 总结
|
|
675
|
+
|
|
676
|
+
Edge 节点实现的关键点:
|
|
677
|
+
|
|
678
|
+
1. **双向代理** - 正确处理 Gateway 和设备之间的消息转发
|
|
679
|
+
2. **连接管理** - 可靠的连接维护和自动重连
|
|
680
|
+
3. **状态跟踪** - 准确的设备状态管理
|
|
681
|
+
4. **性能优化** - 批处理、连接池等优化手段
|
|
682
|
+
5. **高可用性** - 容错、缓存、重试机制
|
|
683
|
+
|
|
684
|
+
遵循这些指南,可以构建一个稳定、高效的 Edge 代理服务。
|