mm_ip 1.0.2 → 1.0.4
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/cache/ip/black.json +5 -5
- package/cache/ip/violation.json +1 -1
- package/cache/ip/white.json +1 -1
- package/eslint.config.js +68 -0
- package/index.js +564 -570
- package/package.json +2 -2
- package/test.js +0 -11
package/index.js
CHANGED
|
@@ -9,50 +9,62 @@ require('mm_expand');
|
|
|
9
9
|
* @class
|
|
10
10
|
*/
|
|
11
11
|
class Ip {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
12
|
+
static config = {
|
|
13
|
+
max_white: 1000,
|
|
14
|
+
max_black: 1000,
|
|
15
|
+
check_interval: 60000, // 1分钟
|
|
16
|
+
max_req_per_min: 100,
|
|
17
|
+
auto_black_enable: true, // 启用自动高频拉黑
|
|
18
|
+
auto_black_threshold: 50, // 自动拉黑阈值(每分钟请求数)
|
|
19
|
+
auto_black_duration: 3600000, // 自动拉黑持续时间(1小时)
|
|
20
|
+
violation_enable: true, // 启用违规记录
|
|
21
|
+
violation_max_count: 10, // 最大违规次数
|
|
22
|
+
violation_reset_time: 86400000, // 违规记录重置时间(24小时)
|
|
23
|
+
violation_auto_black: true, // 违规次数超限自动拉黑
|
|
24
|
+
dir: './cache/ip' // 持久化存储目录
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 创建IP管理实例
|
|
28
|
+
* @param {Object} config - 配置选项
|
|
29
|
+
*/
|
|
30
|
+
constructor(config) {
|
|
31
|
+
this._config = Object.assign({}, Ip.config);
|
|
32
|
+
|
|
33
|
+
this._white = new Set();
|
|
34
|
+
this._black = new Set();
|
|
35
|
+
this._req_count = new Map();
|
|
36
|
+
this._req_time = new Map();
|
|
37
|
+
this._black_time = new Map(); // 记录拉黑时间
|
|
38
|
+
this._violation_count = new Map(); // 违规次数记录
|
|
39
|
+
this._violation_time = new Map(); // 违规时间记录
|
|
40
|
+
this._logger = $.log || console;
|
|
41
|
+
|
|
42
|
+
this.setConfig(config);
|
|
43
|
+
// 自动加载持久化数据
|
|
44
|
+
if (this._config.dir) {
|
|
45
|
+
this.load();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 设置配置选项
|
|
52
|
+
* @param {Object} config - 配置选项
|
|
53
|
+
*/
|
|
54
|
+
Ip.prototype.setConfig = function(config) {
|
|
55
|
+
if (config) {
|
|
56
|
+
Object.assign(this._config, config);
|
|
57
|
+
}
|
|
46
58
|
}
|
|
47
59
|
|
|
48
60
|
/**
|
|
49
61
|
* 设置日志器
|
|
50
62
|
* @param {Object} logger - 日志对象
|
|
51
63
|
*/
|
|
52
|
-
Ip.prototype.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
64
|
+
Ip.prototype.init = function(logger) {
|
|
65
|
+
if (logger) {
|
|
66
|
+
this._logger = logger;
|
|
67
|
+
}
|
|
56
68
|
};
|
|
57
69
|
|
|
58
70
|
/**
|
|
@@ -61,8 +73,8 @@ Ip.prototype.setup = function (logger) {
|
|
|
61
73
|
* @param {string} message - 日志消息
|
|
62
74
|
* @param {...*} args - 可选的日志参数
|
|
63
75
|
*/
|
|
64
|
-
Ip.prototype.
|
|
65
|
-
|
|
76
|
+
Ip.prototype.log = function(level, message, ...args) {
|
|
77
|
+
this._logger[level](`${this.constructor.name} ${message}`, ...args);
|
|
66
78
|
};
|
|
67
79
|
|
|
68
80
|
/**
|
|
@@ -71,23 +83,22 @@ Ip.prototype.logger = function (level, message, ...args) {
|
|
|
71
83
|
* @returns {boolean} 是否添加成功
|
|
72
84
|
* @throws {TypeError} 当IP非字符串时
|
|
73
85
|
*/
|
|
74
|
-
Ip.prototype.addWhite = function
|
|
75
|
-
|
|
86
|
+
Ip.prototype.addWhite = function(ip) {
|
|
87
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
76
88
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
89
|
+
if (this.getAllWhite().length >= this._config.max_white) {
|
|
90
|
+
this.log('warn', '白名单已满,无法添加IP:', ip);
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
81
93
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return true;
|
|
94
|
+
this._white.add(ip);
|
|
95
|
+
|
|
96
|
+
// 自动保存到持久化存储
|
|
97
|
+
if (this._config.dir) {
|
|
98
|
+
this.save();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return true;
|
|
91
102
|
};
|
|
92
103
|
|
|
93
104
|
/**
|
|
@@ -96,23 +107,23 @@ Ip.prototype.addWhite = function (ip) {
|
|
|
96
107
|
* @returns {boolean} 是否添加成功
|
|
97
108
|
* @throws {TypeError} 当IP非字符串时
|
|
98
109
|
*/
|
|
99
|
-
Ip.prototype.addBlack = function
|
|
100
|
-
|
|
110
|
+
Ip.prototype.addBlack = function(ip) {
|
|
111
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
112
|
+
|
|
113
|
+
if (this.getAllBlack().length >= this._config.max_black) {
|
|
114
|
+
this.log('warn', '黑名单已满,无法添加IP:', ip);
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
101
117
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
118
|
+
this._black.add(ip);
|
|
119
|
+
this.log('info', '添加黑名单IP:', ip);
|
|
106
120
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return true;
|
|
121
|
+
// 自动保存到持久化存储
|
|
122
|
+
if (this._config.dir) {
|
|
123
|
+
this.save();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return true;
|
|
116
127
|
};
|
|
117
128
|
|
|
118
129
|
/**
|
|
@@ -121,19 +132,19 @@ Ip.prototype.addBlack = function (ip) {
|
|
|
121
132
|
* @returns {boolean} 是否删除成功
|
|
122
133
|
* @throws {TypeError} 当IP非字符串时
|
|
123
134
|
*/
|
|
124
|
-
Ip.prototype.delWhite = function
|
|
125
|
-
|
|
135
|
+
Ip.prototype.delWhite = function(ip) {
|
|
136
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
137
|
+
|
|
138
|
+
const result = this._white.delete(ip);
|
|
139
|
+
if (result) {
|
|
140
|
+
this.log('info', '删除白名单IP:', ip);
|
|
126
141
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
this.save();
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return result;
|
|
142
|
+
// 自动保存到持久化存储
|
|
143
|
+
if (this._config.dir) {
|
|
144
|
+
this.save();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
137
148
|
};
|
|
138
149
|
|
|
139
150
|
/**
|
|
@@ -142,19 +153,19 @@ Ip.prototype.delWhite = function (ip) {
|
|
|
142
153
|
* @returns {boolean} 是否删除成功
|
|
143
154
|
* @throws {TypeError} 当IP非字符串时
|
|
144
155
|
*/
|
|
145
|
-
Ip.prototype.delBlack = function
|
|
146
|
-
|
|
156
|
+
Ip.prototype.delBlack = function(ip) {
|
|
157
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
158
|
+
|
|
159
|
+
const result = this._black.delete(ip);
|
|
160
|
+
if (result) {
|
|
161
|
+
this.log('info', '删除黑名单IP:', ip);
|
|
147
162
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
this.save();
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return result;
|
|
163
|
+
// 自动保存到持久化存储
|
|
164
|
+
if (this._config.dir) {
|
|
165
|
+
this.save();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
158
169
|
};
|
|
159
170
|
|
|
160
171
|
/**
|
|
@@ -163,9 +174,9 @@ Ip.prototype.delBlack = function (ip) {
|
|
|
163
174
|
* @returns {boolean} 是否在白名单
|
|
164
175
|
* @throws {TypeError} 当IP非字符串时
|
|
165
176
|
*/
|
|
166
|
-
Ip.prototype.isWhite = function
|
|
167
|
-
|
|
168
|
-
|
|
177
|
+
Ip.prototype.isWhite = function(ip) {
|
|
178
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
179
|
+
return this._white.has(ip);
|
|
169
180
|
};
|
|
170
181
|
|
|
171
182
|
/**
|
|
@@ -174,9 +185,9 @@ Ip.prototype.isWhite = function (ip) {
|
|
|
174
185
|
* @returns {boolean} 是否在黑名单
|
|
175
186
|
* @throws {TypeError} 当IP非字符串时
|
|
176
187
|
*/
|
|
177
|
-
Ip.prototype.isBlack = function
|
|
178
|
-
|
|
179
|
-
|
|
188
|
+
Ip.prototype.isBlack = function(ip) {
|
|
189
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
190
|
+
return this._black.has(ip);
|
|
180
191
|
};
|
|
181
192
|
|
|
182
193
|
/**
|
|
@@ -185,54 +196,53 @@ Ip.prototype.isBlack = function (ip) {
|
|
|
185
196
|
* @returns {boolean} 是否允许请求
|
|
186
197
|
* @throws {TypeError} 当IP非字符串时
|
|
187
198
|
*/
|
|
188
|
-
Ip.prototype.record = function
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return true;
|
|
199
|
+
Ip.prototype.record = function(ip) {
|
|
200
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
201
|
+
|
|
202
|
+
// 检查黑名单(包括过期检查)
|
|
203
|
+
if (this.isBlack(ip)) {
|
|
204
|
+
// 检查是否过期
|
|
205
|
+
if (this._isBlackExpired(ip)) {
|
|
206
|
+
this.delBlack(ip);
|
|
207
|
+
} else {
|
|
208
|
+
this.log('warn', '黑名单IP请求被拒绝:', ip);
|
|
209
|
+
// 记录违规:黑名单IP尝试访问
|
|
210
|
+
if (this._config.violation_enable) {
|
|
211
|
+
this.recordViolation(ip, '黑名单IP尝试访问');
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// 白名单直接通过
|
|
218
|
+
if (this.isWhite(ip)) {
|
|
219
|
+
this._updateReqCount(ip);
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// 检查频率限制
|
|
224
|
+
if (!this._checkRateLimit(ip)) {
|
|
225
|
+
this.log('warn', 'IP请求频率超限:', ip);
|
|
226
|
+
|
|
227
|
+
// 记录违规:请求频率超限
|
|
228
|
+
if (this._config.violation_enable) {
|
|
229
|
+
this.recordViolation(ip, '请求频率超限');
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// 检查是否触发自动拉黑
|
|
233
|
+
if (this._config.auto_black_enable) {
|
|
234
|
+
const rate = this.getReqRate(ip);
|
|
235
|
+
if (rate >= this._config.auto_black_threshold) {
|
|
236
|
+
this._autoBlack(ip);
|
|
237
|
+
return false;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
this._updateReqCount(ip);
|
|
245
|
+
return true;
|
|
236
246
|
};
|
|
237
247
|
|
|
238
248
|
/**
|
|
@@ -241,9 +251,9 @@ Ip.prototype.record = function (ip) {
|
|
|
241
251
|
* @returns {number} 请求次数
|
|
242
252
|
* @throws {TypeError} 当IP非字符串时
|
|
243
253
|
*/
|
|
244
|
-
Ip.prototype.getReqCount = function
|
|
245
|
-
|
|
246
|
-
|
|
254
|
+
Ip.prototype.getReqCount = function(ip) {
|
|
255
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
256
|
+
return this._req_count.get(ip) || 0;
|
|
247
257
|
};
|
|
248
258
|
|
|
249
259
|
/**
|
|
@@ -252,106 +262,106 @@ Ip.prototype.getReqCount = function (ip) {
|
|
|
252
262
|
* @returns {number} 请求频率(每分钟请求数)
|
|
253
263
|
* @throws {TypeError} 当IP非字符串时
|
|
254
264
|
*/
|
|
255
|
-
Ip.prototype.getReqRate = function
|
|
256
|
-
|
|
265
|
+
Ip.prototype.getReqRate = function(ip) {
|
|
266
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
257
267
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
268
|
+
const now = Date.now();
|
|
269
|
+
const lastTime = this._req_time.get(ip) || now;
|
|
270
|
+
const count = this._req_count.get(ip) || 0;
|
|
261
271
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
272
|
+
if (now - lastTime < this._config.check_interval) {
|
|
273
|
+
// 在检查间隔内,计算实际频率
|
|
274
|
+
const elapsed = Math.max(1, now - lastTime); // 避免除零
|
|
275
|
+
return Math.round((count / elapsed) * this._config.check_interval);
|
|
276
|
+
} else {
|
|
277
|
+
// 超过检查间隔,频率为0
|
|
278
|
+
return 0;
|
|
279
|
+
}
|
|
270
280
|
};
|
|
271
281
|
|
|
272
282
|
/**
|
|
273
283
|
* 获取所有IP的请求统计信息
|
|
274
284
|
* @returns {Object} IP统计信息对象
|
|
275
285
|
*/
|
|
276
|
-
Ip.prototype.getAllStats = function
|
|
277
|
-
|
|
278
|
-
|
|
286
|
+
Ip.prototype.getAllStats = function() {
|
|
287
|
+
const stats = {};
|
|
288
|
+
const now = Date.now();
|
|
279
289
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
290
|
+
// 收集所有有请求记录的IP
|
|
291
|
+
for (const ip of this.getAllReqIps()) {
|
|
292
|
+
const lastTime = this._req_time.get(ip) || now;
|
|
293
|
+
const isActive = now - lastTime < this._config.check_interval;
|
|
284
294
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
295
|
+
stats[ip] = {
|
|
296
|
+
count: this.getReqCount(ip),
|
|
297
|
+
rate: isActive ? this.getReqRate(ip) : 0,
|
|
298
|
+
last_time: lastTime,
|
|
299
|
+
is_white: this.isWhite(ip),
|
|
300
|
+
is_black: this.isBlack(ip),
|
|
301
|
+
black_time: this.getBlackExpireTime(ip)
|
|
302
|
+
};
|
|
303
|
+
}
|
|
294
304
|
|
|
295
|
-
|
|
305
|
+
return stats;
|
|
296
306
|
};
|
|
297
307
|
|
|
298
308
|
/**
|
|
299
309
|
* 获取所有白名单IP
|
|
300
310
|
* @returns {Array} 白名单IP数组
|
|
301
311
|
*/
|
|
302
|
-
Ip.prototype.getAllWhite = function
|
|
303
|
-
|
|
312
|
+
Ip.prototype.getAllWhite = function() {
|
|
313
|
+
return Array.from(this._white);
|
|
304
314
|
};
|
|
305
315
|
|
|
306
316
|
/**
|
|
307
317
|
* 获取所有黑名单IP
|
|
308
318
|
* @returns {Array} 黑名单IP数组
|
|
309
319
|
*/
|
|
310
|
-
Ip.prototype.getAllBlack = function
|
|
311
|
-
|
|
320
|
+
Ip.prototype.getAllBlack = function() {
|
|
321
|
+
return Array.from(this._black);
|
|
312
322
|
};
|
|
313
323
|
|
|
314
324
|
/**
|
|
315
325
|
* 获取所有有请求记录的IP
|
|
316
326
|
* @returns {Array} 有请求记录的IP数组
|
|
317
327
|
*/
|
|
318
|
-
Ip.prototype.getAllReqIps = function
|
|
319
|
-
|
|
328
|
+
Ip.prototype.getAllReqIps = function() {
|
|
329
|
+
return Array.from(this._req_count.keys());
|
|
320
330
|
};
|
|
321
331
|
|
|
322
332
|
/**
|
|
323
333
|
* 清空白名单
|
|
324
334
|
*/
|
|
325
335
|
Ip.prototype.clearWhite = function() {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
336
|
+
this._white.clear();
|
|
337
|
+
this.log('info', '清空白名单');
|
|
338
|
+
|
|
339
|
+
// 自动保存到持久化存储
|
|
340
|
+
if (this._config.dir) {
|
|
341
|
+
this.save();
|
|
342
|
+
}
|
|
333
343
|
};
|
|
334
344
|
|
|
335
345
|
/**
|
|
336
346
|
* 清空黑名单
|
|
337
347
|
*/
|
|
338
348
|
Ip.prototype.clearBlack = function() {
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
349
|
+
this._black.clear();
|
|
350
|
+
this.log('info', '清空黑名单');
|
|
351
|
+
|
|
352
|
+
// 自动保存到持久化存储
|
|
353
|
+
if (this._config.dir) {
|
|
354
|
+
this.save();
|
|
355
|
+
}
|
|
346
356
|
};
|
|
347
357
|
|
|
348
358
|
/**
|
|
349
359
|
* 清空所有请求记录
|
|
350
360
|
*/
|
|
351
|
-
Ip.prototype.clearReq = function
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
361
|
+
Ip.prototype.clearReq = function() {
|
|
362
|
+
this._req_count.clear();
|
|
363
|
+
this._req_time.clear();
|
|
364
|
+
this.log('info', '清空请求记录');
|
|
355
365
|
};
|
|
356
366
|
|
|
357
367
|
/**
|
|
@@ -360,22 +370,22 @@ Ip.prototype.clearReq = function () {
|
|
|
360
370
|
* @param {string} ip - IP地址
|
|
361
371
|
* @returns {boolean} 是否允许请求
|
|
362
372
|
*/
|
|
363
|
-
Ip.prototype._checkRateLimit = function
|
|
364
|
-
|
|
365
|
-
|
|
373
|
+
Ip.prototype._checkRateLimit = function(ip) {
|
|
374
|
+
const now = Date.now();
|
|
375
|
+
const lastTime = this._req_time.get(ip) || 0;
|
|
366
376
|
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
+
// 检查时间间隔
|
|
378
|
+
if (now - lastTime < this._config.check_interval) {
|
|
379
|
+
const count = this.getReqCount(ip);
|
|
380
|
+
if (count >= this._config.max_req_per_min) {
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
// 重置计数
|
|
385
|
+
this._req_count.set(ip, 0);
|
|
386
|
+
}
|
|
377
387
|
|
|
378
|
-
|
|
388
|
+
return true;
|
|
379
389
|
};
|
|
380
390
|
|
|
381
391
|
/**
|
|
@@ -383,10 +393,10 @@ Ip.prototype._checkRateLimit = function (ip) {
|
|
|
383
393
|
* @private
|
|
384
394
|
* @param {string} ip - IP地址
|
|
385
395
|
*/
|
|
386
|
-
Ip.prototype._updateReqCount = function
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
396
|
+
Ip.prototype._updateReqCount = function(ip) {
|
|
397
|
+
const count = this.getReqCount(ip) + 1;
|
|
398
|
+
this._req_count.set(ip, count);
|
|
399
|
+
this._req_time.set(ip, Date.now());
|
|
390
400
|
};
|
|
391
401
|
|
|
392
402
|
/**
|
|
@@ -395,9 +405,9 @@ Ip.prototype._updateReqCount = function (ip) {
|
|
|
395
405
|
* @param {string} ip - IP地址
|
|
396
406
|
* @returns {boolean} 是否有效IP
|
|
397
407
|
*/
|
|
398
|
-
Ip.prototype._validIp = function
|
|
399
|
-
|
|
400
|
-
|
|
408
|
+
Ip.prototype._validIp = function(ip) {
|
|
409
|
+
const ipRegex = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
|
|
410
|
+
return ipRegex.test(ip);
|
|
401
411
|
};
|
|
402
412
|
|
|
403
413
|
/**
|
|
@@ -405,18 +415,18 @@ Ip.prototype._validIp = function (ip) {
|
|
|
405
415
|
* @param {Array} ip_list - IP地址数组
|
|
406
416
|
* @returns {number} 成功添加的数量
|
|
407
417
|
*/
|
|
408
|
-
Ip.prototype.addWhiteBatch = function
|
|
409
|
-
|
|
418
|
+
Ip.prototype.addWhiteBatch = function(ip_list) {
|
|
419
|
+
if (!Array.isArray(ip_list)) throw new TypeError('ip_list must be array');
|
|
410
420
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
421
|
+
let success = 0;
|
|
422
|
+
for (const ip of ip_list) {
|
|
423
|
+
if (typeof ip === 'string' && this._validIp(ip)) {
|
|
424
|
+
if (this.addWhite(ip)) {
|
|
425
|
+
success++;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
return success;
|
|
420
430
|
};
|
|
421
431
|
|
|
422
432
|
/**
|
|
@@ -424,18 +434,18 @@ Ip.prototype.addWhiteBatch = function (ip_list) {
|
|
|
424
434
|
* @param {Array} ip_list - IP地址数组
|
|
425
435
|
* @returns {number} 成功添加的数量
|
|
426
436
|
*/
|
|
427
|
-
Ip.prototype.addBlackBatch = function
|
|
428
|
-
|
|
437
|
+
Ip.prototype.addBlackBatch = function(ip_list) {
|
|
438
|
+
if (!Array.isArray(ip_list)) throw new TypeError('ip_list must be array');
|
|
429
439
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
440
|
+
let success = 0;
|
|
441
|
+
for (const ip of ip_list) {
|
|
442
|
+
if (typeof ip === 'string' && this._validIp(ip)) {
|
|
443
|
+
if (this.addBlack(ip)) {
|
|
444
|
+
success++;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return success;
|
|
439
449
|
};
|
|
440
450
|
|
|
441
451
|
/**
|
|
@@ -445,41 +455,40 @@ Ip.prototype.addBlackBatch = function (ip_list) {
|
|
|
445
455
|
* @returns {boolean} 是否拉黑成功
|
|
446
456
|
* @throws {TypeError} 当IP非字符串时
|
|
447
457
|
*/
|
|
448
|
-
Ip.prototype.blackByHighFreq = function
|
|
449
|
-
|
|
458
|
+
Ip.prototype.blackByHighFreq = function(ip, duration) {
|
|
459
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
450
460
|
|
|
451
|
-
|
|
461
|
+
const black_duration = duration || this._config.auto_black_duration;
|
|
452
462
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
463
|
+
if (this.addBlack(ip)) {
|
|
464
|
+
this._black_time.set(ip, Date.now() + black_duration);
|
|
465
|
+
this.log('warn', '手动高频拉黑IP:', ip, '持续时间:', black_duration + 'ms');
|
|
466
|
+
return true;
|
|
467
|
+
}
|
|
458
468
|
|
|
459
|
-
|
|
469
|
+
return false;
|
|
460
470
|
};
|
|
461
471
|
|
|
462
472
|
/**
|
|
463
473
|
* 检查并清理过期的黑名单IP(私有方法)
|
|
464
474
|
* @private
|
|
465
475
|
*/
|
|
466
|
-
Ip.prototype._cleanExpiredBlack = function
|
|
467
|
-
|
|
468
|
-
|
|
476
|
+
Ip.prototype._cleanExpiredBlack = function() {
|
|
477
|
+
const now = Date.now();
|
|
478
|
+
const expired = [];
|
|
469
479
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
480
|
+
// 获取所有有过期时间的黑名单IP
|
|
481
|
+
for (const ip of this.getAllBlack()) {
|
|
482
|
+
const expireTime = this.getBlackExpireTime(ip);
|
|
483
|
+
if (expireTime && now > expireTime) {
|
|
484
|
+
expired.push(ip);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
477
487
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
}
|
|
488
|
+
for (const ip of expired) {
|
|
489
|
+
this.delBlack(ip);
|
|
490
|
+
this._black_time.delete(ip);
|
|
491
|
+
}
|
|
483
492
|
};
|
|
484
493
|
|
|
485
494
|
/**
|
|
@@ -488,11 +497,11 @@ Ip.prototype._cleanExpiredBlack = function () {
|
|
|
488
497
|
* @param {string} ip - IP地址
|
|
489
498
|
* @returns {boolean} 是否过期
|
|
490
499
|
*/
|
|
491
|
-
Ip.prototype._isBlackExpired = function
|
|
492
|
-
|
|
493
|
-
|
|
500
|
+
Ip.prototype._isBlackExpired = function(ip) {
|
|
501
|
+
const expireTime = this.getBlackExpireTime(ip);
|
|
502
|
+
if (!expireTime) return false; // 没有设置过期时间,永不过期
|
|
494
503
|
|
|
495
|
-
|
|
504
|
+
return Date.now() > expireTime;
|
|
496
505
|
};
|
|
497
506
|
|
|
498
507
|
/**
|
|
@@ -500,13 +509,13 @@ Ip.prototype._isBlackExpired = function (ip) {
|
|
|
500
509
|
* @private
|
|
501
510
|
* @param {string} ip - IP地址
|
|
502
511
|
*/
|
|
503
|
-
Ip.prototype._autoBlack = function
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
512
|
+
Ip.prototype._autoBlack = function(ip) {
|
|
513
|
+
if (this.addBlack(ip)) {
|
|
514
|
+
this._black_time.set(ip, Date.now() + this._config.auto_black_duration);
|
|
515
|
+
this.log('warn', '自动高频拉黑IP:', ip,
|
|
516
|
+
'频率:', this.getReqRate(ip) + '/分钟',
|
|
517
|
+
'持续时间:', this._config.auto_black_duration + 'ms');
|
|
518
|
+
}
|
|
510
519
|
};
|
|
511
520
|
|
|
512
521
|
/**
|
|
@@ -515,23 +524,23 @@ Ip.prototype._autoBlack = function (ip) {
|
|
|
515
524
|
* @returns {number|null} 过期时间戳(毫秒),null表示永不过期
|
|
516
525
|
* @throws {TypeError} 当IP非字符串时
|
|
517
526
|
*/
|
|
518
|
-
Ip.prototype.getBlackExpireTime = function
|
|
519
|
-
|
|
527
|
+
Ip.prototype.getBlackExpireTime = function(ip) {
|
|
528
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
520
529
|
|
|
521
|
-
|
|
530
|
+
if (!this.isBlack(ip)) return null;
|
|
522
531
|
|
|
523
|
-
|
|
524
|
-
|
|
532
|
+
const expireTime = this._black_time.get(ip);
|
|
533
|
+
return expireTime || null;
|
|
525
534
|
};
|
|
526
535
|
|
|
527
536
|
/**
|
|
528
537
|
* 清理所有过期的黑名单IP
|
|
529
538
|
* @returns {number} 清理的数量
|
|
530
539
|
*/
|
|
531
|
-
Ip.prototype.cleanExpiredBlack = function
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
540
|
+
Ip.prototype.cleanExpiredBlack = function() {
|
|
541
|
+
const beforeCount = this.getAllBlack().length;
|
|
542
|
+
this._cleanExpiredBlack();
|
|
543
|
+
return beforeCount - this.getAllBlack().length;
|
|
535
544
|
};
|
|
536
545
|
|
|
537
546
|
/**
|
|
@@ -541,26 +550,28 @@ Ip.prototype.cleanExpiredBlack = function () {
|
|
|
541
550
|
* @returns {number} 当前违规次数
|
|
542
551
|
*/
|
|
543
552
|
Ip.prototype.recordViolation = function(ip, reason) {
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
553
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
554
|
+
if (!this._config.violation_enable) return 0;
|
|
555
|
+
|
|
556
|
+
this._cleanExpiredViolation();
|
|
557
|
+
|
|
558
|
+
const now = Date.now();
|
|
559
|
+
const count = this.getViolationCount(ip) + 1;
|
|
560
|
+
|
|
561
|
+
this._violation_count.set(ip, count);
|
|
562
|
+
this._violation_time.set(ip, now);
|
|
563
|
+
|
|
564
|
+
// 检查是否达到自动拉黑条件
|
|
565
|
+
if (this._config.violation_auto_black && count >= this._config.violation_max_count) {
|
|
566
|
+
this.blackByHighFreq(ip, this._config.auto_black_duration);
|
|
567
|
+
this.log('warn', 'IP因违规次数超限自动拉黑:', {
|
|
568
|
+
ip,
|
|
569
|
+
count,
|
|
570
|
+
duration: this._config.auto_black_duration
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return count;
|
|
564
575
|
};
|
|
565
576
|
|
|
566
577
|
/**
|
|
@@ -569,10 +580,10 @@ Ip.prototype.recordViolation = function(ip, reason) {
|
|
|
569
580
|
* @returns {number} 违规次数
|
|
570
581
|
*/
|
|
571
582
|
Ip.prototype.getViolationCount = function(ip) {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
583
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
584
|
+
|
|
585
|
+
this._cleanExpiredViolation();
|
|
586
|
+
return this._violation_count.get(ip) || 0;
|
|
576
587
|
};
|
|
577
588
|
|
|
578
589
|
/**
|
|
@@ -581,13 +592,13 @@ Ip.prototype.getViolationCount = function(ip) {
|
|
|
581
592
|
* @returns {boolean} 是否成功重置
|
|
582
593
|
*/
|
|
583
594
|
Ip.prototype.resetViolation = function(ip) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
595
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
596
|
+
|
|
597
|
+
const hadRecord = this._violation_count.has(ip);
|
|
598
|
+
this._violation_count.delete(ip);
|
|
599
|
+
this._violation_time.delete(ip);
|
|
600
|
+
|
|
601
|
+
return hadRecord;
|
|
591
602
|
};
|
|
592
603
|
|
|
593
604
|
/**
|
|
@@ -595,7 +606,7 @@ Ip.prototype.resetViolation = function(ip) {
|
|
|
595
606
|
* @returns {Array} 有违规记录的IP数组
|
|
596
607
|
*/
|
|
597
608
|
Ip.prototype.getAllViolationIps = function() {
|
|
598
|
-
|
|
609
|
+
return Array.from(this._violation_count.keys());
|
|
599
610
|
};
|
|
600
611
|
|
|
601
612
|
/**
|
|
@@ -604,15 +615,15 @@ Ip.prototype.getAllViolationIps = function() {
|
|
|
604
615
|
* @returns {Object|null} 违规信息
|
|
605
616
|
*/
|
|
606
617
|
Ip.prototype.getViolationInfo = function(ip) {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
618
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
619
|
+
|
|
620
|
+
const count = this.getViolationCount(ip);
|
|
621
|
+
if (count === 0) return null;
|
|
622
|
+
|
|
623
|
+
return {
|
|
624
|
+
count: count,
|
|
625
|
+
time: this._violation_time.get(ip)
|
|
626
|
+
};
|
|
616
627
|
};
|
|
617
628
|
|
|
618
629
|
/**
|
|
@@ -620,14 +631,14 @@ Ip.prototype.getViolationInfo = function(ip) {
|
|
|
620
631
|
* @returns {Object} 违规统计信息
|
|
621
632
|
*/
|
|
622
633
|
Ip.prototype.getAllViolations = function() {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
634
|
+
this._cleanExpiredViolation();
|
|
635
|
+
|
|
636
|
+
const violations = {};
|
|
637
|
+
for (const ip of this.getAllViolationIps()) {
|
|
638
|
+
violations[ip] = this.getViolationInfo(ip);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return violations;
|
|
631
642
|
};
|
|
632
643
|
|
|
633
644
|
/**
|
|
@@ -635,34 +646,34 @@ Ip.prototype.getAllViolations = function() {
|
|
|
635
646
|
* @private
|
|
636
647
|
*/
|
|
637
648
|
Ip.prototype._cleanExpiredViolation = function() {
|
|
638
|
-
|
|
639
|
-
|
|
649
|
+
const now = Date.now();
|
|
650
|
+
const expired = [];
|
|
640
651
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
652
|
+
// 直接访问内部数据结构避免递归调用
|
|
653
|
+
for (const [ip, time] of this._violation_time) {
|
|
654
|
+
if (now - time > this._config.violation_reset_time) {
|
|
655
|
+
expired.push(ip);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
647
658
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
659
|
+
for (const ip of expired) {
|
|
660
|
+
this._violation_count.delete(ip);
|
|
661
|
+
this._violation_time.delete(ip);
|
|
662
|
+
}
|
|
652
663
|
};
|
|
653
664
|
|
|
654
665
|
/**
|
|
655
666
|
* 清空所有违规记录
|
|
656
667
|
*/
|
|
657
668
|
Ip.prototype.clearViolations = function() {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
669
|
+
this._violation_count.clear();
|
|
670
|
+
this._violation_time.clear();
|
|
671
|
+
this.log('info', '清空所有违规记录');
|
|
672
|
+
|
|
673
|
+
// 自动保存到持久化存储
|
|
674
|
+
if (this._config.dir) {
|
|
675
|
+
this.save();
|
|
676
|
+
}
|
|
666
677
|
};
|
|
667
678
|
|
|
668
679
|
/**
|
|
@@ -670,53 +681,53 @@ Ip.prototype.clearViolations = function() {
|
|
|
670
681
|
* @returns {boolean} 是否保存成功
|
|
671
682
|
*/
|
|
672
683
|
Ip.prototype.save = function() {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
684
|
+
try {
|
|
685
|
+
const fs = require('fs');
|
|
686
|
+
const path = require('path');
|
|
687
|
+
|
|
688
|
+
// 确保存储目录存在
|
|
689
|
+
const dir = this._config.dir;
|
|
690
|
+
dir.addDir();
|
|
691
|
+
|
|
692
|
+
// 分别保存不同数据到不同文件
|
|
693
|
+
const timestamp = Date.now();
|
|
694
|
+
|
|
695
|
+
// 保存白名单数据
|
|
696
|
+
const whiteData = {
|
|
697
|
+
white: this.getAllWhite(),
|
|
698
|
+
timestamp: timestamp
|
|
699
|
+
};
|
|
700
|
+
const whitePath = path.join(dir, 'white.json');
|
|
701
|
+
fs.writeFileSync(whitePath, JSON.stringify(whiteData, null, 2), 'utf8');
|
|
702
|
+
|
|
703
|
+
// 保存黑名单数据
|
|
704
|
+
const blackData = {
|
|
705
|
+
black: this.getAllBlack(),
|
|
706
|
+
black_time: Array.from(this._black_time.entries()),
|
|
707
|
+
timestamp: timestamp
|
|
708
|
+
};
|
|
709
|
+
const blackPath = path.join(dir, 'black.json');
|
|
710
|
+
fs.writeFileSync(blackPath, JSON.stringify(blackData, null, 2), 'utf8');
|
|
711
|
+
|
|
712
|
+
// 保存违规记录数据
|
|
713
|
+
const violationData = {
|
|
714
|
+
violation_count: Array.from(this._violation_count.entries()),
|
|
715
|
+
violation_time: Array.from(this._violation_time.entries()),
|
|
716
|
+
timestamp: timestamp
|
|
717
|
+
};
|
|
718
|
+
const violationPath = path.join(dir, 'violation.json');
|
|
719
|
+
fs.writeFileSync(violationPath, JSON.stringify(violationData, null, 2), 'utf8');
|
|
720
|
+
|
|
721
|
+
this.log('info', 'IP数据已分文件保存:', {
|
|
722
|
+
white: whitePath,
|
|
723
|
+
black: blackPath,
|
|
724
|
+
violation: violationPath
|
|
725
|
+
});
|
|
726
|
+
return true;
|
|
727
|
+
} catch (err) {
|
|
728
|
+
this.log('error', '保存IP数据失败:', err.message);
|
|
729
|
+
return false;
|
|
730
|
+
}
|
|
720
731
|
};
|
|
721
732
|
|
|
722
733
|
/**
|
|
@@ -724,101 +735,87 @@ Ip.prototype.save = function() {
|
|
|
724
735
|
* @returns {boolean} 是否加载成功
|
|
725
736
|
*/
|
|
726
737
|
Ip.prototype.load = function() {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
} else {
|
|
809
|
-
this.logger('info', 'IP数据加载完成,统计:', {
|
|
810
|
-
white_count: this.getAllWhite().length,
|
|
811
|
-
black_count: this.getAllBlack().length,
|
|
812
|
-
violation_count: this.getAllViolationIps().length,
|
|
813
|
-
files_loaded: loadedCount
|
|
814
|
-
});
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
return true;
|
|
818
|
-
} catch (err) {
|
|
819
|
-
this.logger('error', '加载IP数据失败:', err.message);
|
|
820
|
-
return false;
|
|
821
|
-
}
|
|
738
|
+
try {
|
|
739
|
+
const fs = require('fs');
|
|
740
|
+
const path = require('path');
|
|
741
|
+
|
|
742
|
+
const dir = this._config.dir;
|
|
743
|
+
let loadedCount = 0;
|
|
744
|
+
|
|
745
|
+
// 加载白名单数据
|
|
746
|
+
const whitePath = path.join(dir, 'white.json');
|
|
747
|
+
if (fs.existsSync(whitePath)) {
|
|
748
|
+
const content = fs.readFileSync(whitePath, 'utf8');
|
|
749
|
+
const data = JSON.parse(content);
|
|
750
|
+
|
|
751
|
+
if (Array.isArray(data.white)) {
|
|
752
|
+
for (const ip of data.white) {
|
|
753
|
+
if (typeof ip === 'string') {
|
|
754
|
+
this._white.add(ip);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
loadedCount++;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// 加载黑名单数据
|
|
762
|
+
const blackPath = path.join(dir, 'black.json');
|
|
763
|
+
if (fs.existsSync(blackPath)) {
|
|
764
|
+
const content = fs.readFileSync(blackPath, 'utf8');
|
|
765
|
+
const data = JSON.parse(content);
|
|
766
|
+
|
|
767
|
+
// 加载黑名单
|
|
768
|
+
if (Array.isArray(data.black)) {
|
|
769
|
+
for (const ip of data.black) {
|
|
770
|
+
if (typeof ip === 'string') {
|
|
771
|
+
this._black.add(ip);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// 加载黑名单过期时间
|
|
777
|
+
if (Array.isArray(data.black_time)) {
|
|
778
|
+
for (const [ip, time] of data.black_time) {
|
|
779
|
+
if (typeof ip === 'string' && typeof time === 'number') {
|
|
780
|
+
this._black_time.set(ip, time);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
loadedCount++;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// 加载违规记录数据
|
|
789
|
+
const violationPath = path.join(dir, 'violation.json');
|
|
790
|
+
if (fs.existsSync(violationPath)) {
|
|
791
|
+
const content = fs.readFileSync(violationPath, 'utf8');
|
|
792
|
+
const data = JSON.parse(content);
|
|
793
|
+
|
|
794
|
+
// 加载违规记录
|
|
795
|
+
if (Array.isArray(data.violation_count)) {
|
|
796
|
+
for (const [ip, count] of data.violation_count) {
|
|
797
|
+
if (typeof ip === 'string' && typeof count === 'number') {
|
|
798
|
+
this._violation_count.set(ip, count);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
if (Array.isArray(data.violation_time)) {
|
|
804
|
+
for (const [ip, time] of data.violation_time) {
|
|
805
|
+
if (typeof ip === 'string' && typeof time === 'number') {
|
|
806
|
+
this._violation_time.set(ip, time);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
loadedCount++;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
return true;
|
|
815
|
+
} catch (err) {
|
|
816
|
+
this.log('error', '加载IP数据失败:', err.message);
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
822
819
|
};
|
|
823
820
|
|
|
824
821
|
/**
|
|
@@ -826,26 +823,23 @@ Ip.prototype.load = function() {
|
|
|
826
823
|
* @param {number} interval - 保存间隔(毫秒),默认5分钟
|
|
827
824
|
*/
|
|
828
825
|
Ip.prototype.autoSave = function(interval = 300000) {
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
this.logger('info', '自动保存已启动,间隔:', interval, 'ms');
|
|
826
|
+
if (this._autoSaveTimer) {
|
|
827
|
+
clearInterval(this._autoSaveTimer);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
this._autoSaveTimer = setInterval(() => {
|
|
831
|
+
this.save();
|
|
832
|
+
}, interval);
|
|
838
833
|
};
|
|
839
834
|
|
|
840
835
|
/**
|
|
841
836
|
* 停止自动保存
|
|
842
837
|
*/
|
|
843
838
|
Ip.prototype.stopAutoSave = function() {
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
}
|
|
839
|
+
if (this._autoSaveTimer) {
|
|
840
|
+
clearInterval(this._autoSaveTimer);
|
|
841
|
+
this._autoSaveTimer = null;
|
|
842
|
+
}
|
|
849
843
|
};
|
|
850
844
|
|
|
851
845
|
/**
|
|
@@ -855,61 +849,61 @@ Ip.prototype.stopAutoSave = function() {
|
|
|
855
849
|
* @throws {TypeError} 当IP非字符串时
|
|
856
850
|
*/
|
|
857
851
|
Ip.prototype.del = function(ip) {
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
return result;
|
|
852
|
+
if (typeof ip !== 'string') throw new TypeError('ip must be string');
|
|
853
|
+
|
|
854
|
+
const result = {
|
|
855
|
+
black: false,
|
|
856
|
+
white: false,
|
|
857
|
+
violation: false,
|
|
858
|
+
black_time: false,
|
|
859
|
+
violation_count: false,
|
|
860
|
+
violation_time: false
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
// 从黑名单删除
|
|
864
|
+
if (this._black.has(ip)) {
|
|
865
|
+
this._black.delete(ip);
|
|
866
|
+
result.black = true;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// 从白名单删除
|
|
870
|
+
if (this._white.has(ip)) {
|
|
871
|
+
this._white.delete(ip);
|
|
872
|
+
result.white = true;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// 从违规记录删除
|
|
876
|
+
if (this._violation_count.has(ip)) {
|
|
877
|
+
this._violation_count.delete(ip);
|
|
878
|
+
result.violation_count = true;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
if (this._violation_time.has(ip)) {
|
|
882
|
+
this._violation_time.delete(ip);
|
|
883
|
+
result.violation_time = true;
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
// 从黑名单过期时间记录删除
|
|
887
|
+
if (this._black_time.has(ip)) {
|
|
888
|
+
this._black_time.delete(ip);
|
|
889
|
+
result.black_time = true;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// 如果有任何删除操作,记录日志并自动保存
|
|
893
|
+
if (Object.values(result).some(Boolean)) {
|
|
894
|
+
// 自动保存到持久化存储
|
|
895
|
+
if (this._config.dir) {
|
|
896
|
+
this.save();
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
return result;
|
|
909
901
|
};
|
|
910
902
|
|
|
911
903
|
if (global.$ && !$.ip) {
|
|
912
|
-
|
|
904
|
+
$.ip = new Ip();
|
|
913
905
|
}
|
|
914
906
|
|
|
915
|
-
module.exports = {
|
|
907
|
+
module.exports = {
|
|
908
|
+
Ip
|
|
909
|
+
};
|