mm_mongodb 1.4.4 → 1.4.6

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.js CHANGED
@@ -1,14 +1,20 @@
1
1
  /**
2
- * @fileOverview mongodb_cache缓存帮助类函数
2
+ * @fileOverview MongoDB缓存帮助类函数
3
3
  * @author <a href="http://qww.elins.cn">邱文武</a>
4
4
  * @version 1.2
5
5
  */
6
- const {
7
- MongoDB
8
- } = require('./index.js');
6
+ const { MongoDB } = require('./index.js');
9
7
 
10
- /* 创建Mongodb_cache帮助类函数 */
11
- /// 构造函数
8
+ /**
9
+ * 确保全局$对象存在
10
+ */
11
+ if (!global.$) {
12
+ global.$ = {};
13
+ }
14
+
15
+ /**
16
+ * @description MongoDB缓存帮助类
17
+ */
12
18
  class Mongodb_cache extends MongoDB {
13
19
  /**
14
20
  * @description 创建Mongodb_cache帮助类函数 (构造函数)
@@ -18,127 +24,442 @@ class Mongodb_cache extends MongoDB {
18
24
  */
19
25
  constructor(scope, dir, config) {
20
26
  super(scope, dir, config);
27
+ // 默认使用键值对表
28
+ this.table = 'keyValue';
29
+ // 数据库连接状态
30
+ this.connected = false;
31
+ // 连接Promise缓存
32
+ this.connectPromise = null;
33
+ // 连接重试次数
34
+ this.connectAttempts = 0;
35
+ this.maxConnectAttempts = 3;
36
+ }
37
+
38
+ /**
39
+ * @description 确保数据库连接已建立
40
+ * @private
41
+ * @return {Promise<void>}
42
+ */
43
+ async _ensureConnected() {
44
+ // 如果已有连接Promise,等待其完成
45
+ if (this.connectPromise) {
46
+ return this.connectPromise;
47
+ }
21
48
 
22
- // 消息订阅器
23
- this.conn_rss;
24
-
25
- // 订阅回调函数
26
- this.func_list = {};
49
+ // 创建新的连接Promise
50
+ this.connectPromise = (async () => {
51
+ try {
52
+ // 检查连接状态
53
+ if (!this.db || !this.connected) {
54
+ // 记录连接尝试
55
+ this.connectAttempts++;
56
+
57
+ // 尝试连接
58
+ await super.open();
59
+
60
+ // 检查连接是否成功
61
+ if (this.db && this.db.collection) {
62
+ this.connected = true;
63
+ this.connectAttempts = 0;
64
+
65
+ // 确保索引已创建
66
+ await this._ensureIndexes();
67
+
68
+ $.log.info(`[MongoDB cache] 成功连接到数据库,表名: ${this.table}`);
69
+ } else {
70
+ throw new Error('数据库连接失败,db对象为空');
71
+ }
72
+ }
73
+ } catch (error) {
74
+ $.log.error(`[MongoDB cache] 连接失败 (尝试 ${this.connectAttempts}/${this.maxConnectAttempts}):`, error.message);
75
+
76
+ // 重置连接Promise,允许下次重试
77
+ this.connectPromise = null;
78
+
79
+ // 如果尝试次数未超过限制,抛出错误让上层处理
80
+ if (this.connectAttempts < this.maxConnectAttempts) {
81
+ throw error;
82
+ } else {
83
+ // 超过重试次数,记录严重错误
84
+ $.log.error('[MongoDB cache] 达到最大连接重试次数,连接失败');
85
+ throw new Error('数据库连接失败,已达到最大重试次数');
86
+ }
87
+ }
88
+ })();
89
+
90
+ return this.connectPromise;
91
+ }
92
+
93
+ /**
94
+ * @description 确保索引已创建
95
+ * @private
96
+ * @return {Promise<void>}
97
+ */
98
+ async _ensureIndexes() {
99
+ try {
100
+ // 确保db对象有效
101
+ if (!this.db || !this.db.collection) {
102
+ throw new Error('数据库连接无效,无法创建索引');
103
+ }
104
+
105
+ // 创建TTL索引用于自动过期
106
+ await this.db.collection(this.table).createIndex(
107
+ { expireAt: 1 },
108
+ { expireAfterSeconds: 0 }
109
+ );
110
+ // 创建_id索引(通常自动创建,但显式指定更清晰)
111
+ await this.db.collection(this.table).createIndex({ _id: 1 });
112
+ } catch (error) {
113
+ // 忽略索引已存在的错误
114
+ if (error.code !== 85) {
115
+ $.log.error('[MongoDB cache] 创建索引失败:', error);
116
+ }
117
+ }
27
118
  }
28
119
  }
29
120
 
30
121
  /**
31
- * 消息订阅
32
- * @param {String} channel 订阅的频道
33
- * @param {Function} func 操作回调函数,可判断成功与否
122
+ * @description 增加整数值
123
+ * @param {String} key 键
124
+ * @param {Number} value 增量值
125
+ * @return {Promise|Number} 增加后的值
34
126
  */
