mm_os 3.2.7 → 3.2.9

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.
@@ -10,9 +10,6 @@ class MQTT {
10
10
  * @param {Object} config 配置参数
11
11
  */
12
12
  constructor(config) {
13
- /**
14
- * 配置参数
15
- */
16
13
  this.config = Object.assign({
17
14
  "state": true,
18
15
  // mqtt访问端口号
@@ -23,6 +20,34 @@ class MQTT {
23
20
  "http_port": 8083,
24
21
  // 缓存方式
25
22
  "cache": "mongodb", // "mongodb",
23
+ // 消息保留时长(秒)
24
+ "message_retention": 86400,
25
+ // 消息频率限制配置
26
+ "rate_limit": {
27
+ // 时间窗口(秒)
28
+ "time_window": 60, // 默认1分钟
29
+ // 时间窗口内允许的最大消息数
30
+ "max_messages": 100, // 人脸识别门禁优化:1分钟内最多20条消息(门禁通常每次识别仅发送1-3条消息)
31
+ // 拉黑时长(秒)
32
+ "block_duration": 3600 // 人脸识别门禁优化:拉黑30分钟(更合理的临时限制时长)
33
+ },
34
+ // 安全配置 - 扁平化且共用参数
35
+ // 通用安全配置
36
+ "time_window": 60, // 时间窗口(秒)- 共用参数
37
+ "max_attempts": 5, // 最大尝试次数 - 共用参数
38
+ "block_duration": 3600, // 默认拉黑时长(秒)- 共用参数
39
+ // 重连检测特定配置
40
+ "reconnect_ip_duration": 3600, // 同一客户端ID不同IP的禁止时间(秒)
41
+ "reconnect_client_duration": 7200, // 客户端ID禁止重连时间(秒)
42
+ "reconnect_frequency_threshold": 30, // 频繁重连时间阈值(秒)
43
+ // 密码尝试特定配置
44
+ "password_block_ip": true, // 密码错误时是否同时拉黑IP
45
+ // 受限主题列表,用于限制某些敏感主题只能由特定客户端订阅
46
+ restrictedTopics: [],
47
+ // 允许订阅受限主题的客户端列表
48
+ allowedClients: [],
49
+ // 敏感主题列表,用于限制某些敏感主题只能由管理员发布
50
+ sensitiveTopics: [],
26
51
  redis: {
27
52
  host: "localhost",
28
53
  port: 6379,
@@ -44,24 +69,82 @@ class MQTT {
44
69
  this.server = null;
45
70
 
46
71
  this.list = [];
72
+
73
+ /**
74
+ * 客户端消息计数器
75
+ * 格式: { clientId: { count: 消息数, lastReset: 上次重置时间戳 } }
76
+ */
77
+ this.messageCounters = {};
78
+
79
+ /**
80
+ * 拉黑的客户端列表
81
+ * 格式: { clientId: 解除拉黑时间戳 }
82
+ */
83
+ this.blockedClients = {};
84
+
85
+ /**
86
+ * 被禁止的IP列表
87
+ * 格式: { ip: 解除禁止时间戳 }
88
+ */
89
+ this.bannedIps = {};
90
+
91
+ /**
92
+ * 安全记录(合并客户端连接历史和密码错误记录)
93
+ * 格式: {
94
+ * client: { clientId: { ip: 客户端IP, connectCount: 连接次数, lastConnectTime: 上次连接时间, disconnectTime: 上次断开时间, reconnectAttempts: 重连尝试次数 } },
95
+ * password: { username: { count: 错误次数, firstAttempt: 首次尝试时间, lastAttempt: 上次尝试时间, ip: 尝试IP } }
96
+ * }
97
+ */
98
+ this.securityRecords = {
99
+ client: {},
100
+ password: {}
101
+ };
102
+
103
+ /**
104
+ * 黑名单存储类型
105
+ * 可选值: 'memory', 'redis', 'mongodb', 'mysql'
106
+ */
107
+ this.blacklistStorageType = this.config.blacklistStorageType || 'memory';
47
108
  }
48
109
  }
49
110
 
50
111
  /**
51
- * 客户端连接成功
112
+ * 异步客户端连接成功
52
113
  * @param {Object} client 客户端信息
114
+ * @returns {Promise<Boolean>} true表示连接成功,false表示连接被拒绝
53
115
  */
54
- MQTT.prototype.connected = async function(client) {
55
- //监听连接
56
- console.info('client connected', client.id);
57
- return true;
116
+ MQTT.prototype.connected = async function (client) {
117
+ try {
118
+ // 检测是否有异常重连行为 - 现在是异步函数
119
+ const hasReconnectAbuse = await this.detectReconnectAbuse(client);
120
+ if (hasReconnectAbuse) {
121
+ $.log.warn(`拒绝客户端 ${client.id} 连接:检测到异常重连行为`);
122
+ // 断开客户端连接
123
+ if (client && client.close) {
124
+ try {
125
+ client.close();
126
+ } catch (error) {
127
+ $.log.error(`断开异常重连客户端 ${client.id} 连接时出错:`, error);
128
+ }
129
+ }
130
+ return false;
131
+ }
132
+
133
+ // 正常连接
134
+ $.log.info('client connected', client.id);
135
+ return true;
136
+ } catch (error) {
137
+ $.log.error(`处理客户端 ${client.id} 连接时出错:`, error);
138
+ // 出错时默认拒绝连接
139
+ return false;
140
+ }
58
141
  }
59
142
 
60
143
  /**
61
144
  * 初始化
62
145
  * @param {Object} config
63
146
  */
64
- MQTT.prototype.init = function(config) {
147
+ MQTT.prototype.init = function (config) {
65
148
  if (config) {
66
149
  this.config = Object.assign(this.config, config);
67
150
  }
@@ -105,17 +188,17 @@ MQTT.prototype.init = function(config) {
105
188
  conf.persistence = Object.assign({
106
189
  factory: mosca.persistence.Redis,
107
190
  ttl: {
108
- // TTL for subscriptions is 23 hour
109
- subscriptions: 23 * 60 * 60 * 1000,
110
- // TTL for packets is 23 hour
111
- packets: 23 * 60 * 60 * 1000,
191
+ // TTL for subscriptions
192
+ subscriptions: cg.message_retention * 1000, // 恢复为毫秒
193
+ // TTL for packets
194
+ packets: cg.message_retention * 1000, // 恢复为毫秒
112
195
  }
113
196
  }, conf.backend);
114
197
  } else if (cg.cache === 'mongodb' || cg.cache === 'mongo') {
115
198
  var url = `mongodb://${mongodb.host}:${mongodb.port}/${mongodb.database}`;
116
199
  if (mongodb.user) {
117
200
  url =
118
- `mongodb://${mongodb.user}:${mongodb.password}@${mongodb.host}:${mongodb.port}/${mongodb.database}`
201
+ `mongodb://${mongodb.user}:${mongodb.password}@${mongodb.host}:${mongodb.port}/${mongodb.database}`;
119
202
  }
120
203
  conf.backend = {
121
204
  // 增加了此项
@@ -123,16 +206,16 @@ MQTT.prototype.init = function(config) {
123
206
  url,
124
207
  pubsubCollection: 'ascoltatori',
125
208
  mongo: {}
126
- }
209
+ };
127
210
 
128
211
  // 数据持久化参数设置
129
212
  conf.persistence = Object.assign({
130
213
  factory: mosca.persistence.Mongo,
131
214
  ttl: {
132
- // TTL for subscriptions is 23 hour
133
- subscriptions: 23 * 60 * 60 * 1000,
134
- // TTL for packets is 23 hour
135
- packets: 23 * 60 * 60 * 1000,
215
+ // TTL for subscriptions
216
+ subscriptions: cg.message_retention * 1000, // 恢复为毫秒
217
+ // TTL for packets
218
+ packets: cg.message_retention * 1000, // 恢复为毫秒
136
219
  }
137
220
  }, conf.backend);
138
221
  }
@@ -144,7 +227,7 @@ MQTT.prototype.init = function(config) {
144
227
  * 引用
145
228
  * @param {Function} 函数
146
229
  */
147
- MQTT.prototype.use = function(func) {
230
+ MQTT.prototype.use = function (func) {
148
231
  // this.server.use(func);
149
232
  };
150
233
 
@@ -152,7 +235,7 @@ MQTT.prototype.use = function(func) {
152
235
  * 运行主程序
153
236
  * @param {String} state 状态
154
237
  */
155
- MQTT.prototype.main = function(state) {
238
+ MQTT.prototype.main = function (state) {
156
239
  var cg = this.config;
157
240
  var sr = this.server;
158
241
 
@@ -162,16 +245,34 @@ MQTT.prototype.main = function(state) {
162
245
  * 对服务器端口进行配置,在此端口进行监听
163
246
  * @param {Object} client 客户端信息
164
247
  */
165
- sr.on('clientConnected', async function(client) {
248
+ sr.on('clientConnected', async function (client) {
166
249
  return await _this.connected(client);
167
250
  });
168
-
251
+
169
252
  /**
170
253
  * 监听MQTT主题消息
171
254
  * @param {Object} packet 订阅消息
172
255
  * @param {Object} client 客户端信息
173
256
  */
174
- sr.on('published', function(packet, client) {
257
+ sr.on('published', async function (packet, client) {
258
+ // 检查客户端是否高频请求
259
+ if (client) {
260
+ try {
261
+ const isHighFrequency = await _this.checkHighFrequency(client);
262
+ if (isHighFrequency) {
263
+ // 高频请求,拉黑并断开连接
264
+ await _this.blockClient(client);
265
+ return;
266
+ }
267
+ } catch (error) {
268
+ $.log.error(`检查客户端高频请求时出错:`, error);
269
+ // 发生错误时,默认拒绝请求
270
+ if (client && client.close) {
271
+ client.close();
272
+ }
273
+ return;
274
+ }
275
+ }
175
276
  _this.published(packet, client);
176
277
  });
177
278
 
@@ -180,7 +281,7 @@ MQTT.prototype.main = function(state) {
180
281
  * @param {String} topic 订阅主题
181
282
  * @param {Object} client 客户端信息
182
283
  */
183
- sr.on('subscribed', function(topic, client) {
284
+ sr.on('subscribed', function (topic, client) {
184
285
  _this.subscribed(topic, client);
185
286
  });
186
287
 
@@ -188,35 +289,56 @@ MQTT.prototype.main = function(state) {
188
289
  * 身份验证
189
290
  */
190
291
  sr.authenticate = (client, username, password, callback) => {
191
- $.log.info("收到身份验证", username, password);
192
- this.auth(client, username, password, callback);
292
+ // 正确获取客户端IP地址
293
+ const clientIp = client.connection && client.connection.stream ? client.connection.stream.remoteAddress || 'unknown' : 'unknown';
294
+ $.log.info("收到身份验证", clientIp, client.id, username);
295
+ // 因为auth现在是异步函数,需要使用async/await处理
296
+ (async () => {
297
+ try {
298
+ // 调用异步的auth函数,但仍然使用callback返回结果
299
+ // auth函数内部已经处理了callback调用
300
+ await this.auth(client, username, password, callback);
301
+ } catch (error) {
302
+ $.log.error(`身份验证过程发生异常:`, error);
303
+ callback(null, false);
304
+ }
305
+ })();
306
+ // 返回true表示继续处理
193
307
  return true;
194
308
  };
195
-
309
+
196
310
  /**
197
311
  * 验证发布,决定客户端可以发布哪些主题
198
312
  */
199
313
  sr.authorizePublish = (client, topic, payload, callback) => {
200
- this.authPublish(client, topic, payload, callback);
314
+ const isAllowed = this.authPublish(client, topic, payload);
315
+ callback(null, isAllowed);
201
316
  return true;
202
317
  };
203
-
204
-
318
+
205
319
  /**
206
320
  * 验证订阅,决定客户端可以订阅哪些主题
207
321
  */
208
322
  sr.authorizeSubscribe = (client, topic, callback) => {
209
- this.authSubscribe(client, topic, callback);
323
+ const isAllowed = this.authSubscribe(client, topic);
324
+ callback(null, isAllowed);
210
325
  return true;
211
326
  };
212
-
213
-
327
+
328
+
214
329
  /**
215
330
  * 当服务开启时
216
331
  */
217
- sr.on('ready', function() {
332
+ sr.on('ready', function () {
218
333
  // 当服务开启时
219
- console.info(`MQTT访问 mqtt://127.0.0.1:${cg.socket_port} || ws://127.0.0.1:${cg.http_port}`);
334
+ $.log.info(`MQTT访问 mqtt://127.0.0.1:${cg.socket_port} || ws://127.0.0.1:${cg.http_port}`);
335
+ });
336
+
337
+ /**
338
+ * 监听客户端断开连接
339
+ */
340
+ sr.on('clientDisconnected', async function (client) {
341
+ _this.clientDisconnected(client);
220
342
  });
221
343
  }
222
344
 
@@ -225,165 +347,259 @@ MQTT.prototype.main = function(state) {
225
347
  * @param {String} topic 订阅主题
226
348
  * @param {Object} client 客户端信息
227
349
  */
228
- MQTT.prototype.subscribed = function(topic, client) {
350
+ MQTT.prototype.subscribed = function (topic, client) {
229
351
  // console.log("订阅", topic, client.id);
230
352
  };
231
353
 
354
+ /**
355
+ * 客户端断开连接时的处理
356
+ * @param {Object} client 客户端对象
357
+ */
358
+ MQTT.prototype.clientDisconnected = function (client) {
359
+ const clientId = client.id;
360
+ const now = Date.now();
361
+
362
+ $.log.info(`MQTT客户端 ${clientId} 断开连接`);
363
+
364
+ // 更新客户端断开连接时间
365
+ if (this.securityRecords.client[clientId]) {
366
+ this.securityRecords.client[clientId].disconnectTime = now;
367
+ }
368
+ };
369
+
232
370
  /**
233
371
  * 收到推送消息时
234
372
  * @param {Object} packet 订阅的消息
235
373
  * @param {Object} client 客户端信息
236
374
  */
237
- MQTT.prototype.published = function(packet, client) {
375
+ MQTT.prototype.published = function (packet, client) {
238
376
  // console.log("发布", packet.topic, packet.payload.toString());
239
377
  };
240
378
 
241
379
  /**
242
- * 身份验证 - 使用MySQL数据库验证
380
+ * 身份验证 - 主要入口函数
381
+ * @param {Object} client 客户端
382
+ * @param {String} username 用户名
383
+ * @param {String} password 密码
384
+ * @param {Function} callback 回调函数,回调返回true,则表示验证通过。
385
+ */
386
+ MQTT.prototype.auth = async function (client, username, password, callback) {
387
+ try {
388
+ // 检查客户端状态(拉黑、异常重连等)- 现在是异步操作
389
+ const clientStatusResult = await this.checkClientStatus(client);
390
+ if (clientStatusResult === false) {
391
+ callback(null, false);
392
+ return;
393
+ }
394
+
395
+ // 密码可能是Buffer类型,需要转换为字符串
396
+ const passwordStr = password ? password.toString() : '';
397
+
398
+ // 验证凭证格式
399
+ const credentialsResult = this.validateCredentials(client, username, passwordStr);
400
+ if (credentialsResult === false) {
401
+ callback(null, false);
402
+ return;
403
+ }
404
+
405
+ // 检查密码错误次数限制(防暴力破解)
406
+ const passwordLimitResult = await this.checkPasswordAttemptLimit(client, username);
407
+ if (passwordLimitResult === false) {
408
+ callback(null, false);
409
+ return;
410
+ }
411
+
412
+ // 查询用户密码并验证(异步操作,需要callback)
413
+ this.checkAccount(client, username, passwordStr, callback);
414
+ } catch (error) {
415
+ $.log.error(`MQTT客户端 ${client.id} 身份验证过程中出错:`, error);
416
+ callback(null, false);
417
+ }
418
+ };
419
+
420
+ /**
421
+ * 异步检查客户端状态(拉黑、异常重连等)
422
+ * @param {Object} client 客户端
423
+ * @returns {Promise<Boolean>} true表示客户端状态正常,false表示客户端状态异常
424
+ */
425
+ MQTT.prototype.checkClientStatus = async function (client) {
426
+ try {
427
+ // 检查客户端是否被拉黑(使用新的异步函数)
428
+ const isBlocked = await this.isClientBlocked(client.id);
429
+ if (isBlocked) {
430
+ $.log.warn(`MQTT客户端 ${client.id} 处于拉黑状态,拒绝身份验证`);
431
+ return false;
432
+ }
433
+
434
+ // 检测是否有异常重连行为
435
+ // 注意:detectReconnectAbuse内部也有黑名单相关操作,后续可以考虑重构为使用异步函数
436
+ const hasReconnectAbuse = await this.detectReconnectAbuse(client);
437
+ if (hasReconnectAbuse) {
438
+ $.log.warn(`拒绝客户端 ${client.id} 身份验证:检测到异常重连行为`);
439
+ return false;
440
+ }
441
+
442
+ return true;
443
+ } catch (error) {
444
+ $.log.error(`检查客户端 ${client.id} 状态时出错:`, error);
445
+ // 出错时默认视为客户端状态异常
446
+ return false;
447
+ }
448
+ };
449
+
450
+ /**
451
+ * 验证凭证格式
243
452
  * @param {Object} client 客户端
244
453
  * @param {String} username 用户名
245
- * @param {String} password 密码
246
- * @param {Function} callback 回调函数,回调返回true,则表示验证通过。
454
+ * @param {String} passwordStr 密码字符串
455
+ * @returns {Boolean} true表示凭证格式有效,false表示凭证格式无效
247
456
  */
248
- MQTT.prototype.auth = function(client, username, password, callback) {
249
- // 密码可能是Buffer类型,需要转换为字符串
250
- const passwordStr = password ? password.toString() : '';
251
-
457
+ MQTT.prototype.validateCredentials = function (client, username, passwordStr) {
252
458
  // 检查必要参数
253
459
  if (!username || !passwordStr) {
254
- console.warn(`MQTT客户端 ${client.id} 身份验证失败:用户名或密码为空`);
255
- callback(null, false);
256
- return;
460
+ $.log.warn(`MQTT客户端 ${client.id} 身份验证失败:用户名或密码为空`);
461
+ return false;
257
462
  }
258
-
463
+
259
464
  // 检查是否有可用的MySQL连接
260
- if (!$.sql || typeof $.sql.query !== 'function') {
261
- console.error(`MQTT客户端 ${client.id} 身份验证错误:MySQL连接不可用`);
262
- callback(null, false);
465
+ if (!$.mysql_admin) {
466
+ $.log.error(`MQTT客户端 ${client.id} 身份验证错误:MySQL连接不可用`);
467
+ return false;
468
+ }
469
+
470
+ return true;
471
+ };
472
+
473
+ /**
474
+ * 查询用户密码并验证
475
+ * @param {Object} client 客户端
476
+ * @param {String} username 用户名
477
+ * @param {String} password 密码字符串
478
+ * @param {Function} callback 回调函数
479
+ */
480
+ MQTT.prototype.checkAccount = async function (client, username, password, callback) {
481
+ if (!username || !password) {
263
482
  return;
264
483
  }
265
-
266
- // 使用参数化查询以防止SQL注入
267
- const sql = `SELECT password FROM mqtt_users WHERE username = ? LIMIT 1`;
268
- const params = [username];
269
-
270
- // 执行查询验证用户
271
- $.sql.query(sql, params, (err, results) => {
272
- if (err) {
273
- console.error(`MQTT客户端 ${client.id} 身份验证数据库错误:`, err);
274
- callback(null, false);
275
- return;
276
- }
277
-
278
- // 检查用户是否存在
279
- if (!results || results.length === 0) {
280
- console.warn(`MQTT客户端 ${client.id} 身份验证失败:用户不存在,用户名: ${username}`);
281
- callback(null, false);
282
- return;
283
- }
284
-
285
- // 获取数据库中的密码
286
- const dbPassword = results[0].password;
287
-
288
- // 验证密码
289
- // 注意:在实际应用中,应该使用bcrypt等密码哈希算法进行验证,而不是明文比较
290
- // 如果数据库中存储的是哈希密码,需要使用相应的哈希算法进行验证
291
- try {
292
- // 尝试使用bcrypt验证(如果已安装bcrypt模块)
293
- const bcrypt = require('bcrypt');
294
- const isMatch = bcrypt.compareSync(passwordStr, dbPassword);
295
-
296
- if (isMatch) {
297
- console.info(`MQTT客户端 ${client.id} 身份验证成功,用户: ${username}`);
298
- } else {
299
- console.warn(`MQTT客户端 ${client.id} 身份验证失败:密码不匹配,用户名: ${username}`);
484
+ var sql = $.mysql_admin('sys');
485
+ try {
486
+ var dbs = sql.db();
487
+ // 先尝试通过客户端ID和用户名精确匹配
488
+
489
+ // 判断设备连接
490
+ var db1 = dbs.new(this.config.table_name || 'face_device', "");
491
+ var obj = await db1.getObj({
492
+ username,
493
+ clientid: client.id
494
+ });
495
+ if (obj) {
496
+ if (obj.available && obj.state) {
497
+ if (obj.password == password) {
498
+ // 账号密码正确
499
+ return callback(null, true);
500
+ }
501
+ else {
502
+ $.log.warn(`MQTT客户端 ${client.id} 身份验证失败:密码错误,用户名: ${username}`);
503
+ // 记录密码错误尝试
504
+ await this.recordPasswordFailure(client, username);
505
+ }
300
506
  }
301
-
302
- callback(null, isMatch);
303
- } catch (err) {
304
- // 如果bcrypt不可用,回退到简单的密码比较(不推荐在生产环境中使用)
305
- console.warn('bcrypt模块不可用,使用简单密码比较');
306
- const isMatch = dbPassword === passwordStr;
307
-
308
- if (isMatch) {
309
- console.info(`MQTT客户端 ${client.id} 身份验证成功,用户: ${username}`);
310
- } else {
311
- console.warn(`MQTT客户端 ${client.id} 身份验证失败:密码不匹配,用户名: ${username}`);
507
+ else {
508
+ $.log.warn(`MQTT客户端 ${client.id} 身份验证失败:账户不可用,用户名: ${username}`);
509
+ }
510
+ } else {
511
+ // 判断应用连接
512
+ var db2 = dbs.new("sys_app", "app_id");
513
+ var o = await db2.getObj({
514
+ appid: username
515
+ });
516
+ if (o) {
517
+ if (o.available) {
518
+ if (o.appsecret == password) {
519
+ // 账号密码正确
520
+ return callback(null, true);
521
+ }
522
+ else {
523
+ $.log.warn(`MQTT客户端 ${client.id} 身份验证失败:密码错误,用户名: ${username}`);
524
+ // 记录密码错误尝试
525
+ await this.recordPasswordFailure(client, username);
526
+ }
527
+ }
528
+ else {
529
+ $.log.warn(`MQTT客户端 ${client.id} 身份验证失败:账户不可用,用户名: ${username}`);
530
+ }
312
531
  }
313
-
314
- callback(null, isMatch);
315
532
  }
316
- });
317
- };
533
+ }
534
+ catch (err) {
535
+ $.log.error(`MQTT客户端 ${client.id} 身份验证数据库错误:`, err);
536
+ }
537
+ callback(null, false);
538
+ }
318
539
 
319
540
  /**
320
541
  * 验证发布,决定客户端可以发布哪些主题
321
542
  * @param {Object} client 客户端
322
543
  * @param {String} topic 主题
323
544
  * @param {Object} payload 参数
324
- * @param {Function} callback 回调函数,回调返回true,则表示验证通过。
545
+ * @returns {Boolean} true表示验证通过,false表示验证失败
325
546
  */
326
- MQTT.prototype.authPublish = function(client, topic, payload, callback) {
547
+ MQTT.prototype.authPublish = function (client, topic, payload) {
327
548
  // 示例:可以基于客户端ID或用户名实现更细粒度的权限控制
328
549
  // 在实际应用中,应该从数据库或配置中获取客户端的发布权限
329
550
  let isAllowed = true;
330
-
551
+
331
552
  // 例如:限制某些敏感主题只能由管理员发布
332
- const sensitiveTopics = ['system/settings', 'admin/commands'];
333
- const isSensitiveTopic = sensitiveTopics.some(sensitiveTopic =>
334
- topic === sensitiveTopic || topic.startsWith(sensitiveTopic + '/')
553
+ const isSensitiveTopic = (this.config.sensitiveTopics || []).some(sensitiveTopic =>
554
+ topic === sensitiveTopic || topic.startsWith(sensitiveTopic + '/')
335
555
  );
336
-
556
+
337
557
  // 简单示例:检查客户端ID是否表示管理员客户端
338
- const isAdminClient = client.id === 'admin_client' || client.id.startsWith('admin_');
339
-
558
+ const isAdminClient = client.id === 'admin' || client.id.startsWith('server_');
559
+
340
560
  if (isSensitiveTopic && !isAdminClient) {
341
561
  isAllowed = false;
342
- console.warn(`MQTT客户端 ${client.id} 被拒绝发布到敏感主题: ${topic}`);
562
+ $.log.warn(`MQTT客户端 ${client.id} 被拒绝发布到敏感主题: ${topic}`);
343
563
  }
344
-
345
- // 回调第二个参数为true表示验证通过, 为false表示验证失败
346
- callback(null, isAllowed);
564
+
565
+ return isAllowed;
347
566
  };
348
567
 
349
568
  /**
350
569
  * 验证订阅,决定客户端可以订阅哪些主题
351
570
  * @param {Object} client 客户端
352
571
  * @param {String} topic 主题
353
- * @param {Function} callback 回调函数,回调返回true,则表示验证通过。
572
+ * @returns {Boolean} true表示验证通过,false表示验证失败
354
573
  */
355
- MQTT.prototype.authSubscribe = function(client, topic, callback) {
574
+ MQTT.prototype.authSubscribe = function (client, topic) {
356
575
  // 示例:可以基于客户端ID或用户名实现更细粒度的权限控制
357
576
  // 在实际应用中,应该从数据库或配置中获取客户端的订阅权限
358
577
  let isAllowed = true;
359
-
578
+
360
579
  // 例如:限制某些敏感主题只能由特定客户端订阅
361
- const restrictedTopics = ['private/data', 'user/+/profile'];
362
- const isRestrictedTopic = restrictedTopics.some(restrictedTopic => {
580
+ const isRestrictedTopic = (this.config.restrictedTopics || []).some(restrictedTopic => {
363
581
  // 处理通配符,例如:user/+/profile 匹配 user/123/profile, user/456/profile 等
364
582
  const pattern = restrictedTopic.replace(/\+/g, '[^/]+');
365
583
  const regex = new RegExp(`^${pattern}$`);
366
584
  return regex.test(topic);
367
585
  });
368
-
586
+
369
587
  // 简单示例:允许特定客户端订阅受限主题
370
- const allowedClients = ['trusted_client', 'monitoring_service'];
371
- const isAllowedClient = allowedClients.includes(client.id);
372
-
588
+ const isAllowedClient = (this.config.allowedClients || []).includes(client.id);
589
+
373
590
  if (isRestrictedTopic && !isAllowedClient) {
374
591
  isAllowed = false;
375
- console.warn(`MQTT客户端 ${client.id} 被拒绝订阅受限主题: ${topic}`);
592
+ $.log.warn(`MQTT客户端 ${client.id} 被拒绝订阅受限主题: ${topic}`);
376
593
  }
377
-
378
- // 回调第二个参数为true表示验证通过, 为false表示验证失败
379
- callback(null, isAllowed);
594
+
595
+ return isAllowed;
380
596
  };
381
597
 
382
598
  /**
383
599
  * 运行主程序前
384
600
  * @param {String} state 状态
385
601
  */
386
- MQTT.prototype.before = async function(state) {
602
+ MQTT.prototype.before = async function (state) {
387
603
  var list = this.list;
388
604
  for (var i = 0; i < list.length; i++) {
389
605
  var o = list[i];
@@ -398,16 +614,494 @@ MQTT.prototype.before = async function(state) {
398
614
  * 运行主程序后
399
615
  * @param {String} state 状态
400
616
  */
401
- MQTT.prototype.after = async function(state) {};
617
+ MQTT.prototype.after = async function (state) { };
618
+
619
+ /**
620
+ * 检查客户端是否高频请求
621
+ * @param {Object} client 客户端对象
622
+ * @returns {Boolean} true表示高频请求,false表示正常请求
623
+ */
624
+ MQTT.prototype.checkHighFrequency = async function (client) {
625
+ const clientId = client.id;
626
+ const now = Date.now();
627
+ const config = this.config.rate_limit;
628
+
629
+ // 检查是否已经被拉黑
630
+ try {
631
+ const isBlocked = await this.isClientBlocked(clientId);
632
+ if (isBlocked) {
633
+ $.log.warn(`MQTT客户端 ${clientId} 处于拉黑状态,拒绝请求`);
634
+ return true;
635
+ }
636
+ } catch (error) {
637
+ $.log.error(`检查客户端 ${clientId} 拉黑状态时出错:`, error);
638
+ // 发生错误时,默认拒绝请求
639
+ return true;
640
+ }
641
+
642
+ // 初始化或更新消息计数器
643
+ if (!this.messageCounters[clientId]) {
644
+ this.messageCounters[clientId] = {
645
+ count: 1,
646
+ lastReset: now
647
+ };
648
+ } else {
649
+ // 检查是否需要重置计数器(注意:time_window现在是秒,需要转换为毫秒进行比较)
650
+ if (now - this.messageCounters[clientId].lastReset > config.time_window * 1000) {
651
+ this.messageCounters[clientId].count = 1;
652
+ this.messageCounters[clientId].lastReset = now;
653
+ } else {
654
+ // 增加消息计数
655
+ this.messageCounters[clientId].count++;
656
+ }
657
+ }
658
+
659
+ // 检查是否超过频率限制
660
+ if (this.messageCounters[clientId].count > config.max_messages) {
661
+ // 拉黑客户端 - 使用新的异步函数
662
+ try {
663
+ await this.addClientToBlacklist(clientId, config.block_duration);
664
+ } catch (error) {
665
+ $.log.error(`拉黑高频请求客户端 ${clientId} 时出错:`, error);
666
+ }
667
+ return true;
668
+ }
669
+
670
+ // 正常请求
671
+ return false;
672
+ };
673
+
674
+ /**
675
+ * 异步拉黑客户端并断开连接
676
+ * @param {Object} client 客户端对象
677
+ */
678
+ MQTT.prototype.blockClient = async function (client) {
679
+ const clientId = client.id;
680
+ const now = Date.now();
681
+ const config = this.config.rate_limit;
682
+
683
+ try {
684
+ // 使用新的异步函数添加到黑名单
685
+ await this.addClientToBlacklist(clientId, config.block_duration);
686
+ } catch (error) {
687
+ $.log.error(`拉黑MQTT客户端 ${clientId} 时出错:`, error);
688
+ }
689
+
690
+ // 断开客户端连接
691
+ if (client && client.close) {
692
+ try {
693
+ client.close();
694
+ } catch (error) {
695
+ $.log.error(`断开MQTT客户端 ${clientId} 连接时出错:`, error);
696
+ }
697
+ }
698
+ };
699
+
700
+ /**
701
+ * 异步检测客户端重连行为
702
+ * @param {Object} client 客户端对象
703
+ * @returns {Promise<Boolean>} true表示检测到异常重连,false表示正常连接
704
+ */
705
+ MQTT.prototype.detectReconnectAbuse = async function (client) {
706
+ try {
707
+ const clientId = client.id;
708
+ const now = Date.now();
709
+ const config = this.config;
710
+
711
+ // 从client对象中获取IP
712
+ const clientIp = client.connection && client.connection.stream ? client.connection.stream.remoteAddress || 'unknown' : 'unknown';
713
+
714
+ // 检查IP是否被禁止
715
+ const isIpBanned = await this.isIpBanned(clientIp);
716
+ if (isIpBanned) {
717
+ $.log.warn(`IP ${clientIp} 处于禁止状态,拒绝连接请求`);
718
+ return true;
719
+ }
720
+
721
+ // 检查是否是首次连接
722
+ if (!this.securityRecords.client[clientId]) {
723
+ // 首次连接,初始化连接历史
724
+ this.securityRecords.client[clientId] = {
725
+ ip: clientIp,
726
+ connectCount: 1,
727
+ lastConnectTime: now,
728
+ disconnectTime: null,
729
+ reconnectAttempts: 0
730
+ };
731
+ return false;
732
+ }
733
+
734
+ // 获取客户端历史记录
735
+ const history = this.securityRecords.client[clientId];
736
+
737
+ // 检查是否是来自不同IP的连接
738
+ if (history.ip !== clientIp) {
739
+ $.log.warn(`检测到客户端ID ${clientId} 从不同IP连接: 原IP ${history.ip}, 新IP ${clientIp}`);
740
+
741
+ // 记录新IP的连接行为
742
+ const oldIp = history.ip;
743
+ history.ip = clientIp;
744
+ history.connectCount++;
745
+ history.lastConnectTime = now;
746
+
747
+ // 禁止新IP连接
748
+ try {
749
+ await this.addIpToBanlist(clientIp, config.reconnect_ip_duration);
750
+ } catch (error) {
751
+ $.log.error(`禁止IP ${clientIp} 时出错:`, error);
752
+ }
753
+ return true;
754
+ }
755
+
756
+ // 检查是否在短时间内频繁重连
757
+ const timeSinceLastConnect = now - history.lastConnectTime;
758
+
759
+ // 如果两次连接间隔很短,增加重连尝试计数
760
+ if (timeSinceLastConnect < config.reconnect_frequency_threshold * 1000) {
761
+ history.reconnectAttempts++;
762
+ } else {
763
+ // 重置重连计数
764
+ history.reconnectAttempts = 0;
765
+ }
766
+
767
+ // 更新连接计数和时间
768
+ history.connectCount++;
769
+ history.lastConnectTime = now;
770
+
771
+ // 检查是否超过最大重连尝试次数
772
+ if (history.reconnectAttempts > config.max_attempts) {
773
+ $.log.warn(`客户端 ${clientId} 重连过于频繁,已尝试 ${history.reconnectAttempts} 次重连`);
774
+
775
+ // 禁止该客户端ID重连
776
+ try {
777
+ await this.addClientToBlacklist(clientId, config.reconnect_client_duration);
778
+ } catch (error) {
779
+ $.log.error(`禁止频繁重连客户端 ${clientId} 时出错:`, error);
780
+ }
781
+ return true;
782
+ }
783
+
784
+ // 正常连接
785
+ return false;
786
+ } catch (error) {
787
+ $.log.error(`检测客户端 ${client.id} 重连行为时出错:`, error);
788
+ // 出错时默认视为正常连接,避免误判
789
+ return false;
790
+ }
791
+ };
792
+
793
+ /**
794
+ * 异步检查密码尝试次数限制
795
+ * @param {Object} client 客户端
796
+ * @param {String} username 用户名
797
+ * @returns {Promise<Boolean>} true表示未超过限制,false表示已超过限制
798
+ */
799
+ MQTT.prototype.checkPasswordAttemptLimit = async function (client, username) {
800
+ try {
801
+ const now = Date.now();
802
+ const config = this.config;
803
+
804
+ // 从client对象中获取IP
805
+ const clientIp = client.connection && client.connection.stream ? client.connection.stream.remoteAddress || 'unknown' : 'unknown';
806
+
807
+ // 检查用户名是否已被禁止(密码错误次数过多)
808
+ if (this.securityRecords.password[username]) {
809
+ const failureRecord = this.securityRecords.password[username];
810
+ const timeSinceFirstFailure = now - failureRecord.firstAttempt;
811
+ const timeSinceLastFailure = now - failureRecord.lastAttempt;
812
+
813
+ // 如果超过了时间窗口,重置错误计数
814
+ if (timeSinceFirstFailure > config.time_window * 1000) {
815
+ // 重置错误计数
816
+ delete this.securityRecords.password[username];
817
+ return true;
818
+ }
819
+
820
+ // 检查错误次数是否超过限制
821
+ if (failureRecord.count >= config.max_attempts) {
822
+ // 记录被拒绝的尝试
823
+ $.log.warn(`拒绝客户端 ${client.id} (IP: ${clientIp}) 连接:用户名 ${username} 密码错误次数过多`);
824
+ return false;
825
+ }
826
+ }
827
+
828
+ return true;
829
+ } catch (error) {
830
+ $.log.error(`检查客户端 ${client.id} 密码尝试次数限制时出错:`, error);
831
+ // 出错时默认视为未超过限制,避免误判
832
+ return true;
833
+ }
834
+ };
835
+
836
+ /**
837
+ * 异步记录密码错误尝试
838
+ * @param {Object} client 客户端
839
+ * @param {String} username 用户名
840
+ */
841
+ MQTT.prototype.recordPasswordFailure = async function (client, username) {
842
+ try {
843
+ const now = Date.now();
844
+ const config = this.config;
845
+
846
+ // 从client对象中获取IP
847
+ const clientIp = client.connection && client.connection.stream ? client.connection.stream.remoteAddress || 'unknown' : 'unknown';
848
+
849
+ // 初始化或更新密码错误记录
850
+ if (!this.securityRecords.password[username]) {
851
+ this.securityRecords.password[username] = {
852
+ count: 1,
853
+ firstAttempt: now,
854
+ lastAttempt: now,
855
+ ip: clientIp
856
+ };
857
+ } else {
858
+ this.securityRecords.password[username].count++;
859
+ this.securityRecords.password[username].lastAttempt = now;
860
+ }
861
+
862
+ const failureCount = this.securityRecords.password[username].count;
863
+ $.log.warn(`MQTT客户端 ${client.id} (IP: ${clientIp}) 密码错误,用户名: ${username},当前错误次数: ${failureCount}`);
864
+
865
+ // 检查是否达到最大错误次数,需要拉黑
866
+ if (failureCount >= config.max_attempts) {
867
+ // 拉黑客户端
868
+ await this.addClientToBlacklist(client.id, config.block_duration);
869
+
870
+ // 如果配置了同时拉黑IP,也拉黑IP
871
+ if (config.password_block_ip) {
872
+ await this.addIpToBanlist(clientIp, config.block_duration);
873
+ }
874
+
875
+ $.log.warn(`用户名 ${username} 密码错误次数达到上限(${config.max_attempts}次),已拉黑客户端 ${client.id} 和IP ${clientIp},时长 ${config.block_duration / 3600}小时`);
876
+ }
877
+
878
+ } catch (error) {
879
+ $.log.error(`记录客户端 ${client.id} 密码错误时出错:`, error);
880
+ }
881
+ };
402
882
 
403
883
  /**
404
884
  * 运行
405
885
  * @param {String} state 状态
406
886
  */
407
- MQTT.prototype.run = async function(state = 'start') {
887
+ MQTT.prototype.run = async function (state = 'start') {
408
888
  await this.before(state);
409
889
  await this.main(state);
410
890
  await this.after(state);
411
891
  };
412
892
 
893
+ /**
894
+ * 异步检查客户端是否被拉黑
895
+ * @param {String} clientId 客户端ID
896
+ * @returns {Promise<Boolean>} true表示被拉黑,false表示未被拉黑
897
+ */
898
+ MQTT.prototype.isClientBlocked = async function (clientId) {
899
+ const now = Date.now();
900
+ let isBlocked = false;
901
+ let unblockTime = null;
902
+
903
+ try {
904
+ // 根据存储类型选择不同的实现方式
905
+ if (this.blacklistStorageType === 'memory') {
906
+ // 内存存储方式
907
+ if (this.blockedClients[clientId]) {
908
+ unblockTime = this.blockedClients[clientId];
909
+ isBlocked = now < unblockTime;
910
+ // 如果已过期,自动移除
911
+ if (!isBlocked) {
912
+ delete this.blockedClients[clientId];
913
+ $.log.info(`MQTT客户端 ${clientId} 解除拉黑`);
914
+ }
915
+ }
916
+ } else {
917
+ // 其他存储方式(redis、mongodb、mysql)可以在这里扩展实现
918
+ // 目前仅实现内存存储,其他存储方式需要根据实际需求补充
919
+ $.log.warn(`黑名单存储类型 ${this.blacklistStorageType} 尚未实现,默认使用内存存储`);
920
+ if (this.blockedClients[clientId]) {
921
+ unblockTime = this.blockedClients[clientId];
922
+ isBlocked = now < unblockTime;
923
+ }
924
+ }
925
+ } catch (error) {
926
+ $.log.error(`检查客户端 ${clientId} 是否被拉黑时出错:`, error);
927
+ // 出错时默认视为未被拉黑
928
+ }
929
+
930
+ return isBlocked;
931
+ };
932
+
933
+ /**
934
+ * 异步添加客户端到黑名单
935
+ * @param {String} clientId 客户端ID
936
+ * @param {Number} duration 拉黑时长(毫秒)
937
+ * @returns {Promise<Boolean>} true表示添加成功,false表示添加失败
938
+ */
939
+ MQTT.prototype.addClientToBlacklist = async function (clientId, duration) {
940
+ const now = Date.now();
941
+ const unblockTime = now + (duration || this.config.rate_limit.block_duration) * 1000; // 转换为毫秒
942
+
943
+ try {
944
+ // 根据存储类型选择不同的实现方式
945
+ if (this.blacklistStorageType === 'memory') {
946
+ // 内存存储方式
947
+ this.blockedClients[clientId] = unblockTime;
948
+ } else {
949
+ // 其他存储方式(redis、mongodb、mysql)可以在这里扩展实现
950
+ // 目前仅实现内存存储,其他存储方式需要根据实际需求补充
951
+ $.log.warn(`黑名单存储类型 ${this.blacklistStorageType} 尚未实现,默认使用内存存储`);
952
+ this.blockedClients[clientId] = unblockTime;
953
+ }
954
+
955
+ $.log.warn(`MQTT客户端 ${clientId} 被添加到黑名单,限制时长: ${(duration || this.config.rate_limit.block_duration) / 3600}小时`);
956
+ return true;
957
+ } catch (error) {
958
+ $.log.error(`添加客户端 ${clientId} 到黑名单时出错:`, error);
959
+ return false;
960
+ }
961
+ };
962
+
963
+ /**
964
+ * 异步检查IP是否被禁止
965
+ * @param {String} ip IP地址
966
+ * @returns {Promise<Boolean>} true表示被禁止,false表示未被禁止
967
+ */
968
+ MQTT.prototype.isIpBanned = async function (ip) {
969
+ const now = Date.now();
970
+ let isBanned = false;
971
+ let unbanTime = null;
972
+
973
+ try {
974
+ // 根据存储类型选择不同的实现方式
975
+ if (this.blacklistStorageType === 'memory') {
976
+ // 内存存储方式
977
+ if (this.bannedIps[ip]) {
978
+ unbanTime = this.bannedIps[ip];
979
+ isBanned = now < unbanTime;
980
+ // 如果已过期,自动移除
981
+ if (!isBanned) {
982
+ delete this.bannedIps[ip];
983
+ $.log.info(`IP ${ip} 解除禁止`);
984
+ }
985
+ }
986
+ } else {
987
+ // 其他存储方式(redis、mongodb、mysql)可以在这里扩展实现
988
+ // 目前仅实现内存存储,其他存储方式需要根据实际需求补充
989
+ $.log.warn(`黑名单存储类型 ${this.blacklistStorageType} 尚未实现,默认使用内存存储`);
990
+ if (this.bannedIps[ip]) {
991
+ unbanTime = this.bannedIps[ip];
992
+ isBanned = now < unbanTime;
993
+ }
994
+ }
995
+ } catch (error) {
996
+ $.log.error(`检查IP ${ip} 是否被禁止时出错:`, error);
997
+ // 出错时默认视为未被禁止
998
+ }
999
+
1000
+ return isBanned;
1001
+ };
1002
+
1003
+ /**
1004
+ * 异步添加IP到禁止列表
1005
+ * @param {String} ip IP地址
1006
+ * @param {Number} duration 禁止时长(毫秒)
1007
+ * @returns {Promise<Boolean>} true表示添加成功,false表示添加失败
1008
+ */
1009
+ MQTT.prototype.addIpToBanlist = async function (ip, duration) {
1010
+ const now = Date.now();
1011
+ const unbanTime = now + (duration || this.config.reconnect_ip_duration) * 1000; // 转换为毫秒
1012
+
1013
+ try {
1014
+ // 根据存储类型选择不同的实现方式
1015
+ if (this.blacklistStorageType === 'memory') {
1016
+ // 内存存储方式
1017
+ this.bannedIps[ip] = unbanTime;
1018
+ } else {
1019
+ // 其他存储方式(redis、mongodb、mysql)可以在这里扩展实现
1020
+ // 目前仅实现内存存储,其他存储方式需要根据实际需求补充
1021
+ $.log.warn(`黑名单存储类型 ${this.blacklistStorageType} 尚未实现,默认使用内存存储`);
1022
+ this.bannedIps[ip] = unbanTime;
1023
+ }
1024
+
1025
+ $.log.warn(`IP ${ip} 被添加到禁止列表,限制时长: ${(duration || this.config.reconnect_ip_duration) / 3600}小时`);
1026
+ return true;
1027
+ } catch (error) {
1028
+ $.log.error(`添加IP ${ip} 到禁止列表时出错:`, error);
1029
+ return false;
1030
+ }
1031
+ };
1032
+
1033
+ /**
1034
+ * 异步从黑名单中移出客户端
1035
+ * @param {String} clientId 客户端ID
1036
+ * @returns {Promise<Boolean>} true表示移出成功,false表示移出失败
1037
+ */
1038
+ MQTT.prototype.removeClientFromBlacklist = async function (clientId) {
1039
+ try {
1040
+ // 根据存储类型选择不同的实现方式
1041
+ if (this.blacklistStorageType === 'memory') {
1042
+ // 内存存储方式
1043
+ if (this.blockedClients[clientId]) {
1044
+ delete this.blockedClients[clientId];
1045
+ $.log.info(`MQTT客户端 ${clientId} 已从黑名单中移出`);
1046
+ return true;
1047
+ } else {
1048
+ $.log.info(`MQTT客户端 ${clientId} 不在黑名单中`);
1049
+ return false;
1050
+ }
1051
+ } else {
1052
+ // 其他存储方式(redis、mongodb、mysql)可以在这里扩展实现
1053
+ // 目前仅实现内存存储,其他存储方式需要根据实际需求补充
1054
+ $.log.warn(`黑名单存储类型 ${this.blacklistStorageType} 尚未实现,默认使用内存存储`);
1055
+ if (this.blockedClients[clientId]) {
1056
+ delete this.blockedClients[clientId];
1057
+ $.log.info(`MQTT客户端 ${clientId} 已从黑名单中移出`);
1058
+ return true;
1059
+ } else {
1060
+ $.log.info(`MQTT客户端 ${clientId} 不在黑名单中`);
1061
+ return false;
1062
+ }
1063
+ }
1064
+ } catch (error) {
1065
+ $.log.error(`从黑名单中移出客户端 ${clientId} 时出错:`, error);
1066
+ return false;
1067
+ }
1068
+ };
1069
+
1070
+ /**
1071
+ * 异步从禁止列表中移出IP
1072
+ * @param {String} ip IP地址
1073
+ * @returns {Promise<Boolean>} true表示移出成功,false表示移出失败
1074
+ */
1075
+ MQTT.prototype.removeIpFromBanlist = async function (ip) {
1076
+ try {
1077
+ // 根据存储类型选择不同的实现方式
1078
+ if (this.blacklistStorageType === 'memory') {
1079
+ // 内存存储方式
1080
+ if (this.bannedIps[ip]) {
1081
+ delete this.bannedIps[ip];
1082
+ $.log.info(`IP ${ip} 已从禁止列表中移出`);
1083
+ return true;
1084
+ } else {
1085
+ $.log.info(`IP ${ip} 不在禁止列表中`);
1086
+ return false;
1087
+ }
1088
+ } else {
1089
+ // 其他存储方式(redis、mongodb、mysql)可以在这里扩展实现
1090
+ // 目前仅实现内存存储,其他存储方式需要根据实际需求补充
1091
+ $.log.warn(`黑名单存储类型 ${this.blacklistStorageType} 尚未实现,默认使用内存存储`);
1092
+ if (this.bannedIps[ip]) {
1093
+ delete this.bannedIps[ip];
1094
+ $.log.info(`IP ${ip} 已从禁止列表中移出`);
1095
+ return true;
1096
+ } else {
1097
+ $.log.info(`IP ${ip} 不在禁止列表中`);
1098
+ return false;
1099
+ }
1100
+ }
1101
+ } catch (error) {
1102
+ $.log.error(`从禁止列表中移出IP ${ip} 时出错:`, error);
1103
+ return false;
1104
+ }
1105
+ };
1106
+
413
1107
  module.exports = MQTT;
@@ -56,6 +56,7 @@ App.prototype.update_config_all = async function(path, accurate) {
56
56
  }
57
57
  }
58
58
  dir_app = (path + "/app/").fullname();
59
+ dir_app.addDir();
59
60
  }
60
61
 
61
62
  if (dir_app.hasDir()) {
package/index.js CHANGED
@@ -41,13 +41,7 @@ class OS {
41
41
  "static": true,
42
42
  "maxAge": 7200,
43
43
  "static_path": "./static",
44
- "proxy": {},
45
- "rateLimit": {
46
- "windowMs": 15 * 60 * 1000, // 时间窗口,默认15分钟
47
- "maxRequests": 100, // 每个时间窗口内的最大请求数
48
- "message": "请求过于频繁,请稍后再试", // 超过限制时的提示信息
49
- "statusCode": 429 // 超过限制时的HTTP状态码
50
- }
44
+ "proxy": {}
51
45
  },
52
46
  "mqtt": {
53
47
  "state": true,
@@ -8,12 +8,14 @@ module.exports = function(server, config) {
8
8
  // 初始化速率限制配置
9
9
  const cg = {
10
10
  // 默认配置
11
- windowMs: 15 * 60 * 1000, // 时间窗口,默认15分钟
12
- maxRequests: 100, // 每个时间窗口内的最大请求数
11
+ windowMs: 60, // 时间窗口,调整为1分钟(更精细的限制)
12
+ maxRequests: 5000, // 每个时间窗口内的最大请求数,从1000提高到5000
13
13
  message: '请求过于频繁,请稍后再试', // 超过限制时的提示信息
14
14
  statusCode: 429, // 超过限制时的HTTP状态码
15
+ // 白名单路径,这些路径不受速率限制
16
+ whitelistPaths: [],
15
17
  // 合并用户配置
16
- ...(config && config.rateLimit ? config.rateLimit : {})
18
+ ...(config && config.rate_limit ? config.rate_limit : {})
17
19
  };
18
20
 
19
21
  // 生成基于IP的唯一标识符
@@ -39,7 +41,7 @@ module.exports = function(server, config) {
39
41
  const count = await $.cache.addInt(key, 1);
40
42
 
41
43
  // 设置过期时间
42
- await $.cache.ttl(key, Math.ceil(cg.windowMs / 1000));
44
+ await $.cache.ttl(key, Math.ceil(cg.windowMs));
43
45
 
44
46
  return count || 0;
45
47
  }
@@ -60,6 +62,13 @@ module.exports = function(server, config) {
60
62
  return;
61
63
  }
62
64
 
65
+ // 检查是否是白名单路径
66
+ if (cg.whitelistPaths && cg.whitelistPaths.some(path =>
67
+ ctx.path === path || ctx.path.startsWith(path + '/'))) {
68
+ await next();
69
+ return;
70
+ }
71
+
63
72
  // 获取客户端唯一标识符
64
73
  const clientKey = getClientKey(ctx);
65
74
 
@@ -97,7 +106,7 @@ module.exports = function(server, config) {
97
106
  });
98
107
 
99
108
  // 记录中间件初始化信息
100
- $.log.info(`速率限制中间件已加载: ${cg.maxRequests}请求/${cg.windowMs/1000}秒`);
109
+ $.log.info(`速率限制中间件已加载: ${cg.maxRequests}请求/${cg.windowMs}秒`);
101
110
 
102
111
  return server;
103
112
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mm_os",
3
- "version": "3.2.7",
3
+ "version": "3.2.9",
4
4
  "description": "这是超级美眉服务端框架,用于快速构建应用程序。",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/test.js CHANGED
@@ -1,5 +1,10 @@
1
1
  var OS = require("./index.js");
2
2
 
3
+ $.sql = $.mysql_admin('sys', __dirname);
4
+ $.sql.config.database = "face";
5
+ $.sql.config.password = "Asd159357";
6
+ $.sql.open();
7
+
3
8
  var os = new OS();
4
9
 
5
10
  os.run();