@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.
Files changed (78) hide show
  1. package/ACK_MESSAGES_IMPLEMENTATION_SUMMARY.md +128 -0
  2. package/ACK_MESSAGE_DESIGN.md +457 -0
  3. package/CHANGELOG.md +58 -0
  4. package/COMMAND_VALIDATION_RULES.md +178 -0
  5. package/DOCUMENTATION_REORGANIZATION_SUMMARY.md +81 -0
  6. package/DOCUMENTATION_STRUCTURE.md +106 -0
  7. package/GATEWAY_MIGRATION_GUIDE.md +130 -0
  8. package/GATEWAY_PROTOCOL_COMPARISON.md +216 -0
  9. package/INTEGRATION_GUIDE.md +190 -0
  10. package/OPTIONAL_FIELDS_WITHOUT_DEFAULTS.md +97 -0
  11. package/PROTOCOL_UTILS_USAGE.md +278 -0
  12. package/README.md +237 -0
  13. package/TYPE_FIXES_SUMMARY.md +210 -0
  14. package/UPDATE_ENUM_VALUES.md +139 -0
  15. package/dist/asyncapi-sync.d.ts +47 -0
  16. package/dist/asyncapi-sync.d.ts.map +1 -0
  17. package/dist/asyncapi-sync.js +85 -0
  18. package/dist/asyncapi-sync.js.map +1 -0
  19. package/dist/command-factory.d.ts +62 -0
  20. package/dist/command-factory.d.ts.map +1 -0
  21. package/dist/command-factory.js +137 -0
  22. package/dist/command-factory.js.map +1 -0
  23. package/dist/command-types.d.ts +27 -0
  24. package/dist/command-types.d.ts.map +1 -0
  25. package/dist/command-types.js +31 -0
  26. package/dist/command-types.js.map +1 -0
  27. package/dist/index.d.ts +403 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +413 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/message-validator.d.ts +102 -0
  32. package/dist/message-validator.d.ts.map +1 -0
  33. package/dist/message-validator.js +640 -0
  34. package/dist/message-validator.js.map +1 -0
  35. package/dist/protocol-utils.d.ts +108 -0
  36. package/dist/protocol-utils.d.ts.map +1 -0
  37. package/dist/protocol-utils.js +293 -0
  38. package/dist/protocol-utils.js.map +1 -0
  39. package/docs/01-protocol/README.md +45 -0
  40. package/docs/01-protocol/design-rationale.md +198 -0
  41. package/docs/01-protocol/message-types.md +669 -0
  42. package/docs/01-protocol/specification.md +1466 -0
  43. package/docs/02-commands/README.md +56 -0
  44. package/docs/02-commands/batch-command.md +435 -0
  45. package/docs/02-commands/complex-command.md +537 -0
  46. package/docs/02-commands/simple-command.md +332 -0
  47. package/docs/02-commands/typed-commands.md +362 -0
  48. package/docs/03-architecture/README.md +66 -0
  49. package/docs/03-architecture/device-protocol.md +430 -0
  50. package/docs/03-architecture/edge-proxy.md +727 -0
  51. package/docs/03-architecture/routing-flow.md +893 -0
  52. package/docs/04-integration/README.md +144 -0
  53. package/docs/04-integration/backend-guide.md +551 -0
  54. package/docs/04-integration/edge-guide.md +684 -0
  55. package/docs/04-integration/gateway-guide.md +180 -0
  56. package/docs/04-integration/migration-guide.md +226 -0
  57. package/docs/05-examples/README.md +141 -0
  58. package/docs/05-examples/progress-update-examples.md +757 -0
  59. package/docs/06-reference/README.md +67 -0
  60. package/docs/06-reference/api.md +572 -0
  61. package/docs/06-reference/faq.md +302 -0
  62. package/docs/06-reference/glossary.md +232 -0
  63. package/examples/backend-upgrade.ts +279 -0
  64. package/examples/edge-multi-device.ts +513 -0
  65. package/examples/gateway-upgrade.ts +150 -0
  66. package/examples/protocol-implementation.ts +715 -0
  67. package/package.json +48 -0
  68. package/scripts/validate-asyncapi.ts +78 -0
  69. package/src/__tests__/protocol.test.ts +297 -0
  70. package/src/asyncapi-sync.ts +84 -0
  71. package/src/command-factory.ts +183 -0
  72. package/src/command-types.ts +72 -0
  73. package/src/edge-proxy.ts +494 -0
  74. package/src/gateway-extensions.ts +278 -0
  75. package/src/index.ts +792 -0
  76. package/src/message-validator.ts +726 -0
  77. package/src/protocol-utils.ts +355 -0
  78. package/tsconfig.json +24 -0
