@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,726 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 消息验证器 - 提供全面的消息验证功能
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
MessageType,
|
|
7
|
+
ClientType,
|
|
8
|
+
Priority,
|
|
9
|
+
CommandStatus,
|
|
10
|
+
OperationType,
|
|
11
|
+
CommandType,
|
|
12
|
+
ProgressStatus,
|
|
13
|
+
ProgressPhase,
|
|
14
|
+
ReportLevel,
|
|
15
|
+
ProgramType,
|
|
16
|
+
ProgramDirection,
|
|
17
|
+
BaseMessage,
|
|
18
|
+
RegisterMessage,
|
|
19
|
+
CommandMessage,
|
|
20
|
+
ProgramMessage,
|
|
21
|
+
ProgressUpdateMessage,
|
|
22
|
+
HeartbeatMessage,
|
|
23
|
+
ErrorMessage,
|
|
24
|
+
isRegisterMessage,
|
|
25
|
+
isCommandMessage,
|
|
26
|
+
isProgramMessage,
|
|
27
|
+
isProgressUpdateMessage,
|
|
28
|
+
isHeartbeatMessage,
|
|
29
|
+
isErrorMessage,
|
|
30
|
+
PROTOCOL_VERSION
|
|
31
|
+
} from './index';
|
|
32
|
+
|
|
33
|
+
// 验证结果接口
|
|
34
|
+
export interface ValidationResult {
|
|
35
|
+
valid: boolean;
|
|
36
|
+
errors: string[];
|
|
37
|
+
warnings?: string[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 验证模式定义
|
|
41
|
+
interface ValidationSchema {
|
|
42
|
+
required: string[];
|
|
43
|
+
optional?: string[];
|
|
44
|
+
types: Record<string, string>;
|
|
45
|
+
validators?: Record<string, (value: any) => boolean>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 消息验证器类
|
|
50
|
+
*/
|
|
51
|
+
export class MessageValidator {
|
|
52
|
+
/**
|
|
53
|
+
* 通用验证方法,返回详细的验证结果
|
|
54
|
+
*/
|
|
55
|
+
static validate(message: any): ValidationResult {
|
|
56
|
+
const errors: string[] = [];
|
|
57
|
+
const warnings: string[] = [];
|
|
58
|
+
|
|
59
|
+
// 基本结构验证
|
|
60
|
+
if (!message || typeof message !== 'object') {
|
|
61
|
+
errors.push('Message must be an object');
|
|
62
|
+
return { valid: false, errors };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!message.type) {
|
|
66
|
+
errors.push('Message type is required');
|
|
67
|
+
} else if (!this.isValidMessageType(message.type)) {
|
|
68
|
+
errors.push(`Invalid message type: ${message.type}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 根据消息类型进行特定验证
|
|
72
|
+
switch (message.type) {
|
|
73
|
+
case MessageType.REGISTER:
|
|
74
|
+
return this.validateRegisterMessage(message);
|
|
75
|
+
case MessageType.COMMAND:
|
|
76
|
+
return this.validateCommandMessage(message);
|
|
77
|
+
case MessageType.PROGRAM:
|
|
78
|
+
return this.validateProgramMessage(message);
|
|
79
|
+
case MessageType.PROGRESS_UPDATE:
|
|
80
|
+
return this.validateProgressUpdate(message);
|
|
81
|
+
case MessageType.HEARTBEAT:
|
|
82
|
+
return this.validateHeartbeatMessage(message);
|
|
83
|
+
case MessageType.ERROR:
|
|
84
|
+
return this.validateErrorMessage(message);
|
|
85
|
+
default:
|
|
86
|
+
// 其他消息类型的基本验证
|
|
87
|
+
if (!message.timestamp) {
|
|
88
|
+
warnings.push('Timestamp is missing');
|
|
89
|
+
}
|
|
90
|
+
if (!message.version) {
|
|
91
|
+
warnings.push('Version is missing');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
valid: errors.length === 0,
|
|
97
|
+
errors,
|
|
98
|
+
warnings: warnings.length > 0 ? warnings : undefined
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 验证消息基本结构
|
|
104
|
+
*/
|
|
105
|
+
static validateStructure(message: any): boolean {
|
|
106
|
+
if (!message || typeof message !== 'object') return false;
|
|
107
|
+
if (!message.type || typeof message.type !== 'string') return false;
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* 验证命令消息
|
|
113
|
+
*/
|
|
114
|
+
static validateCommandMessage(message: any): ValidationResult {
|
|
115
|
+
const errors: string[] = [];
|
|
116
|
+
const warnings: string[] = [];
|
|
117
|
+
|
|
118
|
+
if (!this.validateStructure(message)) {
|
|
119
|
+
errors.push('Invalid message structure');
|
|
120
|
+
return { valid: false, errors };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!isCommandMessage(message)) {
|
|
124
|
+
errors.push('Not a valid command message');
|
|
125
|
+
return { valid: false, errors };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// 必需字段验证
|
|
129
|
+
if (!message.requestRef) {
|
|
130
|
+
errors.push('requestRef is required');
|
|
131
|
+
}
|
|
132
|
+
if (!message.targetClientId) {
|
|
133
|
+
errors.push('targetClientId is required');
|
|
134
|
+
}
|
|
135
|
+
if (!message.callback) {
|
|
136
|
+
errors.push('callback is required');
|
|
137
|
+
}
|
|
138
|
+
if (!message.priority) {
|
|
139
|
+
errors.push('priority is required');
|
|
140
|
+
} else if (!this.isValidPriority(message.priority)) {
|
|
141
|
+
errors.push(`Invalid priority: ${message.priority}`);
|
|
142
|
+
}
|
|
143
|
+
if (message.timeout === undefined || message.timeout === null) {
|
|
144
|
+
errors.push('timeout is required');
|
|
145
|
+
} else if (typeof message.timeout !== 'number' || message.timeout <= 0) {
|
|
146
|
+
errors.push('timeout must be a positive number');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 命令字段验证
|
|
150
|
+
if (!message.command) {
|
|
151
|
+
errors.push('command is required');
|
|
152
|
+
} else {
|
|
153
|
+
if (!message.command.commandCode) {
|
|
154
|
+
errors.push('command.commandCode is required');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const commandType = message.command.commandType || CommandType.SIMPLE;
|
|
158
|
+
|
|
159
|
+
switch (commandType) {
|
|
160
|
+
case CommandType.SIMPLE:
|
|
161
|
+
case CommandType.BATCH:
|
|
162
|
+
if (!message.command.deviceType) {
|
|
163
|
+
errors.push('command.deviceType is required for SIMPLE/BATCH commands');
|
|
164
|
+
}
|
|
165
|
+
if (!message.command.operationType) {
|
|
166
|
+
errors.push('command.operationType is required for SIMPLE/BATCH commands');
|
|
167
|
+
} else if (!this.isValidOperationType(message.command.operationType)) {
|
|
168
|
+
errors.push(`Invalid operationType: ${message.command.operationType}`);
|
|
169
|
+
}
|
|
170
|
+
if (message.command.deviceId === undefined) {
|
|
171
|
+
errors.push('command.deviceId is required for SIMPLE/BATCH commands');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 命令类型特定验证
|
|
175
|
+
if (commandType === CommandType.SIMPLE) {
|
|
176
|
+
// SIMPLE 命令的 deviceId 必须是单个设备
|
|
177
|
+
if (Array.isArray(message.command.deviceId)) {
|
|
178
|
+
errors.push('SIMPLE command deviceId must be a single value (number or string), not an array');
|
|
179
|
+
}
|
|
180
|
+
} else if (commandType === CommandType.BATCH) {
|
|
181
|
+
// BATCH 命令的 deviceId 必须是数组或范围字符串
|
|
182
|
+
if (!Array.isArray(message.command.deviceId) && typeof message.command.deviceId !== 'string') {
|
|
183
|
+
errors.push('BATCH command deviceId must be an array or string (range expression)');
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
break;
|
|
187
|
+
|
|
188
|
+
case CommandType.COMPLEX:
|
|
189
|
+
// Complex 命令不需要 deviceType, deviceId, operationType
|
|
190
|
+
break;
|
|
191
|
+
|
|
192
|
+
default:
|
|
193
|
+
errors.push(`Invalid commandType: ${commandType}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// 时间戳和版本验证
|
|
198
|
+
if (!message.timestamp) {
|
|
199
|
+
warnings.push('timestamp is missing');
|
|
200
|
+
}
|
|
201
|
+
if (!message.version) {
|
|
202
|
+
warnings.push('version is missing');
|
|
203
|
+
} else if (message.version !== PROTOCOL_VERSION) {
|
|
204
|
+
warnings.push(`Protocol version mismatch: expected ${PROTOCOL_VERSION}, got ${message.version}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
valid: errors.length === 0,
|
|
209
|
+
errors,
|
|
210
|
+
warnings: warnings.length > 0 ? warnings : undefined
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* 验证程序消息
|
|
216
|
+
*/
|
|
217
|
+
static validateProgramMessage(message: any): ValidationResult {
|
|
218
|
+
const errors: string[] = [];
|
|
219
|
+
const warnings: string[] = [];
|
|
220
|
+
|
|
221
|
+
if (!this.validateStructure(message)) {
|
|
222
|
+
errors.push('Invalid message structure');
|
|
223
|
+
return { valid: false, errors };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!isProgramMessage(message)) {
|
|
227
|
+
errors.push('Not a valid program message');
|
|
228
|
+
return { valid: false, errors };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 必需字段验证
|
|
232
|
+
if (!message.requestRef) {
|
|
233
|
+
errors.push('requestRef is required');
|
|
234
|
+
}
|
|
235
|
+
if (!message.targetClientId) {
|
|
236
|
+
errors.push('targetClientId is required');
|
|
237
|
+
}
|
|
238
|
+
if (!message.callback) {
|
|
239
|
+
errors.push('callback is required');
|
|
240
|
+
}
|
|
241
|
+
if (!message.priority) {
|
|
242
|
+
errors.push('priority is required');
|
|
243
|
+
} else if (!this.isValidPriority(message.priority)) {
|
|
244
|
+
errors.push(`Invalid priority: ${message.priority}`);
|
|
245
|
+
}
|
|
246
|
+
if (message.timeout === undefined || message.timeout === null) {
|
|
247
|
+
errors.push('timeout is required');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 命令和参数验证
|
|
251
|
+
if (!message.command) {
|
|
252
|
+
errors.push('command is required');
|
|
253
|
+
} else {
|
|
254
|
+
if (message.command.commandCode !== 'UPLOAD_PROGRAM') {
|
|
255
|
+
errors.push('command.commandCode must be UPLOAD_PROGRAM');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (!message.command.parameters) {
|
|
259
|
+
errors.push('command.parameters is required');
|
|
260
|
+
} else {
|
|
261
|
+
const params = message.command.parameters;
|
|
262
|
+
|
|
263
|
+
// 验证程序参数
|
|
264
|
+
const requiredParams = [
|
|
265
|
+
'deviceId', 'taskId', 'programId', 'programName',
|
|
266
|
+
'programNumber', 'programType', 'width', 'height',
|
|
267
|
+
'direction', 'publishTime', 'unpublishTime',
|
|
268
|
+
'downloadUrl', 'checksum', 'hashAlgorithm', 'fileSize'
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
for (const param of requiredParams) {
|
|
272
|
+
if ((params as any)[param] === undefined || (params as any)[param] === null) {
|
|
273
|
+
errors.push(`command.parameters.${param} is required`);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 验证特定字段的值
|
|
278
|
+
if (params.programType && !this.isValidProgramType(params.programType)) {
|
|
279
|
+
errors.push(`Invalid programType: ${params.programType}`);
|
|
280
|
+
}
|
|
281
|
+
if (params.direction && !this.isValidProgramDirection(params.direction)) {
|
|
282
|
+
errors.push(`Invalid direction: ${params.direction}`);
|
|
283
|
+
}
|
|
284
|
+
if (params.hashAlgorithm && params.hashAlgorithm !== 'SHA256') {
|
|
285
|
+
errors.push('hashAlgorithm must be SHA256');
|
|
286
|
+
}
|
|
287
|
+
if (params.programNumber !== undefined && (params.programNumber < 1 || params.programNumber > 10)) {
|
|
288
|
+
errors.push('programNumber must be between 1 and 10');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
valid: errors.length === 0,
|
|
295
|
+
errors,
|
|
296
|
+
warnings: warnings.length > 0 ? warnings : undefined
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* 验证进度更新消息
|
|
302
|
+
*/
|
|
303
|
+
static validateProgressUpdate(message: any): ValidationResult {
|
|
304
|
+
const errors: string[] = [];
|
|
305
|
+
const warnings: string[] = [];
|
|
306
|
+
|
|
307
|
+
if (!this.validateStructure(message)) {
|
|
308
|
+
errors.push('Invalid message structure');
|
|
309
|
+
return { valid: false, errors };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (!isProgressUpdateMessage(message)) {
|
|
313
|
+
errors.push('Not a valid progress update message');
|
|
314
|
+
return { valid: false, errors };
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// 必需字段验证
|
|
318
|
+
if (!message.requestRef) {
|
|
319
|
+
errors.push('requestRef is required');
|
|
320
|
+
}
|
|
321
|
+
if (!message.status) {
|
|
322
|
+
errors.push('status is required');
|
|
323
|
+
} else if (!this.isValidProgressStatus(message.status)) {
|
|
324
|
+
errors.push(`Invalid status: ${message.status}`);
|
|
325
|
+
}
|
|
326
|
+
if (!message.phase) {
|
|
327
|
+
errors.push('phase is required');
|
|
328
|
+
}
|
|
329
|
+
if (message.progress === undefined || message.progress === null) {
|
|
330
|
+
errors.push('progress is required');
|
|
331
|
+
} else if (message.progress < 0 || message.progress > 100) {
|
|
332
|
+
errors.push('progress must be between 0 and 100');
|
|
333
|
+
}
|
|
334
|
+
if (!message.sourceType) {
|
|
335
|
+
errors.push('sourceType is required');
|
|
336
|
+
} else if (!['COMMAND', 'SYSTEM'].includes(message.sourceType)) {
|
|
337
|
+
errors.push('sourceType must be COMMAND or SYSTEM');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// 可选字段验证
|
|
341
|
+
if (message.report) {
|
|
342
|
+
if (!message.report.level || !this.isValidReportLevel(message.report.level)) {
|
|
343
|
+
errors.push('Invalid report.level');
|
|
344
|
+
}
|
|
345
|
+
if (!message.report.message) {
|
|
346
|
+
errors.push('report.message is required when report is present');
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
valid: errors.length === 0,
|
|
352
|
+
errors,
|
|
353
|
+
warnings: warnings.length > 0 ? warnings : undefined
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* 验证注册消息
|
|
359
|
+
*/
|
|
360
|
+
static validateRegisterMessage(message: any): ValidationResult {
|
|
361
|
+
const errors: string[] = [];
|
|
362
|
+
const warnings: string[] = [];
|
|
363
|
+
|
|
364
|
+
if (!this.validateStructure(message)) {
|
|
365
|
+
errors.push('Invalid message structure');
|
|
366
|
+
return { valid: false, errors };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
if (!isRegisterMessage(message)) {
|
|
370
|
+
errors.push('Not a valid register message');
|
|
371
|
+
return { valid: false, errors };
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// 必需字段验证
|
|
375
|
+
if (!message.clientId) {
|
|
376
|
+
errors.push('clientId is required');
|
|
377
|
+
}
|
|
378
|
+
if (!message.clientType) {
|
|
379
|
+
errors.push('clientType is required');
|
|
380
|
+
} else if (!this.isValidClientType(message.clientType)) {
|
|
381
|
+
errors.push(`Invalid clientType: ${message.clientType}`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// 可选字段验证
|
|
385
|
+
if (message.clientInfo) {
|
|
386
|
+
if (message.clientInfo.capabilities && !Array.isArray(message.clientInfo.capabilities)) {
|
|
387
|
+
errors.push('clientInfo.capabilities must be an array');
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (message.edgeInfo) {
|
|
392
|
+
if (!message.edgeInfo.edgeId) {
|
|
393
|
+
errors.push('edgeInfo.edgeId is required when edgeInfo is present');
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
valid: errors.length === 0,
|
|
399
|
+
errors,
|
|
400
|
+
warnings: warnings.length > 0 ? warnings : undefined
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* 验证心跳消息
|
|
406
|
+
*/
|
|
407
|
+
static validateHeartbeatMessage(message: any): ValidationResult {
|
|
408
|
+
const errors: string[] = [];
|
|
409
|
+
const warnings: string[] = [];
|
|
410
|
+
|
|
411
|
+
if (!this.validateStructure(message)) {
|
|
412
|
+
errors.push('Invalid message structure');
|
|
413
|
+
return { valid: false, errors };
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (!isHeartbeatMessage(message)) {
|
|
417
|
+
errors.push('Not a valid heartbeat message');
|
|
418
|
+
return { valid: false, errors };
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (!message.clientId) {
|
|
422
|
+
errors.push('clientId is required');
|
|
423
|
+
}
|
|
424
|
+
if (message.sequence === undefined || message.sequence === null) {
|
|
425
|
+
errors.push('sequence is required');
|
|
426
|
+
}
|
|
427
|
+
if (!message.clientTime) {
|
|
428
|
+
errors.push('clientTime is required');
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return {
|
|
432
|
+
valid: errors.length === 0,
|
|
433
|
+
errors,
|
|
434
|
+
warnings: warnings.length > 0 ? warnings : undefined
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* 验证错误消息
|
|
440
|
+
*/
|
|
441
|
+
static validateErrorMessage(message: any): ValidationResult {
|
|
442
|
+
const errors: string[] = [];
|
|
443
|
+
const warnings: string[] = [];
|
|
444
|
+
|
|
445
|
+
if (!this.validateStructure(message)) {
|
|
446
|
+
errors.push('Invalid message structure');
|
|
447
|
+
return { valid: false, errors };
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (!isErrorMessage(message)) {
|
|
451
|
+
errors.push('Not a valid error message');
|
|
452
|
+
return { valid: false, errors };
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (!message.code) {
|
|
456
|
+
errors.push('code is required');
|
|
457
|
+
}
|
|
458
|
+
if (!message.message) {
|
|
459
|
+
errors.push('message is required');
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
valid: errors.length === 0,
|
|
464
|
+
errors,
|
|
465
|
+
warnings: warnings.length > 0 ? warnings : undefined
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// 字段验证辅助方法
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* 验证消息类型
|
|
473
|
+
*/
|
|
474
|
+
static isValidMessageType(type: string): boolean {
|
|
475
|
+
return Object.values(MessageType).includes(type as MessageType);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* 验证客户端类型
|
|
480
|
+
*/
|
|
481
|
+
static isValidClientType(clientType: string): boolean {
|
|
482
|
+
return Object.values(ClientType).includes(clientType as ClientType);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* 验证优先级
|
|
487
|
+
*/
|
|
488
|
+
static isValidPriority(priority: string): boolean {
|
|
489
|
+
return Object.values(Priority).includes(priority as Priority);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* 验证命令状态
|
|
494
|
+
*/
|
|
495
|
+
static isValidCommandStatus(status: string): boolean {
|
|
496
|
+
return Object.values(CommandStatus).includes(status as CommandStatus);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* 验证操作类型
|
|
501
|
+
*/
|
|
502
|
+
static isValidOperationType(operationType: string): boolean {
|
|
503
|
+
return Object.values(OperationType).includes(operationType as OperationType);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* 验证命令类型
|
|
508
|
+
*/
|
|
509
|
+
static isValidCommandType(commandType: string): boolean {
|
|
510
|
+
return Object.values(CommandType).includes(commandType as CommandType);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* 验证进度状态
|
|
515
|
+
*/
|
|
516
|
+
static isValidProgressStatus(status: string): boolean {
|
|
517
|
+
return Object.values(ProgressStatus).includes(status as ProgressStatus);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* 验证进度阶段
|
|
522
|
+
*/
|
|
523
|
+
static isValidProgressPhase(phase: string): boolean {
|
|
524
|
+
return Object.values(ProgressPhase).includes(phase as ProgressPhase);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/**
|
|
528
|
+
* 验证报告级别
|
|
529
|
+
*/
|
|
530
|
+
static isValidReportLevel(level: string): boolean {
|
|
531
|
+
return Object.values(ReportLevel).includes(level as ReportLevel);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* 验证程序类型
|
|
536
|
+
*/
|
|
537
|
+
static isValidProgramType(type: string): boolean {
|
|
538
|
+
return Object.values(ProgramType).includes(type as ProgramType);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* 验证程序方向
|
|
543
|
+
*/
|
|
544
|
+
static isValidProgramDirection(direction: string): boolean {
|
|
545
|
+
return Object.values(ProgramDirection).includes(direction as ProgramDirection);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* 获取验证模式
|
|
550
|
+
*/
|
|
551
|
+
static getValidationSchema(messageType: MessageType): ValidationSchema | null {
|
|
552
|
+
const schemas: Record<MessageType, ValidationSchema> = {
|
|
553
|
+
[MessageType.REGISTER]: {
|
|
554
|
+
required: ['type', 'clientId', 'clientType', 'timestamp', 'version'],
|
|
555
|
+
optional: ['clientInfo', 'edgeInfo'],
|
|
556
|
+
types: {
|
|
557
|
+
type: 'string',
|
|
558
|
+
clientId: 'string',
|
|
559
|
+
clientType: 'string',
|
|
560
|
+
timestamp: 'string',
|
|
561
|
+
version: 'string',
|
|
562
|
+
clientInfo: 'object',
|
|
563
|
+
edgeInfo: 'object'
|
|
564
|
+
}
|
|
565
|
+
},
|
|
566
|
+
[MessageType.REGISTER_ACK]: {
|
|
567
|
+
required: ['type', 'clientId', 'success', 'timestamp', 'version'],
|
|
568
|
+
optional: ['sessionId', 'error', 'serverInfo'],
|
|
569
|
+
types: {
|
|
570
|
+
type: 'string',
|
|
571
|
+
clientId: 'string',
|
|
572
|
+
success: 'boolean',
|
|
573
|
+
sessionId: 'string',
|
|
574
|
+
error: 'object',
|
|
575
|
+
serverInfo: 'object',
|
|
576
|
+
timestamp: 'string',
|
|
577
|
+
version: 'string'
|
|
578
|
+
}
|
|
579
|
+
},
|
|
580
|
+
[MessageType.COMMAND]: {
|
|
581
|
+
required: ['type', 'requestRef', 'targetClientId', 'command', 'priority', 'timeout', 'callback', 'timestamp', 'version'],
|
|
582
|
+
optional: ['retryCount', 'metadata'],
|
|
583
|
+
types: {
|
|
584
|
+
type: 'string',
|
|
585
|
+
requestRef: 'string',
|
|
586
|
+
targetClientId: 'string',
|
|
587
|
+
command: 'object',
|
|
588
|
+
priority: 'string',
|
|
589
|
+
timeout: 'number',
|
|
590
|
+
callback: 'string',
|
|
591
|
+
retryCount: 'number',
|
|
592
|
+
metadata: 'object',
|
|
593
|
+
timestamp: 'string',
|
|
594
|
+
version: 'string'
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
[MessageType.COMMAND_RESPONSE]: {
|
|
598
|
+
required: ['type', 'requestRef', 'status', 'timestamp', 'version'],
|
|
599
|
+
optional: ['result', 'report', 'executionTime'],
|
|
600
|
+
types: {
|
|
601
|
+
type: 'string',
|
|
602
|
+
requestRef: 'string',
|
|
603
|
+
status: 'string',
|
|
604
|
+
result: 'object',
|
|
605
|
+
report: 'object',
|
|
606
|
+
executionTime: 'number',
|
|
607
|
+
timestamp: 'string',
|
|
608
|
+
version: 'string'
|
|
609
|
+
}
|
|
610
|
+
},
|
|
611
|
+
[MessageType.PROGRAM]: {
|
|
612
|
+
required: ['type', 'requestRef', 'targetClientId', 'command', 'priority', 'timeout', 'callback', 'timestamp', 'version'],
|
|
613
|
+
optional: [],
|
|
614
|
+
types: {
|
|
615
|
+
type: 'string',
|
|
616
|
+
requestRef: 'string',
|
|
617
|
+
targetClientId: 'string',
|
|
618
|
+
command: 'object',
|
|
619
|
+
priority: 'string',
|
|
620
|
+
timeout: 'number',
|
|
621
|
+
callback: 'string',
|
|
622
|
+
timestamp: 'string',
|
|
623
|
+
version: 'string'
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
[MessageType.PROGRAM_RESPONSE]: {
|
|
627
|
+
required: ['type', 'requestRef', 'status', 'timestamp', 'version'],
|
|
628
|
+
optional: ['context', 'report', 'executionTime'],
|
|
629
|
+
types: {
|
|
630
|
+
type: 'string',
|
|
631
|
+
requestRef: 'string',
|
|
632
|
+
status: 'string',
|
|
633
|
+
context: 'object',
|
|
634
|
+
report: 'object',
|
|
635
|
+
executionTime: 'number',
|
|
636
|
+
timestamp: 'string',
|
|
637
|
+
version: 'string'
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
[MessageType.HEARTBEAT]: {
|
|
641
|
+
required: ['type', 'clientId', 'sequence', 'clientTime', 'timestamp', 'version'],
|
|
642
|
+
optional: [],
|
|
643
|
+
types: {
|
|
644
|
+
type: 'string',
|
|
645
|
+
clientId: 'string',
|
|
646
|
+
sequence: 'number',
|
|
647
|
+
clientTime: 'string',
|
|
648
|
+
timestamp: 'string',
|
|
649
|
+
version: 'string'
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
[MessageType.HEARTBEAT_ACK]: {
|
|
653
|
+
required: ['type', 'clientId', 'sequence', 'clientTime', 'serverTime', 'timestamp', 'version'],
|
|
654
|
+
optional: ['latency', 'serverStatus'],
|
|
655
|
+
types: {
|
|
656
|
+
type: 'string',
|
|
657
|
+
clientId: 'string',
|
|
658
|
+
sequence: 'number',
|
|
659
|
+
clientTime: 'string',
|
|
660
|
+
serverTime: 'string',
|
|
661
|
+
latency: 'number',
|
|
662
|
+
serverStatus: 'object',
|
|
663
|
+
timestamp: 'string',
|
|
664
|
+
version: 'string'
|
|
665
|
+
}
|
|
666
|
+
},
|
|
667
|
+
[MessageType.PROGRESS_UPDATE]: {
|
|
668
|
+
required: ['type', 'requestRef', 'status', 'phase', 'progress', 'sourceType', 'timestamp', 'version'],
|
|
669
|
+
optional: ['context', 'command', 'report'],
|
|
670
|
+
types: {
|
|
671
|
+
type: 'string',
|
|
672
|
+
requestRef: 'string',
|
|
673
|
+
status: 'string',
|
|
674
|
+
phase: 'string',
|
|
675
|
+
progress: 'number',
|
|
676
|
+
sourceType: 'string',
|
|
677
|
+
context: 'object',
|
|
678
|
+
command: 'object',
|
|
679
|
+
report: 'object',
|
|
680
|
+
timestamp: 'string',
|
|
681
|
+
version: 'string'
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
[MessageType.ERROR]: {
|
|
685
|
+
required: ['type', 'code', 'message', 'timestamp', 'version'],
|
|
686
|
+
optional: ['severity', 'category', 'context', 'retryable'],
|
|
687
|
+
types: {
|
|
688
|
+
type: 'string',
|
|
689
|
+
code: 'string',
|
|
690
|
+
message: 'string',
|
|
691
|
+
severity: 'string',
|
|
692
|
+
category: 'string',
|
|
693
|
+
context: 'any',
|
|
694
|
+
retryable: 'boolean',
|
|
695
|
+
timestamp: 'string',
|
|
696
|
+
version: 'string'
|
|
697
|
+
}
|
|
698
|
+
},
|
|
699
|
+
[MessageType.UNREGISTER]: {
|
|
700
|
+
required: ['type', 'clientId', 'timestamp', 'version'],
|
|
701
|
+
optional: ['reason'],
|
|
702
|
+
types: {
|
|
703
|
+
type: 'string',
|
|
704
|
+
clientId: 'string',
|
|
705
|
+
reason: 'string',
|
|
706
|
+
timestamp: 'string',
|
|
707
|
+
version: 'string'
|
|
708
|
+
}
|
|
709
|
+
},
|
|
710
|
+
[MessageType.UNREGISTER_ACK]: {
|
|
711
|
+
required: ['type', 'clientId', 'success', 'timestamp', 'version'],
|
|
712
|
+
optional: ['cleanupInfo'],
|
|
713
|
+
types: {
|
|
714
|
+
type: 'string',
|
|
715
|
+
clientId: 'string',
|
|
716
|
+
success: 'boolean',
|
|
717
|
+
cleanupInfo: 'object',
|
|
718
|
+
timestamp: 'string',
|
|
719
|
+
version: 'string'
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
|
|
724
|
+
return schemas[messageType] || null;
|
|
725
|
+
}
|
|
726
|
+
}
|