35
- Mongodb_cache.prototype.subscribe = function(channel, func) {
36
- this.conn_rss.subscribe(channel);
37
- this.func_list[channel] = func;
127
+ Mongodb_cache.prototype.addInt = async function(key, value) {
128
+ try {
129
+ // 触发before事件
130
+ this.emit('addInt_before', { key, value });
131
+
132
+ // 确保连接已建立
133
+ await this._ensureConnected();
134
+
135
+ // 验证参数
136
+ if (!key || typeof value !== 'number' || !Number.isInteger(value)) {
137
+ throw new Error('参数无效:key不能为空且value必须是整数');
138
+ }
139
+
140
+ // 使用$inc操作符增加整数值
141
+ const result = await this.db.collection(this.table).findOneAndUpdate(
142
+ { _id: key },
143
+ { $inc: { value: value } },
144
+ { upsert: true, returnDocument: 'after', projection: { value: 1 } }
145
+ );
146
+
147
+ const returnValue = result.value ? result.value.value : value;
148
+
149
+ // 触发after事件
150
+ this.emit('addInt_after', { key, value, result: returnValue });
151
+
152
+ return returnValue;
153
+ } catch (error) {
154
+ // 触发error事件
155
+ this.emit('addInt_error', { key, value, error });
156
+ $.log.error('[MongoDB cache] 增加整数值失败:', error);
157
+ throw error;
158
+ }
38
159
  };
39
160
 
40
161
  /**
41
- * 取消订阅
42
- * @param {String} channel 订阅的频道
162
+ * @description 增加浮点数值
163
+ * @param {String} key 键
164
+ * @param {Number} value 增量值
165
+ * @return {Promise|Number} 增加后的值
43
166
  */
44
- Mongodb_cache.prototype.unsubscribe = function(channel) {
45
- this.conn_rss.unsubscribe(channel);
46
- delete this.func_list[channel];
167
+ Mongodb_cache.prototype.addFloat = async function(key, value) {
168
+ try {
169
+ // 触发before事件
170
+ this.emit('addFloat_before', { key, value });
171
+
172
+ // 确保连接已建立
173
+ await this._ensureConnected();
174
+
175
+ // 验证参数
176
+ if (!key || typeof value !== 'number') {
177
+ throw new Error('参数无效:key不能为空且value必须是数字');
178
+ }
179
+
180
+ // 使用$inc操作符增加浮点数值
181
+ const result = await this.db.collection(this.table).findOneAndUpdate(
182
+ { _id: key },
183
+ { $inc: { value: value } },
184
+ { upsert: true, returnDocument: 'after', projection: { value: 1 } }
185
+ );
186
+
187
+ const returnValue = result.value ? result.value.value : value;
188
+
189
+ // 触发after事件
190
+ this.emit('addFloat_after', { key, value, result: returnValue });
191
+
192
+ return returnValue;
193
+ } catch (error) {
194
+ // 触发error事件
195
+ this.emit('addFloat_error', { key, value, error });
196
+ $.log.error('[MongoDB cache] 增加浮点数值失败:', error);
197
+ throw error;
198
+ }
47
199
  };
48
200
 
49
201
  /**
50
- * 发布消息
51
- * @param {String} channel 频道
52
- * @param {String} message 订阅的消息
53
- * @param {Function} func 消息发布成功回调函数
202
+ * @description 增加字符串值
203
+ * @param {String} key 键
204
+ * @param {String} value 要增加的字符串
205
+ * @return {Promise|String} 增加后的字符串
54
206
  */
55
- Mongodb_cache.prototype.publish = function(channel, message, func) {
56
- this.conn.publish(channel, message, func);
207
+ Mongodb_cache.prototype.addStr = async function(key, value) {
208
+ try {
209
+ // 触发before事件
210
+ this.emit('addStr_before', { key, value });
211
+
212
+ // 确保连接已建立
213
+ await this._ensureConnected();
214
+
215
+ // 验证参数
216
+ if (!key || typeof value !== 'string') {
217
+ throw new Error('参数无效:key不能为空且value必须是字符串');
218
+ }
219
+
220
+ // 获取现有值
221
+ const current = await this.db.collection(this.table).findOne({ _id: key }, { projection: { value: 1 } });
222
+ const currentValue = current ? current.value : '';
223
+
224
+ // 确保当前值是字符串
225
+ if (typeof currentValue !== 'string') {
226
+ throw new Error('现有值不是字符串类型,无法追加');
227
+ }
228
+
229
+ // 更新字符串
230
+ const newValue = currentValue + value;
231
+ await this.db.collection(this.table).updateOne(
232
+ { _id: key },
233
+ { $set: { value: newValue } },
234
+ { upsert: true }
235
+ );
236
+
237
+ // 触发after事件
238
+ this.emit('addStr_after', { key, value, result: newValue });
239
+
240
+ return newValue;
241
+ } catch (error) {
242
+ // 触发error事件
243
+ this.emit('addStr_error', { key, value, error });
244
+ $.log.error('[MongoDB cache] 增加字符串值失败:', error);
245
+ throw error;
246
+ }
57
247
  };
58
248
 
59
249
  /**
60
- * 订阅消息处理, 如果没有订阅函数,则其他的订阅消息会进入该函数
61
- * @param {String} channel
62
- * @param {String} message
63
- */
64
- Mongodb_cache.prototype.main = async function(channel, message) {};
65
-
66
- /**
67
- * 消息通知
68
- * @param {String} channel 频道
69
- * @param {String} message 消息
70
- */
71
- Mongodb_cache.prototype.message = function(channel, message) {
72
- // console.log("我接收到信息了" + message);
73
- var func = this.func_list[channel];
74
- if (func) {
75
- func(message);
76
- } else {
77
- this.main(channel, message);
78
- }
79
- };
80
-
81
- /**z
82
- * @description 增加整数值(负数为减)
250
+ * @description 删除缓存
83
251
  * @param {String} key 键
84
- * @param {Number} num 数值
85
- * @return {Promise|Number} 计算后的结果
252
+ * @return {Promise|Boolean} 成功返回true,失败返回false
86
253
  */
87
- Mongodb_cache.prototype.addInt = function(key, num) {
88
- if (num > 0) {
89
- return this.conn.incrby(key, num);
90
- } else {
91
- return this.conn.decrby(key, -num);
92
- }
254
+ Mongodb_cache.prototype.del = async function(key) {
255
+ try {
256
+ // 触发before事件
257
+ this.emit('del_before', { key });
258
+
259
+ // 确保连接已建立
260
+ await this._ensureConnected();
261
+
262
+ // 验证参数
263
+ if (!key) {
264
+ throw new Error('参数无效:key不能为空');
265
+ }
266
+
267
+ const result = await this.db.collection(this.table).deleteOne({ _id: key });
268
+ const success = result.deletedCount > 0;
269
+
270
+ // 触发after事件
271
+ this.emit('del_after', { key, result: success });
272
+
273
+ return success;
274
+ } catch (error) {
275
+ // 触发error事件
276
+ this.emit('del_error', { key, error });
277
+ $.log.error('[MongoDB cache] 删除缓存失败:', error);
278
+ return false;
279
+ }
93
280
  };
94
281
 
95
282
  /**
96
- * @description 增加浮点数值(负数为减)
97
- * @param {String} key 键
98
- * @param {Number} num 数值
99
- * @return {Promise|Number} 计算后的结果
283
+ * @description 批量删除缓存 (Redis风格)
284
+ * @param {String[]} keys 键数组
285
+ * @return {Promise|Number} 删除的键数量
100
286
  */
101
- Mongodb_cache.prototype.addFloat = function(key, num) {
102
- if (num > 0) {
103
- return this.conn.incrbyfloat(key, num);
104
- } else {
105
- return this.conn.decrby(key, -num);
106
- };
287
+ Mongodb_cache.prototype.delKeys = async function(...keys) {
288
+ try {
289
+ // 触发before事件
290
+ this.emit('delKeys_before', { keys });
291
+
292
+ // 确保连接已建立
293
+ await this._ensureConnected();
294
+
295
+ if (!keys || keys.length === 0) {
296
+ // 触发after事件
297
+ this.emit('delKeys_after', { keys, result: 0 });
298
+ return 0;
299
+ }
300
+
301
+ const result = await this.db.collection(this.table).deleteMany({
302
+ _id: { $in: keys }
303
+ });
304
+
305
+ // 触发after事件
306
+ this.emit('delKeys_after', { keys, result: result.deletedCount });
307
+
308
+ return result.deletedCount;
309
+ } catch (error) {
310
+ // 触发error事件
311
+ this.emit('delKeys_error', { keys, error });
312
+ $.log.error('[MongoDB cache] 批量删除缓存失败:', error);
313
+ return 0;
314
+ }
107
315
  };
108
316
 
109
317
  /**
110
- * @description 增加字符串值到指定缓存
318
+ * @description 修改缓存
111
319
  * @param {String} key 键
112
- * @param {String} str 添加的字符串
113
- * @return {Promise|String} 添加后的字符串
320
+ * @param {Object} value 值
321
+ * @param {Number} seconds 过期时间(秒)
322
+ * @return {Promise|Object} 修改后的值
114
323
  */
115
- Mongodb_cache.prototype.addStr = function(key, str) {
116
- return this.conn.append(key, str);
324
+ Mongodb_cache.prototype.set = async function(key, value, seconds) {
325
+ try {
326
+ // 触发before事件
327
+ this.emit('set_before', { key, value, seconds });
328
+
329
+ // 确保连接已建立
330
+ await this._ensureConnected();
331
+
332
+ // 验证参数
333
+ if (!key) {
334
+ throw new Error('参数无效:key不能为空');
335
+ }
336
+
337
+ // 准备更新数据
338
+ const updateData = { $set: { value } };
339
+
340
+ // 设置过期时间
341
+ if (seconds) {
342
+ const expireAt = new Date(Date.now() + seconds * 1000);
343
+ updateData.$set.expireAt = expireAt;
344
+ } else {
345
+ // 如果没有设置过期时间,移除可能存在的expireAt字段
346
+ updateData.$unset = { expireAt: 1 };
347
+ }
348
+
349
+ await this.db.collection(this.table).updateOne(
350
+ { _id: key },
351
+ updateData,
352
+ { upsert: true }
353
+ );
354
+
355
+ // 触发after事件
356
+ this.emit('set_after', { key, value, seconds, result: value });
357
+
358
+ return value;
359
+ } catch (error) {
360
+ // 触发error事件
361
+ this.emit('set_error', { key, value, seconds, error });
362
+ $.log.error('[MongoDB cache] 修改缓存失败:', error);
363
+ throw error;
364
+ }
117
365
  };
118
366
 
119
367
  /**
120
- * @description 删除缓存
368
+ * @description 条件设置缓存 (Redis SETNX)
121
369
  * @param {String} key 键
122
- * @return {Promise|Boolean} 成功返回true,失败返回false
370
+ * @param {Object} value 值
371
+ * @return {Promise|Boolean} 如果键不存在且设置成功返回true,否则返回false
123
372
  */
124
- Mongodb_cache.prototype.del = function(key) {
125
- return this.conn.del(key);
373
+ Mongodb_cache.prototype.setnx = async function(key, value) {
374
+ try {
375
+ // 触发before事件
376
+ this.emit('setnx_before', { key, value });
377
+
378
+ // 确保连接已建立
379
+ await this._ensureConnected();
380
+
381
+ // 验证参数
382
+ if (!key) {
383
+ throw new Error('参数无效:key不能为空');
384
+ }
385
+
386
+ // 检查键是否存在
387
+ const exists = await this.db.collection(this.table).findOne({
388
+ _id: key,
389
+ $or: [
390
+ { expireAt: { $exists: false } },
391
+ { expireAt: { $gt: new Date() } }
392
+ ]
393
+ }, { projection: { _id: 1 } });
394
+
395
+ // 如果键存在,返回false
396
+ if (exists) {
397
+ // 触发after事件
398
+ this.emit('setnx_after', { key, value, result: false });
399
+ return false;
400
+ }
401
+
402
+ // 否则设置值
403
+ await this.db.collection(this.table).insertOne({
404
+ _id: key,
405
+ value
406
+ });
407
+
408
+ // 触发after事件
409
+ this.emit('setnx_after', { key, value, result: true });
410
+
411
+ return true;
412
+ } catch (error) {
413
+ // 处理重复键错误
414
+ if (error.code === 11000) {
415
+ // 触发after事件
416
+ this.emit('setnx_after', { key, value, result: false });
417
+ return false;
418
+ }
419
+ // 触发error事件
420
+ this.emit('setnx_error', { key, value, error });
421
+ $.log.error('[MongoDB cache] 条件设置缓存失败:', error);
422
+ return false;
423
+ }
126
424
  };
127
425
 
128
426
  /**
129
- * @description 修改缓存
427
+ * @description 设置带过期时间的缓存 (Redis SETEX)
130
428
  * @param {String} key 键
429
+ * @param {Number} seconds 过期时间(秒)
131
430
  * @param {Object} value 值
132
- * @param {Number} seconds 秒
133
- * @return {Object} 值
431
+ * @return {Promise|Boolean} 是否设置成功
134
432
  */
135
- Mongodb_cache.prototype.set = async function(key, value, seconds) {
136
- var ret;
137
- ret = await this.conn.set(key, value);
138
- if (seconds) {
139
- ret = await this.conn.expire(key, seconds);
140
- }
141
- return ret
433
+ Mongodb_cache.prototype.setex = async function(key, seconds, value) {
434
+ try {
435
+ // 触发before事件
436
+ this.emit('setex_before', { key, seconds, value });
437
+
438
+ // 确保连接已建立
439
+ await this._ensureConnected();
440
+
441
+ // 验证参数
442
+ if (!key || typeof seconds !== 'number' || seconds <= 0) {
443
+ throw new Error('参数无效:key不能为空且seconds必须是正数');
444
+ }
445
+
446
+ const expireAt = new Date(Date.now() + seconds * 1000);
447
+ await this.db.collection(this.table).updateOne(
448
+ { _id: key },
449
+ { $set: { value, expireAt } },
450
+ { upsert: true }
451
+ );
452
+
453
+ // 触发after事件
454
+ this.emit('setex_after', { key, seconds, value, result: true });
455
+
456
+ return true;
457
+ } catch (error) {
458
+ // 触发error事件
459
+ this.emit('setex_error', { key, seconds, value, error });
460
+ $.log.error('[MongoDB cache] 设置带过期时间的缓存失败:', error);
461
+ return false;
462
+ }
142
463
  };
143
464
 
144
465
  /**
@@ -149,15 +470,51 @@ Mongodb_cache.prototype.set = async function(key, value, seconds) {
149
470
  * @return {Promise|Object} 值
150
471
  */
151
472
  Mongodb_cache.prototype.add = async function(key, value, seconds) {
152
- var ret;
153
- var has = await this.conn.exists(key);
154
- if (!has) {
155
- ret = await this.conn.set(key, value);
156
- }
157
- if (seconds) {
158
- ret = await this.conn.expire(key, seconds);
159
- }
160
- return ret
473
+ try {
474
+ // 触发before事件
475
+ this.emit('add_before', { key, value, seconds });
476
+
477
+ // 验证参数
478
+ if (!key) {
479
+ throw new Error('参数无效:key不能为空');
480
+ }
481
+
482
+ const exists = await this.db.collection(this.table).findOne({ _id: key }, { projection: { _id: 1, expireAt: 1 } });
483
+
484
+ // 检查是否存在且未过期
485
+ if (exists) {
486
+ // 检查是否过期
487
+ if (exists.expireAt && new Date(exists.expireAt) < new Date()) {
488
+ // 过期则删除旧数据
489
+ await this.db.collection(this.table).deleteOne({ _id: key });
490
+ } else {
491
+ // 触发after事件
492
+ this.emit('add_after', { key, value, seconds, result: value });
493
+ // 未过期则返回现有值
494
+ return value;
495
+ }
496
+ }
497
+
498
+ const data = { _id: key, value };
499
+
500
+ // 设置过期时间
501
+ if (seconds) {
502
+ const expireAt = new Date(Date.now() + seconds * 1000);
503
+ data.expireAt = expireAt;
504
+ }
505
+
506
+ await this.db.collection(this.table).insertOne(data);
507
+
508
+ // 触发after事件
509
+ this.emit('add_after', { key, value, seconds, result: value });
510
+
511
+ return value;
512
+ } catch (error) {
513
+ // 触发error事件
514
+ this.emit('add_error', { key, value, seconds, error });
515
+ $.log.error('[MongoDB cache] 添加缓存失败:', error);
516
+ throw error;
517
+ }
161
518
  };
162
519
 
163
520
  /**
@@ -165,8 +522,108 @@ Mongodb_cache.prototype.add = async function(key, value, seconds) {
165
522
  * @param {String} key 键
166
523
  * @return {Promise|Object} 查询值
167
524
  */
168
- Mongodb_cache.prototype.get = function(key) {
169
- return this.conn.get(key);
525
+ Mongodb_cache.prototype.get = async function(key) {
526
+ try {
527
+ // 确保连接已建立
528
+ await this._ensureConnected();
529
+
530
+ // 验证参数
531
+ if (!key) {
532
+ throw new Error('参数无效:key不能为空');
533
+ }
534
+
535
+ const result = await this.db.collection(this.table).findOne({ _id: key });
536
+
537
+ let returnValue;
538
+ // 检查是否过期
539
+ if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
540
+ // 过期则删除并返回null
541
+ await this.db.collection(this.table).deleteOne({ _id: key });
542
+ returnValue = null;
543
+ } else {
544
+ returnValue = result ? result.value : null;
545
+ }
546
+
547
+ return returnValue;
548
+ } catch (error) {
549
+ $.log.error('[MongoDB cache] 查询缓存失败:', error);
550
+ return null;
551
+ }
552
+ };
553
+
554
+ /**
555
+ * @description 批量查询缓存 (Redis MGET)
556
+ * @param {String[]} keys 键数组
557
+ * @return {Promise|Array} 值数组,顺序与键对应
558
+ */
559
+ Mongodb_cache.prototype.mget = async function(...keys) {
560
+ try {
561
+ if (!keys || keys.length === 0) {
562
+ return [];
563
+ }
564
+
565
+ const results = await this.db.collection(this.table).find({
566
+ _id: { $in: keys },
567
+ $or: [
568
+ { expireAt: { $exists: false } },
569
+ { expireAt: { $gt: new Date() } }
570
+ ]
571
+ }).toArray();
572
+
573
+ // 创建结果映射
574
+ const resultMap = {};
575
+ results.forEach(item => {
576
+ resultMap[item._id] = item.value;
577
+ });
578
+
579
+ // 按输入顺序返回结果
580
+ const resultArray = keys.map(key => resultMap[key] !== undefined ? resultMap[key] : null);
581
+ return resultArray;
582
+ } catch (error) {
583
+ $.log.error('[MongoDB cache] 批量查询缓存失败:', error);
584
+ return [];
585
+ }
586
+ };
587
+
588
+ /**
589
+ * @description 批量设置缓存 (Redis MSET)
590
+ * @param {Object} keyValuePairs 键值对对象
591
+ * @return {Promise|Boolean} 是否设置成功
592
+ */
593
+ Mongodb_cache.prototype.mset = async function(keyValuePairs) {
594
+ try {
595
+ // 触发before事件
596
+ this.emit('mset_before', { keyValuePairs });
597
+
598
+ if (!keyValuePairs || typeof keyValuePairs !== 'object') {
599
+ // 触发after事件
600
+ this.emit('mset_after', { keyValuePairs, result: false });
601
+ return false;
602
+ }
603
+
604
+ // 准备批量操作
605
+ const bulkOps = Object.entries(keyValuePairs).map(([key, value]) => ({
606
+ updateOne: {
607
+ filter: { _id: key },
608
+ update: { $set: { value }, $unset: { expireAt: 1 } },
609
+ upsert: true
610
+ }
611
+ }));
612
+
613
+ if (bulkOps.length > 0) {
614
+ await this.db.collection(this.table).bulkWrite(bulkOps);
615
+ }
616
+
617
+ // 触发after事件
618
+ this.emit('mset_after', { keyValuePairs, result: true });
619
+
620
+ return true;
621
+ } catch (error) {
622
+ // 触发error事件
623
+ this.emit('mset_error', { keyValuePairs, error });
624
+ $.log.error('[MongoDB cache] 批量设置缓存失败:', error);
625
+ return false;
626
+ }
170
627
  };
171
628
 
172
629
  /**
@@ -174,8 +631,42 @@ Mongodb_cache.prototype.get = function(key) {
174
631
  * @param {String} key 键
175
632
  * @return {Promise|Boolean} 有返回true, 没有返回false
176
633
  */
177
- Mongodb_cache.prototype.has = function(key) {
178
- return this.conn.exists(key);
634
+ Mongodb_cache.prototype.has = async function(key) {
635
+ try {
636
+ // 触发before事件
637
+ this.emit('has_before', { key });
638
+
639
+ // 确保连接已建立
640
+ await this._ensureConnected();
641
+
642
+ // 验证参数
643
+ if (!key) {
644
+ throw new Error('参数无效:key不能为空');
645
+ }
646
+
647
+ const result = await this.db.collection(this.table).findOne({ _id: key }, { projection: { _id: 1, expireAt: 1 } });
648
+ // 检查是否过期
649
+ if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
650
+ // 过期则删除并返回false
651
+ await this.db.collection(this.table).deleteOne({ _id: key });
652
+
653
+ // 触发after事件
654
+ this.emit('has_after', { key, result: false });
655
+ return false;
656
+ }
657
+
658
+ const hasResult = !!result;
659
+
660
+ // 触发after事件
661
+ this.emit('has_after', { key, result: hasResult });
662
+
663
+ return hasResult;
664
+ } catch (error) {
665
+ // 触发error事件
666
+ this.emit('has_error', { key, error });
667
+ $.log.error('[MongoDB cache] 判断键是否存在失败:', error);
668
+ return false;
669
+ }
179
670
  };
180
671
 
181
672
  /**
@@ -185,8 +676,32 @@ Mongodb_cache.prototype.has = function(key) {
185
676
  * @param {Number} end 结束位置
186
677
  * @return {Promise|String} 查询值
187
678
  */
188
- Mongodb_cache.prototype.getrange = function(key, start, end) {
189
- return this.conn.getrange(key, start, end);
679
+ Mongodb_cache.prototype.getrange = async function(key, start, end) {
680
+ try {
681
+ // 触发before事件
682
+ this.emit('getrange_before', { key, start, end });
683
+
684
+ // 验证参数
685
+ if (!key || typeof start !== 'number' || typeof end !== 'number') {
686
+ throw new Error('参数无效:key不能为空且start和end必须是数字');
687
+ }
688
+
689
+ const value = await this.get(key);
690
+ let result = '';
691
+ if (typeof value === 'string') {
692
+ result = value.substring(start, end + 1);
693
+ }
694
+
695
+ // 触发after事件
696
+ this.emit('getrange_after', { key, start, end, result: result });
697
+
698
+ return result;
699
+ } catch (error) {
700
+ // 触发error事件
701
+ this.emit('getrange_error', { key, start, end, error });
702
+ $.log.error('[MongoDB cache] 查询字符串范围失败:', error);
703
+ return '';
704
+ }
190
705
  };
191
706
 
192
707
  /**
@@ -196,55 +711,238 @@ Mongodb_cache.prototype.getrange = function(key, start, end) {
196
711
  * @param {String} value 变更的值
197
712
  * @return {Promise|Number} 字符串长度
198
713
  */
199
- Mongodb_cache.prototype.setrange = function(key, index, value) {
200
- return this.conn.getrange(key, index, value);
714
+ Mongodb_cache.prototype.setrange = async function(key, index, value) {
715
+ try {
716
+ // 触发before事件
717
+ this.emit('setrange_before', { key, index, value });
718
+
719
+ // 验证参数
720
+ if (!key || typeof index !== 'number' || typeof value !== 'string') {
721
+ throw new Error('参数无效:key不能为空,index必须是数字,value必须是字符串');
722
+ }
723
+
724
+ // 获取当前值
725
+ let currentValue = await this.get(key) || '';
726
+
727
+ // 确保当前值是字符串
728
+ if (typeof currentValue !== 'string') {
729
+ currentValue = '';
730
+ }
731
+
732
+ // 处理index超出范围的情况
733
+ if (index > currentValue.length) {
734
+ // 补空格
735
+ currentValue = currentValue.padEnd(index, ' ');
736
+ }
737
+
738
+ // 构建新字符串
739
+ const newValue = currentValue.substring(0, index) + value + currentValue.substring(index + value.length);
740
+
741
+ // 保存新值
742
+ await this.set(key, newValue);
743
+
744
+ const resultLength = newValue.length;
745
+ // 触发after事件
746
+ this.emit('setrange_after', { key, index, value, result: resultLength });
747
+
748
+ return resultLength;
749
+ } catch (error) {
750
+ // 触发error事件
751
+ this.emit('setrange_error', { key, index, value, error });
752
+ $.log.error('[MongoDB cache] 设置字符串范围失败:', error);
753
+ return 0;
754
+ }
755
+ };
756
+
757
+ /**
758
+ * @description 获取字符串长度 (Redis STRLEN)
759
+ * @param {String} key 键
760
+ * @return {Promise|Number} 字符串长度,非字符串返回0
761
+ */
762
+ Mongodb_cache.prototype.strlen = async function(key) {
763
+ try {
764
+ // 触发before事件
765
+ this.emit('strlen_before', { key });
766
+
767
+ const value = await this.get(key);
768
+ const result = typeof value === 'string' ? value.length : 0;
769
+
770
+ // 触发after事件
771
+ this.emit('strlen_after', { key, result: result });
772
+
773
+ return result;
774
+ } catch (error) {
775
+ // 触发error事件
776
+ this.emit('strlen_error', { key, error });
777
+ $.log.error('[MongoDB cache] 获取字符串长度失败:', error);
778
+ return 0;
779
+ }
201
780
  };
202
781
 
203
782
  /**
204
783
  * @description 清空缓存
205
- * @param {String} key 键, 为空则清空所有
206
- * @return {Promise|Array} 执行结果
784
+ * @param {String} prefix 键前缀
785
+ * @return {Promise|Boolean} 成功返回true,失败返回false
207
786
  */
208
- Mongodb_cache.prototype.clear = function(key) {
209
- if (key) {
210
- return this.conn.del(key);
211
- } else {
212
- return this.conn.flushdb();
213
- }
787
+ Mongodb_cache.prototype.clear = async function(prefix) {
788
+ try {
789
+ // 触发before事件
790
+ this.emit('clear_before', { prefix });
791
+
792
+ let query = {};
793
+
794
+ // 如果提供了前缀,使用正则匹配
795
+ if (prefix) {
796
+ query._id = { $regex: `^${prefix}` };
797
+ }
798
+
799
+ const result = await this.db.collection(this.table).deleteMany(query);
800
+ $.log.info(`[MongoDB cache] 清空缓存成功,删除了${result.deletedCount}条记录`);
801
+
802
+ // 触发after事件
803
+ this.emit('clear_after', { prefix, result: true, deletedCount: result.deletedCount });
804
+
805
+ return true;
806
+ } catch (error) {
807
+ // 触发error事件
808
+ this.emit('clear_error', { prefix, error });
809
+ $.log.error('[MongoDB cache] 清空缓存失败:', error);
810
+ return false;
811
+ }
214
812
  };
215
813
 
216
814
  /**
217
815
  * @description 排序
218
- * @param {String} key 键
219
- * @param {String} way = [asc|desc]排序方式, 可以为空
220
- * @param {String} obj_key 排序成员的键
221
- * @return {Promise|Array} 排序后的数组
816
+ * @param {String} prefix 键前缀
817
+ * @param {Number} count 数量
818
+ * @param {Number} type 类型 1: 从小到大,0: 从大到小
819
+ * @return {Promise|Array} 执行结果
222
820
  */
223
- Mongodb_cache.prototype.sort = function(key) {
224
- return this.conn.sort(key);
821
+ Mongodb_cache.prototype.sort = async function(prefix, count, type) {
822
+ try {
823
+ // 触发before事件
824
+ this.emit('sort_before', { prefix, count, type });
825
+
826
+ // 验证参数
827
+ if (typeof count !== 'number' || (type !== 0 && type !== 1)) {
828
+ throw new Error('参数无效:count必须是数字,type必须是0或1');
829
+ }
830
+
831
+ // 构建查询
832
+ let query = {};
833
+ if (prefix) {
834
+ query._id = { $regex: `^${prefix}` };
835
+ }
836
+
837
+ // 排除过期的数据
838
+ query.$or = [
839
+ { expireAt: { $exists: false } },
840
+ { expireAt: { $gt: new Date() } }
841
+ ];
842
+
843
+ // 执行查询并排序
844
+ const cursor = this.db.collection(this.table)
845
+ .find(query)
846
+ .sort({ value: type === 1 ? 1 : -1 })
847
+ .limit(count);
848
+
849
+ const results = await cursor.toArray();
850
+
851
+ // 转换为所需格式
852
+ const formattedResults = results.map(item => [item._id, item.value]);
853
+
854
+ // 触发after事件
855
+ this.emit('sort_after', { prefix, count, type, result: formattedResults });
856
+
857
+ return formattedResults;
858
+ } catch (error) {
859
+ // 触发error事件
860
+ this.emit('sort_error', { prefix, count, type, error });
861
+ $.log.error('[MongoDB cache] 排序失败:', error);
862
+ return [];
863
+ }
225
864
  };
226
865
 
227
866
  /**
228
- * @description 获取所有键名
229
- * @param {String} key 键 支持*号, 前面加*表示后面名称一致, 前后加*表示包含名称, 后面加*表示前面名称一致
230
- * @return {Promise|Array} 键数组
867
+ * @description 获取键名
868
+ * @param {String} pattern 匹配模式
869
+ * @return {Promise|Array} 键名数组
231
870
  */
232
- Mongodb_cache.prototype.keys = function(key) {
233
- if (!key) {
234
- key = "*";
235
- }
236
- return this.conn.keys(key);
871
+ Mongodb_cache.prototype.keys = async function(pattern) {
872
+ try {
873
+ // 触发before事件
874
+ this.emit('keys_before', { pattern });
875
+
876
+ // 构建查询
877
+ let query = {};
878
+
879
+ // 处理通配符
880
+ if (pattern) {
881
+ // 将Redis风格的通配符转换为正则表达式
882
+ const regexPattern = pattern
883
+ .replace(/\*/g, '.*') // * 转换为 .*
884
+ .replace(/\?/g, '.'); // ? 转换为 .
885
+ query._id = { $regex: `^${regexPattern}$` };
886
+ }
887
+
888
+ // 排除过期的数据
889
+ query.$or = [
890
+ { expireAt: { $exists: false } },
891
+ { expireAt: { $gt: new Date() } }
892
+ ];
893
+
894
+ // 只获取_id字段
895
+ const cursor = this.db.collection(this.table)
896
+ .find(query)
897
+ .project({ _id: 1 });
898
+
899
+ const results = await cursor.toArray();
900
+
901
+ // 提取键名
902
+ const keysList = results.map(item => item._id);
903
+
904
+ // 触发after事件
905
+ this.emit('keys_after', { pattern, result: keysList });
906
+
907
+ return keysList;
908
+ } catch (error) {
909
+ // 触发error事件
910
+ this.emit('keys_error', { pattern, error });
911
+ $.log.error('[MongoDB cache] 获取键名失败:', error);
912
+ return [];
913
+ }
237
914
  };
238
915
 
239
916
  /**
240
917
  * @description 修改数组缓存
241
918
  * @param {String} key 键
242
- * @param {Object} value 值
919
+ * @param {Array} value 值
243
920
  * @param {Number} seconds 秒
244
921
  * @return {Promise|Array} 执行结果
245
922
  */
246
- Mongodb_cache.prototype.list_set = function(key, value, seconds) {
247
- return this.conn.rpush(key, value);
923
+ Mongodb_cache.prototype.list_set = async function(key, value, seconds) {
924
+ try {
925
+ // 触发before事件
926
+ this.emit('list_set_before', { key, value, seconds });
927
+
928
+ // 验证参数
929
+ if (!key) {
930
+ throw new Error('参数无效:key不能为空');
931
+ }
932
+
933
+ const finalValue = Array.isArray(value) ? value : [value];
934
+ const result = await this.set(key, finalValue, seconds);
935
+
936
+ // 触发after事件
937
+ this.emit('list_set_after', { key, value: finalValue, seconds, result });
938
+
939
+ return result;
940
+ } catch (error) {
941
+ // 触发error事件
942
+ this.emit('list_set_error', { key, value, seconds, error });
943
+ $.log.error('[MongoDB cache] 修改数组缓存失败:', error);
944
+ throw error;
945
+ }
248
946
  };
249
947
 
250
948
  /**
@@ -253,8 +951,186 @@ Mongodb_cache.prototype.list_set = function(key, value, seconds) {
253
951
  * @param {Object} value 值
254
952
  * @return {Promise|String} 追加后的数组
255
953
  */
256
- Mongodb_cache.prototype.list_push = function(key, value) {
257
- return this.conn.rpush(key, value);
954
+ Mongodb_cache.prototype.list_push = async function(key, value) {
955
+ try {
956
+ // 触发before事件
957
+ this.emit('list_push_before', { key, value });
958
+
959
+ // 验证参数
960
+ if (!key) {
961
+ throw new Error('参数无效:key不能为空');
962
+ }
963
+
964
+ // 获取现有值
965
+ let currentValue = await this.get(key);
966
+
967
+ // 如果不存在或不是数组,则初始化为空数组
968
+ if (!Array.isArray(currentValue)) {
969
+ currentValue = [];
970
+ }
971
+
972
+ // 追加新值
973
+ currentValue.push(value);
974
+
975
+ // 保存更新后的值
976
+ await this.set(key, currentValue);
977
+
978
+ // 触发after事件
979
+ this.emit('list_push_after', { key, value, result: currentValue });
980
+
981
+ return currentValue;
982
+ } catch (error) {
983
+ // 触发error事件
984
+ this.emit('list_push_error', { key, value, error });
985
+ $.log.error('[MongoDB cache] 数组缓存追加对象失败:', error);
986
+ throw error;
987
+ }
988
+ };
989
+
990
+ /**
991
+ * @description 在数组头部添加元素 (Redis LPUSH)
992
+ * @param {String} key 键
993
+ * @param {Object} value 值
994
+ * @return {Promise|Array} 添加后的数组
995
+ */
996
+ Mongodb_cache.prototype.lpush = async function(key, value) {
997
+ try {
998
+ // 触发before事件
999
+ this.emit('lpush_before', { key, value });
1000
+
1001
+ // 验证参数
1002
+ if (!key) {
1003
+ throw new Error('参数无效:key不能为空');
1004
+ }
1005
+
1006
+ // 获取现有值
1007
+ let currentValue = await this.get(key);
1008
+
1009
+ // 如果不存在或不是数组,则初始化为空数组
1010
+ if (!Array.isArray(currentValue)) {
1011
+ currentValue = [];
1012
+ }
1013
+
1014
+ // 在头部添加新值
1015
+ currentValue.unshift(value);
1016
+
1017
+ // 保存更新后的值
1018
+ await this.set(key, currentValue);
1019
+
1020
+ // 触发after事件
1021
+ this.emit('lpush_after', { key, value, result: currentValue });
1022
+
1023
+ return currentValue;
1024
+ } catch (error) {
1025
+ // 触发error事件
1026
+ this.emit('lpush_error', { key, value, error });
1027
+ $.log.error('[MongoDB cache] 在数组头部添加元素失败:', error);
1028
+ throw error;
1029
+ }
1030
+ };
1031
+
1032
+ /**
1033
+ * @description 从数组尾部弹出元素 (Redis RPOP)
1034
+ * @param {String} key 键
1035
+ * @return {Promise|*} 弹出的元素,数组不存在或为空返回null
1036
+ */
1037
+ Mongodb_cache.prototype.rpop = async function(key) {
1038
+ try {
1039
+ // 触发before事件
1040
+ this.emit('rpop_before', { key });
1041
+
1042
+ // 验证参数
1043
+ if (!key) {
1044
+ throw new Error('参数无效:key不能为空');
1045
+ }
1046
+
1047
+ // 获取现有值
1048
+ let currentValue = await this.get(key);
1049
+
1050
+ // 如果不存在或不是数组或为空,返回null
1051
+ if (!Array.isArray(currentValue) || currentValue.length === 0) {
1052
+ // 触发after事件
1053
+ this.emit('rpop_after', { key, result: null });
1054
+ return null;
1055
+ }
1056
+
1057
+ // 弹出最后一个元素
1058
+ const popped = currentValue.pop();
1059
+
1060
+ // 保存更新后的值
1061
+ await this.set(key, currentValue);
1062
+
1063
+ // 触发after事件
1064
+ this.emit('rpop_after', { key, result: popped });
1065
+
1066
+ return popped;
1067
+ } catch (error) {
1068
+ // 触发error事件
1069
+ this.emit('rpop_error', { key, error });
1070
+ $.log.error('[MongoDB cache] 从数组尾部弹出元素失败:', error);
1071
+ return null;
1072
+ }
1073
+ };
1074
+
1075
+ /**
1076
+ * @description 从数组头部弹出元素 (Redis LPOP)
1077
+ * @param {String} key 键
1078
+ * @return {Promise|*} 弹出的元素,数组不存在或为空返回null
1079
+ */
1080
+ Mongodb_cache.prototype.lpop = async function(key) {
1081
+ try {
1082
+ // 触发before事件
1083
+ this.emit('lpop_before', { key });
1084
+
1085
+ // 验证参数
1086
+ if (!key) {
1087
+ throw new Error('参数无效:key不能为空');
1088
+ }
1089
+
1090
+ // 获取现有值
1091
+ let currentValue = await this.get(key);
1092
+
1093
+ // 如果不存在或不是数组或为空,返回null
1094
+ if (!Array.isArray(currentValue) || currentValue.length === 0) {
1095
+ // 触发after事件
1096
+ this.emit('lpop_after', { key, result: null });
1097
+ return null;
1098
+ }
1099
+
1100
+ // 弹出第一个元素
1101
+ const popped = currentValue.shift();
1102
+
1103
+ // 保存更新后的值
1104
+ await this.set(key, currentValue);
1105
+
1106
+ // 触发after事件
1107
+ this.emit('lpop_after', { key, result: popped });
1108
+
1109
+ return popped;
1110
+ } catch (error) {
1111
+ // 触发error事件
1112
+ this.emit('lpop_error', { key, error });
1113
+ $.log.error('[MongoDB cache] 从数组头部弹出元素失败:', error);
1114
+ return null;
1115
+ }
1116
+ };
1117
+
1118
+ /**
1119
+ * @description 获取数组长度 (Redis LLEN)
1120
+ * @param {String} key 键
1121
+ * @return {Promise|Number} 数组长度,不存在或不是数组返回0
1122
+ */
1123
+ Mongodb_cache.prototype.llen = async function(key) {
1124
+ try {
1125
+ // 确保连接已建立
1126
+ await this._ensureConnected();
1127
+
1128
+ const value = await this.get(key);
1129
+ return Array.isArray(value) ? value.length : 0;
1130
+ } catch (error) {
1131
+ $.log.error('[MongoDB cache] 获取数组长度失败:', error);
1132
+ return 0;
1133
+ }
258
1134
  };
259
1135
 
260
1136
  /**
@@ -264,19 +1140,40 @@ Mongodb_cache.prototype.list_push = function(key, value) {
264
1140
  * @return {Promise|Boolean} 存在返回true, 否则返回false
265
1141
  */
266
1142
  Mongodb_cache.prototype.list_has = async function(key, value) {
267
- if (!value) {
268
- throw '值不能为空';
269
- }
270
- var arr = await this.list_get(key);
271
- var has = false;
272
- var len = arr.length;
273
- for (var i = 0; i < len; i++) {
274
- if (value == arr[i]) {
275
- has = true;
276
- break;
277
- }
278
- }
279
- return has;
1143
+ if (!value) {
1144
+ throw '值不能为空';
1145
+ }
1146
+ try {
1147
+ // 触发before事件
1148
+ this.emit('list_has_before', { key, value });
1149
+
1150
+ // 确保连接已建立
1151
+ await this._ensureConnected();
1152
+
1153
+ // 验证参数
1154
+ if (!key) {
1155
+ throw new Error('参数无效:key不能为空');
1156
+ }
1157
+
1158
+ const arr = await this.get(key) || [];
1159
+ if (!Array.isArray(arr)) {
1160
+ // 触发after事件
1161
+ this.emit('list_has_after', { key, value, result: false });
1162
+ return false;
1163
+ }
1164
+
1165
+ const result = arr.some(item => item === value || JSON.stringify(item) === JSON.stringify(value));
1166
+
1167
+ // 触发after事件
1168
+ this.emit('list_has_after', { key, value, result });
1169
+
1170
+ return result;
1171
+ } catch (error) {
1172
+ // 触发error事件
1173
+ this.emit('list_has_error', { key, value, error });
1174
+ $.log.error('[MongoDB cache] 判断成员是否存在失败:', error);
1175
+ return false;
1176
+ }
280
1177
  };
281
1178
 
282
1179
  /**
@@ -286,41 +1183,1648 @@ Mongodb_cache.prototype.list_has = async function(key, value) {
286
1183
  * @param {Number} end 结束位置
287
1184
  * @return {Promise|Array} 查询到的数组
288
1185
  */
289
- Mongodb_cache.prototype.list_get = function(key, start, end) {
290
- if (start === undefined) {
291
- start = 0;
292
- }
293
- if (end === undefined) {
294
- end = -1;
295
- }
296
- return this.conn.lrange(key, start, end);
1186
+ Mongodb_cache.prototype.list_get = async function(key, start, end) {
1187
+ try {
1188
+ // 确保连接已建立
1189
+ await this._ensureConnected();
1190
+
1191
+ // 验证参数
1192
+ if (!key) {
1193
+ throw new Error('参数无效:key不能为空');
1194
+ }
1195
+
1196
+ const arr = await this.get(key) || [];
1197
+ if (!Array.isArray(arr)) {
1198
+ return [];
1199
+ }
1200
+ if (start === undefined) {
1201
+ start = 0;
1202
+ }
1203
+ if (end === undefined) {
1204
+ end = -1;
1205
+ }
1206
+ // 处理负数索引
1207
+ if (start < 0) start = Math.max(0, arr.length + start);
1208
+ if (end < 0) end = arr.length + end;
1209
+
1210
+ const result = arr.slice(start, end + 1);
1211
+
1212
+ return result;
1213
+ } catch (error) {
1214
+ $.log.error('[MongoDB cache] 查询数组缓存失败:', error);
1215
+ return [];
1216
+ }
297
1217
  };
298
1218
 
299
1219
  /**
300
1220
  * @description 清空数组缓存
301
1221
  * @param {String} key 键
302
1222
  * @param {Array} value 新成员, 没有则删除数组
303
- * @return {Promise|Boolean} 成功返回true,是否返回false
1223
+ * @return {Promise|Boolean} 成功返回true,失败返回false
304
1224
  */
305
1225
  Mongodb_cache.prototype.list_clear = async function(key, value) {
306
- var ret;
307
- if (key) {
308
- if (value) {
309
- // 如果有值则删除其他成员,保留新值
310
- try {
311
- var len = await this.list_push(key, value);
312
- ret = this.conn.ltrim(key, len - value.length, len - 1);
313
- } catch (e) {
1226
+ try {
1227
+ // 触发before事件
1228
+ this.emit('list_clear_before', { key, value });
1229
+
1230
+ // 确保连接已建立
1231
+ await this._ensureConnected();
1232
+
1233
+ if (!key) {
1234
+ throw '键不能为空';
1235
+ }
1236
+
1237
+ let result;
1238
+ if (value) {
1239
+ // 设置新的数组值
1240
+ await this.set(key, Array.isArray(value) ? value : [value]);
1241
+ result = true;
1242
+ } else {
1243
+ // 直接删除键
1244
+ result = await this.del(key);
1245
+ }
1246
+
1247
+ // 触发after事件
1248
+ this.emit('list_clear_after', { key, value, result });
1249
+
1250
+ return result;
1251
+ } catch (error) {
1252
+ // 触发error事件
1253
+ this.emit('list_clear_error', { key, value, error });
1254
+ $.log.error('[MongoDB cache] 清空数组缓存失败:', error);
1255
+ return false;
1256
+ }
1257
+ };
314
1258
 
315
- }
316
- } else {
317
- // 直接删除键
318
- return await this.conn.del(key);
319
- }
320
- } else {
321
- throw '键不能为空';
322
- }
323
- return ret
1259
+ /**
1260
+ * @description 设置缓存过期时间
1261
+ * @param {String} key 键
1262
+ * @param {Number} seconds 过期时间(秒)
1263
+ * @return {Promise|Boolean} 成功返回true,失败返回false
1264
+ */
1265
+ Mongodb_cache.prototype.expire = async function(key, seconds) {
1266
+ try {
1267
+ // 触发before事件
1268
+ this.emit('expire_before', { key, seconds });
1269
+
1270
+ // 确保连接已建立
1271
+ await this._ensureConnected();
1272
+
1273
+ // 验证参数
1274
+ if (!key || typeof seconds !== 'number' || seconds < 0) {
1275
+ throw new Error('参数无效:key不能为空且seconds必须是非负数字');
1276
+ }
1277
+
1278
+ const expireAt = new Date(Date.now() + seconds * 1000);
1279
+ const result = await this.db.collection(this.table).updateOne(
1280
+ { _id: key },
1281
+ { $set: { expireAt } }
1282
+ );
1283
+
1284
+ const success = result.modifiedCount > 0;
1285
+
1286
+ // 触发after事件
1287
+ this.emit('expire_after', { key, seconds, result: success });
1288
+
1289
+ return success;
1290
+ } catch (error) {
1291
+ // 触发error事件
1292
+ this.emit('expire_error', { key, seconds, error });
1293
+ $.log.error('[MongoDB cache] 设置过期时间失败:', error);
1294
+ return false;
1295
+ }
1296
+ };
1297
+
1298
+ /**
1299
+ * @description 获取缓存剩余过期时间
1300
+ * @param {String} key 键
1301
+ * @return {Promise|Number} 剩余过期时间(秒),-1表示永不过期,-2表示不存在
1302
+ */
1303
+ Mongodb_cache.prototype.ttl = async function(key) {
1304
+ try {
1305
+ // 触发before事件
1306
+ this.emit('ttl_before', { key });
1307
+
1308
+ // 确保连接已建立
1309
+ await this._ensureConnected();
1310
+
1311
+ // 验证参数
1312
+ if (!key) {
1313
+ throw new Error('参数无效:key不能为空');
1314
+ }
1315
+
1316
+ const result = await this.db.collection(this.table).findOne({ _id: key }, { projection: { expireAt: 1 } });
1317
+
1318
+ let ttlValue;
1319
+ if (!result) {
1320
+ ttlValue = -2; // 键不存在
1321
+ } else if (!result.expireAt) {
1322
+ ttlValue = -1; // 永不过期
1323
+ } else {
1324
+ const now = new Date();
1325
+ const expireTime = new Date(result.expireAt);
1326
+ ttlValue = Math.ceil((expireTime - now) / 1000);
1327
+ ttlValue = Math.max(0, ttlValue); // 返回剩余秒数,最少为0
1328
+ }
1329
+
1330
+ // 触发after事件
1331
+ this.emit('ttl_after', { key, result: ttlValue });
1332
+
1333
+ return ttlValue;
1334
+ } catch (error) {
1335
+ // 触发error事件
1336
+ this.emit('ttl_error', { key, error });
1337
+ $.log.error('[MongoDB cache] 获取过期时间失败:', error);
1338
+ return -2; // 出错时默认返回不存在
1339
+ }
1340
+ };
1341
+
1342
+ /**
1343
+ * @description 移除过期时间 (Redis PERSIST)
1344
+ * @param {String} key 键
1345
+ * @return {Promise|Boolean} 成功返回true,失败返回false
1346
+ */
1347
+ Mongodb_cache.prototype.persist = async function(key) {
1348
+ try {
1349
+ // 触发before事件
1350
+ this.emit('persist_before', { key });
1351
+
1352
+ // 确保连接已建立
1353
+ await this._ensureConnected();
1354
+
1355
+ // 验证参数
1356
+ if (!key) {
1357
+ throw new Error('参数无效:key不能为空');
1358
+ }
1359
+
1360
+ const result = await this.db.collection(this.table).updateOne(
1361
+ { _id: key },
1362
+ { $unset: { expireAt: 1 } }
1363
+ );
1364
+
1365
+ const success = result.modifiedCount > 0;
1366
+
1367
+ // 触发after事件
1368
+ this.emit('persist_after', { key, result: success });
1369
+
1370
+ return success;
1371
+ } catch (error) {
1372
+ // 触发error事件
1373
+ this.emit('persist_error', { key, error });
1374
+ $.log.error('[MongoDB cache] 移除过期时间失败:', error);
1375
+ return false;
1376
+ }
1377
+ };
1378
+
1379
+ // 哈希操作方法
1380
+
1381
+ /**
1382
+ * @description 设置哈希字段 (Redis HSET)
1383
+ * @param {String} key 键
1384
+ * @param {String} field 字段名
1385
+ * @param {*} value 字段值
1386
+ * @return {Promise|Boolean} 是否设置成功
1387
+ */
1388
+ Mongodb_cache.prototype.hset = async function(key, field, value) {
1389
+ try {
1390
+ // 触发before事件
1391
+ this.emit('hset_before', { key, field, value });
1392
+
1393
+ // 确保连接已建立
1394
+ await this._ensureConnected();
1395
+
1396
+ // 验证参数
1397
+ if (!key || !field) {
1398
+ throw new Error('参数无效:key和field不能为空');
1399
+ }
1400
+
1401
+ // 准备更新对象
1402
+ const updateField = `fields.${field}`;
1403
+ const updateData = { $set: {} };
1404
+ updateData.$set[updateField] = value;
1405
+
1406
+ // 执行更新
1407
+ await this.db.collection(this.table).updateOne(
1408
+ { _id: key },
1409
+ updateData,
1410
+ { upsert: true }
1411
+ );
1412
+
1413
+ // 触发after事件
1414
+ this.emit('hset_after', { key, field, value, result: true });
1415
+
1416
+ return true;
1417
+ } catch (error) {
1418
+ // 触发error事件
1419
+ this.emit('hset_error', { key, field, value, error });
1420
+ $.log.error('[MongoDB cache] 设置哈希字段失败:', error);
1421
+ return false;
1422
+ }
1423
+ };
1424
+
1425
+ /**
1426
+ * @description 获取哈希字段值 (Redis HGET)
1427
+ * @param {String} key 键
1428
+ * @param {String} field 字段名
1429
+ * @return {Promise|null} 字段值,不存在返回null
1430
+ */
1431
+ Mongodb_cache.prototype.hget = async function(key, field) {
1432
+ try {
1433
+ // 确保连接已建立
1434
+ await this._ensureConnected();
1435
+
1436
+ // 验证参数
1437
+ if (!key || !field) {
1438
+ throw new Error('参数无效:key和field不能为空');
1439
+ }
1440
+
1441
+ const result = await this.db.collection(this.table).findOne(
1442
+ { _id: key },
1443
+ { projection: { [`fields.${field}`]: 1 } }
1444
+ );
1445
+
1446
+ // 检查是否过期
1447
+ if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
1448
+ // 过期则删除并返回null
1449
+ await this.db.collection(this.table).deleteOne({ _id: key });
1450
+ // 触发after事件
1451
+ this.emit('hget_after', { key, field, result: null });
1452
+ return null;
1453
+ }
1454
+
1455
+ // 确保字段存在才返回值,否则返回null
1456
+ const fieldValue = result && result.fields && field in result.fields ? result.fields[field] : null;
1457
+
1458
+ return fieldValue;
1459
+ } catch (error) {
1460
+ $.log.error('[MongoDB cache] 获取哈希字段值失败:', error);
1461
+ return null;
1462
+ }
1463
+ };
1464
+
1465
+ /**
1466
+ * @description 获取哈希所有字段和值 (Redis HGETALL)
1467
+ * @param {String} key 键
1468
+ * @return {Promise|Object} 所有字段和值,不存在返回{}
1469
+ */
1470
+ Mongodb_cache.prototype.hgetall = async function(key) {
1471
+ try {
1472
+ // 确保连接已建立
1473
+ await this._ensureConnected();
1474
+
1475
+ // 验证参数
1476
+ if (!key) {
1477
+ throw new Error('参数无效:key不能为空');
1478
+ }
1479
+
1480
+ const result = await this.db.collection(this.table).findOne(
1481
+ { _id: key },
1482
+ { projection: { fields: 1, expireAt: 1 } }
1483
+ );
1484
+
1485
+ // 检查是否过期
1486
+ if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
1487
+ // 过期则删除并返回{}
1488
+ await this.db.collection(this.table).deleteOne({ _id: key });
1489
+ return {};
1490
+ }
1491
+
1492
+ const fieldsData = result && result.fields ? result.fields : {};
1493
+
1494
+ return fieldsData;
1495
+ } catch (error) {
1496
+ $.log.error('[MongoDB cache] 获取哈希所有字段和值失败:', error);
1497
+ return {};
1498
+ }
1499
+ };
1500
+
1501
+ /**
1502
+ * @description 删除哈希字段 (Redis HDEL)
1503
+ * @param {String} key 键
1504
+ * @param {String} field 字段名
1505
+ * @return {Promise|null} 删除成功返回null
1506
+ */
1507
+ Mongodb_cache.prototype.hdel = async function(key, field) {
1508
+ try {
1509
+ // 触发before事件
1510
+ this.emit('hdel_before', { key, field });
1511
+
1512
+ // 确保连接已建立
1513
+ await this._ensureConnected();
1514
+
1515
+ // 验证参数
1516
+ if (!key || !field) {
1517
+ throw new Error('参数无效:key和field不能为空');
1518
+ }
1519
+
1520
+ // 准备更新对象
1521
+ const unsetField = `fields.${field}`;
1522
+ const updateData = { $unset: {} };
1523
+ updateData.$unset[unsetField] = '';
1524
+
1525
+ // 执行更新
1526
+ await this.db.collection(this.table).updateOne(
1527
+ { _id: key },
1528
+ updateData
1529
+ );
1530
+
1531
+ // 触发after事件
1532
+ this.emit('hdel_after', { key, field, result: null });
1533
+
1534
+ // 返回null以匹配Redis行为
1535
+ return null;
1536
+ } catch (error) {
1537
+ // 触发error事件
1538
+ this.emit('hdel_error', { key, field, error });
1539
+ $.log.error('[MongoDB cache] 删除哈希字段失败:', error);
1540
+ return null;
1541
+ }
1542
+ };
1543
+
1544
+ /**
1545
+ * @description 判断哈希字段是否存在 (Redis HEXISTS)
1546
+ * @param {String} key 键
1547
+ * @param {String} field 字段名
1548
+ * @return {Promise|Boolean} 存在返回true,不存在返回false
1549
+ */
1550
+ Mongodb_cache.prototype.hexists = async function(key, field) {
1551
+ try {
1552
+ // 触发before事件
1553
+ this.emit('hexists_before', { key, field });
1554
+
1555
+ // 确保连接已建立
1556
+ await this._ensureConnected();
1557
+
1558
+ // 验证参数
1559
+ if (!key || !field) {
1560
+ throw new Error('参数无效:key和field不能为空');
1561
+ }
1562
+
1563
+ const result = await this.db.collection(this.table).findOne(
1564
+ {
1565
+ _id: key,
1566
+ $or: [
1567
+ { expireAt: { $exists: false } },
1568
+ { expireAt: { $gt: new Date() } }
1569
+ ],
1570
+ [`fields.${field}`]: { $exists: true }
1571
+ },
1572
+ { projection: { _id: 1 } }
1573
+ );
1574
+
1575
+ const exists = !!result;
1576
+
1577
+ // 触发after事件
1578
+ this.emit('hexists_after', { key, field, result: exists });
1579
+
1580
+ return exists;
1581
+ } catch (error) {
1582
+ // 触发error事件
1583
+ this.emit('hexists_error', { key, field, error });
1584
+ $.log.error('[MongoDB cache] 判断哈希字段是否存在失败:', error);
1585
+ return false;
1586
+ }
1587
+ };
1588
+
1589
+ /**
1590
+ * @description 获取哈希所有字段名 (Redis HKEYS)
1591
+ * @param {String} key 键
1592
+ * @return {Promise|Array} 字段名数组
1593
+ */
1594
+ Mongodb_cache.prototype.hkeys = async function(key) {
1595
+ try {
1596
+ // 触发before事件
1597
+ this.emit('hkeys_before', { key });
1598
+
1599
+ // 确保连接已建立
1600
+ await this._ensureConnected();
1601
+
1602
+ // 验证参数
1603
+ if (!key) {
1604
+ throw new Error('参数无效:key不能为空');
1605
+ }
1606
+
1607
+ const result = await this.db.collection(this.table).findOne(
1608
+ { _id: key },
1609
+ { projection: { fields: 1, expireAt: 1 } }
1610
+ );
1611
+
1612
+ // 检查是否过期
1613
+ if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
1614
+ // 过期则删除并返回[]
1615
+ await this.db.collection(this.table).deleteOne({ _id: key });
1616
+ // 触发after事件
1617
+ this.emit('hkeys_after', { key, result: [] });
1618
+ return [];
1619
+ }
1620
+
1621
+ const fieldsList = result && result.fields ? Object.keys(result.fields) : [];
1622
+
1623
+ // 触发after事件
1624
+ this.emit('hkeys_after', { key, result: fieldsList });
1625
+
1626
+ return fieldsList;
1627
+ } catch (error) {
1628
+ // 触发error事件
1629
+ this.emit('hkeys_error', { key, error });
1630
+ $.log.error('[MongoDB cache] 获取哈希所有字段名失败:', error);
1631
+ return [];
1632
+ }
1633
+ };
1634
+
1635
+ /**
1636
+ * @description 获取哈希字段数量 (Redis HLEN)
1637
+ * @param {String} key 键
1638
+ * @return {Promise|Number} 字段数量
1639
+ */
1640
+ Mongodb_cache.prototype.hlen = async function(key) {
1641
+ try {
1642
+ // 触发before事件
1643
+ this.emit('hlen_before', { key });
1644
+
1645
+ // 确保连接已建立
1646
+ await this._ensureConnected();
1647
+
1648
+ // 验证参数
1649
+ if (!key) {
1650
+ throw new Error('参数无效:key不能为空');
1651
+ }
1652
+
1653
+ const result = await this.db.collection(this.table).findOne(
1654
+ { _id: key },
1655
+ { projection: { fields: 1, expireAt: 1 } }
1656
+ );
1657
+
1658
+ // 检查是否过期
1659
+ if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
1660
+ // 过期则删除并返回0
1661
+ await this.db.collection(this.table).deleteOne({ _id: key });
1662
+ // 触发after事件
1663
+ this.emit('hlen_after', { key, result: 0 });
1664
+ return 0;
1665
+ }
1666
+
1667
+ const length = result && result.fields ? Object.keys(result.fields).length : 0;
1668
+
1669
+ // 触发after事件
1670
+ this.emit('hlen_after', { key, result: length });
1671
+
1672
+ return length;
1673
+ } catch (error) {
1674
+ // 触发error事件
1675
+ this.emit('hlen_error', { key, error });
1676
+ $.log.error('[MongoDB cache] 获取哈希字段数量失败:', error);
1677
+ return 0;
1678
+ }
1679
+ };
1680
+
1681
+ // 集合操作方法
1682
+
1683
+ /**
1684
+ * @description 向集合添加成员 (Redis SADD)
1685
+ * @param {String} key 键
1686
+ * @param {*} member 成员
1687
+ * @return {Promise|Number} 添加的成员数量
1688
+ */
1689
+ Mongodb_cache.prototype.sadd = async function(key, member) {
1690
+ try {
1691
+ // 触发before事件
1692
+ this.emit('sadd_before', { key, member });
1693
+
1694
+ // 确保连接已建立
1695
+ await this._ensureConnected();
1696
+
1697
+ // 验证参数
1698
+ if (!key) {
1699
+ throw new Error('参数无效:key不能为空');
1700
+ }
1701
+
1702
+ // 准备更新数据
1703
+ const updateData = {
1704
+ $addToSet: { setMembers: member }
1705
+ };
1706
+
1707
+ // 执行更新
1708
+ const result = await this.db.collection(this.table).updateOne(
1709
+ { _id: key },
1710
+ updateData,
1711
+ { upsert: true }
1712
+ );
1713
+
1714
+ // 检查是否新增了成员
1715
+ const doc = await this.db.collection(this.table).findOne(
1716
+ { _id: key },
1717
+ { projection: { setMembers: 1 } }
1718
+ );
1719
+
1720
+ const addedCount = 1; // MongoDB不直接返回是否添加成功,这里假设成功
1721
+
1722
+ // 触发after事件
1723
+ this.emit('sadd_after', { key, member, result: addedCount });
1724
+
1725
+ return addedCount;
1726
+ } catch (error) {
1727
+ // 触发error事件
1728
+ this.emit('sadd_error', { key, member, error });
1729
+ $.log.error('[MongoDB cache] 向集合添加成员失败:', error);
1730
+ return 0;
1731
+ }
1732
+ };
1733
+
1734
+ /**
1735
+ * @description 从集合移除成员 (Redis SREM)
1736
+ * @param {String} key 键
1737
+ * @param {*} member 成员
1738
+ * @return {Promise|Number} 移除的成员数量
1739
+ */
1740
+ Mongodb_cache.prototype.srem = async function(key, member) {
1741
+ try {
1742
+ // 触发before事件
1743
+ this.emit('srem_before', { key, member });
1744
+
1745
+ // 确保连接已建立
1746
+ await this._ensureConnected();
1747
+
1748
+ // 验证参数
1749
+ if (!key) {
1750
+ throw new Error('参数无效:key不能为空');
1751
+ }
1752
+
1753
+ // 准备更新数据
1754
+ const updateData = {
1755
+ $pull: { setMembers: member }
1756
+ };
1757
+
1758
+ // 执行更新
1759
+ const result = await this.db.collection(this.table).updateOne(
1760
+ { _id: key },
1761
+ updateData
1762
+ );
1763
+
1764
+ const removedCount = result.modifiedCount;
1765
+
1766
+ // 触发after事件
1767
+ this.emit('srem_after', { key, member, result: removedCount });
1768
+
1769
+ return removedCount;
1770
+ } catch (error) {
1771
+ // 触发error事件
1772
+ this.emit('srem_error', { key, member, error });
1773
+ $.log.error('[MongoDB cache] 从集合移除成员失败:', error);
1774
+ return 0;
1775
+ }
1776
+ };
1777
+
1778
+ /**
1779
+ * @description 获取集合所有成员 (Redis SMEMBERS)
1780
+ * @param {String} key 键
1781
+ * @return {Promise|Array} 成员数组
1782
+ */
1783
+ Mongodb_cache.prototype.smembers = async function(key) {
1784
+ try {
1785
+ // 确保连接已建立
1786
+ await this._ensureConnected();
1787
+
1788
+ // 验证参数
1789
+ if (!key) {
1790
+ throw new Error('参数无效:key不能为空');
1791
+ }
1792
+
1793
+ const result = await this.db.collection(this.table).findOne(
1794
+ { _id: key },
1795
+ { projection: { setMembers: 1, expireAt: 1 } }
1796
+ );
1797
+
1798
+ // 检查是否过期
1799
+ if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
1800
+ // 过期则删除并返回[]
1801
+ await this.db.collection(this.table).deleteOne({ _id: key });
1802
+ return [];
1803
+ }
1804
+
1805
+ const members = result && Array.isArray(result.setMembers) ? result.setMembers : [];
1806
+
1807
+ return members;
1808
+ } catch (error) {
1809
+ $.log.error('[MongoDB cache] 获取集合所有成员失败:', error);
1810
+ return [];
1811
+ }
1812
+ };
1813
+
1814
+ /**
1815
+ * @description 判断成员是否在集合中 (Redis SISMEMBER)
1816
+ * @param {String} key 键
1817
+ * @param {*} member 成员
1818
+ * @return {Promise|Boolean} 存在返回true,不存在返回false
1819
+ */
1820
+ Mongodb_cache.prototype.sismember = async function(key, member) {
1821
+ try {
1822
+ // 确保连接已建立
1823
+ await this._ensureConnected();
1824
+
1825
+ // 验证参数
1826
+ if (!key) {
1827
+ throw new Error('参数无效:key不能为空');
1828
+ }
1829
+
1830
+ const result = await this.db.collection(this.table).findOne(
1831
+ {
1832
+ _id: key,
1833
+ $or: [
1834
+ { expireAt: { $exists: false } },
1835
+ { expireAt: { $gt: new Date() } }
1836
+ ],
1837
+ setMembers: member
1838
+ },
1839
+ { projection: { _id: 1 } }
1840
+ );
1841
+
1842
+ const exists = !!result;
1843
+
1844
+ return exists;
1845
+ } catch (error) {
1846
+ $.log.error('[MongoDB cache] 判断成员是否在集合中失败:', error);
1847
+ return false;
1848
+ }
1849
+ };
1850
+
1851
+ /**
1852
+ * @description 获取集合成员数量 (Redis SCARD)
1853
+ * @param {String} key 键
1854
+ * @return {Promise|Number} 成员数量
1855
+ */
1856
+ Mongodb_cache.prototype.scard = async function(key) {
1857
+ try {
1858
+ // 确保连接已建立
1859
+ await this._ensureConnected();
1860
+
1861
+ // 验证参数
1862
+ if (!key) {
1863
+ throw new Error('参数无效:key不能为空');
1864
+ }
1865
+
1866
+ const result = await this.db.collection(this.table).findOne(
1867
+ { _id: key },
1868
+ { projection: { setMembers: 1, expireAt: 1 } }
1869
+ );
1870
+
1871
+ // 检查是否过期
1872
+ if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
1873
+ // 过期则删除并返回0
1874
+ await this.db.collection(this.table).deleteOne({ _id: key });
1875
+ return 0;
1876
+ }
1877
+
1878
+ const count = result && Array.isArray(result.setMembers) ? result.setMembers.length : 0;
1879
+
1880
+ return count;
1881
+ } catch (error) {
1882
+ $.log.error('[MongoDB cache] 获取集合成员数量失败:', error);
1883
+ return 0;
1884
+ }
1885
+ };
1886
+
1887
+ // 基本操作补充方法
1888
+
1889
+ /**
1890
+ * @description 原子递增 (Redis INCR)
1891
+ * @param {String} key 键
1892
+ * @return {Promise|Number} 递增后的值
1893
+ */
1894
+ Mongodb_cache.prototype.incr = async function(key) {
1895
+ try {
1896
+ // 触发before事件
1897
+ this.emit('incr_before', { key });
1898
+
1899
+ // 确保连接已建立
1900
+ await this._ensureConnected();
1901
+
1902
+ // 验证参数
1903
+ if (!key) {
1904
+ throw new Error('参数无效:key不能为空');
1905
+ }
1906
+
1907
+ // 使用$inc操作符增加1
1908
+ const result = await this.db.collection(this.table).findOneAndUpdate(
1909
+ { _id: key },
1910
+ { $inc: { value: 1 } },
1911
+ { upsert: true, returnDocument: 'after', projection: { value: 1 } }
1912
+ );
1913
+
1914
+ const newValue = result.value ? result.value.value : 1;
1915
+
1916
+ // 触发after事件
1917
+ this.emit('incr_after', { key, result: newValue });
1918
+
1919
+ return newValue;
1920
+ } catch (error) {
1921
+ // 触发error事件
1922
+ this.emit('incr_error', { key, error });
1923
+ $.log.error('[MongoDB cache] 原子递增失败:', error);
1924
+ throw error;
1925
+ }
1926
+ };
1927
+
1928
+ /**
1929
+ * @description 原子递减 (Redis DECR)
1930
+ * @param {String} key 键
1931
+ * @return {Promise|Number} 递减后的值
1932
+ */
1933
+ Mongodb_cache.prototype.decr = async function(key) {
1934
+ try {
1935
+ // 触发before事件
1936
+ this.emit('decr_before', { key });
1937
+
1938
+ // 确保连接已建立
1939
+ await this._ensureConnected();
1940
+
1941
+ // 验证参数
1942
+ if (!key) {
1943
+ throw new Error('参数无效:key不能为空');
1944
+ }
1945
+
1946
+ // 使用$inc操作符减少1
1947
+ const result = await this.db.collection(this.table).findOneAndUpdate(
1948
+ { _id: key },
1949
+ { $inc: { value: -1 } },
1950
+ { upsert: true, returnDocument: 'after', projection: { value: 1 } }
1951
+ );
1952
+
1953
+ const newValue = result.value ? result.value.value : -1;
1954
+
1955
+ // 触发after事件
1956
+ this.emit('decr_after', { key, result: newValue });
1957
+
1958
+ return newValue;
1959
+ } catch (error) {
1960
+ // 触发error事件
1961
+ this.emit('decr_error', { key, error });
1962
+ $.log.error('[MongoDB cache] 原子递减失败:', error);
1963
+ throw error;
1964
+ }
1965
+ };
1966
+
1967
+ /**
1968
+ * @description 原子递增指定值 (Redis INCRBY)
1969
+ * @param {String} key 键
1970
+ * @param {Number} value 递增值
1971
+ * @return {Promise|Number} 递增后的值
1972
+ */
1973
+ Mongodb_cache.prototype.incrby = async function(key, value) {
1974
+ try {
1975
+ // 触发before事件
1976
+ this.emit('incrby_before', { key, value });
1977
+
1978
+ // 确保连接已建立
1979
+ await this._ensureConnected();
1980
+
1981
+ // 验证参数
1982
+ if (!key || typeof value !== 'number' || !Number.isInteger(value)) {
1983
+ throw new Error('参数无效:key不能为空且value必须是整数');
1984
+ }
1985
+
1986
+ const result = await this.addInt(key, value);
1987
+
1988
+ // 触发after事件
1989
+ this.emit('incrby_after', { key, value, result });
1990
+
1991
+ return result;
1992
+ } catch (error) {
1993
+ // 触发error事件
1994
+ this.emit('incrby_error', { key, value, error });
1995
+ $.log.error('[MongoDB cache] 原子递增指定值失败:', error);
1996
+ throw error;
1997
+ }
1998
+ };
1999
+
2000
+ /**
2001
+ * @description 原子递减指定值 (Redis DECRBY)
2002
+ * @param {String} key 键
2003
+ * @param {Number} value 递减值
2004
+ * @return {Promise|Number} 递减后的值
2005
+ */
2006
+ Mongodb_cache.prototype.decrby = async function(key, value) {
2007
+ try {
2008
+ // 触发before事件
2009
+ this.emit('decrby_before', { key, value });
2010
+
2011
+ // 确保连接已建立
2012
+ await this._ensureConnected();
2013
+
2014
+ // 验证参数
2015
+ if (!key || typeof value !== 'number' || !Number.isInteger(value)) {
2016
+ throw new Error('参数无效:key不能为空且value必须是整数');
2017
+ }
2018
+
2019
+ // 递减就是增加负值
2020
+ const result = await this.addInt(key, -value);
2021
+
2022
+ // 触发after事件
2023
+ this.emit('decrby_after', { key, value, result });
2024
+
2025
+ return result;
2026
+ } catch (error) {
2027
+ // 触发error事件
2028
+ this.emit('decrby_error', { key, value, error });
2029
+ $.log.error('[MongoDB cache] 原子递减指定值失败:', error);
2030
+ throw error;
2031
+ }
2032
+ };
2033
+
2034
+ /**
2035
+ * @description 追加字符串 (Redis APPEND)
2036
+ * @param {String} key 键
2037
+ * @param {String} value 要追加的字符串
2038
+ * @return {Promise|Number} 追加后的字符串长度
2039
+ */
2040
+ Mongodb_cache.prototype.append = async function(key, value) {
2041
+ try {
2042
+ // 触发before事件
2043
+ this.emit('append_before', { key, value });
2044
+
2045
+ // 确保连接已建立
2046
+ await this._ensureConnected();
2047
+
2048
+ // 验证参数
2049
+ if (!key || typeof value !== 'string') {
2050
+ throw new Error('参数无效:key不能为空且value必须是字符串');
2051
+ }
2052
+
2053
+ // 使用addStr方法追加字符串
2054
+ const newValue = await this.addStr(key, value);
2055
+ const length = newValue.length;
2056
+
2057
+ // 触发after事件
2058
+ this.emit('append_after', { key, value, result: length });
2059
+
2060
+ return length;
2061
+ } catch (error) {
2062
+ // 触发error事件
2063
+ this.emit('append_error', { key, value, error });
2064
+ $.log.error('[MongoDB cache] 追加字符串失败:', error);
2065
+ return 0;
2066
+ }
2067
+ };
2068
+
2069
+ /**
2070
+ * @description 重命名键 (Redis RENAME)
2071
+ * @param {String} oldKey 旧键名
2072
+ * @param {String} newKey 新键名
2073
+ * @return {Promise|Boolean} 是否重命名成功
2074
+ */
2075
+ Mongodb_cache.prototype.rename = async function(oldKey, newKey) {
2076
+ try {
2077
+ // 触发before事件
2078
+ this.emit('rename_before', { oldKey, newKey });
2079
+
2080
+ // 确保连接已建立
2081
+ await this._ensureConnected();
2082
+
2083
+ // 验证参数
2084
+ if (!oldKey || !newKey) {
2085
+ throw new Error('参数无效:旧键和新键不能为空');
2086
+ }
2087
+
2088
+ // 获取旧键的值
2089
+ const result = await this.db.collection(this.table).findOne({ _id: oldKey });
2090
+ if (!result) {
2091
+ // 触发after事件
2092
+ this.emit('rename_after', { oldKey, newKey, result: false });
2093
+ return false;
2094
+ }
2095
+
2096
+ // 创建新键
2097
+ const newDoc = { ...result, _id: newKey };
2098
+ delete newDoc._id; // 移除旧的_id字段
2099
+
2100
+ // 先删除可能存在的新键
2101
+ await this.db.collection(this.table).deleteOne({ _id: newKey });
2102
+
2103
+ // 插入新键
2104
+ await this.db.collection(this.table).insertOne({ ...newDoc, _id: newKey });
2105
+
2106
+ // 删除旧键
2107
+ await this.db.collection(this.table).deleteOne({ _id: oldKey });
2108
+
2109
+ // 触发after事件
2110
+ this.emit('rename_after', { oldKey, newKey, result: true });
2111
+
2112
+ return true;
2113
+ } catch (error) {
2114
+ // 触发error事件
2115
+ this.emit('rename_error', { oldKey, newKey, error });
2116
+ $.log.error('[MongoDB cache] 重命名键失败:', error);
2117
+ return false;
2118
+ }
2119
+ };
2120
+
2121
+ /**
2122
+ * @description 条件重命名键 (Redis RENAMENX)
2123
+ * @param {String} oldKey 旧键名
2124
+ * @param {String} newKey 新键名
2125
+ * @return {Promise|Boolean} 如果新键不存在且重命名成功返回true,否则返回false
2126
+ */
2127
+ Mongodb_cache.prototype.renamenx = async function(oldKey, newKey) {
2128
+ try {
2129
+ // 触发before事件
2130
+ this.emit('renamenx_before', { oldKey, newKey });
2131
+
2132
+ // 确保连接已建立
2133
+ await this._ensureConnected();
2134
+
2135
+ // 验证参数
2136
+ if (!oldKey || !newKey) {
2137
+ throw new Error('参数无效:旧键和新键不能为空');
2138
+ }
2139
+
2140
+ // 检查新键是否存在
2141
+ const newKeyExists = await this.db.collection(this.table).findOne({ _id: newKey }, { projection: { _id: 1 } });
2142
+ if (newKeyExists) {
2143
+ // 触发after事件
2144
+ this.emit('renamenx_after', { oldKey, newKey, result: false });
2145
+ return false;
2146
+ }
2147
+
2148
+ // 获取旧键的值
2149
+ const result = await this.db.collection(this.table).findOne({ _id: oldKey });
2150
+ if (!result) {
2151
+ // 触发after事件
2152
+ this.emit('renamenx_after', { oldKey, newKey, result: false });
2153
+ return false;
2154
+ }
2155
+
2156
+ // 创建新键
2157
+ const newDoc = { ...result };
2158
+ delete newDoc._id; // 移除旧的_id字段
2159
+
2160
+ // 插入新键
2161
+ await this.db.collection(this.table).insertOne({ ...newDoc, _id: newKey });
2162
+
2163
+ // 删除旧键
2164
+ await this.db.collection(this.table).deleteOne({ _id: oldKey });
2165
+
2166
+ // 触发after事件
2167
+ this.emit('renamenx_after', { oldKey, newKey, result: true });
2168
+
2169
+ return true;
2170
+ } catch (error) {
2171
+ // 触发error事件
2172
+ this.emit('renamenx_error', { oldKey, newKey, error });
2173
+ $.log.error('[MongoDB cache] 条件重命名键失败:', error);
2174
+ return false;
2175
+ }
2176
+ };
2177
+
2178
+ /**
2179
+ * @description 获取数据库中的键数量 (Redis DBSIZE)
2180
+ * @return {Promise|Number} 键的数量
2181
+ */
2182
+ Mongodb_cache.prototype.dbsize = async function() {
2183
+ try {
2184
+ // 确保连接已建立
2185
+ await this._ensureConnected();
2186
+
2187
+ // 查询有效(未过期)的键数量
2188
+ const count = await this.db.collection(this.table).countDocuments({
2189
+ $or: [
2190
+ { expireAt: { $exists: false } },
2191
+ { expireAt: { $gt: new Date() } }
2192
+ ]
2193
+ });
2194
+
2195
+ return count;
2196
+ } catch (error) {
2197
+ $.log.error('[MongoDB cache] 获取键数量失败:', error);
2198
+ return 0;
2199
+ }
2200
+ };
2201
+
2202
+ // 列表操作补充方法
2203
+
2204
+ /**
2205
+ * @description 从右侧推入元素 (Redis RPUSH)
2206
+ * @param {String} key 键
2207
+ * @param {*} value 值
2208
+ * @return {Promise|Array} 操作后的数组
2209
+ */
2210
+ Mongodb_cache.prototype.rpush = async function(key, value) {
2211
+ try {
2212
+ // 触发before事件
2213
+ this.emit('rpush_before', { key, value });
2214
+
2215
+ // 验证参数
2216
+ if (!key) {
2217
+ throw new Error('参数无效:key不能为空');
2218
+ }
2219
+
2220
+ // 使用现有的list_push方法
2221
+ const result = await this.list_push(key, value);
2222
+
2223
+ // 触发after事件
2224
+ this.emit('rpush_after', { key, value, result });
2225
+
2226
+ return result;
2227
+ } catch (error) {
2228
+ // 触发error事件
2229
+ this.emit('rpush_error', { key, value, error });
2230
+ $.log.error('[MongoDB cache] 从右侧推入元素失败:', error);
2231
+ throw error;
2232
+ }
2233
+ };
2234
+
2235
+ /**
2236
+ * @description 获取列表指定范围的元素 (Redis LRANGE)
2237
+ * @param {String} key 键
2238
+ * @param {Number} start 起始位置
2239
+ * @param {Number} stop 结束位置
2240
+ * @return {Promise|Array} 指定范围的元素数组
2241
+ */
2242
+ Mongodb_cache.prototype.lrange = async function(key, start, stop) {
2243
+ try {
2244
+ // 验证参数
2245
+ if (!key) {
2246
+ throw new Error('参数无效:key不能为空');
2247
+ }
2248
+
2249
+ // 使用现有的list_get方法
2250
+ const result = await this.list_get(key, start, stop);
2251
+
2252
+ return result;
2253
+ } catch (error) {
2254
+ $.log.error('[MongoDB cache] 获取列表范围失败:', error);
2255
+ return [];
2256
+ }
2257
+ };
2258
+
2259
+ /**
2260
+ * @description 获取列表指定索引的元素 (Redis LINDEX)
2261
+ * @param {String} key 键
2262
+ * @param {Number} index 索引
2263
+ * @return {Promise|*} 索引处的元素,不存在返回null
2264
+ */
2265
+ Mongodb_cache.prototype.lindex = async function(key, index) {
2266
+ try {
2267
+ // 验证参数
2268
+ if (!key || typeof index !== 'number') {
2269
+ throw new Error('参数无效:key不能为空且index必须是数字');
2270
+ }
2271
+
2272
+ // 获取列表
2273
+ const list = await this.get(key) || [];
2274
+ if (!Array.isArray(list)) {
2275
+ return null;
2276
+ }
2277
+
2278
+ // 处理负数索引
2279
+ if (index < 0) {
2280
+ index = list.length + index;
2281
+ }
2282
+
2283
+ // 检查索引是否有效
2284
+ let result = null;
2285
+ if (index >= 0 && index < list.length) {
2286
+ result = list[index];
2287
+ }
2288
+
2289
+ return result;
2290
+ } catch (error) {
2291
+ $.log.error('[MongoDB cache] 获取列表索引元素失败:', error);
2292
+ return null;
2293
+ }
2294
+ };
2295
+
2296
+ /**
2297
+ * @description 设置列表指定索引的元素 (Redis LSET)
2298
+ * @param {String} key 键
2299
+ * @param {Number} index 索引
2300
+ * @param {*} value 新值
2301
+ * @return {Promise|Boolean} 是否设置成功
2302
+ */
2303
+ Mongodb_cache.prototype.lset = async function(key, index, value) {
2304
+ try {
2305
+ // 触发before事件
2306
+ this.emit('lset_before', { key, index, value });
2307
+
2308
+ // 验证参数
2309
+ if (!key || typeof index !== 'number') {
2310
+ throw new Error('参数无效:key不能为空且index必须是数字');
2311
+ }
2312
+
2313
+ // 获取列表
2314
+ let list = await this.get(key) || [];
2315
+ if (!Array.isArray(list)) {
2316
+ // 触发after事件
2317
+ this.emit('lset_after', { key, index, value, result: false });
2318
+ return false;
2319
+ }
2320
+
2321
+ // 处理负数索引
2322
+ if (index < 0) {
2323
+ index = list.length + index;
2324
+ }
2325
+
2326
+ // 检查索引是否有效
2327
+ if (index < 0 || index >= list.length) {
2328
+ // 触发after事件
2329
+ this.emit('lset_after', { key, index, value, result: false });
2330
+ return false;
2331
+ }
2332
+
2333
+ // 设置新值
2334
+ list[index] = value;
2335
+
2336
+ // 保存更新后的列表
2337
+ await this.set(key, list);
2338
+
2339
+ // 触发after事件
2340
+ this.emit('lset_after', { key, index, value, result: true });
2341
+
2342
+ return true;
2343
+ } catch (error) {
2344
+ // 触发error事件
2345
+ this.emit('lset_error', { key, index, value, error });
2346
+ $.log.error('[MongoDB cache] 设置列表索引元素失败:', error);
2347
+ return false;
2348
+ }
2349
+ };
2350
+
2351
+ /**
2352
+ * @description 从列表中删除指定元素 (Redis LREM)
2353
+ * @param {String} key 键
2354
+ * @param {Number} count 计数
2355
+ * @param {*} value 要删除的元素
2356
+ * @return {Promise|Number} 删除的元素数量
2357
+ */
2358
+ Mongodb_cache.prototype.lrem = async function(key, count, value) {
2359
+ try {
2360
+ // 触发before事件
2361
+ this.emit('lrem_before', { key, count, value });
2362
+
2363
+ // 验证参数
2364
+ if (!key || typeof count !== 'number') {
2365
+ throw new Error('参数无效:key不能为空且count必须是数字');
2366
+ }
2367
+
2368
+ // 获取列表
2369
+ let list = await this.get(key) || [];
2370
+ if (!Array.isArray(list)) {
2371
+ // 触发after事件
2372
+ this.emit('lrem_after', { key, count, value, result: 0 });
2373
+ return 0;
2374
+ }
2375
+
2376
+ let deletedCount = 0;
2377
+ const valueStr = JSON.stringify(value);
2378
+ const newList = [];
2379
+
2380
+ if (count === 0) {
2381
+ // 删除所有匹配元素
2382
+ for (let i = 0; i < list.length; i++) {
2383
+ if (JSON.stringify(list[i]) !== valueStr) {
2384
+ newList.push(list[i]);
2385
+ } else {
2386
+ deletedCount++;
2387
+ }
2388
+ }
2389
+ } else if (count > 0) {
2390
+ // 从左侧删除count个匹配元素
2391
+ for (let i = 0; i < list.length; i++) {
2392
+ if (JSON.stringify(list[i]) === valueStr && deletedCount < count) {
2393
+ deletedCount++;
2394
+ } else {
2395
+ newList.push(list[i]);
2396
+ }
2397
+ }
2398
+ } else {
2399
+ // 从右侧删除count的绝对值个匹配元素
2400
+ count = Math.abs(count);
2401
+ const tempList = [];
2402
+ for (let i = list.length - 1; i >= 0; i--) {
2403
+ if (JSON.stringify(list[i]) === valueStr && deletedCount < count) {
2404
+ deletedCount++;
2405
+ } else {
2406
+ tempList.unshift(list[i]);
2407
+ }
2408
+ }
2409
+ newList.push(...tempList);
2410
+ }
2411
+
2412
+ // 如果有元素被删除,更新列表
2413
+ if (deletedCount > 0) {
2414
+ await this.set(key, newList);
2415
+ }
2416
+
2417
+ // 触发after事件
2418
+ this.emit('lrem_after', { key, count, value, result: deletedCount });
2419
+
2420
+ return deletedCount;
2421
+ } catch (error) {
2422
+ // 触发error事件
2423
+ this.emit('lrem_error', { key, count, value, error });
2424
+ $.log.error('[MongoDB cache] 删除列表元素失败:', error);
2425
+ return 0;
2426
+ }
2427
+ };
2428
+
2429
+ /**
2430
+ * @description 修剪列表 (Redis LTRIM)
2431
+ * @param {String} key 键
2432
+ * @param {Number} start 起始位置
2433
+ * @param {Number} stop 结束位置
2434
+ * @return {Promise|Boolean} 是否修剪成功
2435
+ */
2436
+ Mongodb_cache.prototype.ltrim = async function(key, start, stop) {
2437
+ try {
2438
+ // 触发before事件
2439
+ this.emit('ltrim_before', { key, start, stop });
2440
+
2441
+ // 验证参数
2442
+ if (!key || typeof start !== 'number' || typeof stop !== 'number') {
2443
+ throw new Error('参数无效:key不能为空且start和stop必须是数字');
2444
+ }
2445
+
2446
+ // 获取列表
2447
+ let list = await this.get(key) || [];
2448
+ if (!Array.isArray(list)) {
2449
+ // 触发after事件
2450
+ this.emit('ltrim_after', { key, start, stop, result: false });
2451
+ return false;
2452
+ }
2453
+
2454
+ // 处理负数索引
2455
+ if (start < 0) start = Math.max(0, list.length + start);
2456
+ if (stop < 0) stop = list.length + stop;
2457
+
2458
+ // 确保索引有效
2459
+ start = Math.max(0, start);
2460
+ stop = Math.min(list.length - 1, stop);
2461
+
2462
+ // 修剪列表
2463
+ const trimmedList = list.slice(start, stop + 1);
2464
+
2465
+ // 保存修剪后的列表
2466
+ await this.set(key, trimmedList);
2467
+
2468
+ // 触发after事件
2469
+ this.emit('ltrim_after', { key, start, stop, result: true });
2470
+
2471
+ return true;
2472
+ } catch (error) {
2473
+ // 触发error事件
2474
+ this.emit('ltrim_error', { key, start, stop, error });
2475
+ $.log.error('[MongoDB cache] 修剪列表失败:', error);
2476
+ return false;
2477
+ }
2478
+ };
2479
+
2480
+ // 集合操作补充方法
2481
+
2482
+ /**
2483
+ * @description 批量添加集合成员 (Redis SADD 多成员)
2484
+ * @param {String} key 键
2485
+ * @param {...*} members 成员列表
2486
+ * @return {Promise|Number} 添加的成员数量
2487
+ */
2488
+ Mongodb_cache.prototype.saddMulti = async function(key, ...members) {
2489
+ try {
2490
+ // 触发before事件
2491
+ this.emit('saddMulti_before', { key, members });
2492
+
2493
+ // 确保连接已建立
2494
+ await this._ensureConnected();
2495
+
2496
+ // 验证参数
2497
+ if (!key || members.length === 0) {
2498
+ // 触发after事件
2499
+ this.emit('saddMulti_after', { key, members, result: 0 });
2500
+ return 0;
2501
+ }
2502
+
2503
+ // 获取当前成员
2504
+ const doc = await this.db.collection(this.table).findOne(
2505
+ { _id: key },
2506
+ { projection: { setMembers: 1 } }
2507
+ );
2508
+ const currentMembers = doc && Array.isArray(doc.setMembers) ? doc.setMembers : [];
2509
+
2510
+ // 找出新成员
2511
+ let addedCount = 0;
2512
+ const newMembers = [];
2513
+
2514
+ for (const member of members) {
2515
+ if (!currentMembers.some(m => JSON.stringify(m) === JSON.stringify(member))) {
2516
+ newMembers.push(member);
2517
+ addedCount++;
2518
+ }
2519
+ }
2520
+
2521
+ if (addedCount > 0) {
2522
+ // 添加新成员
2523
+ await this.db.collection(this.table).updateOne(
2524
+ { _id: key },
2525
+ { $addToSet: { setMembers: { $each: newMembers } } },
2526
+ { upsert: true }
2527
+ );
2528
+ }
2529
+
2530
+ // 触发after事件
2531
+ this.emit('saddMulti_after', { key, members, result: addedCount });
2532
+
2533
+ return addedCount;
2534
+ } catch (error) {
2535
+ // 触发error事件
2536
+ this.emit('saddMulti_error', { key, members, error });
2537
+ $.log.error('[MongoDB cache] 批量添加集合成员失败:', error);
2538
+ return 0;
2539
+ }
2540
+ };
2541
+
2542
+ /**
2543
+ * @description 批量移除集合成员 (Redis SREM 多成员)
2544
+ * @param {String} key 键
2545
+ * @param {...*} members 成员列表
2546
+ * @return {Promise|Number} 移除的成员数量
2547
+ */
2548
+ Mongodb_cache.prototype.sremMulti = async function(key, ...members) {
2549
+ try {
2550
+ // 触发before事件
2551
+ this.emit('sremMulti_before', { key, members });
2552
+
2553
+ // 确保连接已建立
2554
+ await this._ensureConnected();
2555
+
2556
+ // 验证参数
2557
+ if (!key || members.length === 0) {
2558
+ // 触发after事件
2559
+ this.emit('sremMulti_after', { key, members, result: 0 });
2560
+ return 0;
2561
+ }
2562
+
2563
+ // 批量移除成员
2564
+ const result = await this.db.collection(this.table).updateOne(
2565
+ { _id: key },
2566
+ { $pull: { setMembers: { $in: members } } }
2567
+ );
2568
+
2569
+ // 触发after事件
2570
+ this.emit('sremMulti_after', { key, members, result: result.modifiedCount });
2571
+
2572
+ return result.modifiedCount;
2573
+ } catch (error) {
2574
+ // 触发error事件
2575
+ this.emit('sremMulti_error', { key, members, error });
2576
+ $.log.error('[MongoDB cache] 批量移除集合成员失败:', error);
2577
+ return 0;
2578
+ }
2579
+ };
2580
+
2581
+ /**
2582
+ * @description 获取两个集合的差集 (Redis SDIFF)
2583
+ * @param {String} key1 第一个集合键
2584
+ * @param {String} key2 第二个集合键
2585
+ * @return {Promise|Array} 差集成员数组
2586
+ */
2587
+ Mongodb_cache.prototype.sdiff = async function(key1, key2) {
2588
+ try {
2589
+ // 确保连接已建立
2590
+ await this._ensureConnected();
2591
+
2592
+ // 验证参数
2593
+ if (!key1 || !key2) {
2594
+ throw new Error('参数无效:key1和key2不能为空');
2595
+ }
2596
+
2597
+ // 获取两个集合的成员
2598
+ const set1 = await this.smembers(key1);
2599
+ const set2 = await this.smembers(key2);
2600
+
2601
+ // 计算差集
2602
+ const set2Str = new Set(set2.map(item => JSON.stringify(item)));
2603
+ const result = set1.filter(item => !set2Str.has(JSON.stringify(item)));
2604
+
2605
+ return result;
2606
+ } catch (error) {
2607
+ $.log.error('[MongoDB cache] 获取集合差集失败:', error);
2608
+ return [];
2609
+ }
2610
+ };
2611
+
2612
+ /**
2613
+ * @description 获取两个集合的交集 (Redis SINTER)
2614
+ * @param {String} key1 第一个集合键
2615
+ * @param {String} key2 第二个集合键
2616
+ * @return {Promise|Array} 交集成员数组
2617
+ */
2618
+ Mongodb_cache.prototype.sinter = async function(key1, key2) {
2619
+ try {
2620
+ // 确保连接已建立
2621
+ await this._ensureConnected();
2622
+
2623
+ // 验证参数
2624
+ if (!key1 || !key2) {
2625
+ throw new Error('参数无效:key1和key2不能为空');
2626
+ }
2627
+
2628
+ // 获取两个集合的成员
2629
+ const set1 = await this.smembers(key1);
2630
+ const set2 = await this.smembers(key2);
2631
+
2632
+ // 计算交集
2633
+ const set2Str = new Set(set2.map(item => JSON.stringify(item)));
2634
+ const result = set1.filter(item => set2Str.has(JSON.stringify(item)));
2635
+
2636
+ return result;
2637
+ } catch (error) {
2638
+ $.log.error('[MongoDB cache] 获取集合交集失败:', error);
2639
+ return [];
2640
+ }
2641
+ };
2642
+
2643
+ /**
2644
+ * @description 获取两个集合的并集 (Redis SUNION)
2645
+ * @param {String} key1 第一个集合键
2646
+ * @param {String} key2 第二个集合键
2647
+ * @return {Promise|Array} 并集成员数组
2648
+ */
2649
+ Mongodb_cache.prototype.sunion = async function(key1, key2) {
2650
+ try {
2651
+ // 确保连接已建立
2652
+ await this._ensureConnected();
2653
+
2654
+ // 验证参数
2655
+ if (!key1 || !key2) {
2656
+ throw new Error('参数无效:key1和key2不能为空');
2657
+ }
2658
+
2659
+ // 获取两个集合的成员
2660
+ const set1 = await this.smembers(key1);
2661
+ const set2 = await this.smembers(key2);
2662
+
2663
+ // 计算并集
2664
+ const result = [];
2665
+ const seen = new Set();
2666
+
2667
+ for (const item of [...set1, ...set2]) {
2668
+ const itemStr = JSON.stringify(item);
2669
+ if (!seen.has(itemStr)) {
2670
+ seen.add(itemStr);
2671
+ result.push(item);
2672
+ }
2673
+ }
2674
+
2675
+ return result;
2676
+ } catch (error) {
2677
+ $.log.error('[MongoDB cache] 获取集合并集失败:', error);
2678
+ return [];
2679
+ }
2680
+ };
2681
+
2682
+ /**
2683
+ * @description 哈希操作补充方法 - 批量设置哈希字段 (Redis HMSET)
2684
+ * @param {String} key 键
2685
+ * @param {Object} fieldValuePairs 字段值对象
2686
+ * @return {Promise|Boolean} 是否设置成功
2687
+ */
2688
+ Mongodb_cache.prototype.hmset = async function(key, fieldValuePairs) {
2689
+ try {
2690
+ // 触发before事件
2691
+ this.emit('hmset_before', { key, fieldValuePairs });
2692
+
2693
+ // 确保连接已建立
2694
+ await this._ensureConnected();
2695
+
2696
+ // 验证参数
2697
+ if (!key || !fieldValuePairs || typeof fieldValuePairs !== 'object') {
2698
+ throw new Error('参数无效:key不能为空且fieldValuePairs必须是对象');
2699
+ }
2700
+
2701
+ // 准备更新对象
2702
+ const updateData = { $set: {} };
2703
+ Object.entries(fieldValuePairs).forEach(([field, value]) => {
2704
+ updateData.$set[`fields.${field}`] = value;
2705
+ });
2706
+
2707
+ // 执行更新
2708
+ await this.db.collection(this.table).updateOne(
2709
+ { _id: key },
2710
+ updateData,
2711
+ { upsert: true }
2712
+ );
2713
+
2714
+ // 触发after事件
2715
+ this.emit('hmset_after', { key, fieldValuePairs, result: true });
2716
+
2717
+ return true;
2718
+ } catch (error) {
2719
+ // 触发error事件
2720
+ this.emit('hmset_error', { key, fieldValuePairs, error });
2721
+ $.log.error('[MongoDB cache] 批量设置哈希字段失败:', error);
2722
+ return false;
2723
+ }
2724
+ };
2725
+
2726
+ /**
2727
+ * @description 哈希操作补充方法 - 批量获取哈希字段值 (Redis HMGET)
2728
+ * @param {String} key 键
2729
+ * @param {...String} fields 字段名列表
2730
+ * @return {Promise|Array} 字段值数组,顺序与字段名对应
2731
+ */
2732
+ Mongodb_cache.prototype.hmget = async function(key, ...fields) {
2733
+ try {
2734
+ // 确保连接已建立
2735
+ await this._ensureConnected();
2736
+
2737
+ // 验证参数
2738
+ if (!key || fields.length === 0) {
2739
+ return [];
2740
+ }
2741
+
2742
+ // 获取所有字段值
2743
+ const allFields = await this.hgetall(key);
2744
+
2745
+ // 按字段名顺序返回值
2746
+ const result = fields.map(field => allFields[field] !== undefined ? allFields[field] : null);
2747
+
2748
+ return result;
2749
+ } catch (error) {
2750
+ $.log.error('[MongoDB cache] 批量获取哈希字段值失败:', error);
2751
+ return [];
2752
+ }
2753
+ };
2754
+
2755
+ /**
2756
+ * @description 哈希操作补充方法 - 批量删除哈希字段 (Redis HDEL 多字段)
2757
+ * @param {String} key 键
2758
+ * @param {...String} fields 字段名列表
2759
+ * @return {Promise|Number} 删除的字段数量
2760
+ */
2761
+ Mongodb_cache.prototype.hdelMulti = async function(key, ...fields) {
2762
+ try {
2763
+ // 触发before事件
2764
+ this.emit('hdelMulti_before', { key, fields });
2765
+
2766
+ // 确保连接已建立
2767
+ await this._ensureConnected();
2768
+
2769
+ // 验证参数
2770
+ if (!key || fields.length === 0) {
2771
+ // 触发after事件
2772
+ this.emit('hdelMulti_after', { key, fields, result: 0 });
2773
+ return 0;
2774
+ }
2775
+
2776
+ // 准备更新对象
2777
+ const updateData = { $unset: {} };
2778
+ fields.forEach(field => {
2779
+ updateData.$unset[`fields.${field}`] = '';
2780
+ });
2781
+
2782
+ // 执行更新
2783
+ const result = await this.db.collection(this.table).updateOne(
2784
+ { _id: key },
2785
+ updateData
2786
+ );
2787
+
2788
+ const deletedCount = result.modifiedCount ? fields.length : 0;
2789
+
2790
+ // 触发after事件
2791
+ this.emit('hdelMulti_after', { key, fields, result: deletedCount });
2792
+
2793
+ return deletedCount;
2794
+ } catch (error) {
2795
+ // 触发error事件
2796
+ this.emit('hdelMulti_error', { key, fields, error });
2797
+ $.log.error('[MongoDB cache] 批量删除哈希字段失败:', error);
2798
+ return 0;
2799
+ }
2800
+ };
2801
+
2802
+ /**
2803
+ * @description 获取哈希所有值 (Redis HVALS)
2804
+ * @param {String} key 键
2805
+ * @return {Promise|Array} 值数组
2806
+ */
2807
+ Mongodb_cache.prototype.hvals = async function(key) {
2808
+ try {
2809
+ // 确保连接已建立
2810
+ await this._ensureConnected();
2811
+
2812
+ // 验证参数
2813
+ if (!key) {
2814
+ throw new Error('参数无效:key不能为空');
2815
+ }
2816
+
2817
+ // 获取所有字段值
2818
+ const fields = await this.hgetall(key);
2819
+
2820
+ // 返回值数组
2821
+ const result = Object.values(fields);
2822
+
2823
+ return result;
2824
+ } catch (error) {
2825
+ $.log.error('[MongoDB cache] 获取哈希所有值失败:', error);
2826
+ return [];
2827
+ }
324
2828
  };
325
2829
 
326
2830
  /**
@@ -331,6 +2835,9 @@ exports.Mongodb_cache = Mongodb_cache;
331
2835
  /**
332
2836
  * @description mongodb_cache连接池
333
2837
  */
2838
+ if (!$.pool) {
2839
+ $.pool = {};
2840
+ }
334
2841
  if (!$.pool.mongodb_cache) {
335
2842
  $.pool.mongodb_cache = {};
336
2843
  }
@@ -343,7 +2850,7 @@ if (!$.pool.mongodb_cache) {
343
2850
  */
344
2851
  function mongodb_cache_admin(scope, dir) {
345
2852
  if (!scope) {
346
- scope = $.val.scope
2853
+ scope = ($.val && $.val.scope) || 'default';
347
2854
  }
348
2855
  var obj = $.pool.mongodb_cache[scope];
349
2856
  if (!obj) {
@@ -356,4 +2863,4 @@ function mongodb_cache_admin(scope, dir) {
356
2863
  /**
357
2864
  * @description 导出mongodb_cache管理函数
358
2865
  */
359
- exports.mongodb_cache_admin = mongodb_cache_admin;
2866
+ exports.mongodb_cache_admin = mongodb_cache_admin;