@@ -0,0 +1,66 @@
1
+ # 架构文档
2
+
3
+ 本目录包含 JRSoft Subway 系统的架构设计文档。
4
+
5
+ ## 📚 文档列表
6
+
7
+ ### [Edge 代理架构](./edge-proxy.md)
8
+ Edge 节点的设计和实现:
9
+ - Edge 作为设备代理的角色
10
+ - 设备连接管理
11
+ - 消息路由和转发
12
+ - 故障处理和重连机制
13
+
14
+ ### [设备协议](./device-protocol.md)
15
+ 设备到 Edge 的 WebSocket 协议:
16
+ - 设备注册流程
17
+ - 心跳机制(30秒间隔)
18
+ - 命令转发和响应
19
+ - 连接状态管理
20
+
21
+ ### [消息路由流程](./routing-flow.md)
22
+ 系统中的消息路由机制:
23
+ - Backend → Gateway → Edge → Device 流程
24
+ - targetClientId 格式和解析
25
+ - 路由决策逻辑
26
+ - 错误处理和超时
27
+
28
+ ## 🏗️ 系统架构
29
+
30
+ ```
31
+ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ ┌──────────┐
32
+ │ Backend │────▶│ Gateway │────▶│ Edge │────▶│ Device │
33
+ │ (FastAPI) │◀────│(WebSocket) │◀────│ (Proxy) │◀────│(Client) │
34
+ └─────────────┘ └─────────────┘ └──────────┘ └──────────┘
35
+ 18082 18081 Dynamic Dynamic
36
+ ```
37
+
38
+ ## 🔑 核心概念
39
+
40
+ ### 连接管理
41
+ - **Backend-Gateway**: HTTP/WebSocket 混合模式
42
+ - **Gateway-Edge**: 持久 WebSocket 连接
43
+ - **Edge-Device**: 设备主动连接,Edge 管理
44
+
45
+ ### 标识符格式
46
+ - **Edge ID**: `edge-{location}` (如 `edge-001`)
47
+ - **Device ID**: `{type}-{number}` (如 `td-01`, `screen-05`)
48
+ - **Target Client ID**: 直接使用设备ID (如 `td-01`),Gateway自动路由
49
+
50
+ ### 消息流向
51
+ - **下行命令**: Backend → Gateway → Edge → Device
52
+ - **上行响应**: Device → Edge → Gateway → Backend
53
+ - **进度更新**: 支持多次中间状态报告
54
+
55
+ ## 🚀 快速理解
56
+
57
+ 1. **Gateway 是中心枢纽** - 所有消息都经过 Gateway 路由
58
+ 2. **Edge 是设备代理** - 设备不直接连接 Gateway
59
+ 3. **设备主动连接** - 设备连接到 Edge,而非相反
60
+ 4. **自动路由** - Backend只需指定设备ID,Gateway自动查找对应Edge
61
+
62
+ ## 🔗 相关文档
63
+
64
+ - [协议规范](../01-protocol/) - 了解消息格式
65
+ - [命令系统](../02-commands/) - 了解命令类型
66
+ - [集成指南](../04-integration/) - 如何集成各组件
@@ -0,0 +1,430 @@
1
+ # 设备端到 Edge 的 WebSocket 协议
2
+
3
+ ## 概述
4
+
5
+ 设备端通过 WebSocket 协议连接到 Edge 节点,使用与 Gateway 相同的协议规范。Edge 在此架构中扮演本地 Gateway 的角色,为设备端提供注册、心跳、命令执行等服务。
6
+
7
+ ## 架构更新
8
+
9
+ ```
10
+ ┌─────────────────────────────────────────────────┐
11
+ │ Gateway │
12
+ │ (中央路由和管理服务器) │
13
+ └─────────────────────────────────────────────────┘
14
+
15
+ │ WebSocket (Edge→Gateway)
16
+
17
+ ┌─────────────────────────────────────────────────┐
18
+ │ Edge Node (edge-001) │
19
+ │ (本地设备管理和代理服务) │
20
+ │ │
21
+ │ ┌───────────────────────────────────────────┐ │
22
+ │ │ 设备连接管理器 │ │
23
+ │ │ - WebSocket 服务器 (端口: 18083) │ │
24
+ │ │ - 设备注册处理 │ │
25
+ │ │ - 心跳监控 │ │
26
+ │ │ - 命令路由和转发 │ │
27
+ │ └───────────────────────────────────────────┘ │
28
+ └─────────────────────────────────────────────────┘
29
+
30
+ │ WebSocket (Device→Edge)
31
+
32
+ ┌───────┐ ┌───────┐ ┌───────┐ ┌───────┐
33
+ │设备端1 │ │设备端2 │ │设备端3 │ │设备端4 │
34
+ └───────┘ └───────┘ └───────┘ └───────┘
35
+ ```
36
+
37
+ ## 设备端注册流程
38
+
39
+ ### 1. 设备端连接到 Edge
40
+
41
+ ```typescript
42
+ // 设备端代码示例
43
+ const ws = new WebSocket('ws://edge-001:18083');
44
+
45
+ ws.on('open', () => {
46
+ // 发送注册消息
47
+ const registerMsg = {
48
+ type: 'register',
49
+ clientId: 'device-001',
50
+ clientType: 'device',
51
+ clientInfo: {
52
+ version: '1.0.0',
53
+ deviceType: 'media_player',
54
+ capabilities: ['play', 'stop', 'pause', 'status_report']
55
+ }
56
+ };
57
+
58
+ ws.send(JSON.stringify(registerMsg));
59
+ });
60
+ ```
61
+
62
+ ### 2. Edge 处理设备注册
63
+
64
+ ```typescript
65
+ // Edge 端处理逻辑
66
+ class EdgeDeviceHandler {
67
+ private devices = new Map<string, DeviceConnection>();
68
+
69
+ handleDeviceRegister(ws: WebSocket, message: RegisterMessage) {
70
+ const { clientId, clientInfo } = message;
71
+
72
+ // 1. 记录设备连接
73
+ this.devices.set(clientId, {
74
+ ws,
75
+ clientId,
76
+ clientInfo,
77
+ registeredAt: new Date(),
78
+ lastHeartbeat: new Date()
79
+ });
80
+
81
+ // 2. 发送注册确认给设备
82
+ const ack = {
83
+ type: 'register_ack',
84
+ clientId,
85
+ success: true,
86
+ sessionId: generateSessionId(),
87
+ timestamp: new Date().toISOString()
88
+ };
89
+ ws.send(JSON.stringify(ack));
90
+
91
+ // 3. 如果已连接到 Gateway,代理注册到 Gateway
92
+ if (this.gatewayConnected) {
93
+ this.proxyRegisterToGateway(clientId, clientInfo);
94
+ }
95
+ }
96
+
97
+ // 代理注册到 Gateway
98
+ proxyRegisterToGateway(deviceId: string, deviceInfo: ClientInfo) {
99
+ const proxyRegister = {
100
+ type: 'register',
101
+ clientId: deviceId,
102
+ clientType: 'device',
103
+ clientInfo: deviceInfo,
104
+ edgeInfo: {
105
+ edgeId: this.edgeId,
106
+ edgeVersion: this.version,
107
+ connectionTime: new Date().toISOString()
108
+ }
109
+ };
110
+
111
+ this.gatewayWs.send(JSON.stringify(proxyRegister));
112
+ }
113
+ }
114
+ ```
115
+
116
+ ## 心跳机制
117
+
118
+ ### 设备端心跳
119
+
120
+ 设备端需要定期向 Edge 发送心跳,保持连接活跃:
121
+
122
+ ```typescript
123
+ // 设备端心跳实现
124
+ class DeviceClient {
125
+ private heartbeatInterval: NodeJS.Timer;
126
+ private heartbeatSequence = 0;
127
+
128
+ startHeartbeat() {
129
+ this.heartbeatInterval = setInterval(() => {
130
+ const heartbeat = {
131
+ type: 'heartbeat',
132
+ clientId: this.clientId,
133
+ sequence: ++this.heartbeatSequence,
134
+ timestamp: new Date().toISOString()
135
+ };
136
+
137
+ this.ws.send(JSON.stringify(heartbeat));
138
+ }, 30000); // 30秒一次
139
+ }
140
+
141
+ handleHeartbeatAck(message: HeartbeatAckMessage) {
142
+ // 更新最后确认时间
143
+ this.lastHeartbeatAck = new Date();
144
+ }
145
+ }
146
+ ```
147
+
148
+ ### Edge 心跳监控
149
+
150
+ Edge 监控所有设备的心跳状态:
151
+
152
+ ```typescript
153
+ class EdgeHeartbeatMonitor {
154
+ private readonly HEARTBEAT_TIMEOUT = 90000; // 90秒超时
155
+
156
+ startMonitoring() {
157
+ setInterval(() => {
158
+ const now = Date.now();
159
+
160
+ for (const [clientId, device] of this.devices) {
161
+ const lastHeartbeat = device.lastHeartbeat.getTime();
162
+
163
+ if (now - lastHeartbeat > this.HEARTBEAT_TIMEOUT) {
164
+ this.handleDeviceTimeout(clientId);
165
+ }
166
+ }
167
+ }, 10000); // 每10秒检查一次
168
+ }
169
+
170
+ handleHeartbeat(clientId: string, message: HeartbeatMessage) {
171
+ const device = this.devices.get(clientId);
172
+ if (device) {
173
+ device.lastHeartbeat = new Date();
174
+
175
+ // 发送心跳确认
176
+ const ack = {
177
+ type: 'heartbeat_ack',
178
+ clientId,
179
+ sequence: message.sequence
180
+ };
181
+ device.ws.send(JSON.stringify(ack));
182
+ }
183
+ }
184
+
185
+ handleDeviceTimeout(clientId: string) {
186
+ console.log(`Device ${clientId} heartbeat timeout`);
187
+
188
+ // 1. 标记设备为离线
189
+ const device = this.devices.get(clientId);
190
+ if (device) {
191
+ device.status = 'offline';
192
+ }
193
+
194
+ // 2. 通知 Gateway 设备离线
195
+ if (this.gatewayConnected) {
196
+ this.notifyGatewayDeviceOffline(clientId);
197
+ }
198
+
199
+ // 3. 清理连接
200
+ this.cleanupDevice(clientId);
201
+ }
202
+ }
203
+ ```
204
+
205
+ ## 完整通信流程
206
+
207
+ ### 1. 启动时序
208
+
209
+ ```
210
+ 设备端启动 → 连接 Edge → 注册 → 开始心跳
211
+
212
+ Edge 记录设备
213
+
214
+ Edge 连接 Gateway
215
+
216
+ Edge 批量注册所有设备到 Gateway
217
+ ```
218
+
219
+ ### 2. 命令执行流程
220
+
221
+ ```
222
+ Backend → Gateway → Edge → Device
223
+ ↑ ↓
224
+ └───────────────────────────┘
225
+ (响应返回路径)
226
+ ```
227
+
228
+ ### 3. 状态同步
229
+
230
+ ```typescript
231
+ // Edge 定期同步设备状态到 Gateway
232
+ class EdgeStatusReporter {
233
+ reportDeviceStatus() {
234
+ const statusReport = {
235
+ type: 'edge_status_report',
236
+ edgeId: this.edgeId,
237
+ devices: Array.from(this.devices.values()).map(device => ({
238
+ clientId: device.clientId,
239
+ status: device.status,
240
+ lastHeartbeat: device.lastHeartbeat,
241
+ clientInfo: device.clientInfo
242
+ })),
243
+ timestamp: new Date().toISOString()
244
+ };
245
+
246
+ this.gatewayWs.send(JSON.stringify(statusReport));
247
+ }
248
+ }
249
+ ```
250
+
251
+ ## 错误处理
252
+
253
+ ### 1. 设备断线重连
254
+
255
+ ```typescript
256
+ // 设备端重连逻辑
257
+ class ResilientDeviceClient {
258
+ private reconnectAttempts = 0;
259
+ private maxReconnectAttempts = 10;
260
+
261
+ connect() {
262
+ this.ws = new WebSocket(this.edgeUrl);
263
+
264
+ this.ws.on('close', () => {
265
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
266
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempts), 30000);
267
+ setTimeout(() => {
268
+ this.reconnectAttempts++;
269
+ this.connect();
270
+ }, delay);
271
+ }
272
+ });
273
+
274
+ this.ws.on('open', () => {
275
+ this.reconnectAttempts = 0;
276
+ this.register();
277
+ this.startHeartbeat();
278
+ });
279
+ }
280
+ }
281
+ ```
282
+
283
+ ### 2. Edge 处理设备异常
284
+
285
+ ```typescript
286
+ class EdgeDeviceManager {
287
+ handleDeviceError(clientId: string, error: any) {
288
+ console.error(`Device ${clientId} error:`, error);
289
+
290
+ const device = this.devices.get(clientId);
291
+ if (device) {
292
+ // 发送错误消息给设备
293
+ const errorMsg = {
294
+ type: 'error',
295
+ code: 'DEVICE_ERROR',
296
+ message: error.message,
297
+ retryable: true
298
+ };
299
+
300
+ try {
301
+ device.ws.send(JSON.stringify(errorMsg));
302
+ } catch (e) {
303
+ // WebSocket 可能已关闭
304
+ this.cleanupDevice(clientId);
305
+ }
306
+ }
307
+ }
308
+ }
309
+ ```
310
+
311
+ ## 性能优化
312
+
313
+ ### 1. 连接池管理
314
+
315
+ ```typescript
316
+ class EdgeConnectionPool {
317
+ private readonly MAX_CONNECTIONS = 1000;
318
+ private readonly CONNECTION_TIMEOUT = 300000; // 5分钟
319
+
320
+ acceptConnection(ws: WebSocket): boolean {
321
+ if (this.devices.size >= this.MAX_CONNECTIONS) {
322
+ // 拒绝新连接
323
+ ws.close(1008, 'Connection limit exceeded');
324
+ return false;
325
+ }
326
+
327
+ // 清理超时未注册的连接
328
+ this.cleanupStaleConnections();
329
+
330
+ return true;
331
+ }
332
+ }
333
+ ```
334
+
335
+ ### 2. 消息批处理
336
+
337
+ ```typescript
338
+ class EdgeMessageBatcher {
339
+ private messageQueue = new Map<string, any[]>();
340
+ private batchInterval = 100; // 100ms
341
+
342
+ queueMessage(clientId: string, message: any) {
343
+ if (!this.messageQueue.has(clientId)) {
344
+ this.messageQueue.set(clientId, []);
345
+ }
346
+
347
+ this.messageQueue.get(clientId)!.push(message);
348
+
349
+ // 调度批量发送
350
+ this.scheduleBatch(clientId);
351
+ }
352
+
353
+ private scheduleBatch(clientId: string) {
354
+ setTimeout(() => {
355
+ const messages = this.messageQueue.get(clientId);
356
+ if (messages && messages.length > 0) {
357
+ const batchMessage = {
358
+ type: 'batch',
359
+ messages: messages
360
+ };
361
+
362
+ const device = this.devices.get(clientId);
363
+ if (device) {
364
+ device.ws.send(JSON.stringify(batchMessage));
365
+ }
366
+
367
+ this.messageQueue.delete(clientId);
368
+ }
369
+ }, this.batchInterval);
370
+ }
371
+ }
372
+ ```
373
+
374
+ ## 安全考虑
375
+
376
+ ### 1. 设备认证
377
+
378
+ ```typescript
379
+ // Edge 可以要求设备提供认证信息
380
+ interface AuthenticatedRegisterMessage extends RegisterMessage {
381
+ auth?: {
382
+ token?: string;
383
+ apiKey?: string;
384
+ certificate?: string;
385
+ };
386
+ }
387
+
388
+ class SecureEdgeHandler {
389
+ validateDevice(message: AuthenticatedRegisterMessage): boolean {
390
+ if (!message.auth) {
391
+ return false; // 需要认证
392
+ }
393
+
394
+ // 验证 token 或 API key
395
+ return this.authService.validate(message.auth);
396
+ }
397
+ }
398
+ ```
399
+
400
+ ### 2. 消息加密
401
+
402
+ ```typescript
403
+ // 支持加密通信
404
+ class EncryptedDeviceClient {
405
+ sendSecureMessage(message: any) {
406
+ const encrypted = this.encrypt(JSON.stringify(message));
407
+ this.ws.send(encrypted);
408
+ }
409
+
410
+ handleEncryptedMessage(data: any) {
411
+ const decrypted = this.decrypt(data);
412
+ const message = JSON.parse(decrypted);
413
+ this.handleMessage(message);
414
+ }
415
+ }
416
+ ```
417
+
418
+ ## 总结
419
+
420
+ 设备端到 Edge 的通信使用标准 WebSocket 协议,包括:
421
+
422
+ 1. **注册机制** - 设备向 Edge 注册自己的信息和能力
423
+ 2. **心跳保活** - 30秒心跳间隔,90秒超时断开
424
+ 3. **命令执行** - Edge 转发来自 Gateway 的命令
425
+ 4. **状态同步** - Edge 汇总设备状态上报给 Gateway
426
+ 5. **错误处理** - 支持断线重连和异常恢复
427
+ 6. **性能优化** - 连接池管理和消息批处理
428
+ 7. **安全特性** - 可选的认证和加密支持
429
+
430
+ 这种设计确保了设备端与系统其他部分的无缝集成,同时保持了协议的一致性和可扩展性。