mm_os 3.2.6 → 3.2.8
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/core/base/mqtt/index.js +635 -128
- package/core/com/app/index.js +1 -0
- package/core/com/plugin/drive.js +0 -7
- package/index.js +2 -2
- package/middleware/rate_limit/index.js +12 -3
- package/package.json +1 -1
package/core/base/mqtt/index.js
CHANGED
|
@@ -7,12 +7,9 @@ const mosca = require('mosca');
|
|
|
7
7
|
class MQTT {
|
|
8
8
|
/**
|
|
9
9
|
* 构造函数
|
|
10
|
-
* @param {Object} config 配置参数
|
|
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,36 @@ 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": 60000, // 默认1分钟
|
|
29
|
+
// 时间窗口内允许的最大消息数
|
|
30
|
+
"max_messages": 100, // 人脸识别门禁优化:1分钟内最多20条消息(门禁通常每次识别仅发送1-3条消息)
|
|
31
|
+
// 拉黑时长(毫秒)
|
|
32
|
+
"block_duration": 3600000 // 人脸识别门禁优化:拉黑30分钟(更合理的临时限制时长)
|
|
33
|
+
},
|
|
34
|
+
// 重连检测配置
|
|
35
|
+
"reconnect_detection": {
|
|
36
|
+
// 时间窗口(毫秒)
|
|
37
|
+
"time_window": 60000, // 默认1分钟
|
|
38
|
+
// 最大允许重连次数
|
|
39
|
+
"max_reconnect_attempts": 5,
|
|
40
|
+
// 同一客户端ID不同IP的禁止时间(毫秒)
|
|
41
|
+
"ip_ban_duration": 3600000, // 默认1小时
|
|
42
|
+
// 客户端ID禁止重连时间(毫秒)
|
|
43
|
+
"client_id_ban_duration": 7200000, // 默认2小时
|
|
44
|
+
// 频繁重连时间阈值(毫秒)
|
|
45
|
+
"frequent_reconnect_threshold": 30000 // 默认30秒
|
|
46
|
+
},
|
|
47
|
+
// 受限主题列表,用于限制某些敏感主题只能由特定客户端订阅
|
|
48
|
+
restrictedTopics: [],
|
|
49
|
+
// 允许订阅受限主题的客户端列表
|
|
50
|
+
allowedClients: [],
|
|
51
|
+
// 敏感主题列表,用于限制某些敏感主题只能由管理员发布
|
|
52
|
+
sensitiveTopics: [],
|
|
26
53
|
redis: {
|
|
27
54
|
host: "localhost",
|
|
28
55
|
port: 6379,
|
|
@@ -44,24 +71,76 @@ class MQTT {
|
|
|
44
71
|
this.server = null;
|
|
45
72
|
|
|
46
73
|
this.list = [];
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 客户端消息计数器
|
|
77
|
+
* 格式: { clientId: { count: 消息数, lastReset: 上次重置时间戳 } }
|
|
78
|
+
*/
|
|
79
|
+
this.messageCounters = {};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* 拉黑的客户端列表
|
|
83
|
+
* 格式: { clientId: 解除拉黑时间戳 }
|
|
84
|
+
*/
|
|
85
|
+
this.blockedClients = {};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 客户端连接历史记录
|
|
89
|
+
* 格式: { clientId: { ip: 客户端IP, connectCount: 连接次数, lastConnectTime: 上次连接时间, disconnectTime: 上次断开时间, reconnectAttempts: 重连尝试次数 } }
|
|
90
|
+
*/
|
|
91
|
+
this.clientHistory = {};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 被禁止的IP列表
|
|
95
|
+
* 格式: { ip: 解除禁止时间戳 }
|
|
96
|
+
*/
|
|
97
|
+
this.bannedIps = {};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* 黑名单存储类型
|
|
101
|
+
* 可选值: 'memory', 'redis', 'mongodb', 'mysql'
|
|
102
|
+
*/
|
|
103
|
+
this.blacklistStorageType = this.config.blacklistStorageType || 'memory';
|
|
47
104
|
}
|
|
48
105
|
}
|
|
49
106
|
|
|
50
107
|
/**
|
|
51
|
-
*
|
|
108
|
+
* 异步客户端连接成功
|
|
52
109
|
* @param {Object} client 客户端信息
|
|
110
|
+
* @returns {Promise<Boolean>} true表示连接成功,false表示连接被拒绝
|
|
53
111
|
*/
|
|
54
|
-
MQTT.prototype.connected = async function(client) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
112
|
+
MQTT.prototype.connected = async function (client) {
|
|
113
|
+
try {
|
|
114
|
+
// 检测是否有异常重连行为 - 现在是异步函数
|
|
115
|
+
const hasReconnectAbuse = await this.detectReconnectAbuse(client);
|
|
116
|
+
if (hasReconnectAbuse) {
|
|
117
|
+
$.log.warn(`拒绝客户端 ${client.id} 连接:检测到异常重连行为`);
|
|
118
|
+
// 断开客户端连接
|
|
119
|
+
if (client && client.close) {
|
|
120
|
+
try {
|
|
121
|
+
client.close();
|
|
122
|
+
} catch (error) {
|
|
123
|
+
$.log.error(`断开异常重连客户端 ${client.id} 连接时出错:`, error);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 正常连接
|
|
130
|
+
$.log.info('client connected', client.id);
|
|
131
|
+
return true;
|
|
132
|
+
} catch (error) {
|
|
133
|
+
$.log.error(`处理客户端 ${client.id} 连接时出错:`, error);
|
|
134
|
+
// 出错时默认拒绝连接
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
58
137
|
}
|
|
59
138
|
|
|
60
139
|
/**
|
|
61
140
|
* 初始化
|
|
62
141
|
* @param {Object} config
|
|
63
142
|
*/
|
|
64
|
-
MQTT.prototype.init = function(config) {
|
|
143
|
+
MQTT.prototype.init = function (config) {
|
|
65
144
|
if (config) {
|
|
66
145
|
this.config = Object.assign(this.config, config);
|
|
67
146
|
}
|
|
@@ -105,10 +184,10 @@ MQTT.prototype.init = function(config) {
|
|
|
105
184
|
conf.persistence = Object.assign({
|
|
106
185
|
factory: mosca.persistence.Redis,
|
|
107
186
|
ttl: {
|
|
108
|
-
// TTL for subscriptions
|
|
109
|
-
subscriptions:
|
|
110
|
-
// TTL for packets
|
|
111
|
-
packets:
|
|
187
|
+
// TTL for subscriptions
|
|
188
|
+
subscriptions: cg.message_retention * 1000,
|
|
189
|
+
// TTL for packets
|
|
190
|
+
packets: cg.message_retention * 1000,
|
|
112
191
|
}
|
|
113
192
|
}, conf.backend);
|
|
114
193
|
} else if (cg.cache === 'mongodb' || cg.cache === 'mongo') {
|
|
@@ -129,10 +208,10 @@ MQTT.prototype.init = function(config) {
|
|
|
129
208
|
conf.persistence = Object.assign({
|
|
130
209
|
factory: mosca.persistence.Mongo,
|
|
131
210
|
ttl: {
|
|
132
|
-
// TTL for subscriptions
|
|
133
|
-
subscriptions:
|
|
134
|
-
// TTL for packets
|
|
135
|
-
packets:
|
|
211
|
+
// TTL for subscriptions
|
|
212
|
+
subscriptions: cg.message_retention * 1000,
|
|
213
|
+
// TTL for packets
|
|
214
|
+
packets: cg.message_retention * 1000,
|
|
136
215
|
}
|
|
137
216
|
}, conf.backend);
|
|
138
217
|
}
|
|
@@ -144,7 +223,7 @@ MQTT.prototype.init = function(config) {
|
|
|
144
223
|
* 引用
|
|
145
224
|
* @param {Function} 函数
|
|
146
225
|
*/
|
|
147
|
-
MQTT.prototype.use = function(func) {
|
|
226
|
+
MQTT.prototype.use = function (func) {
|
|
148
227
|
// this.server.use(func);
|
|
149
228
|
};
|
|
150
229
|
|
|
@@ -152,7 +231,7 @@ MQTT.prototype.use = function(func) {
|
|
|
152
231
|
* 运行主程序
|
|
153
232
|
* @param {String} state 状态
|
|
154
233
|
*/
|
|
155
|
-
MQTT.prototype.main = function(state) {
|
|
234
|
+
MQTT.prototype.main = function (state) {
|
|
156
235
|
var cg = this.config;
|
|
157
236
|
var sr = this.server;
|
|
158
237
|
|
|
@@ -162,16 +241,34 @@ MQTT.prototype.main = function(state) {
|
|
|
162
241
|
* 对服务器端口进行配置,在此端口进行监听
|
|
163
242
|
* @param {Object} client 客户端信息
|
|
164
243
|
*/
|
|
165
|
-
sr.on('clientConnected', async function(client) {
|
|
244
|
+
sr.on('clientConnected', async function (client) {
|
|
166
245
|
return await _this.connected(client);
|
|
167
246
|
});
|
|
168
|
-
|
|
247
|
+
|
|
169
248
|
/**
|
|
170
249
|
* 监听MQTT主题消息
|
|
171
250
|
* @param {Object} packet 订阅消息
|
|
172
251
|
* @param {Object} client 客户端信息
|
|
173
252
|
*/
|
|
174
|
-
sr.on('published', function(packet, client) {
|
|
253
|
+
sr.on('published', async function (packet, client) {
|
|
254
|
+
// 检查客户端是否高频请求
|
|
255
|
+
if (client) {
|
|
256
|
+
try {
|
|
257
|
+
const isHighFrequency = await _this.checkHighFrequency(client);
|
|
258
|
+
if (isHighFrequency) {
|
|
259
|
+
// 高频请求,拉黑并断开连接
|
|
260
|
+
await _this.blockClient(client);
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
} catch (error) {
|
|
264
|
+
$.log.error(`检查客户端高频请求时出错:`, error);
|
|
265
|
+
// 发生错误时,默认拒绝请求
|
|
266
|
+
if (client && client.close) {
|
|
267
|
+
client.close();
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
175
272
|
_this.published(packet, client);
|
|
176
273
|
});
|
|
177
274
|
|
|
@@ -180,7 +277,7 @@ MQTT.prototype.main = function(state) {
|
|
|
180
277
|
* @param {String} topic 订阅主题
|
|
181
278
|
* @param {Object} client 客户端信息
|
|
182
279
|
*/
|
|
183
|
-
sr.on('subscribed', function(topic, client) {
|
|
280
|
+
sr.on('subscribed', function (topic, client) {
|
|
184
281
|
_this.subscribed(topic, client);
|
|
185
282
|
});
|
|
186
283
|
|
|
@@ -189,34 +286,54 @@ MQTT.prototype.main = function(state) {
|
|
|
189
286
|
*/
|
|
190
287
|
sr.authenticate = (client, username, password, callback) => {
|
|
191
288
|
$.log.info("收到身份验证", username, password);
|
|
192
|
-
|
|
289
|
+
// 因为auth现在是异步函数,需要使用async/await处理
|
|
290
|
+
(async () => {
|
|
291
|
+
try {
|
|
292
|
+
// 调用异步的auth函数,但仍然使用callback返回结果
|
|
293
|
+
// auth函数内部已经处理了callback调用
|
|
294
|
+
await this.auth(client, username, password, callback);
|
|
295
|
+
} catch (error) {
|
|
296
|
+
$.log.error(`身份验证过程发生异常:`, error);
|
|
297
|
+
callback(null, false);
|
|
298
|
+
}
|
|
299
|
+
})();
|
|
300
|
+
// 返回true表示继续处理
|
|
193
301
|
return true;
|
|
194
302
|
};
|
|
195
|
-
|
|
303
|
+
|
|
196
304
|
/**
|
|
197
305
|
* 验证发布,决定客户端可以发布哪些主题
|
|
198
306
|
*/
|
|
199
307
|
sr.authorizePublish = (client, topic, payload, callback) => {
|
|
200
|
-
this.authPublish(client, topic, payload
|
|
308
|
+
const isAllowed = this.authPublish(client, topic, payload);
|
|
309
|
+
callback(null, isAllowed);
|
|
201
310
|
return true;
|
|
202
311
|
};
|
|
203
|
-
|
|
204
|
-
|
|
312
|
+
|
|
313
|
+
|
|
205
314
|
/**
|
|
206
315
|
* 验证订阅,决定客户端可以订阅哪些主题
|
|
207
316
|
*/
|
|
208
317
|
sr.authorizeSubscribe = (client, topic, callback) => {
|
|
209
|
-
this.authSubscribe(client, topic
|
|
318
|
+
const isAllowed = this.authSubscribe(client, topic);
|
|
319
|
+
callback(null, isAllowed);
|
|
210
320
|
return true;
|
|
211
321
|
};
|
|
212
|
-
|
|
213
|
-
|
|
322
|
+
|
|
323
|
+
|
|
214
324
|
/**
|
|
215
325
|
* 当服务开启时
|
|
216
326
|
*/
|
|
217
|
-
sr.on('ready', function() {
|
|
327
|
+
sr.on('ready', function () {
|
|
218
328
|
// 当服务开启时
|
|
219
|
-
|
|
329
|
+
$.log.info(`MQTT访问 mqtt://127.0.0.1:${cg.socket_port} || ws://127.0.0.1:${cg.http_port}`);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* 监听客户端断开连接
|
|
334
|
+
*/
|
|
335
|
+
sr.on('clientDisconnected', async function (client) {
|
|
336
|
+
_this.clientDisconnected(client);
|
|
220
337
|
});
|
|
221
338
|
}
|
|
222
339
|
|
|
@@ -225,95 +342,176 @@ MQTT.prototype.main = function(state) {
|
|
|
225
342
|
* @param {String} topic 订阅主题
|
|
226
343
|
* @param {Object} client 客户端信息
|
|
227
344
|
*/
|
|
228
|
-
MQTT.prototype.subscribed = function(topic, client) {
|
|
345
|
+
MQTT.prototype.subscribed = function (topic, client) {
|
|
229
346
|
// console.log("订阅", topic, client.id);
|
|
230
347
|
};
|
|
231
348
|
|
|
349
|
+
/**
|
|
350
|
+
* 客户端断开连接时的处理
|
|
351
|
+
* @param {Object} client 客户端对象
|
|
352
|
+
*/
|
|
353
|
+
MQTT.prototype.clientDisconnected = function (client) {
|
|
354
|
+
const clientId = client.id;
|
|
355
|
+
const now = Date.now();
|
|
356
|
+
|
|
357
|
+
$.log.info(`MQTT客户端 ${clientId} 断开连接`);
|
|
358
|
+
|
|
359
|
+
// 更新客户端断开连接时间
|
|
360
|
+
if (this.clientHistory[clientId]) {
|
|
361
|
+
this.clientHistory[clientId].disconnectTime = now;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
232
365
|
/**
|
|
233
366
|
* 收到推送消息时
|
|
234
367
|
* @param {Object} packet 订阅的消息
|
|
235
368
|
* @param {Object} client 客户端信息
|
|
236
369
|
*/
|
|
237
|
-
MQTT.prototype.published = function(packet, client) {
|
|
370
|
+
MQTT.prototype.published = function (packet, client) {
|
|
238
371
|
// console.log("发布", packet.topic, packet.payload.toString());
|
|
239
372
|
};
|
|
240
373
|
|
|
241
374
|
/**
|
|
242
|
-
* 身份验证 -
|
|
375
|
+
* 身份验证 - 主要入口函数
|
|
243
376
|
* @param {Object} client 客户端
|
|
244
377
|
* @param {String} username 用户名
|
|
245
378
|
* @param {String} password 密码
|
|
246
379
|
* @param {Function} callback 回调函数,回调返回true,则表示验证通过。
|
|
247
380
|
*/
|
|
248
|
-
MQTT.prototype.auth = function(client, username, password, callback) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (!username || !passwordStr) {
|
|
254
|
-
console.warn(`MQTT客户端 ${client.id} 身份验证失败:用户名或密码为空`);
|
|
255
|
-
callback(null, false);
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// 检查是否有可用的MySQL连接
|
|
260
|
-
if (!$.sql || typeof $.sql.query !== 'function') {
|
|
261
|
-
console.error(`MQTT客户端 ${client.id} 身份验证错误:MySQL连接不可用`);
|
|
262
|
-
callback(null, false);
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
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);
|
|
381
|
+
MQTT.prototype.auth = async function (client, username, password, callback) {
|
|
382
|
+
try {
|
|
383
|
+
// 检查客户端状态(拉黑、异常重连等)- 现在是异步操作
|
|
384
|
+
const clientStatusResult = await this.checkClientStatus(client);
|
|
385
|
+
if (clientStatusResult === false) {
|
|
274
386
|
callback(null, false);
|
|
275
387
|
return;
|
|
276
388
|
}
|
|
277
|
-
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
389
|
+
|
|
390
|
+
// 密码可能是Buffer类型,需要转换为字符串
|
|
391
|
+
const passwordStr = password ? password.toString() : '';
|
|
392
|
+
|
|
393
|
+
// 验证凭证格式
|
|
394
|
+
const credentialsResult = this.validateCredentials(client, username, passwordStr);
|
|
395
|
+
if (credentialsResult === false) {
|
|
281
396
|
callback(null, false);
|
|
282
397
|
return;
|
|
283
398
|
}
|
|
284
|
-
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
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}`);
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
callback(null, isMatch);
|
|
399
|
+
|
|
400
|
+
// 查询用户密码并验证(异步操作,需要callback)
|
|
401
|
+
this.checkAccount(client, username, passwordStr, callback);
|
|
402
|
+
} catch (error) {
|
|
403
|
+
$.log.error(`MQTT客户端 ${client.id} 身份验证过程中出错:`, error);
|
|
404
|
+
callback(null, false);
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* 异步检查客户端状态(拉黑、异常重连等)
|
|
410
|
+
* @param {Object} client 客户端
|
|
411
|
+
* @returns {Promise<Boolean>} true表示客户端状态正常,false表示客户端状态异常
|
|
412
|
+
*/
|
|
413
|
+
MQTT.prototype.checkClientStatus = async function (client) {
|
|
414
|
+
try {
|
|
415
|
+
// 检查客户端是否被拉黑(使用新的异步函数)
|
|
416
|
+
const isBlocked = await this.isClientBlocked(client.id);
|
|
417
|
+
if (isBlocked) {
|
|
418
|
+
$.log.warn(`MQTT客户端 ${client.id} 处于拉黑状态,拒绝身份验证`);
|
|
419
|
+
return false;
|
|
315
420
|
}
|
|
316
|
-
|
|
421
|
+
|
|
422
|
+
// 检测是否有异常重连行为
|
|
423
|
+
// 注意:detectReconnectAbuse内部也有黑名单相关操作,后续可以考虑重构为使用异步函数
|
|
424
|
+
if (this.detectReconnectAbuse(client)) {
|
|
425
|
+
$.log.warn(`拒绝客户端 ${client.id} 身份验证:检测到异常重连行为`);
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
return true;
|
|
430
|
+
} catch (error) {
|
|
431
|
+
$.log.error(`检查客户端 ${client.id} 状态时出错:`, error);
|
|
432
|
+
// 出错时默认视为客户端状态异常
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* 验证凭证格式
|
|
439
|
+
* @param {Object} client 客户端
|
|
440
|
+
* @param {String} username 用户名
|
|
441
|
+
* @param {String} passwordStr 密码字符串
|
|
442
|
+
* @returns {Boolean} true表示凭证格式有效,false表示凭证格式无效
|
|
443
|
+
*/
|
|
444
|
+
MQTT.prototype.validateCredentials = function (client, username, passwordStr) {
|
|
445
|
+
// 检查必要参数
|
|
446
|
+
if (!username || !passwordStr) {
|
|
447
|
+
$.log.warn(`MQTT客户端 ${client.id} 身份验证失败:用户名或密码为空`);
|
|
448
|
+
return false;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// 检查是否有可用的MySQL连接
|
|
452
|
+
if (!$.mysql_admin) {
|
|
453
|
+
$.log.error(`MQTT客户端 ${client.id} 身份验证错误:MySQL连接不可用`);
|
|
454
|
+
return false;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
return true;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* 查询用户密码并验证
|
|
462
|
+
* @param {Object} client 客户端
|
|
463
|
+
* @param {String} username 用户名
|
|
464
|
+
* @param {String} passwordStr 密码字符串
|
|
465
|
+
* @param {Function} callback 回调函数
|
|
466
|
+
*/
|
|
467
|
+
MQTT.prototype.checkAccount = function (client, username, passwordStr, callback) {
|
|
468
|
+
var sql = $.mysql_admin('sys');
|
|
469
|
+
try {
|
|
470
|
+
var db = sql.db();
|
|
471
|
+
db.table = this.config.table_name || 'face_device';
|
|
472
|
+
// 先尝试通过客户端ID和用户名精确匹配
|
|
473
|
+
db.getObj({
|
|
474
|
+
clientid: client.id,
|
|
475
|
+
username: username
|
|
476
|
+
}).then((obj) => {
|
|
477
|
+
if (!obj) {
|
|
478
|
+
$.log.warn(`MQTT客户端 ${client.id} 身份验证失败:用户不存在,用户名: ${username}`);
|
|
479
|
+
callback(null, false);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
// 验证密码
|
|
483
|
+
this.verifyPassword(client, username, passwordStr, obj.password, callback);
|
|
484
|
+
}).catch((err) => {
|
|
485
|
+
$.log.error(`MQTT客户端 ${client.id} 身份验证数据库错误:`, err);
|
|
486
|
+
callback(null, false);
|
|
487
|
+
return;
|
|
488
|
+
});
|
|
489
|
+
} catch (error) {
|
|
490
|
+
$.log.error(`MQTT客户端 ${client.id} 身份验证数据库错误:`, error);
|
|
491
|
+
callback(null, false);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* 验证密码
|
|
498
|
+
* @param {Object} client 客户端
|
|
499
|
+
* @param {String} username 用户名
|
|
500
|
+
* @param {String} passwordStr 密码字符串
|
|
501
|
+
* @param {String} dbPassword 数据库中的密码
|
|
502
|
+
* @param {Function} callback 回调函数
|
|
503
|
+
*/
|
|
504
|
+
MQTT.prototype.verifyPassword = function (client, username, passwordStr, dbPassword, callback) {
|
|
505
|
+
// 使用简单的密码比较(当前实现,可根据需要扩展为bcrypt验证)
|
|
506
|
+
const isMatch = dbPassword === passwordStr;
|
|
507
|
+
|
|
508
|
+
if (isMatch) {
|
|
509
|
+
$.log.info(`MQTT客户端 ${client.id} 身份验证成功,用户: ${username}`);
|
|
510
|
+
} else {
|
|
511
|
+
$.log.warn(`MQTT客户端 ${client.id} 身份验证失败:密码不匹配,用户名: ${username}`);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
callback(null, isMatch);
|
|
317
515
|
};
|
|
318
516
|
|
|
319
517
|
/**
|
|
@@ -321,69 +519,64 @@ MQTT.prototype.auth = function(client, username, password, callback) {
|
|
|
321
519
|
* @param {Object} client 客户端
|
|
322
520
|
* @param {String} topic 主题
|
|
323
521
|
* @param {Object} payload 参数
|
|
324
|
-
* @
|
|
522
|
+
* @returns {Boolean} true表示验证通过,false表示验证失败
|
|
325
523
|
*/
|
|
326
|
-
MQTT.prototype.authPublish = function(client, topic, payload
|
|
524
|
+
MQTT.prototype.authPublish = function (client, topic, payload) {
|
|
327
525
|
// 示例:可以基于客户端ID或用户名实现更细粒度的权限控制
|
|
328
526
|
// 在实际应用中,应该从数据库或配置中获取客户端的发布权限
|
|
329
527
|
let isAllowed = true;
|
|
330
|
-
|
|
528
|
+
|
|
331
529
|
// 例如:限制某些敏感主题只能由管理员发布
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
topic === sensitiveTopic || topic.startsWith(sensitiveTopic + '/')
|
|
530
|
+
const isSensitiveTopic = (this.config.sensitiveTopics || []).some(sensitiveTopic =>
|
|
531
|
+
topic === sensitiveTopic || topic.startsWith(sensitiveTopic + '/')
|
|
335
532
|
);
|
|
336
|
-
|
|
533
|
+
|
|
337
534
|
// 简单示例:检查客户端ID是否表示管理员客户端
|
|
338
|
-
const isAdminClient = client.id === '
|
|
339
|
-
|
|
535
|
+
const isAdminClient = client.id === 'admin' || client.id.startsWith('server_');
|
|
536
|
+
|
|
340
537
|
if (isSensitiveTopic && !isAdminClient) {
|
|
341
538
|
isAllowed = false;
|
|
342
|
-
|
|
539
|
+
$.log.warn(`MQTT客户端 ${client.id} 被拒绝发布到敏感主题: ${topic}`);
|
|
343
540
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
callback(null, isAllowed);
|
|
541
|
+
|
|
542
|
+
return isAllowed;
|
|
347
543
|
};
|
|
348
544
|
|
|
349
545
|
/**
|
|
350
546
|
* 验证订阅,决定客户端可以订阅哪些主题
|
|
351
547
|
* @param {Object} client 客户端
|
|
352
548
|
* @param {String} topic 主题
|
|
353
|
-
* @
|
|
549
|
+
* @returns {Boolean} true表示验证通过,false表示验证失败
|
|
354
550
|
*/
|
|
355
|
-
MQTT.prototype.authSubscribe = function(client, topic
|
|
551
|
+
MQTT.prototype.authSubscribe = function (client, topic) {
|
|
356
552
|
// 示例:可以基于客户端ID或用户名实现更细粒度的权限控制
|
|
357
553
|
// 在实际应用中,应该从数据库或配置中获取客户端的订阅权限
|
|
358
554
|
let isAllowed = true;
|
|
359
|
-
|
|
555
|
+
|
|
360
556
|
// 例如:限制某些敏感主题只能由特定客户端订阅
|
|
361
|
-
const
|
|
362
|
-
const isRestrictedTopic = restrictedTopics.some(restrictedTopic => {
|
|
557
|
+
const isRestrictedTopic = (this.config.restrictedTopics || []).some(restrictedTopic => {
|
|
363
558
|
// 处理通配符,例如:user/+/profile 匹配 user/123/profile, user/456/profile 等
|
|
364
559
|
const pattern = restrictedTopic.replace(/\+/g, '[^/]+');
|
|
365
560
|
const regex = new RegExp(`^${pattern}$`);
|
|
366
561
|
return regex.test(topic);
|
|
367
562
|
});
|
|
368
|
-
|
|
563
|
+
|
|
369
564
|
// 简单示例:允许特定客户端订阅受限主题
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
565
|
+
const isAllowedClient = (this.config.allowedClients || []).includes(client.id);
|
|
566
|
+
|
|
373
567
|
if (isRestrictedTopic && !isAllowedClient) {
|
|
374
568
|
isAllowed = false;
|
|
375
|
-
|
|
569
|
+
$.log.warn(`MQTT客户端 ${client.id} 被拒绝订阅受限主题: ${topic}`);
|
|
376
570
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
callback(null, isAllowed);
|
|
571
|
+
|
|
572
|
+
return isAllowed;
|
|
380
573
|
};
|
|
381
574
|
|
|
382
575
|
/**
|
|
383
576
|
* 运行主程序前
|
|
384
577
|
* @param {String} state 状态
|
|
385
578
|
*/
|
|
386
|
-
MQTT.prototype.before = async function(state) {
|
|
579
|
+
MQTT.prototype.before = async function (state) {
|
|
387
580
|
var list = this.list;
|
|
388
581
|
for (var i = 0; i < list.length; i++) {
|
|
389
582
|
var o = list[i];
|
|
@@ -398,16 +591,330 @@ MQTT.prototype.before = async function(state) {
|
|
|
398
591
|
* 运行主程序后
|
|
399
592
|
* @param {String} state 状态
|
|
400
593
|
*/
|
|
401
|
-
MQTT.prototype.after = async function(state) {};
|
|
594
|
+
MQTT.prototype.after = async function (state) { };
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* 检查客户端是否高频请求
|
|
598
|
+
* @param {Object} client 客户端对象
|
|
599
|
+
* @returns {Boolean} true表示高频请求,false表示正常请求
|
|
600
|
+
*/
|
|
601
|
+
MQTT.prototype.checkHighFrequency = async function (client) {
|
|
602
|
+
const clientId = client.id;
|
|
603
|
+
const now = Date.now();
|
|
604
|
+
const config = this.config.rate_limit;
|
|
605
|
+
|
|
606
|
+
// 检查是否已经被拉黑
|
|
607
|
+
try {
|
|
608
|
+
const isBlocked = await this.isClientBlocked(clientId);
|
|
609
|
+
if (isBlocked) {
|
|
610
|
+
$.log.warn(`MQTT客户端 ${clientId} 处于拉黑状态,拒绝请求`);
|
|
611
|
+
return true;
|
|
612
|
+
}
|
|
613
|
+
} catch (error) {
|
|
614
|
+
$.log.error(`检查客户端 ${clientId} 拉黑状态时出错:`, error);
|
|
615
|
+
// 发生错误时,默认拒绝请求
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// 初始化或更新消息计数器
|
|
620
|
+
if (!this.messageCounters[clientId]) {
|
|
621
|
+
this.messageCounters[clientId] = {
|
|
622
|
+
count: 1,
|
|
623
|
+
lastReset: now
|
|
624
|
+
};
|
|
625
|
+
} else {
|
|
626
|
+
// 检查是否需要重置计数器
|
|
627
|
+
if (now - this.messageCounters[clientId].lastReset > config.time_window) {
|
|
628
|
+
this.messageCounters[clientId].count = 1;
|
|
629
|
+
this.messageCounters[clientId].lastReset = now;
|
|
630
|
+
} else {
|
|
631
|
+
// 增加消息计数
|
|
632
|
+
this.messageCounters[clientId].count++;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// 检查是否超过频率限制
|
|
637
|
+
if (this.messageCounters[clientId].count > config.max_messages) {
|
|
638
|
+
// 拉黑客户端 - 使用新的异步函数
|
|
639
|
+
try {
|
|
640
|
+
await this.addClientToBlacklist(clientId, config.block_duration);
|
|
641
|
+
} catch (error) {
|
|
642
|
+
$.log.error(`拉黑高频请求客户端 ${clientId} 时出错:`, error);
|
|
643
|
+
}
|
|
644
|
+
return true;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// 正常请求
|
|
648
|
+
return false;
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* 异步拉黑客户端并断开连接
|
|
653
|
+
* @param {Object} client 客户端对象
|
|
654
|
+
*/
|
|
655
|
+
MQTT.prototype.blockClient = async function (client) {
|
|
656
|
+
const clientId = client.id;
|
|
657
|
+
const now = Date.now();
|
|
658
|
+
const config = this.config.rate_limit;
|
|
659
|
+
|
|
660
|
+
try {
|
|
661
|
+
// 使用新的异步函数添加到黑名单
|
|
662
|
+
await this.addClientToBlacklist(clientId, config.block_duration);
|
|
663
|
+
} catch (error) {
|
|
664
|
+
$.log.error(`拉黑MQTT客户端 ${clientId} 时出错:`, error);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// 断开客户端连接
|
|
668
|
+
if (client && client.close) {
|
|
669
|
+
try {
|
|
670
|
+
client.close();
|
|
671
|
+
} catch (error) {
|
|
672
|
+
$.log.error(`断开MQTT客户端 ${clientId} 连接时出错:`, error);
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* 异步检测客户端重连行为
|
|
679
|
+
* @param {Object} client 客户端对象
|
|
680
|
+
* @returns {Promise<Boolean>} true表示检测到异常重连,false表示正常连接
|
|
681
|
+
*/
|
|
682
|
+
MQTT.prototype.detectReconnectAbuse = async function (client) {
|
|
683
|
+
try {
|
|
684
|
+
const clientId = client.id;
|
|
685
|
+
const now = Date.now();
|
|
686
|
+
const config = this.config.reconnect_detection;
|
|
687
|
+
|
|
688
|
+
// 从client对象中获取IP(这里可能需要根据实际情况调整获取IP的方式)
|
|
689
|
+
const clientIp = client.connection.stream.remoteAddress || 'unknown';
|
|
690
|
+
|
|
691
|
+
// 检查IP是否被禁止(使用新的异步函数)
|
|
692
|
+
const isIpBanned = await this.isIpBanned(clientIp);
|
|
693
|
+
if (isIpBanned) {
|
|
694
|
+
$.log.warn(`IP ${clientIp} 处于禁止状态,拒绝连接请求`);
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// 检查是否是首次连接
|
|
699
|
+
if (!this.clientHistory[clientId]) {
|
|
700
|
+
// 首次连接,初始化连接历史
|
|
701
|
+
this.clientHistory[clientId] = {
|
|
702
|
+
ip: clientIp,
|
|
703
|
+
connectCount: 1,
|
|
704
|
+
lastConnectTime: now,
|
|
705
|
+
disconnectTime: null,
|
|
706
|
+
reconnectAttempts: 0
|
|
707
|
+
};
|
|
708
|
+
return false;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// 获取客户端历史记录
|
|
712
|
+
const history = this.clientHistory[clientId];
|
|
713
|
+
|
|
714
|
+
// 检查是否是来自不同IP的连接
|
|
715
|
+
if (history.ip !== clientIp) {
|
|
716
|
+
$.log.warn(`检测到客户端ID ${clientId} 从不同IP连接: 原IP ${history.ip}, 新IP ${clientIp}`);
|
|
717
|
+
|
|
718
|
+
// 记录新IP的连接行为
|
|
719
|
+
const oldIp = history.ip;
|
|
720
|
+
history.ip = clientIp;
|
|
721
|
+
history.connectCount++;
|
|
722
|
+
history.lastConnectTime = now;
|
|
723
|
+
|
|
724
|
+
// 禁止新IP连接 - 使用新的异步函数
|
|
725
|
+
try {
|
|
726
|
+
await this.addIpToBanlist(clientIp, config.ip_ban_duration);
|
|
727
|
+
} catch (error) {
|
|
728
|
+
$.log.error(`禁止IP ${clientIp} 时出错:`, error);
|
|
729
|
+
}
|
|
730
|
+
return true;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// 检查是否在短时间内频繁重连
|
|
734
|
+
const timeSinceLastConnect = now - history.lastConnectTime;
|
|
735
|
+
|
|
736
|
+
// 如果两次连接间隔很短,增加重连尝试计数
|
|
737
|
+
if (timeSinceLastConnect < config.frequent_reconnect_threshold) {
|
|
738
|
+
history.reconnectAttempts++;
|
|
739
|
+
} else {
|
|
740
|
+
// 重置重连计数
|
|
741
|
+
history.reconnectAttempts = 0;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// 更新连接计数和时间
|
|
745
|
+
history.connectCount++;
|
|
746
|
+
history.lastConnectTime = now;
|
|
747
|
+
|
|
748
|
+
// 检查是否超过最大重连尝试次数
|
|
749
|
+
if (history.reconnectAttempts > config.max_reconnect_attempts) {
|
|
750
|
+
$.log.warn(`客户端 ${clientId} 重连过于频繁,已尝试 ${history.reconnectAttempts} 次重连`);
|
|
751
|
+
|
|
752
|
+
// 禁止该客户端ID重连 - 使用新的异步函数
|
|
753
|
+
try {
|
|
754
|
+
await this.addClientToBlacklist(clientId, config.client_id_ban_duration);
|
|
755
|
+
} catch (error) {
|
|
756
|
+
$.log.error(`禁止频繁重连客户端 ${clientId} 时出错:`, error);
|
|
757
|
+
}
|
|
758
|
+
return true;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// 正常连接
|
|
762
|
+
return false;
|
|
763
|
+
} catch (error) {
|
|
764
|
+
$.log.error(`检测客户端 ${client.id} 重连行为时出错:`, error);
|
|
765
|
+
// 出错时默认视为正常连接,避免误判
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
768
|
+
};
|
|
402
769
|
|
|
403
770
|
/**
|
|
404
771
|
* 运行
|
|
405
772
|
* @param {String} state 状态
|
|
406
773
|
*/
|
|
407
|
-
MQTT.prototype.run = async function(state = 'start') {
|
|
774
|
+
MQTT.prototype.run = async function (state = 'start') {
|
|
408
775
|
await this.before(state);
|
|
409
776
|
await this.main(state);
|
|
410
777
|
await this.after(state);
|
|
411
778
|
};
|
|
412
779
|
|
|
780
|
+
/**
|
|
781
|
+
* 异步检查客户端是否被拉黑
|
|
782
|
+
* @param {String} clientId 客户端ID
|
|
783
|
+
* @returns {Promise<Boolean>} true表示被拉黑,false表示未被拉黑
|
|
784
|
+
*/
|
|
785
|
+
MQTT.prototype.isClientBlocked = async function (clientId) {
|
|
786
|
+
const now = Date.now();
|
|
787
|
+
let isBlocked = false;
|
|
788
|
+
let unblockTime = null;
|
|
789
|
+
|
|
790
|
+
try {
|
|
791
|
+
// 根据存储类型选择不同的实现方式
|
|
792
|
+
if (this.blacklistStorageType === 'memory') {
|
|
793
|
+
// 内存存储方式
|
|
794
|
+
if (this.blockedClients[clientId]) {
|
|
795
|
+
unblockTime = this.blockedClients[clientId];
|
|
796
|
+
isBlocked = now < unblockTime;
|
|
797
|
+
// 如果已过期,自动移除
|
|
798
|
+
if (!isBlocked) {
|
|
799
|
+
delete this.blockedClients[clientId];
|
|
800
|
+
$.log.info(`MQTT客户端 ${clientId} 解除拉黑`);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
} else {
|
|
804
|
+
// 其他存储方式(redis、mongodb、mysql)可以在这里扩展实现
|
|
805
|
+
// 目前仅实现内存存储,其他存储方式需要根据实际需求补充
|
|
806
|
+
$.log.warn(`黑名单存储类型 ${this.blacklistStorageType} 尚未实现,默认使用内存存储`);
|
|
807
|
+
if (this.blockedClients[clientId]) {
|
|
808
|
+
unblockTime = this.blockedClients[clientId];
|
|
809
|
+
isBlocked = now < unblockTime;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
} catch (error) {
|
|
813
|
+
$.log.error(`检查客户端 ${clientId} 是否被拉黑时出错:`, error);
|
|
814
|
+
// 出错时默认视为未被拉黑
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
return isBlocked;
|
|
818
|
+
};
|
|
819
|
+
|
|
820
|
+
/**
|
|
821
|
+
* 异步添加客户端到黑名单
|
|
822
|
+
* @param {String} clientId 客户端ID
|
|
823
|
+
* @param {Number} duration 拉黑时长(毫秒)
|
|
824
|
+
* @returns {Promise<Boolean>} true表示添加成功,false表示添加失败
|
|
825
|
+
*/
|
|
826
|
+
MQTT.prototype.addClientToBlacklist = async function (clientId, duration) {
|
|
827
|
+
const now = Date.now();
|
|
828
|
+
const unblockTime = now + (duration || this.config.rate_limit.block_duration);
|
|
829
|
+
|
|
830
|
+
try {
|
|
831
|
+
// 根据存储类型选择不同的实现方式
|
|
832
|
+
if (this.blacklistStorageType === 'memory') {
|
|
833
|
+
// 内存存储方式
|
|
834
|
+
this.blockedClients[clientId] = unblockTime;
|
|
835
|
+
} else {
|
|
836
|
+
// 其他存储方式(redis、mongodb、mysql)可以在这里扩展实现
|
|
837
|
+
// 目前仅实现内存存储,其他存储方式需要根据实际需求补充
|
|
838
|
+
$.log.warn(`黑名单存储类型 ${this.blacklistStorageType} 尚未实现,默认使用内存存储`);
|
|
839
|
+
this.blockedClients[clientId] = unblockTime;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
$.log.warn(`MQTT客户端 ${clientId} 被添加到黑名单,限制时长: ${duration / 3600000}小时`);
|
|
843
|
+
return true;
|
|
844
|
+
} catch (error) {
|
|
845
|
+
$.log.error(`添加客户端 ${clientId} 到黑名单时出错:`, error);
|
|
846
|
+
return false;
|
|
847
|
+
}
|
|
848
|
+
};
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* 异步检查IP是否被禁止
|
|
852
|
+
* @param {String} ip IP地址
|
|
853
|
+
* @returns {Promise<Boolean>} true表示被禁止,false表示未被禁止
|
|
854
|
+
*/
|
|
855
|
+
MQTT.prototype.isIpBanned = async function (ip) {
|
|
856
|
+
const now = Date.now();
|
|
857
|
+
let isBanned = false;
|
|
858
|
+
let unbanTime = null;
|
|
859
|
+
|
|
860
|
+
try {
|
|
861
|
+
// 根据存储类型选择不同的实现方式
|
|
862
|
+
if (this.blacklistStorageType === 'memory') {
|
|
863
|
+
// 内存存储方式
|
|
864
|
+
if (this.bannedIps[ip]) {
|
|
865
|
+
unbanTime = this.bannedIps[ip];
|
|
866
|
+
isBanned = now < unbanTime;
|
|
867
|
+
// 如果已过期,自动移除
|
|
868
|
+
if (!isBanned) {
|
|
869
|
+
delete this.bannedIps[ip];
|
|
870
|
+
$.log.info(`IP ${ip} 解除禁止`);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
} else {
|
|
874
|
+
// 其他存储方式(redis、mongodb、mysql)可以在这里扩展实现
|
|
875
|
+
// 目前仅实现内存存储,其他存储方式需要根据实际需求补充
|
|
876
|
+
$.log.warn(`黑名单存储类型 ${this.blacklistStorageType} 尚未实现,默认使用内存存储`);
|
|
877
|
+
if (this.bannedIps[ip]) {
|
|
878
|
+
unbanTime = this.bannedIps[ip];
|
|
879
|
+
isBanned = now < unbanTime;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
} catch (error) {
|
|
883
|
+
$.log.error(`检查IP ${ip} 是否被禁止时出错:`, error);
|
|
884
|
+
// 出错时默认视为未被禁止
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
return isBanned;
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* 异步添加IP到禁止列表
|
|
892
|
+
* @param {String} ip IP地址
|
|
893
|
+
* @param {Number} duration 禁止时长(毫秒)
|
|
894
|
+
* @returns {Promise<Boolean>} true表示添加成功,false表示添加失败
|
|
895
|
+
*/
|
|
896
|
+
MQTT.prototype.addIpToBanlist = async function (ip, duration) {
|
|
897
|
+
const now = Date.now();
|
|
898
|
+
const unbanTime = now + (duration || this.config.reconnect_detection.ip_ban_duration);
|
|
899
|
+
|
|
900
|
+
try {
|
|
901
|
+
// 根据存储类型选择不同的实现方式
|
|
902
|
+
if (this.blacklistStorageType === 'memory') {
|
|
903
|
+
// 内存存储方式
|
|
904
|
+
this.bannedIps[ip] = unbanTime;
|
|
905
|
+
} else {
|
|
906
|
+
// 其他存储方式(redis、mongodb、mysql)可以在这里扩展实现
|
|
907
|
+
// 目前仅实现内存存储,其他存储方式需要根据实际需求补充
|
|
908
|
+
$.log.warn(`黑名单存储类型 ${this.blacklistStorageType} 尚未实现,默认使用内存存储`);
|
|
909
|
+
this.bannedIps[ip] = unbanTime;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
$.log.warn(`IP ${ip} 被添加到禁止列表,限制时长: ${duration / 3600000}小时`);
|
|
913
|
+
return true;
|
|
914
|
+
} catch (error) {
|
|
915
|
+
$.log.error(`添加IP ${ip} 到禁止列表时出错:`, error);
|
|
916
|
+
return false;
|
|
917
|
+
}
|
|
918
|
+
};
|
|
919
|
+
|
|
413
920
|
module.exports = MQTT;
|
package/core/com/app/index.js
CHANGED
package/core/com/plugin/drive.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const compressing = require('compressing');
|
|
2
2
|
const Item = require('mm_machine').Item;
|
|
3
|
-
const Log = require('./log.js');
|
|
4
3
|
const conf = require('mm_config');
|
|
5
4
|
|
|
6
5
|
/**
|
|
@@ -86,12 +85,6 @@ Drive.prototype.set_config = function(config) {
|
|
|
86
85
|
* 插件初始化之后
|
|
87
86
|
*/
|
|
88
87
|
Drive.prototype.set_config_after = function() {
|
|
89
|
-
var file = this.filename;
|
|
90
|
-
var filename = file.replace('app', 'log').replace($.slash + 'plugin', '').replace('json', 'log').replace($
|
|
91
|
-
.slash + 'plugin.log', '.log');
|
|
92
|
-
this.log = new Log({
|
|
93
|
-
filename
|
|
94
|
-
});
|
|
95
88
|
var options = this.get_config_options();
|
|
96
89
|
this.options = this.merge_options(options);
|
|
97
90
|
this.backup_options();
|
package/index.js
CHANGED
|
@@ -43,8 +43,8 @@ class OS {
|
|
|
43
43
|
"static_path": "./static",
|
|
44
44
|
"proxy": {},
|
|
45
45
|
"rateLimit": {
|
|
46
|
-
"windowMs":
|
|
47
|
-
"maxRequests":
|
|
46
|
+
"windowMs": 60 * 1000, // 时间窗口,默认15分钟
|
|
47
|
+
"maxRequests": 5000, // 每个时间窗口内的最大请求数
|
|
48
48
|
"message": "请求过于频繁,请稍后再试", // 超过限制时的提示信息
|
|
49
49
|
"statusCode": 429 // 超过限制时的HTTP状态码
|
|
50
50
|
}
|
|
@@ -8,12 +8,14 @@ module.exports = function(server, config) {
|
|
|
8
8
|
// 初始化速率限制配置
|
|
9
9
|
const cg = {
|
|
10
10
|
// 默认配置
|
|
11
|
-
windowMs:
|
|
12
|
-
maxRequests:
|
|
11
|
+
windowMs: 60 * 1000, // 时间窗口,调整为1分钟(更精细的限制)
|
|
12
|
+
maxRequests: 5000, // 每个时间窗口内的最大请求数,从1000提高到5000
|
|
13
13
|
message: '请求过于频繁,请稍后再试', // 超过限制时的提示信息
|
|
14
14
|
statusCode: 429, // 超过限制时的HTTP状态码
|
|
15
|
+
// 白名单路径,这些路径不受速率限制
|
|
16
|
+
whitelistPaths: [],
|
|
15
17
|
// 合并用户配置
|
|
16
|
-
...(config && config.
|
|
18
|
+
...(config && config.rate_limit ? config.rate_limit : {})
|
|
17
19
|
};
|
|
18
20
|
|
|
19
21
|
// 生成基于IP的唯一标识符
|
|
@@ -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
|
|