mm_cachebase 1.9.7 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +269 -559
  2. package/index.js +2075 -1324
  3. package/package.json +3 -3
  4. package/test.js +728 -536
package/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * CacheBase 是一个通用缓存基类,支持内存缓存和持久化缓存
3
+ * 接口与 mm_redis 完全对齐:全异步、冒号风格事件、统一签名与返回值
3
4
  * @class CacheBase
4
5
  * @extends Base
5
6
  */
@@ -51,8 +52,14 @@ class CacheBase extends Base {
51
52
  this.is_inited = false;
52
53
  this.is_destroyed = false;
53
54
 
55
+ // 连接状态(与 mm_redis 保持一致)
56
+ this.connected = false;
57
+
58
+ // 发布订阅频道回调
59
+ this._channels = {};
60
+
54
61
  this.setConfig(config);
55
- // 初始化服务
62
+ // 初始化服务(内存缓存特性:构造即初始化)
56
63
  this.init();
57
64
  }
58
65
  }
@@ -66,7 +73,7 @@ CacheBase.prototype.setConfig = function (config) {
66
73
  const external_config = this._loadExternalConfig();
67
74
  // 更新配置
68
75
  Object.assign(this.config, external_config, config);
69
- }
76
+ };
70
77
 
71
78
  /**
72
79
  * 初始化服务
@@ -80,16 +87,17 @@ CacheBase.prototype.init = async function () {
80
87
  try {
81
88
  await this._initService();
82
89
  this.is_inited = true;
90
+ this.connected = true;
83
91
  } catch (error) {
84
92
  this.log('error', 'Failed to initialize CacheBase service:', error);
85
93
  }
86
94
  };
87
95
 
88
96
  /**
89
- * 加载外部配置文件
90
- * @private
91
- * @returns {Object} 外部配置对象
92
- */
97
+ * 加载外部配置文件
98
+ * @private
99
+ * @returns {Object} 外部配置对象
100
+ */
93
101
  CacheBase.prototype._loadExternalConfig = function () {
94
102
  try {
95
103
  const config_path = './config/cachebase.config.json'.fullname(__dirname);
@@ -199,9 +207,9 @@ CacheBase.prototype.save = function () {
199
207
  }
200
208
 
201
209
  try {
202
- this.emit('save_before');
210
+ this.emit('save:before');
203
211
  this._saveData();
204
- this.emit('save_after');
212
+ this.emit('save:after');
205
213
  } catch (error) {
206
214
  this.log('error', 'Failed to save data:', error);
207
215
  }
@@ -260,34 +268,114 @@ CacheBase.prototype.destroy = async function () {
260
268
  this._data = {};
261
269
  this._expires = {};
262
270
  this._types = {};
271
+ this._channels = {};
263
272
 
264
273
  // 设置销毁状态
265
274
  this.is_inited = false;
266
275
  this.is_destroyed = true;
276
+ this.connected = false;
267
277
 
268
- // 触发自定义销毁事件
269
- this.emit('destroy_after');
278
+ // 触发销毁事件(注意:不可使用 'destroy:after' 冒号格式,
279
+ // mm_expand 的 Base.emit 会将冒号格式解释为 do 流程的阶段,触发 _destroyCore)
280
+ this.emit('destroy', { success: true });
270
281
  } catch (error) {
271
282
  this.log('error', 'Failed to destroy service:', error);
272
283
  }
273
284
  };
274
285
 
275
286
  /**
276
- * 关闭缓存服务(兼容方法)
277
- * @returns {Promise<void>}
287
+ * 打开缓存服务(与 mm_redis 接口对齐)
288
+ * @returns {Promise<Boolean>} 连接结果
289
+ */
290
+ CacheBase.prototype.open = async function () {
291
+ try {
292
+ if (!this.is_inited) {
293
+ await this.init();
294
+ }
295
+ return this.connected;
296
+ } catch (error) {
297
+ this.log('error', 'Failed to open CacheBase:', error);
298
+ return false;
299
+ }
300
+ };
301
+
302
+ /**
303
+ * 关闭缓存服务(与 mm_redis 接口对齐)
304
+ * @returns {Promise<Boolean>} 关闭结果
305
+ */
306
+ CacheBase.prototype.close = async function () {
307
+ try {
308
+ await this.destroy();
309
+ return true;
310
+ } catch (error) {
311
+ this.log('error', 'Failed to close CacheBase:', error);
312
+ return false;
313
+ }
314
+ };
315
+
316
+ /**
317
+ * 检查连接状态(与 mm_redis 接口对齐)
318
+ * @returns {Boolean} 是否已连接
319
+ */
320
+ CacheBase.prototype.isConnected = function () {
321
+ return this.connected;
322
+ };
323
+
324
+ /**
325
+ * 清理资源并销毁实例
326
+ * @returns {Boolean} 操作结果
278
327
  */
279
- CacheBase.prototype.close = function () {
280
- return this.destroy();
328
+ CacheBase.prototype.dispose = function () {
329
+ try {
330
+ // 防止重复销毁
331
+ if (this.is_destroyed) {
332
+ return true;
333
+ }
334
+
335
+ // 清除定时任务
336
+ if (this._save_timer) {
337
+ clearInterval(this._save_timer);
338
+ this._save_timer = null;
339
+ }
340
+
341
+ if (this._check_timer) {
342
+ clearInterval(this._check_timer);
343
+ this._check_timer = null;
344
+ }
345
+
346
+ // 清除所有数据
347
+ this._data = {};
348
+ this._expires = {};
349
+ this._types = {};
350
+ this._channels = {};
351
+
352
+ // 设置销毁状态
353
+ this.is_inited = false;
354
+ this.is_destroyed = true;
355
+ this.connected = false;
356
+
357
+ // 触发销毁事件
358
+ this.emit('destroy', {
359
+ success: true
360
+ });
361
+
362
+ return true;
363
+ } catch (error) {
364
+ this.log('error', 'Failed to dispose:', error);
365
+ return false;
366
+ }
281
367
  };
282
368
 
369
+ // ==================== 键值操作 ====================
370
+
283
371
  /**
284
372
  * 设置键值对
285
- * @param {string} key - 键名
286
- * @param {*} value - 值
287
- * @param {number} [ttl] - 过期时间(秒)
288
- * @returns {boolean} 操作结果
373
+ * @param {String} key 键
374
+ * @param {*} value 值
375
+ * @param {Number} [seconds=0] 过期时间(秒),0表示永不过期
376
+ * @returns {Promise<Boolean>} 成功返回true,失败返回false
289
377
  */
290
- CacheBase.prototype.set = function (key, value, ttl) {
378
+ CacheBase.prototype.set = async function (key, value, seconds = 0) {
291
379
  // 检查服务状态
292
380
  if (!this.is_inited || this.is_destroyed) {
293
381
  this.log('error', 'Service is not initialized or has been destroyed');
@@ -300,55 +388,62 @@ CacheBase.prototype.set = function (key, value, ttl) {
300
388
  }
301
389
 
302
390
  try {
303
-
304
- // 触发操作前事件
305
- const event_data = {
391
+ // 触发前置事件
392
+ const ctx = {
306
393
  key,
307
394
  value,
308
- ttl,
395
+ seconds,
309
396
  cancel: false
310
397
  };
311
- this.emit('set_before', event_data);
398
+ this.emit('set:before', ctx);
312
399
 
313
- if (event_data.cancel) {
400
+ if (ctx.cancel) {
401
+ this.log('debug', `set操作被事件处理器取消 [${key}]`);
314
402
  return false;
315
403
  }
316
404
 
405
+ // 使用可能被修改的值
406
+ const {
407
+ key: finalKey,
408
+ value: finalValue,
409
+ seconds: finalSeconds
410
+ } = ctx;
411
+
317
412
  // 保存数据
318
- this._data[key] = value;
413
+ this._data[finalKey] = finalValue;
319
414
 
320
415
  // 设置类型
321
- this._types[key] = this._getDataTypes(value);
416
+ this._types[finalKey] = this._getDataTypes(finalValue);
322
417
 
323
418
  // 设置过期时间
324
- if (ttl && typeof ttl === 'number' && ttl > 0) {
325
- this._expires[key] = Date.now() + ttl * 1000;
419
+ if (finalSeconds && typeof finalSeconds === 'number' && finalSeconds > 0) {
420
+ this._expires[finalKey] = Date.now() + finalSeconds * 1000;
326
421
  } else {
327
- // 清除过期时间
328
- delete this._expires[key];
422
+ delete this._expires[finalKey];
329
423
  }
330
424
 
331
- // 触发操作后事件
332
- this.emit('set_after', {
425
+ // 触发后置事件
426
+ this.emit('set:after', {
333
427
  key,
334
428
  value,
335
- ttl,
336
- result: 'OK'
429
+ seconds,
430
+ success: true
337
431
  });
338
432
 
339
433
  return true;
340
434
  } catch (error) {
341
435
  this.log('error', `Failed to set key ${key}:`, error);
436
+ this.emit('set:error', { key, value, seconds, error });
342
437
  return false;
343
438
  }
344
439
  };
345
440
 
346
441
  /**
347
442
  * 获取键值
348
- * @param {string} key - 键
349
- * @returns {*} 值
443
+ * @param {String} key 键
444
+ * @returns {Promise<Object|null>} 值,不存在或错误返回null
350
445
  */
351
- CacheBase.prototype.get = function (key) {
446
+ CacheBase.prototype.get = async function (key) {
352
447
  // 检查服务状态
353
448
  if (!this.is_inited || this.is_destroyed) {
354
449
  this.log('error', 'Service is not initialized or has been destroyed');
@@ -356,38 +451,41 @@ CacheBase.prototype.get = function (key) {
356
451
  }
357
452
 
358
453
  try {
454
+ this.emit('get:before', { key });
455
+
359
456
  // 检查是否过期
360
457
  if (this._isExpired(key)) {
361
- this.del(key);
458
+ await this.del(key);
459
+ this.emit('get:after', { key, value: null });
362
460
  return null;
363
461
  }
364
462
 
365
- return this._data[key];
463
+ const value = this._data[key];
464
+ this.emit('get:after', { key, value });
465
+ return value !== undefined ? value : null;
366
466
  } catch (error) {
367
467
  this.log('error', `Failed to get key ${key}:`, error);
468
+ this.emit('get:error', { key, error });
368
469
  return null;
369
470
  }
370
471
  };
371
472
 
372
473
  /**
373
474
  * 删除键
374
- * @param {string|Array<string>} key - 键名或键名数组
375
- * @returns {boolean} 是否成功删除
475
+ * @param {String|Array<String>} key 键名或键名数组
476
+ * @returns {Promise<Number>} 删除的键数量
376
477
  */
377
- CacheBase.prototype.del = function (key) {
478
+ CacheBase.prototype.del = async function (key) {
378
479
  // 参数验证
379
480
  if (!key) {
380
481
  throw new TypeError('key必须是非空字符串或非空字符串数组');
381
482
  }
382
483
 
383
- // 如果是单个键,验证是否为非空字符串
384
484
  if (typeof key === 'string') {
385
485
  if (!key) {
386
486
  throw new TypeError('key必须是非空字符串');
387
487
  }
388
- }
389
- // 如果是数组,验证每个元素是否为非空字符串
390
- else if (Array.isArray(key)) {
488
+ } else if (Array.isArray(key)) {
391
489
  for (const k of key) {
392
490
  if (!k || typeof k !== 'string') {
393
491
  throw new TypeError('key必须是非空字符串或非空字符串数组');
@@ -397,85 +495,73 @@ CacheBase.prototype.del = function (key) {
397
495
  throw new TypeError('key必须是非空字符串或非空字符串数组');
398
496
  }
399
497
 
498
+ // 检查服务状态
499
+ if (!this.is_inited || this.is_destroyed) {
500
+ this.log('error', 'Service is not initialized or has been destroyed');
501
+ return 0;
502
+ }
503
+
400
504
  try {
401
- // 触发操作前事件
402
- const event_data = {
505
+ // 触发前置事件
506
+ const ctx = {
403
507
  key,
404
508
  cancel: false
405
509
  };
406
- this.emit('del_before', event_data);
510
+ this.emit('del:before', ctx);
407
511
 
408
- if (event_data.cancel) {
409
- return false;
512
+ if (ctx.cancel) {
513
+ return 0;
410
514
  }
411
515
 
412
- // 使用可能被修改的值
413
- const finalKey = event_data.key;
414
-
415
- let success = true;
416
- let deleted = [];
516
+ const finalKey = ctx.key;
517
+ let deletedCount = 0;
417
518
 
418
519
  if (Array.isArray(finalKey)) {
419
520
  // 批量删除
420
521
  for (const k of finalKey) {
421
- // 检查键是否存在
422
522
  if (k in this._data) {
423
- // 删除键
424
523
  delete this._data[k];
425
524
  delete this._types[k];
426
525
  delete this._expires[k];
427
- deleted.push(k);
428
- } else {
429
- success = false;
526
+ deletedCount++;
430
527
  }
431
528
  }
432
529
  } else {
433
530
  // 单个删除
434
- // 检查键是否存在
435
- if (!(finalKey in this._data)) {
436
- return false;
531
+ if (finalKey in this._data) {
532
+ delete this._data[finalKey];
533
+ delete this._types[finalKey];
534
+ delete this._expires[finalKey];
535
+ deletedCount++;
437
536
  }
438
-
439
- // 删除键
440
- delete this._data[finalKey];
441
- delete this._types[finalKey];
442
- delete this._expires[finalKey];
443
- deleted.push(finalKey);
444
537
  }
445
538
 
446
- // 触发操作后事件
447
- this.emit('del_after', {
539
+ // 触发后置事件
540
+ this.emit('del:after', {
448
541
  key: finalKey,
449
- success,
450
- deleted
542
+ success: deletedCount > 0,
543
+ deleted: deletedCount
451
544
  });
452
545
 
453
- return success;
546
+ return deletedCount;
454
547
  } catch (error) {
455
548
  this.log('error', 'Failed to del:', error);
456
- return false;
549
+ this.emit('del:error', { key, error });
550
+ return 0;
457
551
  }
458
552
  };
459
553
 
460
554
  /**
461
- * 批量删除键(兼容方法,推荐使用del方法)
462
- * @param {Array} keys - 键名数组
463
- * @returns {number} 删除的键数量
555
+ * 批量删除键
556
+ * @param {Array} keys 键名数组
557
+ * @returns {Promise<Number>} 删除的键数量
464
558
  */
465
- CacheBase.prototype.delBatch = function (keys) {
559
+ CacheBase.prototype.delBatch = async function (keys) {
466
560
  try {
467
561
  if (!Array.isArray(keys)) {
468
562
  return 0;
469
563
  }
470
-
471
- let deletedCount = 0;
472
- for (const key of keys) {
473
- if (this.del(key)) {
474
- deletedCount++;
475
- }
476
- }
477
-
478
- return deletedCount;
564
+ return await this.del(keys);
479
565
  } catch (error) {
480
566
  this.log('error', 'Failed to delBatch:', error);
481
567
  return 0;
@@ -484,43 +570,54 @@ CacheBase.prototype.delBatch = function (keys) {
484
570
 
485
571
  /**
486
572
  * 检查键是否存在
487
- * @param {string} key - 键
488
- * @returns {boolean} 存在状态
573
+ * @param {String} key 键
574
+ * @returns {Promise<Number>} 存在返回1,不存在返回0
489
575
  */
490
- CacheBase.prototype.exists = function (key) {
576
+ CacheBase.prototype.exists = async function (key) {
491
577
  try {
578
+ this.emit('exists:before', { key });
579
+
492
580
  if (this._isExpired(key)) {
493
- this.del(key);
494
- return false;
581
+ await this.del(key);
582
+ this.emit('exists:after', { key, result: 0 });
583
+ return 0;
495
584
  }
496
585
 
497
- return key in this._data;
586
+ const result = key in this._data ? 1 : 0;
587
+ this.emit('exists:after', { key, result });
588
+ return result;
498
589
  } catch (error) {
499
590
  this.log('error', `Failed to check key ${key}:`, error);
500
- return false;
591
+ return 0;
501
592
  }
502
593
  };
503
594
 
504
595
  /**
505
596
  * 检查键是否存在
506
- * @param {string} key - 键名
507
- * @returns {boolean} 是否存在
597
+ * @param {String} key 键名
598
+ * @returns {Promise<Boolean>} 是否存在
508
599
  */
509
- CacheBase.prototype.has = function (key) {
510
- return this.exists(key);
600
+ CacheBase.prototype.has = async function (key) {
601
+ const result = await this.exists(key);
602
+ return result > 0;
511
603
  };
512
604
 
513
605
  /**
514
606
  * 清空过期数据
607
+ * @returns {Promise<void>}
515
608
  */
516
- CacheBase.prototype.clearExpire = function () {
609
+ CacheBase.prototype.clearExpire = async function () {
517
610
  try {
518
611
  const now = Date.now();
612
+ const expiredKeys = [];
519
613
  for (const key in this._expires) {
520
614
  if (this._expires[key] < now) {
521
- this.del(key);
615
+ expiredKeys.push(key);
522
616
  }
523
617
  }
618
+ if (expiredKeys.length > 0) {
619
+ await this.del(expiredKeys);
620
+ }
524
621
  } catch (error) {
525
622
  this.log('error', 'Failed to clear expired data:', error);
526
623
  }
@@ -529,8 +626,8 @@ CacheBase.prototype.clearExpire = function () {
529
626
  /**
530
627
  * 检查键是否过期
531
628
  * @private
532
- * @param {string} key - 键名
533
- * @returns {boolean} 是否过期
629
+ * @param {String} key 键名
630
+ * @returns {Boolean} 是否过期
534
631
  */
535
632
  CacheBase.prototype._isExpired = function (key) {
536
633
  // 先检查键是否存在
@@ -546,37 +643,35 @@ CacheBase.prototype._isExpired = function (key) {
546
643
  const now = Date.now();
547
644
  const is_expired = now > this._expires[key];
548
645
 
549
- // 惰性删除:如果过期了,删除键
550
- if (is_expired) {
551
- delete this._data[key];
552
- delete this._types[key];
553
- delete this._expires[key];
554
- }
555
-
646
+ // 仅返回过期状态,不在此处做惰性删除。
647
+ // 删除统一由 del 方法完成,避免事件不成对。
556
648
  return is_expired;
557
649
  };
558
650
 
559
651
  /**
560
652
  * 获取数据类型
561
653
  * @private
562
- * @param {*} value - 值
563
- * @returns {string} 数据类型
654
+ * @param {*} value 值
655
+ * @returns {String} 数据类型
564
656
  */
565
657
  CacheBase.prototype._getDataTypes = function (value) {
566
658
  if (value === null) return 'null';
659
+ if (value instanceof Set) return 'set';
660
+ if (value instanceof Map) return 'zset';
567
661
  if (Array.isArray(value)) return 'array';
568
662
  return typeof value;
569
663
  };
570
664
 
665
+ // ==================== 过期时间管理 ====================
666
+
571
667
  /**
572
668
  * 设置键的过期时间
573
- * @param {string} key - 键名
574
- * @param {number} seconds - 过期时间(秒)
575
- * @returns {boolean} 是否成功
669
+ * @param {String} key 键名
670
+ * @param {Number} seconds 过期时间(秒)
671
+ * @returns {Promise<Boolean>} 是否成功
576
672
  */
577
- CacheBase.prototype.expire = function (key, seconds) {
673
+ CacheBase.prototype.expire = async function (key, seconds) {
578
674
  try {
579
- // 参数验证
580
675
  if (!key || typeof key !== 'string') {
581
676
  return false;
582
677
  }
@@ -585,219 +680,391 @@ CacheBase.prototype.expire = function (key, seconds) {
585
680
  return false;
586
681
  }
587
682
 
588
- // 触发设置过期前事件
589
- const event_data = {
683
+ // 触发前置事件
684
+ const ctx = {
590
685
  key,
591
- seconds,
686
+ ttl: seconds,
592
687
  cancel: false
593
688
  };
594
- this.emit('expire_before', event_data);
689
+ this.emit('expire:before', ctx);
595
690
 
596
- if (event_data.cancel) {
691
+ if (ctx.cancel) {
597
692
  return false;
598
693
  }
599
694
 
695
+ const {
696
+ key: finalKey,
697
+ ttl: finalSeconds
698
+ } = ctx;
699
+
600
700
  // 检查键是否存在
601
- if (!(key in this._data)) {
701
+ if (!(finalKey in this._data)) {
602
702
  return false;
603
703
  }
604
704
 
605
705
  // 设置过期时间
606
- this._expires[key] = Date.now() + seconds * 1000;
706
+ this._expires[finalKey] = Date.now() + finalSeconds * 1000;
607
707
 
608
- // 触发设置过期后事件
609
- this.emit('expire_after', {
610
- key,
611
- seconds,
708
+ // 触发后置事件
709
+ this.emit('expire:after', {
710
+ key: finalKey,
711
+ ttl: finalSeconds,
612
712
  success: true
613
713
  });
614
714
 
615
715
  return true;
616
716
  } catch (error) {
617
717
  this.log('error', `Failed to set expire for key ${key}:`, error);
718
+ this.emit('expire:error', { key, ttl: seconds, error });
618
719
  return false;
619
720
  }
620
721
  };
621
722
 
622
723
  /**
623
- * 设置哈希表字段
624
- * @param {string} key - 哈希表键名
625
- * @param {string} field - 字段名
626
- * @param {*} value - 值
627
- * @returns {number} 1表示新增,0表示更新或失败
724
+ * 设置键的过期时间(毫秒)
725
+ * @param {String} key 键名
726
+ * @param {Number} milliseconds 过期时间(毫秒)
727
+ * @returns {Promise<Boolean>} 是否成功
628
728
  */
629
- CacheBase.prototype.hset = function (key, field, value) {
729
+ CacheBase.prototype.pexpire = async function (key, milliseconds) {
630
730
  try {
631
- // 参数验证
632
- if (!key || typeof key !== 'string' || !field || typeof field !== 'string') {
633
- return 0;
731
+ if (!key || typeof key !== 'string') {
732
+ throw new TypeError('key必须是非空字符串');
733
+ }
734
+ if (typeof milliseconds !== 'number' || milliseconds <= 0) {
735
+ throw new TypeError('milliseconds必须是正数');
634
736
  }
635
737
 
636
- // 触发设置前事件
637
- const event_data = {
738
+ // 触发前置事件
739
+ const ctx = {
638
740
  key,
639
- field,
640
- value,
741
+ ttl: milliseconds,
641
742
  cancel: false
642
743
  };
643
- this.emit('hset_before', event_data);
744
+ this.emit('pexpire:before', ctx);
644
745
 
645
- if (event_data.cancel) {
646
- return 0;
746
+ if (ctx.cancel) {
747
+ return false;
647
748
  }
648
749
 
649
- // 初始化哈希表
650
- if (!(key in this._data)) {
651
- this._data[key] = {};
652
- this._types[key] = 'hash';
653
- } else if (this._types[key] !== 'hash') {
654
- this._data[key] = {};
655
- this._types[key] = 'hash';
656
- }
750
+ const {
751
+ key: finalKey,
752
+ ttl: finalMilliseconds
753
+ } = ctx;
657
754
 
658
- // 检查是否是新增字段
659
- const is_new_field = !(field in this._data[key]);
755
+ // 检查键是否存在
756
+ if (!(finalKey in this._data)) {
757
+ return false;
758
+ }
660
759
 
661
- // 设置字段值
662
- this._data[key][field] = value;
760
+ // 设置过期时间(毫秒)
761
+ this._expires[finalKey] = Date.now() + finalMilliseconds;
663
762
 
664
- // 触发设置后事件
665
- this.emit('hset_after', {
666
- key,
667
- field,
668
- value,
763
+ // 触发后置事件
764
+ this.emit('pexpire:after', {
765
+ key: finalKey,
766
+ ttl: finalMilliseconds,
669
767
  success: true
670
768
  });
671
769
 
672
- return is_new_field ? 1 : 0; // 返回1表示新增,0表示更新
770
+ return true;
673
771
  } catch (error) {
674
- this.log('error', `Failed to hset for key ${key}:`, error);
675
- return 0;
772
+ this.log('error', `Failed to set pexpire for key ${key}:`, error);
773
+ this.emit('pexpire:error', { key, ttl: milliseconds, error });
774
+ return false;
676
775
  }
677
776
  };
678
777
 
679
778
  /**
680
- * 获取哈希表字段值
681
- * @param {string} key - 哈希表键名
682
- * @param {string} field - 字段名
683
- * @returns {*} 字段值
779
+ * 移除键的过期时间
780
+ * @param {String} key 键名
781
+ * @returns {Promise<Boolean>} 是否成功移除
684
782
  */
685
- CacheBase.prototype.hget = function (key, field) {
783
+ CacheBase.prototype.persist = async function (key) {
686
784
  try {
687
- // 参数验证
688
- if (!key || typeof key !== 'string' || !field || typeof field !== 'string') {
689
- return;
785
+ if (!key || typeof key !== 'string') {
786
+ return false;
690
787
  }
691
788
 
692
- // 检查是否过期
693
- if (this._isExpired(key)) {
694
- this.del(key);
695
- return;
696
- }
789
+ // 触发前置事件
790
+ const ctx = {
791
+ key,
792
+ cancel: false
793
+ };
794
+ this.emit('persist:before', ctx);
697
795
 
698
- // 检查键是否存在且类型正确
699
- if (!(key in this._data) || this._types[key] !== 'hash') {
700
- return;
796
+ if (ctx.cancel) {
797
+ return false;
701
798
  }
702
799
 
703
- return this._data[key][field];
704
- } catch (error) {
705
- this.log('error', `Failed to hget for key ${key}:`, error);
706
- }
707
- };
708
-
709
- /**
710
- * 清空所有数据
711
- * @returns {boolean} 操作结果
712
- */
713
- CacheBase.prototype.clear = function () {
714
- return this.flushdb();
715
- };
800
+ const {
801
+ key: finalKey
802
+ } = ctx;
716
803
 
717
- /**
718
- * 获取哈希表所有字段和值
719
- * @param {string} key - 哈希表键名
720
- * @returns {Object} 哈希表内容
721
- */
722
- CacheBase.prototype.hgetall = function (key) {
723
- try {
724
- // 参数验证
725
- if (!key || typeof key !== 'string') {
726
- return {};
804
+ // 检查键是否存在
805
+ if (!(finalKey in this._data)) {
806
+ return false;
727
807
  }
728
808
 
729
809
  // 检查是否过期
730
- if (this._isExpired(key)) {
731
- this.del(key);
732
- return {};
810
+ if (this._isExpired(finalKey)) {
811
+ await this.del(finalKey);
812
+ return false;
733
813
  }
734
814
 
735
- // 检查键是否存在且类型正确
736
- if (!(key in this._data) || this._types[key] !== 'hash') {
737
- return {};
815
+ // 移除过期时间
816
+ if (finalKey in this._expires) {
817
+ delete this._expires[finalKey];
818
+
819
+ this.emit('persist:after', {
820
+ key: finalKey,
821
+ success: true
822
+ });
823
+ return true;
738
824
  }
739
825
 
740
- // 返回副本,避免直接修改
741
- return {
742
- ...this._data[key]
743
- };
826
+ return false;
744
827
  } catch (error) {
745
- this.log('error', `Failed to hgetall for key ${key}:`, error);
746
- return {};
828
+ this.log('error', `Failed to persist key ${key}:`, error);
829
+ this.emit('persist:error', { key, error });
830
+ return false;
747
831
  }
748
832
  };
749
833
 
750
834
  /**
751
- * 删除哈希表字段
752
- * @param {string} key - 哈希表键名
753
- * @param {string|Array<string>} field - 字段名或字段名数组
754
- * @returns {boolean} 删除成功返回true,失败返回false
835
+ * 获取或设置键的过期时间
836
+ * @param {String} key 键名
837
+ * @param {Number} [seconds] 过期时间(秒),不传则获取剩余过期时间
838
+ * @returns {Promise<Number|Boolean>} 不传seconds返回剩余秒数(-1无过期,-2已过期),传seconds返回是否设置成功
755
839
  */
756
- CacheBase.prototype.hdel = function (key, field) {
840
+ CacheBase.prototype.ttl = async function (key, seconds) {
841
+ const isSetMode = arguments.length > 1 && seconds !== undefined;
757
842
  try {
758
- // 参数验证
843
+ this.emit('ttl:before', { key, seconds });
844
+
759
845
  if (!key || typeof key !== 'string') {
760
- return false;
846
+ return isSetMode ? false : -2;
761
847
  }
762
848
 
763
- // 触发操作前事件
764
- const event_data = {
765
- key,
766
- field,
767
- cancel: false
768
- };
769
- this.emit('hdel_before', event_data);
849
+ if (isSetMode) {
850
+ // 设置过期时间模式
851
+ if (typeof seconds !== 'number' || seconds < 0) {
852
+ return false;
853
+ }
770
854
 
771
- if (event_data.cancel) {
772
- return false;
773
- }
855
+ if (!(key in this._data)) {
856
+ return false;
857
+ }
774
858
 
775
- // 检查是否过期
776
- if (this._isExpired(key)) {
777
- this.del(key);
778
- this.emit('hdel_after', {
779
- key,
780
- field,
781
- success: false
782
- });
783
- return false;
784
- }
859
+ if (this._isExpired(key)) {
860
+ await this.del(key);
861
+ return false;
862
+ }
785
863
 
786
- // 检查键是否存在且类型正确
787
- if (!(key in this._data) || this._types[key] !== 'hash') {
788
- this.emit('hdel_after', {
789
- key,
790
- field,
791
- success: false
792
- });
793
- return false;
794
- }
864
+ if (seconds > 0) {
865
+ this._expires[key] = Date.now() + seconds * 1000;
866
+ } else if (seconds === 0) {
867
+ delete this._expires[key];
868
+ }
869
+
870
+ this.emit('ttl:after', { key, seconds, success: true });
871
+ return true;
872
+ } else {
873
+ // 获取剩余过期时间模式
874
+ if (!(key in this._data)) {
875
+ return -2;
876
+ }
877
+
878
+ if (this._isExpired(key)) {
879
+ await this.del(key);
880
+ return -2;
881
+ }
882
+
883
+ if (!(key in this._expires)) {
884
+ return -1;
885
+ }
886
+
887
+ const now = Date.now();
888
+ const remaining = this._expires[key] - now;
889
+ return Math.max(0, Math.ceil(remaining / 1000));
890
+ }
891
+ } catch (error) {
892
+ this.log('error', `Failed to ttl for key ${key}:`, error);
893
+ return isSetMode ? false : -2;
894
+ }
895
+ };
896
+
897
+ // ==================== 哈希表操作 ====================
898
+
899
+ /**
900
+ * 设置哈希表字段
901
+ * @param {String} key 哈希表键名
902
+ * @param {String} field 字段名
903
+ * @param {*} value 值
904
+ * @returns {Promise<Boolean>} 操作结果
905
+ */
906
+ CacheBase.prototype.hset = async function (key, field, value) {
907
+ try {
908
+ if (!key || typeof key !== 'string' || !field || typeof field !== 'string') {
909
+ return false;
910
+ }
911
+
912
+ // 触发前置事件
913
+ const ctx = {
914
+ key,
915
+ field,
916
+ value,
917
+ cancel: false
918
+ };
919
+ this.emit('hset:before', ctx);
920
+
921
+ if (ctx.cancel) {
922
+ return false;
923
+ }
924
+
925
+ const {
926
+ key: finalKey,
927
+ field: finalField,
928
+ value: finalValue
929
+ } = ctx;
930
+
931
+ // 检查是否过期
932
+ if (this._isExpired(finalKey)) {
933
+ await this.del(finalKey);
934
+ }
935
+
936
+ // 初始化哈希表
937
+ if (!(finalKey in this._data) || this._types[finalKey] !== 'hash') {
938
+ this._data[finalKey] = {};
939
+ this._types[finalKey] = 'hash';
940
+ }
941
+
942
+ // 设置字段值
943
+ this._data[finalKey][finalField] = finalValue;
944
+
945
+ this.emit('hset:after', {
946
+ key: finalKey,
947
+ field: finalField,
948
+ value: finalValue,
949
+ success: true
950
+ });
951
+
952
+ return true;
953
+ } catch (error) {
954
+ this.log('error', `Failed to hset for key ${key}:`, error);
955
+ this.emit('hset:error', { key, field, value, error });
956
+ return false;
957
+ }
958
+ };
959
+
960
+ /**
961
+ * 获取哈希表字段值
962
+ * @param {String} key 哈希表键名
963
+ * @param {String} field 字段名
964
+ * @returns {Promise<Object|null>} 字段值,不存在或错误返回null
965
+ */
966
+ CacheBase.prototype.hget = async function (key, field) {
967
+ try {
968
+ this.emit('hget:before', { key, field });
969
+
970
+ if (!key || typeof key !== 'string' || !field || typeof field !== 'string') {
971
+ return null;
972
+ }
973
+
974
+ // 检查是否过期
975
+ if (this._isExpired(key)) {
976
+ await this.del(key);
977
+ return null;
978
+ }
979
+
980
+ // 检查键是否存在且类型正确
981
+ if (!(key in this._data) || this._types[key] !== 'hash') {
982
+ return null;
983
+ }
984
+
985
+ const value = this._data[key][field];
986
+ this.emit('hget:after', { key, field, value });
987
+ return value !== undefined ? value : null;
988
+ } catch (error) {
989
+ this.log('error', `Failed to hget for key ${key}:`, error);
990
+ return null;
991
+ }
992
+ };
993
+
994
+ /**
995
+ * 获取哈希表所有字段和值
996
+ * @param {String} key 哈希表键名
997
+ * @returns {Promise<Object>} 哈希表内容
998
+ */
999
+ CacheBase.prototype.hgetall = async function (key) {
1000
+ try {
1001
+ this.emit('hgetall:before', { key });
1002
+
1003
+ if (!key || typeof key !== 'string') {
1004
+ return {};
1005
+ }
795
1006
 
796
- // 处理字段参数,支持单个字段或字段数组
1007
+ // 检查是否过期
1008
+ if (this._isExpired(key)) {
1009
+ await this.del(key);
1010
+ return {};
1011
+ }
1012
+
1013
+ // 检查键是否存在且类型正确
1014
+ if (!(key in this._data) || this._types[key] !== 'hash') {
1015
+ return {};
1016
+ }
1017
+
1018
+ const result = { ...this._data[key] };
1019
+ this.emit('hgetall:after', { key, result });
1020
+ return result;
1021
+ } catch (error) {
1022
+ this.log('error', `Failed to hgetall for key ${key}:`, error);
1023
+ return {};
1024
+ }
1025
+ };
1026
+
1027
+ /**
1028
+ * 删除哈希表字段
1029
+ * @param {String} key 哈希表键名
1030
+ * @param {String|Array<String>} field 字段名或字段名数组
1031
+ * @returns {Promise<Number>} 删除的字段数量
1032
+ */
1033
+ CacheBase.prototype.hdel = async function (key, field) {
1034
+ try {
1035
+ if (!key || typeof key !== 'string') {
1036
+ return 0;
1037
+ }
1038
+
1039
+ // 触发前置事件
1040
+ const ctx = {
1041
+ key,
1042
+ field,
1043
+ cancel: false
1044
+ };
1045
+ this.emit('hdel:before', ctx);
1046
+
1047
+ if (ctx.cancel) {
1048
+ return 0;
1049
+ }
1050
+
1051
+ // 检查是否过期
1052
+ if (this._isExpired(key)) {
1053
+ await this.del(key);
1054
+ this.emit('hdel:after', { key, field, success: false, deleted: 0 });
1055
+ return 0;
1056
+ }
1057
+
1058
+ // 检查键是否存在且类型正确
1059
+ if (!(key in this._data) || this._types[key] !== 'hash') {
1060
+ this.emit('hdel:after', { key, field, success: false, deleted: 0 });
1061
+ return 0;
1062
+ }
1063
+
1064
+ // 处理字段参数
797
1065
  const fields = Array.isArray(field) ? field : [field];
798
1066
  let deletedCount = 0;
799
1067
 
800
- // 遍历所有字段进行删除
801
1068
  for (const f of fields) {
802
1069
  if (typeof f === 'string' && f && f in this._data[key]) {
803
1070
  delete this._data[key][f];
@@ -812,321 +1079,708 @@ CacheBase.prototype.hdel = function (key, field) {
812
1079
  delete this._expires[key];
813
1080
  }
814
1081
 
815
- // 触发操作后事件
816
- this.emit('hdel_after', {
1082
+ this.emit('hdel:after', {
817
1083
  key,
818
1084
  field,
819
- success: deletedCount > 0
1085
+ success: deletedCount > 0,
1086
+ deleted: deletedCount
820
1087
  });
821
1088
 
822
- return deletedCount > 0;
1089
+ return deletedCount;
823
1090
  } catch (error) {
824
- this.emit('hdel_error', {
1091
+ this.emit('hdel:error', { key, field, error });
1092
+ this.log('error', 'Failed to hdel:', error);
1093
+ return 0;
1094
+ }
1095
+ };
1096
+
1097
+ /**
1098
+ * 批量设置哈希表字段
1099
+ * @param {String} key 哈希表键名
1100
+ * @param {Object} obj 包含多个字段的对象
1101
+ * @returns {Promise<Boolean>} 操作结果
1102
+ */
1103
+ CacheBase.prototype.hmset = async function (key, obj) {
1104
+ try {
1105
+ if (!key || typeof key !== 'string' || !obj || typeof obj !== 'object' || Array.isArray(obj)) {
1106
+ return false;
1107
+ }
1108
+
1109
+ // 触发前置事件
1110
+ const ctx = {
825
1111
  key,
826
- field,
827
- error
1112
+ value: obj,
1113
+ cancel: false
1114
+ };
1115
+ this.emit('hmset:before', ctx);
1116
+
1117
+ if (ctx.cancel) {
1118
+ return false;
1119
+ }
1120
+
1121
+ const {
1122
+ key: finalKey,
1123
+ value: finalObj
1124
+ } = ctx;
1125
+
1126
+ // 检查是否过期
1127
+ if (this._isExpired(finalKey)) {
1128
+ await this.del(finalKey);
1129
+ }
1130
+
1131
+ // 初始化哈希表
1132
+ if (!(finalKey in this._data) || this._types[finalKey] !== 'hash') {
1133
+ this._data[finalKey] = {};
1134
+ this._types[finalKey] = 'hash';
1135
+ }
1136
+
1137
+ // 批量设置字段
1138
+ for (const field in finalObj) {
1139
+ if (Object.prototype.hasOwnProperty.call(finalObj, field)) {
1140
+ this._data[finalKey][field] = finalObj[field];
1141
+ }
1142
+ }
1143
+
1144
+ this.emit('hmset:after', {
1145
+ key: finalKey,
1146
+ value: finalObj,
1147
+ success: true
828
1148
  });
829
- this.log('error', 'Failed to hdel:', error);
1149
+
1150
+ return true;
1151
+ } catch (error) {
1152
+ this.emit('hmset:error', { key, value: obj, error });
1153
+ this.log('error', 'Failed to hmset:', error);
830
1154
  return false;
831
1155
  }
832
1156
  };
833
1157
 
834
1158
  /**
835
- * 增加键的数值
836
- * @param {string} key - 键名
837
- * @param {number} value - 增加的值(默认为1)
838
- * @returns {number} 增加后的值
1159
+ * 批量获取哈希表字段
1160
+ * @param {String} key 哈希表键名
1161
+ * @param {Array} fields 字段名数组
1162
+ * @returns {Promise<Array>} 字段值数组,不存在的字段返回null
839
1163
  */
840
- CacheBase.prototype.incrby = function (key, value) {
841
- // 参数验证
842
- if (!key || typeof key !== 'string' || typeof value !== 'number') {
1164
+ CacheBase.prototype.hmget = async function (key, fields) {
1165
+ try {
1166
+ this.emit('hmget:before', { key, fields });
1167
+
1168
+ if (!key || typeof key !== 'string' || !Array.isArray(fields)) {
1169
+ return [];
1170
+ }
1171
+
1172
+ // 检查是否过期
1173
+ if (this._isExpired(key)) {
1174
+ await this.del(key);
1175
+ return fields.map(() => null);
1176
+ }
1177
+
1178
+ // 检查键是否存在且类型正确
1179
+ if (!(key in this._data) || this._types[key] !== 'hash') {
1180
+ return fields.map(() => null);
1181
+ }
1182
+
1183
+ // 批量获取字段值
1184
+ const results = [];
1185
+ for (const field of fields) {
1186
+ const val = this._data[key][field];
1187
+ results.push(val !== undefined ? val : null);
1188
+ }
1189
+
1190
+ this.emit('hmget:after', { key, fields, results });
1191
+ return results;
1192
+ } catch (error) {
1193
+ this.emit('hmget:error', { key, fields, error });
1194
+ this.log('error', 'Failed to hmget:', error);
1195
+ return [];
1196
+ }
1197
+ };
1198
+
1199
+ /**
1200
+ * 获取哈希表字段数量
1201
+ * @param {String} key 哈希表键名
1202
+ * @returns {Promise<Number>} 字段数量
1203
+ */
1204
+ CacheBase.prototype.hlen = async function (key) {
1205
+ try {
1206
+ this.emit('hlen:before', { key });
1207
+
1208
+ if (!key || typeof key !== 'string') {
1209
+ return 0;
1210
+ }
1211
+
1212
+ // 检查是否过期
1213
+ if (this._isExpired(key)) {
1214
+ await this.del(key);
1215
+ return 0;
1216
+ }
1217
+
1218
+ // 检查键是否存在且类型正确
1219
+ if (!(key in this._data) || this._types[key] !== 'hash') {
1220
+ return 0;
1221
+ }
1222
+
1223
+ const result = Object.keys(this._data[key]).length;
1224
+ this.emit('hlen:after', { key, result });
1225
+ return result;
1226
+ } catch (error) {
1227
+ this.emit('hlen:error', { key, error });
1228
+ this.log('error', 'Failed to hlen:', error);
843
1229
  return 0;
844
1230
  }
1231
+ };
845
1232
 
846
- // 确保_data对象存在
847
- if (!this._data) {
848
- this._data = {};
1233
+ // ==================== 数值操作 ====================
1234
+
1235
+ /**
1236
+ * 将键的值增加1
1237
+ * @param {String} key 键
1238
+ * @param {Number} [seconds=0] 过期时间(秒)
1239
+ * @returns {Promise<Number>} 增加后的值,失败返回null
1240
+ */
1241
+ CacheBase.prototype.incr = async function (key, seconds = 0) {
1242
+ try {
1243
+ if (!key || typeof key !== 'string') {
1244
+ throw new TypeError('键必须是非空字符串');
1245
+ }
1246
+
1247
+ const ctx = {
1248
+ key,
1249
+ ttl: seconds,
1250
+ cancel: false
1251
+ };
1252
+ this.emit('incr:before', ctx);
1253
+
1254
+ if (ctx.cancel) {
1255
+ return null;
1256
+ }
1257
+
1258
+ const {
1259
+ key: finalKey,
1260
+ ttl: finalSeconds
1261
+ } = ctx;
1262
+
1263
+ // 确保_data对象存在
1264
+ if (!this._data) {
1265
+ this._data = {};
1266
+ }
1267
+
1268
+ let current = 0;
1269
+ if (finalKey in this._data && !this._isExpired(finalKey)) {
1270
+ current = Number(this._data[finalKey]) || 0;
1271
+ }
1272
+
1273
+ const newValue = current + 1;
1274
+ this._data[finalKey] = newValue;
1275
+ this._types[finalKey] = 'number';
1276
+
1277
+ if (finalSeconds > 0) {
1278
+ this._expires[finalKey] = Date.now() + finalSeconds * 1000;
1279
+ } else {
1280
+ delete this._expires[finalKey];
1281
+ }
1282
+
1283
+ this.emit('incr:after', {
1284
+ key: finalKey,
1285
+ ttl: finalSeconds,
1286
+ success: true,
1287
+ result: newValue
1288
+ });
1289
+ return newValue;
1290
+ } catch (error) {
1291
+ this.emit('incr:error', { key, ttl: seconds, error });
1292
+ this.log('error', 'incr操作失败:', error);
1293
+ return null;
1294
+ }
1295
+ };
1296
+
1297
+ /**
1298
+ * 将键的值减少1
1299
+ * @param {String} key 键
1300
+ * @param {Number} [seconds=0] 过期时间(秒)
1301
+ * @returns {Promise<Number>} 减少后的值,失败返回null
1302
+ */
1303
+ CacheBase.prototype.decr = async function (key, seconds = 0) {
1304
+ try {
1305
+ if (!key || typeof key !== 'string') {
1306
+ throw new TypeError('键必须是非空字符串');
1307
+ }
1308
+
1309
+ const ctx = {
1310
+ key,
1311
+ ttl: seconds,
1312
+ cancel: false
1313
+ };
1314
+ this.emit('decr:before', ctx);
1315
+
1316
+ if (ctx.cancel) {
1317
+ return null;
1318
+ }
1319
+
1320
+ const {
1321
+ key: finalKey,
1322
+ ttl: finalSeconds
1323
+ } = ctx;
1324
+
1325
+ if (!this._data) {
1326
+ this._data = {};
1327
+ }
1328
+
1329
+ let current = 0;
1330
+ if (finalKey in this._data && !this._isExpired(finalKey)) {
1331
+ current = Number(this._data[finalKey]) || 0;
1332
+ }
1333
+
1334
+ const newValue = current - 1;
1335
+ this._data[finalKey] = newValue;
1336
+ this._types[finalKey] = 'number';
1337
+
1338
+ if (finalSeconds > 0) {
1339
+ this._expires[finalKey] = Date.now() + finalSeconds * 1000;
1340
+ } else {
1341
+ delete this._expires[finalKey];
1342
+ }
1343
+
1344
+ this.emit('decr:after', {
1345
+ key: finalKey,
1346
+ ttl: finalSeconds,
1347
+ success: true,
1348
+ result: newValue
1349
+ });
1350
+ return newValue;
1351
+ } catch (error) {
1352
+ this.emit('decr:error', { key, ttl: seconds, error });
1353
+ this.log('error', 'decr操作失败:', error);
1354
+ return null;
849
1355
  }
1356
+ };
850
1357
 
851
- // 获取当前值,如果存在则使用,不存在则为0
852
- let current = 0;
853
- if (key in this._data) {
854
- // 尝试将现有值转换为数字
855
- current = Number(this._data[key]) || 0;
1358
+ /**
1359
+ * 将键的值增加指定整数
1360
+ * @param {String} key 键
1361
+ * @param {Number} increment 增量
1362
+ * @param {Number} [seconds=0] 过期时间(秒)
1363
+ * @returns {Promise<Number>} 增加后的值,失败返回null
1364
+ */
1365
+ CacheBase.prototype.incrby = async function (key, increment, seconds = 0) {
1366
+ // 检查服务状态
1367
+ if (!this.is_inited || this.is_destroyed) {
1368
+ this.log('error', 'Service is not initialized or has been destroyed');
1369
+ return null;
856
1370
  }
857
1371
 
858
- // 增加指定值
859
- const newValue = current + value;
1372
+ if (!key || typeof key !== 'string' || typeof increment !== 'number') {
1373
+ this.log('error', 'incrby参数错误: key必须是非空字符串,increment必须是数字');
1374
+ return null;
1375
+ }
860
1376
 
861
- // 保存新值
862
- this._data[key] = newValue;
1377
+ try {
1378
+ const ctx = {
1379
+ key,
1380
+ increment,
1381
+ seconds,
1382
+ cancel: false
1383
+ };
1384
+ this.emit('incrby:before', ctx);
863
1385
 
864
- // 确保_types对象存在并设置类型
865
- if (!this._types) {
866
- this._types = {};
1386
+ if (ctx.cancel) {
1387
+ return null;
1388
+ }
1389
+
1390
+ const {
1391
+ key: finalKey,
1392
+ increment: finalIncrement,
1393
+ seconds: finalSeconds
1394
+ } = ctx;
1395
+
1396
+ if (!this._data) {
1397
+ this._data = {};
1398
+ }
1399
+
1400
+ let current = 0;
1401
+ if (finalKey in this._data && !this._isExpired(finalKey)) {
1402
+ current = Number(this._data[finalKey]) || 0;
1403
+ } else if (finalKey in this._data && this._isExpired(finalKey)) {
1404
+ await this.del(finalKey);
1405
+ }
1406
+
1407
+ const newValue = current + finalIncrement;
1408
+ this._data[finalKey] = newValue;
1409
+ this._types[finalKey] = 'number';
1410
+
1411
+ // 设置过期时间(保留 TTL 语义与 Redis 一致)
1412
+ if (finalSeconds > 0) {
1413
+ this._expires[finalKey] = Date.now() + finalSeconds * 1000;
1414
+ }
1415
+
1416
+ this.emit('incrby:after', {
1417
+ key: finalKey,
1418
+ increment: finalIncrement,
1419
+ seconds: finalSeconds,
1420
+ success: true,
1421
+ result: newValue
1422
+ });
1423
+
1424
+ return newValue;
1425
+ } catch (error) {
1426
+ this.emit('incrby:error', { key, increment, seconds, error });
1427
+ this.log('error', 'incrby操作失败:', error);
1428
+ return null;
1429
+ }
1430
+ };
1431
+
1432
+ /**
1433
+ * 将键的值减少指定整数
1434
+ * @param {String} key 键
1435
+ * @param {Number} decrement 减量
1436
+ * @param {Number} [seconds=0] 过期时间(秒)
1437
+ * @returns {Promise<Number>} 减少后的值,失败返回null
1438
+ */
1439
+ CacheBase.prototype.decrby = async function (key, decrement, seconds = 0) {
1440
+ // 检查服务状态
1441
+ if (!this.is_inited || this.is_destroyed) {
1442
+ this.log('error', 'Service is not initialized or has been destroyed');
1443
+ return null;
867
1444
  }
868
- this._types[key] = 'number';
869
1445
 
870
- // 清理过期时间
871
- if (this._expires && this._expires[key]) {
872
- delete this._expires[key];
1446
+ if (!key || typeof key !== 'string' || typeof decrement !== 'number') {
1447
+ this.log('error', 'decrby参数错误: key必须是非空字符串,decrement必须是数字');
1448
+ return null;
873
1449
  }
874
1450
 
875
- return newValue;
1451
+ try {
1452
+ const ctx = {
1453
+ key,
1454
+ decrement,
1455
+ seconds,
1456
+ cancel: false
1457
+ };
1458
+ this.emit('decrby:before', ctx);
1459
+
1460
+ if (ctx.cancel) {
1461
+ return null;
1462
+ }
1463
+
1464
+ const {
1465
+ key: finalKey,
1466
+ decrement: finalDecrement,
1467
+ seconds: finalSeconds
1468
+ } = ctx;
1469
+
1470
+ if (!this._data) {
1471
+ this._data = {};
1472
+ }
1473
+
1474
+ let current = 0;
1475
+ if (finalKey in this._data && !this._isExpired(finalKey)) {
1476
+ current = Number(this._data[finalKey]) || 0;
1477
+ } else if (finalKey in this._data && this._isExpired(finalKey)) {
1478
+ await this.del(finalKey);
1479
+ }
1480
+
1481
+ const newValue = current - finalDecrement;
1482
+ this._data[finalKey] = newValue;
1483
+ this._types[finalKey] = 'number';
1484
+
1485
+ if (finalSeconds > 0) {
1486
+ this._expires[finalKey] = Date.now() + finalSeconds * 1000;
1487
+ }
1488
+
1489
+ this.emit('decrby:after', {
1490
+ key: finalKey,
1491
+ decrement: finalDecrement,
1492
+ seconds: finalSeconds,
1493
+ success: true,
1494
+ result: newValue
1495
+ });
1496
+
1497
+ return newValue;
1498
+ } catch (error) {
1499
+ this.emit('decrby:error', { key, decrement, seconds, error });
1500
+ this.log('error', 'decrby操作失败:', error);
1501
+ return null;
1502
+ }
876
1503
  };
877
1504
 
878
1505
  /**
879
- * 增加键的数值(增加1)
880
- * @param {string} key - 键名
881
- * @param {number} seconds - 过期时间(秒),0表示永不过期
882
- * @returns {number|null} 增加后的值,失败返回null
1506
+ * 将键的值增加指定浮点数
1507
+ * @param {String} key 键
1508
+ * @param {Number} [increment=1.0] 增量
1509
+ * @param {Number} [seconds=0] 过期时间(秒)
1510
+ * @returns {Promise<Number>} 增加后的值,失败返回null
883
1511
  */
884
- CacheBase.prototype.incr = function (key, seconds = 0) {
1512
+ CacheBase.prototype.incrbyfloat = async function (key, increment = 1.0, seconds = 0) {
885
1513
  try {
886
- // 参数验证
887
- if (!key || typeof key !== 'string') {
888
- this.log('error', 'incr参数错误: key必须是非空字符串');
889
- return 0;
1514
+ if (!key || typeof key !== 'string' || typeof increment !== 'number') {
1515
+ this.log('error', 'incrbyfloat参数错误: key必须是非空字符串,increment必须是数字');
1516
+ return null;
890
1517
  }
891
1518
 
892
- this.emit('incr_before', {
1519
+ const ctx = {
893
1520
  key,
1521
+ increment,
894
1522
  ttl: seconds,
895
1523
  cancel: false
896
- });
1524
+ };
1525
+ this.emit('incrbyfloat:before', ctx);
1526
+
1527
+ if (ctx.cancel) {
1528
+ return null;
1529
+ }
1530
+
1531
+ const {
1532
+ key: finalKey,
1533
+ increment: finalIncrement,
1534
+ ttl: finalSeconds
1535
+ } = ctx;
897
1536
 
898
- // 确保_data对象存在
899
1537
  if (!this._data) {
900
1538
  this._data = {};
901
1539
  }
902
1540
 
903
- // 获取当前值,如果存在则使用,不存在则为0
904
1541
  let current = 0;
905
- if (key in this._data && !this._isExpired(key)) {
906
- current = Number(this._data[key]) || 0;
1542
+ if (finalKey in this._data && !this._isExpired(finalKey)) {
1543
+ current = Number(this._data[finalKey]) || 0;
907
1544
  }
908
1545
 
909
- // 增加1
910
- const newValue = current + 1;
911
-
912
- // 保存新值
913
- this._data[key] = newValue;
914
- this._types[key] = 'number';
1546
+ const newValue = current + finalIncrement;
1547
+ this._data[finalKey] = newValue;
1548
+ this._types[finalKey] = 'number';
915
1549
 
916
- if (seconds > 0) {
917
- this._expires[key] = Date.now() + seconds * 1000;
1550
+ if (finalSeconds > 0) {
1551
+ this._expires[finalKey] = Date.now() + finalSeconds * 1000;
918
1552
  } else {
919
- delete this._expires[key];
1553
+ delete this._expires[finalKey];
920
1554
  }
921
1555
 
922
- this.emit('incr_after', {
923
- key,
924
- ttl: seconds,
1556
+ this.emit('incrbyfloat:after', {
1557
+ key: finalKey,
1558
+ increment: finalIncrement,
1559
+ ttl: finalSeconds,
925
1560
  success: true,
926
1561
  result: newValue
927
1562
  });
928
1563
  return newValue;
929
1564
  } catch (error) {
930
- this.emit('incr_error', {
931
- key,
932
- ttl: seconds,
933
- error
934
- });
935
- this.log('error', 'incr操作失败:', error);
936
- return 0;
1565
+ this.emit('incrbyfloat:error', { key, increment, ttl: seconds, error });
1566
+ this.log('error', 'incrbyfloat操作失败:', error);
1567
+ return null;
937
1568
  }
938
1569
  };
939
1570
 
940
- CacheBase.prototype.decrby = function (key, value) {
941
- // 参数验证
942
- if (!key || typeof key !== 'string' || typeof value !== 'number') {
943
- return 0;
944
- }
1571
+ /**
1572
+ * 增加整数值(兼容方法)
1573
+ * @param {String} key 键名
1574
+ * @param {Number} value 要增加的整数值
1575
+ * @param {Number} [seconds=0] 过期时间(秒)
1576
+ * @returns {Promise<Number>} 增加后的值,失败返回null
1577
+ */
1578
+ CacheBase.prototype.addInt = async function (key, value, seconds = 0) {
1579
+ try {
1580
+ if (!key || typeof key !== 'string') {
1581
+ throw new TypeError('key必须是非空字符串');
1582
+ }
1583
+ if (typeof value !== 'number') {
1584
+ throw new TypeError('value必须是数字');
1585
+ }
945
1586
 
946
- // 确保_data对象存在
947
- if (!this._data) {
948
- this._data = {};
949
- }
1587
+ const ctx = {
1588
+ key,
1589
+ value,
1590
+ ttl: seconds,
1591
+ cancel: false
1592
+ };
1593
+ this.emit('addInt:before', ctx);
950
1594
 
951
- // 获取当前值,如果存在则使用,不存在则为0
952
- let current = 0;
953
- if (key in this._data) {
954
- // 尝试将现有值转换为数字
955
- current = Number(this._data[key]) || 0;
956
- }
1595
+ if (ctx.cancel) {
1596
+ return null;
1597
+ }
957
1598
 
958
- // 减少指定值
959
- const newValue = current - value;
1599
+ const {
1600
+ key: finalKey,
1601
+ value: finalValue,
1602
+ ttl: finalSeconds
1603
+ } = ctx;
960
1604
 
961
- // 保存新值
962
- this._data[key] = newValue;
1605
+ let currentValue = 0;
1606
+ if (finalKey in this._data && !this._isExpired(finalKey)) {
1607
+ currentValue = parseInt(this._data[finalKey], 10) || 0;
1608
+ }
1609
+ const result = Math.floor(currentValue + parseInt(finalValue, 10));
963
1610
 
964
- // 确保_types对象存在并设置类型
965
- if (!this._types) {
966
- this._types = {};
967
- }
968
- this._types[key] = 'number';
1611
+ this._data[finalKey] = result;
1612
+ this._types[finalKey] = 'number';
969
1613
 
970
- // 清理过期时间
971
- if (this._expires && this._expires[key]) {
972
- delete this._expires[key];
973
- }
1614
+ if (finalSeconds > 0) {
1615
+ this._expires[finalKey] = Date.now() + finalSeconds * 1000;
1616
+ } else {
1617
+ delete this._expires[finalKey];
1618
+ }
974
1619
 
975
- return newValue;
1620
+ this.emit('addInt:after', {
1621
+ key: finalKey,
1622
+ value: finalValue,
1623
+ ttl: finalSeconds,
1624
+ success: true,
1625
+ result
1626
+ });
1627
+ return result;
1628
+ } catch (error) {
1629
+ this.emit('addInt:error', { key, value, ttl: seconds, error });
1630
+ this.log('error', 'addInt操作失败:', error);
1631
+ return null;
1632
+ }
976
1633
  };
977
1634
 
978
1635
  /**
979
- * 减少键的数值(减少1)
980
- * @param {string} key - 键名
981
- * @param {number} seconds - 过期时间(秒),0表示永不过期
982
- * @returns {number} 减少后的值
1636
+ * 增加浮点数(兼容方法)
1637
+ * @param {String} key 键名
1638
+ * @param {Number} num 要增加的浮点数值
1639
+ * @param {Number} [seconds=0] 过期时间(秒)
1640
+ * @returns {Promise<Number>} 增加后的值,失败返回null
983
1641
  */
984
- CacheBase.prototype.decr = function (key, seconds = 0) {
1642
+ CacheBase.prototype.addFloat = async function (key, num, seconds = 0) {
985
1643
  try {
986
- // 参数验证
987
- if (!key || typeof key !== 'string') {
988
- this.log('error', 'decr参数错误: key必须是非空字符串');
989
- return 0;
1644
+ if (!key || typeof key !== 'string' || typeof num !== 'number') {
1645
+ throw new TypeError('addFloat参数错误: key必须是非空字符串,num必须是数字');
990
1646
  }
991
1647
 
992
- this.emit('decr_before', {
1648
+ const ctx = {
993
1649
  key,
1650
+ num,
994
1651
  ttl: seconds,
995
1652
  cancel: false
996
- });
1653
+ };
1654
+ this.emit('addFloat:before', ctx);
997
1655
 
998
- // 确保_data对象存在
999
- if (!this._data) {
1000
- this._data = {};
1656
+ if (ctx.cancel) {
1657
+ return null;
1001
1658
  }
1002
1659
 
1003
- // 获取当前值,如果存在则使用,不存在则为0
1004
- let current = 0;
1005
- if (key in this._data && !this._isExpired(key)) {
1006
- current = Number(this._data[key]) || 0;
1007
- }
1660
+ const {
1661
+ key: finalKey,
1662
+ num: finalNum,
1663
+ ttl: finalSeconds
1664
+ } = ctx;
1008
1665
 
1009
- // 减少1
1010
- const newValue = current - 1;
1666
+ let currentValue = 0;
1667
+ if (finalKey in this._data && !this._isExpired(finalKey)) {
1668
+ currentValue = Number(this._data[finalKey]) || 0;
1669
+ }
1670
+ const result = currentValue + Number(finalNum);
1011
1671
 
1012
- // 保存新值
1013
- this._data[key] = newValue;
1014
- this._types[key] = 'number';
1672
+ this._data[finalKey] = result;
1673
+ this._types[finalKey] = 'number';
1015
1674
 
1016
- if (seconds > 0) {
1017
- this._expires[key] = Date.now() + seconds * 1000;
1675
+ if (finalSeconds > 0) {
1676
+ this._expires[finalKey] = Date.now() + finalSeconds * 1000;
1018
1677
  } else {
1019
- delete this._expires[key];
1678
+ delete this._expires[finalKey];
1020
1679
  }
1021
1680
 
1022
- this.emit('decr_after', {
1023
- key,
1024
- ttl: seconds,
1681
+ this.emit('addFloat:after', {
1682
+ key: finalKey,
1683
+ num: finalNum,
1684
+ ttl: finalSeconds,
1025
1685
  success: true,
1026
- result: newValue
1686
+ result
1027
1687
  });
1028
- return newValue;
1688
+ return result;
1029
1689
  } catch (error) {
1030
- this.emit('decr_error', {
1031
- key,
1032
- ttl: seconds,
1033
- error
1034
- });
1035
- this.log('error', 'decr操作失败:', error);
1036
- return 0;
1690
+ this.emit('addFloat:error', { key, num, ttl: seconds, error });
1691
+ this.log('error', 'addFloat操作失败:', error);
1692
+ return null;
1037
1693
  }
1038
1694
  };
1039
1695
 
1696
+ // ==================== 字符串操作 ====================
1697
+
1040
1698
  /**
1041
- * 增加整数值(兼容方法)
1042
- * @param {string} key - 键名
1043
- * @param {number} value - 要增加的整数值
1044
- * @param {number} seconds - 过期时间(秒),0表示永不过期
1045
- * @returns {Promise<number>} - 返回增加后的值
1699
+ * 追加字符串(兼容方法,对齐 mm_redis 的 setrange)
1700
+ * @param {String} key 键名
1701
+ * @param {String} value 要追加的字符串
1702
+ * @param {Number} [seconds=0] 过期时间(秒)
1703
+ * @returns {Promise<Number>} 追加后字符串的长度,失败返回null
1046
1704
  */
1047
- CacheBase.prototype.addInt = function (key, value, seconds = 0) {
1048
- // 参数验证
1049
- if (!key || typeof key !== 'string') {
1050
- throw new TypeError('key必须是非空字符串');
1051
- }
1052
- if (typeof value !== 'number') {
1053
- throw new TypeError('value必须是数字');
1054
- }
1055
-
1705
+ CacheBase.prototype.setrange = async function (key, value, seconds = 0) {
1056
1706
  try {
1707
+ if (!key || typeof key !== 'string' || typeof value !== 'string') {
1708
+ throw new TypeError('setrange参数错误: key和value必须是非空字符串');
1709
+ }
1057
1710
 
1058
- this.emit('addInt_before', {
1711
+ const ctx = {
1059
1712
  key,
1060
1713
  value,
1061
1714
  ttl: seconds,
1062
1715
  cancel: false
1063
- });
1716
+ };
1717
+ this.emit('setrange:before', ctx);
1064
1718
 
1065
- // 实现整数递增逻辑
1066
- let currentValue = 0;
1067
- if (key in this._data && !this._isExpired(key)) {
1068
- currentValue = parseInt(this._data[key], 10) || 0;
1719
+ if (ctx.cancel) {
1720
+ return null;
1069
1721
  }
1070
- const result = Math.floor(currentValue + parseInt(value, 10));
1071
1722
 
1072
- // 设置值和过期时间
1073
- this._data[key] = result;
1074
- this._types[key] = 'number';
1723
+ const {
1724
+ key: finalKey,
1725
+ value: finalValue,
1726
+ ttl: finalSeconds
1727
+ } = ctx;
1728
+
1729
+ let currentValue = '';
1730
+ if (finalKey in this._data && !this._isExpired(finalKey)) {
1731
+ currentValue = String(this._data[finalKey]) || '';
1732
+ }
1733
+ const result = currentValue + finalValue;
1734
+ const length = result.length;
1075
1735
 
1076
- if (seconds > 0) {
1077
- this._expires[key] = Date.now() + seconds * 1000;
1736
+ this._data[finalKey] = result;
1737
+ this._types[finalKey] = 'string';
1738
+
1739
+ if (finalSeconds > 0) {
1740
+ this._expires[finalKey] = Date.now() + finalSeconds * 1000;
1078
1741
  } else {
1079
- delete this._expires[key];
1742
+ delete this._expires[finalKey];
1080
1743
  }
1081
1744
 
1082
- this.emit('addInt_after', {
1083
- key,
1084
- value,
1085
- ttl: seconds,
1086
- success: true,
1087
- result
1745
+ this.emit('setrange:after', {
1746
+ key: finalKey,
1747
+ value: finalValue,
1748
+ ttl: finalSeconds,
1749
+ result,
1750
+ length,
1751
+ success: true
1088
1752
  });
1089
- return result;
1753
+ return length;
1090
1754
  } catch (error) {
1091
- this.emit('addInt_error', {
1092
- key,
1093
- value,
1094
- ttl: seconds,
1095
- error
1096
- });
1097
- this.log('error', 'addInt操作失败:', error);
1098
- return 0;
1755
+ this.emit('setrange:error', { key, value, ttl: seconds, error });
1756
+ this.log('error', 'setrange操作失败:', error);
1757
+ return null;
1099
1758
  }
1100
1759
  };
1101
1760
 
1102
1761
  /**
1103
1762
  * 向字符串末尾追加内容
1104
- * @param {string} key - 键名
1105
- * @param {string} append - 要追加的内容
1106
- * @returns {number|null} 追加后的字符串长度,失败返回null
1763
+ * @param {String} key 键名
1764
+ * @param {String} append 要追加的内容
1765
+ * @returns {Promise<Number>} 追加后的字符串长度,失败返回null
1107
1766
  */
1108
- CacheBase.prototype.append = function (key, append) {
1767
+ CacheBase.prototype.append = async function (key, append) {
1109
1768
  try {
1110
- // 参数验证
1111
1769
  if (!key || typeof key !== 'string' || typeof append !== 'string') {
1112
1770
  return null;
1113
1771
  }
1114
1772
 
1115
1773
  // 检查是否过期
1116
1774
  if (this._isExpired(key)) {
1117
- this.del(key);
1775
+ await this.del(key);
1118
1776
  return null;
1119
1777
  }
1120
1778
 
1121
- // 获取当前值
1122
1779
  let current = '';
1123
-
1124
- // 如果键存在且是字符串,使用当前值
1125
1780
  if (key in this._data && typeof this._data[key] === 'string') {
1126
1781
  current = this._data[key];
1127
1782
  }
1128
1783
 
1129
- // 追加内容
1130
1784
  current += append;
1131
1785
  this._data[key] = current;
1132
1786
  this._types[key] = 'string';
@@ -1140,88 +1794,247 @@ CacheBase.prototype.append = function (key, append) {
1140
1794
 
1141
1795
  /**
1142
1796
  * 获取字符串长度
1143
- * @param {string} key - 键名
1144
- * @returns {number} 字符串长度,不存在返回0
1797
+ * @param {String} key 键名
1798
+ * @returns {Promise<Number>} 字符串长度,不存在返回0
1145
1799
  */
1146
- CacheBase.prototype.strlen = function (key) {
1800
+ CacheBase.prototype.strlen = async function (key) {
1147
1801
  try {
1148
- // 参数验证
1149
1802
  if (!key || typeof key !== 'string') {
1150
1803
  return 0;
1151
1804
  }
1152
1805
 
1153
- // 检查是否过期
1154
- if (this._isExpired(key)) {
1155
- this.del(key);
1156
- return 0;
1157
- }
1806
+ if (this._isExpired(key)) {
1807
+ await this.del(key);
1808
+ return 0;
1809
+ }
1810
+
1811
+ if (!(key in this._data)) {
1812
+ return 0;
1813
+ }
1814
+
1815
+ if (typeof this._data[key] !== 'string') {
1816
+ return 0;
1817
+ }
1818
+
1819
+ return this._data[key].length;
1820
+ } catch (error) {
1821
+ this.log('error', `Failed to strlen for key ${key}:`, error);
1822
+ return 0;
1823
+ }
1824
+ };
1825
+
1826
+ /**
1827
+ * 获取并设置值
1828
+ * @param {String} key 键名
1829
+ * @param {*} value 新值
1830
+ * @param {Number} [seconds=0] 过期时间(秒)
1831
+ * @returns {Promise<Object|null>} 旧值
1832
+ */
1833
+ CacheBase.prototype.getset = async function (key, value, seconds = 0) {
1834
+ try {
1835
+ if (!key || typeof key !== 'string') {
1836
+ return null;
1837
+ }
1838
+
1839
+ // 获取旧值
1840
+ const oldValue = await this.get(key);
1841
+
1842
+ // 设置新值
1843
+ await this.set(key, value, seconds);
1844
+
1845
+ return oldValue;
1846
+ } catch (error) {
1847
+ this.log('error', 'Failed to getset:', error);
1848
+ return null;
1849
+ }
1850
+ };
1851
+
1852
+ /**
1853
+ * 仅当键不存在时设置值(兼容方法,类似 Redis SETNX 语义)
1854
+ * @param {String} key 键名
1855
+ * @param {*} value 要设置的值
1856
+ * @param {Number} [seconds=0] 过期时间(秒)
1857
+ * @returns {Promise<Boolean>} 键不存在并设置成功返回true,键已存在返回false
1858
+ */
1859
+ CacheBase.prototype.add = async function (key, value, seconds = 0) {
1860
+ try {
1861
+ if (!key || typeof key !== 'string') {
1862
+ throw new TypeError('add参数错误: key必须是非空字符串');
1863
+ }
1864
+
1865
+ const ctx = {
1866
+ key,
1867
+ value,
1868
+ ttl: seconds,
1869
+ cancel: false
1870
+ };
1871
+ this.emit('add:before', ctx);
1872
+
1873
+ if (ctx.cancel) {
1874
+ this.log('debug', `add操作被事件处理器取消 [${key}]`);
1875
+ return false;
1876
+ }
1877
+
1878
+ const {
1879
+ key: finalKey,
1880
+ value: finalValue,
1881
+ ttl: finalSeconds
1882
+ } = ctx;
1883
+
1884
+ // 检查键是否存在且未过期
1885
+ if (finalKey in this._data && !this._isExpired(finalKey)) {
1886
+ this.emit('add:after', {
1887
+ key: finalKey,
1888
+ value: finalValue,
1889
+ ttl: finalSeconds,
1890
+ success: false
1891
+ });
1892
+ return false;
1893
+ }
1894
+
1895
+ // 键不存在或已过期,设置新值
1896
+ this._data[finalKey] = finalValue;
1897
+ this._types[finalKey] = this._getDataTypes(finalValue);
1898
+
1899
+ if (finalSeconds > 0) {
1900
+ this._expires[finalKey] = Date.now() + finalSeconds * 1000;
1901
+ } else {
1902
+ delete this._expires[finalKey];
1903
+ }
1904
+
1905
+ this.emit('add:after', {
1906
+ key: finalKey,
1907
+ value: finalValue,
1908
+ ttl: finalSeconds,
1909
+ success: true
1910
+ });
1911
+ return true;
1912
+ } catch (error) {
1913
+ this.log('error', `add操作失败 [${key}]:`, error);
1914
+ this.emit('add:error', { key, value, ttl: seconds, error });
1915
+ return false;
1916
+ }
1917
+ };
1918
+
1919
+ // ==================== 批量操作 ====================
1920
+
1921
+ /**
1922
+ * 一次性设置多个键值对
1923
+ * @param {Object} obj 键值对对象
1924
+ * @param {Number} [seconds=0] 过期时间(秒)
1925
+ * @returns {Promise<Boolean>} 成功返回true,失败返回false
1926
+ */
1927
+ CacheBase.prototype.mset = async function (obj, seconds = 0) {
1928
+ try {
1929
+ // 参数验证:必须是对象
1930
+ if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
1931
+ throw new TypeError('参数必须是键值对对象');
1932
+ }
1933
+
1934
+ // 触发前置事件
1935
+ const ctx = {
1936
+ keyValuePairs: obj,
1937
+ seconds,
1938
+ cancel: false
1939
+ };
1940
+ this.emit('mset:before', ctx);
1941
+
1942
+ if (ctx.cancel) {
1943
+ this.log('debug', '[mset] 操作被事件处理器取消');
1944
+ return false;
1945
+ }
1946
+
1947
+ const {
1948
+ keyValuePairs: finalKeyValuePairs,
1949
+ seconds: finalSeconds
1950
+ } = ctx;
1951
+
1952
+ // 批量设置键值对
1953
+ for (const [key, value] of Object.entries(finalKeyValuePairs)) {
1954
+ if (typeof key !== 'string' || !key) {
1955
+ throw new TypeError('键必须是非空字符串');
1956
+ }
1957
+
1958
+ // 检查键是否过期
1959
+ if (this._isExpired(key)) {
1960
+ await this.del(key);
1961
+ }
1158
1962
 
1159
- // 检查键是否存在
1160
- if (!(key in this._data)) {
1161
- return 0;
1162
- }
1963
+ this._data[key] = value;
1964
+ this._types[key] = this._getDataTypes(value);
1163
1965
 
1164
- // 检查值类型
1165
- if (typeof this._data[key] !== 'string') {
1166
- return 0;
1966
+ if (finalSeconds > 0) {
1967
+ this._expires[key] = Date.now() + finalSeconds * 1000;
1968
+ } else {
1969
+ delete this._expires[key];
1970
+ }
1167
1971
  }
1168
1972
 
1169
- return this._data[key].length;
1973
+ this.emit('mset:after', {
1974
+ keyValuePairs: finalKeyValuePairs,
1975
+ seconds: finalSeconds,
1976
+ result: true,
1977
+ success: true
1978
+ });
1979
+ return true;
1170
1980
  } catch (error) {
1171
- this.log('error', `Failed to strlen for key ${key}:`, error);
1172
- return 0;
1981
+ this.emit('mset:error', { keyValuePairs: obj, error });
1982
+ this.log('error', 'Failed to mset:', error);
1983
+ return false;
1173
1984
  }
1174
1985
  };
1175
1986
 
1176
1987
  /**
1177
- * 清空所有数据库键
1178
- * @returns {boolean} 操作结果
1988
+ * 批量获取多个键的值
1989
+ * @param {Array} keys 键名数组
1990
+ * @returns {Promise<Array>} 值数组,按键的顺序返回
1179
1991
  */
1180
- CacheBase.prototype.flushdb = function () {
1992
+ CacheBase.prototype.mget = async function (keys) {
1181
1993
  try {
1182
- // 触发操作前事件
1183
- const event_data = {
1184
- cancel: false
1185
- };
1186
- this.emit('flushdb_before', event_data);
1994
+ this.emit('mget:before', { keys });
1187
1995
 
1188
- if (event_data.cancel) {
1189
- return false;
1996
+ if (!Array.isArray(keys)) {
1997
+ return [];
1190
1998
  }
1191
1999
 
1192
- // 清空所有数据
1193
- this._data = {};
1194
- this._expires = {};
1195
- this._types = {};
1196
-
1197
- // 触发操作后事件
1198
- this.emit('flushdb_after', {
1199
- success: true
1200
- });
2000
+ const results = [];
2001
+ for (const key of keys) {
2002
+ if (typeof key === 'string') {
2003
+ results.push(await this.get(key));
2004
+ } else {
2005
+ results.push(null);
2006
+ }
2007
+ }
1201
2008
 
1202
- return true;
2009
+ this.emit('mget:after', { keys, results });
2010
+ return results;
1203
2011
  } catch (error) {
1204
- this.log('error', 'Failed to flushdb:', error);
1205
- return false;
2012
+ this.log('error', 'Failed to mget:', error);
2013
+ return [];
1206
2014
  }
1207
2015
  };
2016
+
2017
+ // ==================== 键管理 ====================
2018
+
1208
2019
  /**
1209
2020
  * 获取所有键名
1210
- * @param {string} [pattern] - 键名匹配模式,支持*通配符
1211
- * @returns {Array} 所有键名数组
2021
+ * @param {String} [pattern] 键名匹配模式,支持*通配符
2022
+ * @returns {Promise<Array>} 所有键名数组
1212
2023
  */
1213
- CacheBase.prototype.keys = function (pattern) {
2024
+ CacheBase.prototype.keys = async function (pattern) {
1214
2025
  try {
2026
+ this.emit('keys:before', { pattern });
2027
+
1215
2028
  const result = [];
1216
2029
  for (const key in this._data) {
1217
- // 调用_is_expired方法检查过期并自动删除,无需再调用del
1218
2030
  if (!this._isExpired(key)) {
1219
- // 添加基于pattern的键过滤条件
1220
2031
  if (!pattern || this._matchPattern(key, pattern)) {
1221
2032
  result.push(key);
1222
2033
  }
1223
2034
  }
1224
2035
  }
2036
+
2037
+ this.emit('keys:after', { pattern, result });
1225
2038
  return result;
1226
2039
  } catch (error) {
1227
2040
  this.log('error', 'Failed to get keys:', error);
@@ -1232,31 +2045,29 @@ CacheBase.prototype.keys = function (pattern) {
1232
2045
  /**
1233
2046
  * 检查键是否匹配指定模式
1234
2047
  * @private
1235
- * @param {string} key - 要检查的键名
1236
- * @param {string} pattern - 匹配模式,支持*通配符
1237
- * @returns {boolean} 是否匹配
2048
+ * @param {String} key 要检查的键名
2049
+ * @param {String} pattern 匹配模式,支持*通配符
2050
+ * @returns {Boolean} 是否匹配
1238
2051
  */
1239
2052
  CacheBase.prototype._matchPattern = function (key, pattern) {
1240
- // 将模式转换为正则表达式
1241
2053
  try {
2054
+ // 转义正则元字符(包含 ])
1242
2055
  var regexPattern = pattern
1243
- .replace(/[.+?^${}()|[\\]/g, '\\$&')
2056
+ .replace(/[.+?^${}()|[\]\\]/g, '\\$&')
1244
2057
  .replace(/\*/g, '.*');
1245
2058
  var regex = new RegExp('^' + regexPattern + '$');
1246
2059
  return regex.test(key);
1247
2060
  } catch (error) {
1248
- // 如果正则表达式无效,返回false
1249
2061
  return false;
1250
2062
  }
1251
2063
  };
1252
2064
 
1253
2065
  /**
1254
2066
  * 获取键的数量
1255
- * @returns {number} 键的数量
2067
+ * @returns {Promise<Number>} 键的数量
1256
2068
  */
1257
- CacheBase.prototype.dbsize = function () {
2069
+ CacheBase.prototype.dbsize = async function () {
1258
2070
  try {
1259
- // 计算未过期的键数量
1260
2071
  let count = 0;
1261
2072
  for (const key in this._data) {
1262
2073
  if (!this._isExpired(key)) {
@@ -1271,1105 +2082,1045 @@ CacheBase.prototype.dbsize = function () {
1271
2082
  };
1272
2083
 
1273
2084
  /**
1274
- * 增加浮点数(兼容方法)
1275
- * @param {string} key - 键名
1276
- * @param {number} value - 增加的浮点数
1277
- * @param {number} seconds - 过期时间(秒),0表示永不过期
1278
- * @returns {number} 增加后的值,失败返回0
2085
+ * 清空所有数据库键
2086
+ * @returns {Promise<Boolean>} 操作结果
2087
+ */
2088
+ CacheBase.prototype.flushdb = async function () {
2089
+ try {
2090
+ const ctx = {
2091
+ cancel: false
2092
+ };
2093
+ this.emit('clear:before', ctx);
2094
+
2095
+ if (ctx.cancel) {
2096
+ return false;
2097
+ }
2098
+
2099
+ this._data = {};
2100
+ this._expires = {};
2101
+ this._types = {};
2102
+
2103
+ this.emit('clear:after', { success: true });
2104
+ return true;
2105
+ } catch (error) {
2106
+ this.log('error', 'Failed to flushdb:', error);
2107
+ return false;
2108
+ }
2109
+ };
2110
+
2111
+ /**
2112
+ * 清空所有数据(flushdb 别名)
2113
+ * @returns {Promise<Boolean>} 操作结果
2114
+ */
2115
+ CacheBase.prototype.clear = async function () {
2116
+ return this.flushdb();
2117
+ };
2118
+
2119
+ // ==================== 列表操作 ====================
2120
+
2121
+ /**
2122
+ * 设置列表
2123
+ * @param {String} key 键名
2124
+ * @param {Array} value 列表值
2125
+ * @param {Number} [seconds=0] 过期时间(秒)
2126
+ * @returns {Promise<Number>} 列表长度
1279
2127
  */
1280
- CacheBase.prototype.incrbyfloat = function (key, value = 1.0, seconds = 0) {
2128
+ CacheBase.prototype.setForList = async function (key, value, seconds = 0) {
1281
2129
  try {
1282
- // 参数验证
1283
- if (!key || typeof key !== 'string' || typeof value !== 'number') {
1284
- this.log('error', 'incrbyfloat参数错误: key必须是非空字符串,value必须是数字');
2130
+ if (!key || typeof key !== 'string') {
1285
2131
  return 0;
1286
2132
  }
1287
2133
 
1288
- this.emit('incrbyfloat_before', {
2134
+ // 确保value是数组
2135
+ let stringValues = [];
2136
+ if (Array.isArray(value)) {
2137
+ stringValues = value.map(item => String(item));
2138
+ } else {
2139
+ stringValues = [String(value)];
2140
+ }
2141
+
2142
+ const ctx = {
1289
2143
  key,
1290
2144
  value,
1291
- ttl: seconds,
2145
+ seconds,
1292
2146
  cancel: false
1293
- });
2147
+ };
2148
+ this.emit('setForList:before', ctx);
1294
2149
 
1295
- // 确保_data对象存在
1296
- if (!this._data) {
1297
- this._data = {};
2150
+ if (ctx.cancel) {
2151
+ this.log('debug', `setForList操作被事件处理器取消 [${key}]`);
2152
+ return 0;
1298
2153
  }
1299
2154
 
1300
- // 获取当前值,如果存在则使用,不存在则为0
1301
- let current = 0;
1302
- if (key in this._data && !this._isExpired(key)) {
1303
- current = Number(this._data[key]) || 0;
2155
+ const {
2156
+ key: finalKey,
2157
+ value: finalValue,
2158
+ seconds: finalSeconds
2159
+ } = ctx;
2160
+
2161
+ // 重新处理 finalValue
2162
+ let finalStringValues = [];
2163
+ if (Array.isArray(finalValue)) {
2164
+ finalStringValues = finalValue.map(item => String(item));
2165
+ } else {
2166
+ finalStringValues = [String(finalValue)];
1304
2167
  }
1305
2168
 
1306
- // 增加指定值
1307
- const newValue = current + value;
2169
+ // 检查键是否存在且未过期
2170
+ if (finalKey in this._data && !this._isExpired(finalKey)) {
2171
+ if (!Array.isArray(this._data[finalKey])) {
2172
+ delete this._data[finalKey];
2173
+ delete this._types[finalKey];
2174
+ delete this._expires[finalKey];
2175
+ }
2176
+ }
1308
2177
 
1309
- // 保存新值
1310
- this._data[key] = newValue;
1311
- this._types[key] = 'number';
2178
+ this._data[finalKey] = finalStringValues;
2179
+ this._types[finalKey] = 'array';
1312
2180
 
1313
- if (seconds > 0) {
1314
- this._expires[key] = Date.now() + seconds * 1000;
2181
+ if (finalSeconds > 0) {
2182
+ this._expires[finalKey] = Date.now() + finalSeconds * 1000;
1315
2183
  } else {
1316
- delete this._expires[key];
2184
+ delete this._expires[finalKey];
1317
2185
  }
1318
2186
 
1319
- this.emit('incrbyfloat_after', {
1320
- key,
1321
- value,
1322
- ttl: seconds,
1323
- success: true,
1324
- result: newValue
2187
+ const length = finalStringValues.length;
2188
+ this.emit('setForList:after', {
2189
+ key: finalKey,
2190
+ value: finalStringValues,
2191
+ seconds: finalSeconds,
2192
+ result: length
1325
2193
  });
1326
- return newValue;
2194
+ return length;
1327
2195
  } catch (error) {
1328
- this.emit('incrbyfloat_error', {
1329
- key,
1330
- value,
1331
- ttl: seconds,
1332
- error
1333
- });
1334
- this.log('error', 'incrbyfloat操作失败:', error);
2196
+ this.emit('setForList:error', { key, value, seconds, error });
2197
+ this.log('error', 'setForList操作失败:', error);
1335
2198
  return 0;
1336
2199
  }
1337
2200
  };
1338
2201
 
1339
2202
  /**
1340
- * 获取并设置值
1341
- * @param {string} key - 键名
1342
- * @param {*} value - 新值
1343
- * @param {number} expire - 过期时间(毫秒)
1344
- * @returns {*} 旧值
2203
+ * 向列表添加元素
2204
+ * @param {String} key 键名
2205
+ * @param {*} value 要添加的元素
2206
+ * @param {Number} [seconds=0] 过期时间(秒)
2207
+ * @returns {Promise<Number>} 添加后列表长度
1345
2208
  */
1346
- CacheBase.prototype.getset = function (key, value, expire) {
2209
+ CacheBase.prototype.addForList = async function (key, value, seconds = 0) {
1347
2210
  try {
1348
- // 参数验证
1349
2211
  if (!key || typeof key !== 'string') {
1350
- return null;
2212
+ return 0;
1351
2213
  }
1352
2214
 
1353
- // 获取旧值
1354
- const oldValue = this.get(key);
2215
+ const ctx = {
2216
+ key,
2217
+ value,
2218
+ seconds,
2219
+ cancel: false
2220
+ };
2221
+ this.emit('addForList:before', ctx);
1355
2222
 
1356
- // 设置新值
1357
- this.set(key, value, expire);
2223
+ if (ctx.cancel) {
2224
+ this.log('debug', `addForList操作被事件处理器取消 [${key}]`);
2225
+ return 0;
2226
+ }
1358
2227
 
1359
- return oldValue;
2228
+ const {
2229
+ key: finalKey,
2230
+ value: finalValue,
2231
+ seconds: finalSeconds
2232
+ } = ctx;
2233
+
2234
+ const stringValue = String(finalValue);
2235
+
2236
+ // 检查键是否存在且未过期
2237
+ if (finalKey in this._data && !this._isExpired(finalKey)) {
2238
+ if (!Array.isArray(this._data[finalKey])) {
2239
+ this._data[finalKey] = [stringValue];
2240
+ this._types[finalKey] = 'array';
2241
+ } else {
2242
+ this._data[finalKey].push(stringValue);
2243
+ }
2244
+ } else {
2245
+ this._data[finalKey] = [stringValue];
2246
+ this._types[finalKey] = 'array';
2247
+ }
2248
+
2249
+ if (finalSeconds > 0) {
2250
+ this._expires[finalKey] = Date.now() + finalSeconds * 1000;
2251
+ } else if (finalKey in this._expires) {
2252
+ delete this._expires[finalKey];
2253
+ }
2254
+
2255
+ const length = this._data[finalKey].length;
2256
+ this.emit('addForList:after', {
2257
+ key: finalKey,
2258
+ value: stringValue,
2259
+ seconds: finalSeconds,
2260
+ result: length
2261
+ });
2262
+ return length;
1360
2263
  } catch (error) {
1361
- this.log('error', 'Failed to getset:', error);
1362
- return null;
2264
+ this.emit('addForList:error', { key, value, seconds, error });
2265
+ this.log('error', 'addForList操作失败:', error);
2266
+ return 0;
1363
2267
  }
1364
2268
  };
1365
2269
 
1366
2270
  /**
1367
- * 增加整数值 - 兼容方法
1368
- * @param {string} key - 键名
1369
- * @param {*} value - 要设置的值
1370
- * @param {number} maxAge - 过期时间(秒),0表示永不过期
1371
- * @returns {Promise<boolean>} - 操作结果,键不存在并设置成功返回true,键已存在返回false
2271
+ * 检查列表中是否包含指定元素
2272
+ * @param {String} key 键名
2273
+ * @param {*} value 要检查的元素
2274
+ * @returns {Promise<Boolean>} 是否包含
1372
2275
  */
1373
- CacheBase.prototype.add = function (key, value, maxAge = 0) {
2276
+ CacheBase.prototype.hasForList = async function (key, value) {
1374
2277
  try {
1375
- // 参数验证
1376
2278
  if (!key || typeof key !== 'string') {
1377
- throw new TypeError('add参数错误: key必须是非空字符串');
2279
+ return false;
1378
2280
  }
1379
2281
 
1380
- // 触发前置事件
1381
- const beforeEventData = {
2282
+ const ctx = {
1382
2283
  key,
1383
2284
  value,
1384
- maxAge,
1385
2285
  cancel: false
1386
2286
  };
1387
- this.emit('add_before', beforeEventData);
2287
+ this.emit('hasForList:before', ctx);
1388
2288
 
1389
- if (beforeEventData.cancel) {
1390
- this.log('debug', `add操作被事件处理器取消 [${key}]`);
2289
+ if (ctx.cancel) {
2290
+ this.log('debug', `hasForList操作被事件处理器取消 [${key}]`);
1391
2291
  return false;
1392
2292
  }
1393
2293
 
1394
2294
  const {
1395
2295
  key: finalKey,
1396
- value: finalValue,
1397
- maxAge: finalMaxAge
1398
- } = beforeEventData;
2296
+ value: finalValue
2297
+ } = ctx;
2298
+
2299
+ const stringValue = String(finalValue);
1399
2300
 
1400
- // 检查键是否存在且未过期
1401
2301
  if (finalKey in this._data && !this._isExpired(finalKey)) {
1402
- // 键已存在,返回false
1403
- this.emit('add_after', {
1404
- key: finalKey,
1405
- value: finalValue,
1406
- maxAge: finalMaxAge,
1407
- success: false
1408
- });
2302
+ if (Array.isArray(this._data[finalKey])) {
2303
+ const result = this._data[finalKey].includes(stringValue);
2304
+ this.emit('hasForList:after', { key: finalKey, value: finalValue, result });
2305
+ return result;
2306
+ }
2307
+ }
2308
+
2309
+ this.emit('hasForList:after', { key: finalKey, value: finalValue, result: false });
2310
+ return false;
2311
+ } catch (error) {
2312
+ this.emit('hasForList:error', { key, value, error });
2313
+ this.log('error', 'hasForList操作失败:', error);
2314
+ return false;
2315
+ }
2316
+ };
2317
+
2318
+ /**
2319
+ * 获取列表缓存
2320
+ * @param {String} key 键名
2321
+ * @param {Number} [start=0] 起始索引
2322
+ * @param {Number} [end=-1] 结束索引
2323
+ * @returns {Promise<Array>} 列表数组
2324
+ */
2325
+ CacheBase.prototype.getForList = async function (key, start = 0, end = -1) {
2326
+ try {
2327
+ this.emit('getForList:before', { key, start, end });
2328
+
2329
+ if (!key || typeof key !== 'string') {
2330
+ return [];
2331
+ }
2332
+
2333
+ // 检查是否过期
2334
+ if (this._isExpired(key)) {
2335
+ await this.del(key);
2336
+ return [];
2337
+ }
2338
+
2339
+ // 检查键是否存在
2340
+ if (!(key in this._data)) {
2341
+ return [];
2342
+ }
2343
+
2344
+ // 检查是否为数组
2345
+ if (Array.isArray(this._data[key])) {
2346
+ const array = this._data[key];
2347
+
2348
+ // 处理负索引,符合 Redis LRANGE 语义
2349
+ const startIndex = start < 0
2350
+ ? Math.max(0, array.length + start)
2351
+ : start;
2352
+ const endIndex = end < 0
2353
+ ? Math.max(0, array.length + end + 1)
2354
+ : end + 1;
2355
+
2356
+ const result = array.slice(startIndex, endIndex);
2357
+ this.emit('getForList:after', { key, start, end, result });
2358
+ return result;
2359
+ }
2360
+
2361
+ return [];
2362
+ } catch (error) {
2363
+ this.log('error', 'getForList操作失败:', error);
2364
+ return [];
2365
+ }
2366
+ };
2367
+
2368
+ /**
2369
+ * 清空列表
2370
+ * @param {String} key 键名
2371
+ * @returns {Promise<Boolean>} 操作结果
2372
+ */
2373
+ CacheBase.prototype.clearForList = async function (key) {
2374
+ try {
2375
+ const ctx = {
2376
+ key,
2377
+ cancel: false
2378
+ };
2379
+ this.emit('clearForList:before', ctx);
2380
+
2381
+ if (ctx.cancel) {
1409
2382
  return false;
1410
2383
  }
1411
2384
 
1412
- // 键不存在,设置新值
1413
- this._data[finalKey] = finalValue;
1414
- this._types[finalKey] = this._getDataTypes(finalValue);
2385
+ const {
2386
+ key: finalKey
2387
+ } = ctx;
1415
2388
 
1416
- // 设置过期时间
1417
- if (finalMaxAge > 0) {
1418
- this._expires[finalKey] = Date.now() + finalMaxAge * 1000;
1419
- } else {
1420
- delete this._expires[finalKey];
2389
+ if (!finalKey || typeof finalKey !== 'string') {
2390
+ return false;
1421
2391
  }
1422
2392
 
1423
- // 触发后置事件
1424
- this.emit('add_after', {
1425
- key: finalKey,
1426
- value: finalValue,
1427
- maxAge: finalMaxAge,
1428
- success: true
1429
- });
2393
+ if (finalKey in this._data && !this._isExpired(finalKey)) {
2394
+ if (Array.isArray(this._data[finalKey])) {
2395
+ this._data[finalKey] = [];
2396
+ this.emit('clearForList:after', { key: finalKey, result: true });
2397
+ return true;
2398
+ } else {
2399
+ delete this._data[finalKey];
2400
+ delete this._types[finalKey];
2401
+ delete this._expires[finalKey];
2402
+ this.emit('clearForList:after', { key: finalKey, result: false });
2403
+ return false;
2404
+ }
2405
+ }
2406
+
2407
+ this._data[finalKey] = [];
2408
+ this._types[finalKey] = 'array';
2409
+ this.emit('clearForList:after', { key: finalKey, result: true });
1430
2410
  return true;
1431
2411
  } catch (error) {
1432
- this.log('error', `add操作失败 [${key}]:`, error);
1433
- this.emit('add_error', {
1434
- key,
1435
- value,
1436
- maxAge,
1437
- error
1438
- });
2412
+ this.emit('clearForList:error', { key, error });
2413
+ this.log('error', 'clearForList操作失败:', error);
1439
2414
  return false;
1440
2415
  }
1441
2416
  };
1442
2417
 
2418
+ // ==================== 集合操作 ====================
2419
+
1443
2420
  /**
1444
- * 增加浮点数(兼容方法)
1445
- * @param {string} key - 键名
1446
- * @param {number} num - 要增加的浮点数值
1447
- * @param {number} seconds - 过期时间(秒),0表示永不过期
1448
- * @returns {Promise<number>} - 返回增加后的值
2421
+ * 将成员添加到集合
2422
+ * @param {String} key 键
2423
+ * @param {String|Array} members 成员或成员数组
2424
+ * @returns {Promise<Number>} 添加的成员数量
1449
2425
  */
1450
- CacheBase.prototype.addFloat = function (key, num, seconds = 0) {
2426
+ CacheBase.prototype.sadd = async function (key, members) {
1451
2427
  try {
1452
- // 参数验证
1453
- if (!key || typeof key !== 'string' || typeof num !== 'number') {
1454
- throw new TypeError('addFloat参数错误: key必须是非空字符串,num必须是数字');
1455
- }
1456
-
1457
- this.emit('addFloat_before', {
2428
+ const ctx = {
1458
2429
  key,
1459
- num,
1460
- ttl: seconds,
2430
+ members: Array.isArray(members) ? members : [members],
1461
2431
  cancel: false
1462
- });
2432
+ };
2433
+ this.emit('sadd:before', ctx);
1463
2434
 
1464
- // 实现浮点数递增逻辑
1465
- let currentValue = 0;
1466
- if (key in this._data && !this._isExpired(key)) {
1467
- currentValue = Number(this._data[key]) || 0;
2435
+ if (ctx.cancel) {
2436
+ return 0;
1468
2437
  }
1469
- const result = currentValue + Number(num);
1470
2438
 
1471
- // 设置值和过期时间
1472
- this._data[key] = result;
1473
- this._types[key] = 'number';
2439
+ const {
2440
+ key: finalKey,
2441
+ members: finalMembers
2442
+ } = ctx;
1474
2443
 
1475
- if (seconds > 0) {
1476
- this._expires[key] = Date.now() + seconds * 1000;
1477
- } else {
1478
- delete this._expires[key];
2444
+ // 检查是否过期
2445
+ if (this._isExpired(finalKey)) {
2446
+ await this.del(finalKey);
1479
2447
  }
1480
2448
 
1481
- this.emit('addFloat_after', {
1482
- key,
1483
- num,
1484
- ttl: seconds,
1485
- success: true,
1486
- result
2449
+ // 初始化集合
2450
+ if (!(finalKey in this._data) || this._types[finalKey] !== 'set') {
2451
+ this._data[finalKey] = new Set();
2452
+ this._types[finalKey] = 'set';
2453
+ }
2454
+
2455
+ const set = this._data[finalKey];
2456
+ let addedCount = 0;
2457
+ for (const member of finalMembers) {
2458
+ const memberStr = String(member);
2459
+ if (!set.has(memberStr)) {
2460
+ set.add(memberStr);
2461
+ addedCount++;
2462
+ }
2463
+ }
2464
+
2465
+ this.emit('sadd:after', {
2466
+ key: finalKey,
2467
+ members: finalMembers,
2468
+ result: addedCount
1487
2469
  });
1488
- return result;
2470
+ return addedCount;
1489
2471
  } catch (error) {
1490
- this.emit('addFloat_error', {
1491
- key,
1492
- num,
1493
- ttl: seconds,
1494
- error
1495
- });
1496
- this.log('error', 'addFloat操作失败:', error);
2472
+ this.log('error', `添加集合成员失败 [${key}]:`, error);
2473
+ this.emit('sadd:error', { key, members, error });
1497
2474
  return 0;
1498
2475
  }
1499
2476
  };
1500
2477
 
1501
2478
  /**
1502
- * 追加字符串(兼容方法)
1503
- * @param {string} key - 键名
1504
- * @param {string} value - 要追加的字符串
1505
- * @param {number} seconds - 过期时间(秒),0表示永不过期
1506
- * @returns {Promise<number>} - 返回追加后字符串的长度
2479
+ * 从集合中移除成员
2480
+ * @param {String} key 键
2481
+ * @param {String|Array} members 成员或成员数组
2482
+ * @returns {Promise<Number>} 移除的成员数量
1507
2483
  */
1508
- CacheBase.prototype.addStr = function (key, value, seconds = 0) {
2484
+ CacheBase.prototype.srem = async function (key, members) {
1509
2485
  try {
1510
- // 参数验证
1511
- if (!key || typeof key !== 'string' || typeof value !== 'string') {
1512
- throw new TypeError('addStr参数错误: key和value必须是非空字符串');
1513
- }
1514
-
1515
- this.emit('addStr_before', {
2486
+ const ctx = {
1516
2487
  key,
1517
- value,
1518
- ttl: seconds,
2488
+ members: Array.isArray(members) ? members : [members],
1519
2489
  cancel: false
1520
- });
2490
+ };
2491
+ this.emit('srem:before', ctx);
1521
2492
 
1522
- // 实现字符串追加逻辑
1523
- let currentValue = '';
1524
- if (key in this._data && !this._isExpired(key)) {
1525
- currentValue = String(this._data[key]) || '';
2493
+ if (ctx.cancel) {
2494
+ return 0;
1526
2495
  }
1527
- const result = currentValue + value;
1528
- const length = result.length;
1529
2496
 
1530
- // 设置值和过期时间
1531
- this._data[key] = result;
1532
- this._types[key] = 'string';
2497
+ const {
2498
+ key: finalKey,
2499
+ members: finalMembers
2500
+ } = ctx;
1533
2501
 
1534
- if (seconds > 0) {
1535
- this._expires[key] = Date.now() + seconds * 1000;
1536
- } else {
1537
- delete this._expires[key];
2502
+ if (!(finalKey in this._data) || this._types[finalKey] !== 'set') {
2503
+ return 0;
1538
2504
  }
1539
2505
 
1540
- this.emit('addStr_after', {
1541
- key,
1542
- value,
1543
- ttl: seconds,
1544
- result,
1545
- length,
1546
- success: true
2506
+ const set = this._data[finalKey];
2507
+ let removedCount = 0;
2508
+ for (const member of finalMembers) {
2509
+ const memberStr = String(member);
2510
+ if (set.has(memberStr)) {
2511
+ set.delete(memberStr);
2512
+ removedCount++;
2513
+ }
2514
+ }
2515
+
2516
+ // 如果集合为空,删除整个键
2517
+ if (set.size === 0) {
2518
+ delete this._data[finalKey];
2519
+ delete this._types[finalKey];
2520
+ delete this._expires[finalKey];
2521
+ }
2522
+
2523
+ this.emit('srem:after', {
2524
+ key: finalKey,
2525
+ members: finalMembers,
2526
+ result: removedCount
1547
2527
  });
1548
- return length;
2528
+ return removedCount;
1549
2529
  } catch (error) {
1550
- this.emit('addStr_error', {
1551
- key,
1552
- value,
1553
- ttl: seconds,
1554
- error
1555
- });
1556
- this.log('error', 'addStr操作失败:', error);
2530
+ this.log('error', `移除集合成员失败 [${key}]:`, error);
2531
+ this.emit('srem:error', { key, members, error });
1557
2532
  return 0;
1558
2533
  }
1559
2534
  };
1560
2535
 
1561
2536
  /**
1562
- * 一次性设置多个键值对 - 兼容Redis的MSET命令
1563
- * @param {string} key1, value1, key2, value2... - 键值对参数列表
1564
- * @returns {boolean} - 成功返回true,失败返回false
2537
+ * 检查成员是否在集合中
2538
+ * @param {String} key 键
2539
+ * @param {String} member 成员
2540
+ * @returns {Promise<Boolean>} 是否存在
1565
2541
  */
1566
- CacheBase.prototype.mset = function (...args) {
2542
+ CacheBase.prototype.sismember = async function (key, member) {
1567
2543
  try {
1568
- // 参数验证 - 特别处理null参数情况
1569
- if (args.length === 1 && args[0] === null) {
1570
- throw new TypeError('参数数量必须为偶数');
1571
- }
2544
+ this.emit('sismember:before', { key, member });
1572
2545
 
1573
- // 常规参数验证
1574
- if (args.length === 0 || args.length % 2 !== 0) {
1575
- throw new TypeError('参数数量必须为偶数');
2546
+ if (!key || typeof key !== 'string') {
2547
+ throw new TypeError('key必须是非空字符串');
1576
2548
  }
1577
-
1578
- // 将参数列表转换为键值对对象
1579
- const keyValuePairs = {};
1580
- for (let i = 0; i < args.length; i += 2) {
1581
- const key = args[i];
1582
- const value = args[i + 1];
1583
-
1584
- if (typeof key !== 'string' || !key) {
1585
- throw new TypeError('键必须是非空字符串');
1586
- }
1587
-
1588
- keyValuePairs[key] = value;
2549
+ if (member === undefined || member === null) {
2550
+ throw new TypeError('member不能为undefined或null');
1589
2551
  }
1590
2552
 
1591
- // 触发前置事件
1592
- const beforeEventData = {
1593
- keyValuePairs,
1594
- cancel: false
1595
- };
1596
- this.emit('mset_before', beforeEventData);
1597
-
1598
- if (beforeEventData.cancel) {
1599
- this.log('debug', '[mset] 操作被事件处理器取消');
2553
+ if (this._isExpired(key)) {
2554
+ await this.del(key);
1600
2555
  return false;
1601
2556
  }
1602
2557
 
1603
- const {
1604
- keyValuePairs: finalKeyValuePairs
1605
- } = beforeEventData;
1606
-
1607
- // 批量设置键值对
1608
- for (const [key, value] of Object.entries(finalKeyValuePairs)) {
1609
- // 检查键是否过期
1610
- if (this._isExpired(key)) {
1611
- this.del(key);
1612
- }
1613
-
1614
- // 保存数据
1615
- this._data[key] = value;
1616
- // 设置数据类型
1617
- this._types[key] = this._getDataTypes(value);
1618
- // 清除过期时间(与Redis MSET一致)
1619
- delete this._expires[key];
2558
+ if (!(key in this._data) || this._types[key] !== 'set') {
2559
+ return false;
1620
2560
  }
1621
2561
 
1622
- // 触发后置事件
1623
- this.emit('mset_after', {
1624
- keyValuePairs: finalKeyValuePairs,
1625
- result: true,
1626
- success: true
1627
- });
1628
- return true;
2562
+ const memberStr = String(member);
2563
+ const result = this._data[key].has(memberStr);
2564
+ this.emit('sismember:after', { key, member, result });
2565
+ return result;
1629
2566
  } catch (error) {
1630
- // 确保触发错误事件
1631
- this.emit('mset_error', {
1632
- args,
1633
- error
1634
- });
1635
- this.log('error', 'Failed to mset:', error);
2567
+ this.emit('sismember:error', { key, member, error });
2568
+ this.log('error', `检查集合成员失败 [${key}:${member}]:`, error);
1636
2569
  return false;
1637
2570
  }
1638
2571
  };
1639
2572
 
1640
2573
  /**
1641
- * 获取或设置键的过期时间
1642
- * @param {String} key 键名
1643
- * @param {Number} [seconds] 过期时间(秒),不传此参数则获取剩余过期时间
1644
- * @return {Promise<Number|Boolean>} 不传seconds时返回剩余过期时间(-1表示无过期设置,-2表示已过期),传seconds时返回是否设置成功
2574
+ * 获取集合中的所有成员
2575
+ * @param {String} key 键
2576
+ * @returns {Promise<Array>} 成员数组
1645
2577
  */
1646
- CacheBase.prototype.ttl = function (key, seconds) {
2578
+ CacheBase.prototype.smembers = async function (key) {
1647
2579
  try {
1648
- // 参数验证
1649
- if (!key || typeof key !== 'string') {
1650
- return arguments.length === 1 ? -2 : false;
1651
- }
1652
-
1653
- // 判断是否为设置过期时间模式
1654
- if (arguments.length > 1 && seconds !== undefined) {
1655
- // 设置过期时间模式
1656
- if (typeof seconds !== 'number' || seconds < 0) {
1657
- return false;
1658
- }
1659
-
1660
- // 检查键是否存在
1661
- if (!(key in this._data)) {
1662
- return false;
1663
- }
1664
-
1665
- // 检查是否过期
1666
- if (this._isExpired(key)) {
1667
- this.del(key);
1668
- return false;
1669
- }
1670
-
1671
- // 设置过期时间
1672
- if (seconds > 0) {
1673
- this._expires[key] = Date.now() + (seconds * 1000);
1674
- } else if (seconds === 0) {
1675
- // 移除过期时间
1676
- delete this._expires[key];
1677
- }
1678
-
1679
- return true;
1680
- } else {
1681
- // 获取剩余过期时间模式
1682
- // 检查键是否存在
1683
- if (!(key in this._data)) {
1684
- return -2;
1685
- }
1686
-
1687
- // 检查是否过期
1688
- if (this._isExpired(key)) {
1689
- this.del(key);
1690
- return -2;
1691
- }
1692
-
1693
- // 检查是否设置了过期时间
1694
- if (!(key in this._expires)) {
1695
- return -1; // 无过期时间
1696
- }
2580
+ this.emit('smembers:before', { key });
1697
2581
 
1698
- // 计算剩余时间
1699
- const now = Date.now();
1700
- const remaining = this._expires[key] - now;
2582
+ if (this._isExpired(key)) {
2583
+ await this.del(key);
2584
+ return [];
2585
+ }
1701
2586
 
1702
- return Math.max(0, Math.ceil(remaining / 1000));
2587
+ if (!(key in this._data) || this._types[key] !== 'set') {
2588
+ return [];
1703
2589
  }
2590
+
2591
+ const result = Array.from(this._data[key]);
2592
+ this.emit('smembers:after', { key, result });
2593
+ return result;
1704
2594
  } catch (error) {
1705
- const errorMsg = arguments.length > 1 && seconds !== undefined
1706
- ? `Failed to set ttl for key ${key}: ${error.message}`
1707
- : `Failed to get ttl for key ${key}: ${error.message}`;
1708
- this.log('error', errorMsg, error);
1709
- return arguments.length > 1 && seconds !== undefined ? false : -2;
2595
+ this.log('error', `获取集合所有成员失败 [${key}]:`, error);
2596
+ this.emit('smembers:error', { key, error });
2597
+ return [];
1710
2598
  }
1711
2599
  };
1712
2600
 
1713
2601
  /**
1714
- * 移除键的过期时间
1715
- * @param {string} key - 键名
1716
- * @returns {boolean} 是否成功移除
2602
+ * 获取集合中的成员数量
2603
+ * @param {String} key 键
2604
+ * @returns {Promise<Number>} 成员数量
1717
2605
  */
1718
- CacheBase.prototype.persist = function (key) {
2606
+ CacheBase.prototype.scard = async function (key) {
1719
2607
  try {
1720
- // 参数验证
1721
- if (!key || typeof key !== 'string') {
1722
- return false;
1723
- }
1724
-
1725
- // 触发操作前事件
1726
- const event_data = {
1727
- key,
1728
- cancel: false
1729
- };
1730
- this.emit('persist_before', event_data);
1731
-
1732
- if (event_data.cancel) {
1733
- return false;
1734
- }
1735
-
1736
- // 检查键是否存在
1737
- if (!(key in this._data)) {
1738
- return false;
1739
- }
1740
-
1741
- // 检查是否过期
1742
2608
  if (this._isExpired(key)) {
1743
- this.del(key);
1744
- return false;
2609
+ await this.del(key);
2610
+ return 0;
1745
2611
  }
1746
2612
 
1747
- // 移除过期时间
1748
- if (key in this._expires) {
1749
- delete this._expires[key];
1750
-
1751
- // 触发操作后事件
1752
- this.emit('persist_after', {
1753
- key
1754
- });
1755
- return true;
2613
+ if (!(key in this._data) || this._types[key] !== 'set') {
2614
+ return 0;
1756
2615
  }
1757
2616
 
1758
- return false; // 键没有设置过期时间
2617
+ return this._data[key].size;
1759
2618
  } catch (error) {
1760
- this.log('error', `Failed to persist key ${key}:`, error);
1761
- return false;
2619
+ this.log('error', `获取集合成员数量失败 [${key}]:`, error);
2620
+ return 0;
1762
2621
  }
1763
2622
  };
2623
+
1764
2624
  /**
1765
- * 批量获取多个键的值
1766
- * @param {Array} keys - 键名数组
1767
- * @returns {Array} 值数组,按键的顺序返回
2625
+ * 获取多个集合的交集
2626
+ * @param {...String} keys 键名列表
2627
+ * @returns {Promise<Array>} 交集成员数组
1768
2628
  */
1769
- CacheBase.prototype.mget = function (keys) {
2629
+ CacheBase.prototype.sinter = async function (...keys) {
1770
2630
  try {
1771
- // 参数验证
1772
- if (!Array.isArray(keys)) {
2631
+ if (keys.length === 0) {
1773
2632
  return [];
1774
2633
  }
1775
2634
 
1776
- const results = [];
1777
-
1778
- // 批量获取值
2635
+ const sets = [];
1779
2636
  for (const key of keys) {
1780
- if (typeof key === 'string') {
1781
- results.push(this.get(key));
1782
- } else {
1783
- results.push(null);
2637
+ if (this._isExpired(key)) {
2638
+ await this.del(key);
2639
+ return [];
1784
2640
  }
2641
+ if (!(key in this._data) || this._types[key] !== 'set') {
2642
+ return [];
2643
+ }
2644
+ sets.push(this._data[key]);
1785
2645
  }
1786
2646
 
1787
- return results;
2647
+ const result = Array.from(sets[0]).filter(member =>
2648
+ sets.every(set => set.has(member))
2649
+ );
2650
+ return result;
1788
2651
  } catch (error) {
1789
- this.log('error', 'Failed to mget:', error);
2652
+ this.log('error', '获取集合交集失败:', error);
1790
2653
  return [];
1791
2654
  }
1792
2655
  };
1793
2656
 
1794
2657
  /**
1795
- * 清理资源并销毁实例
1796
- * @returns {boolean} 操作结果
2658
+ * 获取多个集合的并集
2659
+ * @param {...String} keys 键名列表
2660
+ * @returns {Promise<Array>} 并集成员数组
1797
2661
  */
1798
- CacheBase.prototype.dispose = function () {
2662
+ CacheBase.prototype.sunion = async function (...keys) {
1799
2663
  try {
1800
- // 清除定时任务
1801
- if (this._auto_save_timer) {
1802
- clearInterval(this._auto_save_timer);
1803
- this._auto_save_timer = null;
2664
+ if (keys.length === 0) {
2665
+ return [];
1804
2666
  }
1805
2667
 
1806
- // 清除所有数据
1807
- this._data = {};
1808
- this._expires = {};
1809
- this._types = {};
1810
-
1811
- // 触发销毁事件
1812
- this.emit('destroy', {
1813
- success: true
1814
- });
2668
+ const resultSet = new Set();
2669
+ for (const key of keys) {
2670
+ if (this._isExpired(key)) {
2671
+ await this.del(key);
2672
+ continue;
2673
+ }
2674
+ if (key in this._data && this._types[key] === 'set') {
2675
+ for (const member of this._data[key]) {
2676
+ resultSet.add(member);
2677
+ }
2678
+ }
2679
+ }
1815
2680
 
1816
- return true;
2681
+ return Array.from(resultSet);
1817
2682
  } catch (error) {
1818
- this.log('error', 'Failed to dispose:', error);
1819
- return false;
2683
+ this.log('error', '获取集合并集失败:', error);
2684
+ return [];
1820
2685
  }
1821
2686
  };
1822
2687
 
1823
2688
  /**
1824
- * 确保连接池对象存在
2689
+ * 获取多个集合的差集(第一个集合减去其余集合)
2690
+ * @param {...String} keys 键名列表
2691
+ * @returns {Promise<Array>} 差集成员数组
1825
2692
  */
1826
- global.$ = global.$ || {};
1827
- if (!$.pool) {
1828
- $.pool = {};
1829
- }
1830
- if (!$.pool.cachebase) {
1831
- $.pool.cachebase = {};
1832
- }
2693
+ CacheBase.prototype.sdiff = async function (...keys) {
2694
+ try {
2695
+ if (keys.length === 0) {
2696
+ return [];
2697
+ }
1833
2698
 
1834
- /**
1835
- * @description cachebase管理器,用于创建缓存
1836
- * @param {String} scope 作用域
1837
- * @param {String} dir 当前路径
1838
- * @return {Object} 返回一个cachebase类实例
1839
- */
1840
- function cachebase_admin(scope, dir) {
1841
- if (!scope) {
1842
- scope = 'sys';
1843
- }
1844
- var obj = $.pool.cachebase[scope];
1845
- if (!obj) {
1846
- const config = {
1847
- file_path: dir ? `./cachebase_${scope}.json`.fullname(dir) : `/cache/cachebase_${scope}.json`
1848
- };
1849
- $.pool.cachebase[scope] = new CacheBase(config);
1850
- obj = $.pool.cachebase[scope];
2699
+ const firstKey = keys[0];
2700
+ if (this._isExpired(firstKey)) {
2701
+ await this.del(firstKey);
2702
+ return [];
2703
+ }
2704
+ if (!(firstKey in this._data) || this._types[firstKey] !== 'set') {
2705
+ return [];
2706
+ }
2707
+
2708
+ const firstSet = this._data[firstKey];
2709
+ const otherSets = [];
2710
+ for (let i = 1; i < keys.length; i++) {
2711
+ const key = keys[i];
2712
+ if (this._isExpired(key)) {
2713
+ await this.del(key);
2714
+ continue;
2715
+ }
2716
+ if (key in this._data && this._types[key] === 'set') {
2717
+ otherSets.push(this._data[key]);
2718
+ }
2719
+ }
2720
+
2721
+ const result = Array.from(firstSet).filter(member =>
2722
+ !otherSets.some(set => set.has(member))
2723
+ );
2724
+ return result;
2725
+ } catch (error) {
2726
+ this.log('error', '获取集合差集失败:', error);
2727
+ return [];
1851
2728
  }
1852
- return obj;
1853
- }
2729
+ };
2730
+
2731
+ // ==================== 有序集合操作 ====================
1854
2732
 
1855
2733
  /**
1856
- * 设置列表
1857
- * @param {string} key - 键名
1858
- * @param {Array} value - 列表值
1859
- * @param {number} [maxAge=0] - 过期时间(秒),0表示永不过期
1860
- * @returns {Promise<number>} - 列表长度
2734
+ * 添加成员到有序集合
2735
+ * @param {String} key 键
2736
+ * @param {Number} score 分数
2737
+ * @param {String} member 成员
2738
+ * @returns {Promise<Boolean>} 是否成功(新成员返回true,更新返回false)
1861
2739
  */
1862
- CacheBase.prototype.setForList = async function (key, value, maxAge = 0) {
2740
+ CacheBase.prototype.zadd = async function (key, score, member) {
1863
2741
  try {
1864
- // 参数验证
1865
- if (!key || typeof key !== 'string') {
1866
- return 0;
1867
- }
1868
-
1869
- // 确保value是数组
1870
- let stringValues = [];
1871
- if (Array.isArray(value)) {
1872
- stringValues = value.map(item => String(item));
1873
- } else {
1874
- stringValues = [String(value)];
1875
- }
1876
-
1877
- // 触发前置事件
1878
- const beforeEventData = {
2742
+ const ctx = {
1879
2743
  key,
1880
- value,
1881
- maxAge,
2744
+ score,
2745
+ member,
1882
2746
  cancel: false
1883
2747
  };
1884
- this.emit('setForList_before', beforeEventData);
2748
+ this.emit('zadd:before', ctx);
1885
2749
 
1886
- if (beforeEventData.cancel) {
1887
- this.log('debug', `setForList操作被事件处理器取消 [${key}]`);
1888
- return 0;
2750
+ if (ctx.cancel) {
2751
+ return false;
1889
2752
  }
1890
2753
 
1891
- // 检查键是否存在且未过期
1892
- if (key in this._data && !this._isExpired(key)) {
1893
- // 如果已存在但不是数组,删除并重新设置
1894
- if (!Array.isArray(this._data[key])) {
1895
- delete this._data[key];
1896
- delete this._types[key];
1897
- delete this._expires[key];
1898
- }
1899
- }
2754
+ const {
2755
+ key: finalKey,
2756
+ score: finalScore,
2757
+ member: finalMember
2758
+ } = ctx;
1900
2759
 
1901
- // 设置列表数据
1902
- this._data[key] = stringValues;
1903
- this._types[key] = 'array';
2760
+ // 检查是否过期
2761
+ if (this._isExpired(finalKey)) {
2762
+ await this.del(finalKey);
2763
+ }
1904
2764
 
1905
- // 设置过期时间
1906
- if (maxAge > 0) {
1907
- this._expires[key] = Date.now() + maxAge * 1000;
1908
- } else {
1909
- delete this._expires[key];
2765
+ // 初始化有序集合
2766
+ if (!(finalKey in this._data) || this._types[finalKey] !== 'zset') {
2767
+ this._data[finalKey] = new Map();
2768
+ this._types[finalKey] = 'zset';
1910
2769
  }
1911
2770
 
1912
- const length = stringValues.length;
2771
+ const map = this._data[finalKey];
2772
+ const memberStr = String(finalMember);
2773
+ const isNew = !map.has(memberStr);
2774
+ map.set(memberStr, Number(finalScore));
1913
2775
 
1914
- // 触发后置事件
1915
- this.emit('setForList_after', {
1916
- key,
1917
- value: stringValues,
1918
- maxAge,
1919
- result: length
2776
+ this.emit('zadd:after', {
2777
+ key: finalKey,
2778
+ score: finalScore,
2779
+ member: finalMember,
2780
+ success: isNew
1920
2781
  });
1921
- return length;
2782
+ return isNew;
1922
2783
  } catch (error) {
1923
- // 触发错误事件
1924
- this.emit('setForList_error', {
1925
- key,
1926
- value,
1927
- maxAge,
1928
- error
1929
- });
1930
- this.log('error', 'setForList操作失败:', error);
1931
- return 0;
2784
+ this.log('error', `添加有序集合成员失败 [${key}]:`, error);
2785
+ this.emit('zadd:error', { key, score, member, error });
2786
+ return false;
1932
2787
  }
1933
2788
  };
1934
2789
 
1935
2790
  /**
1936
- * 向列表添加元素
1937
- * @param {string} key - 键名
1938
- * @param {*} value - 要添加的元素
1939
- * @param {number} [maxAge=0] - 过期时间(秒),0表示永不过期
1940
- * @returns {Promise<number>} - 添加后列表长度
2791
+ * 从有序集合中移除成员
2792
+ * @param {String} key 键
2793
+ * @param {String|Array} members 成员或成员数组
2794
+ * @returns {Promise<Number>} 移除的成员数量
1941
2795
  */
1942
- CacheBase.prototype.addForList = async function (key, value, maxAge = 0) {
2796
+ CacheBase.prototype.zrem = async function (key, members) {
1943
2797
  try {
1944
- // 参数验证
1945
- if (!key || typeof key !== 'string') {
1946
- return 0;
1947
- }
1948
-
1949
- // 确保value是字符串
1950
- const stringValue = String(value);
1951
-
1952
- // 触发前置事件
1953
- const beforeEventData = {
2798
+ const ctx = {
1954
2799
  key,
1955
- value,
1956
- maxAge,
2800
+ members: Array.isArray(members) ? members : [members],
1957
2801
  cancel: false
1958
2802
  };
1959
- this.emit('addForList_before', beforeEventData);
2803
+ this.emit('zrem:before', ctx);
1960
2804
 
1961
- if (beforeEventData.cancel) {
1962
- this.log('debug', `addForList操作被事件处理器取消 [${key}]`);
2805
+ if (ctx.cancel) {
1963
2806
  return 0;
1964
2807
  }
1965
2808
 
1966
- // 检查键是否存在且未过期
1967
- if (key in this._data && !this._isExpired(key)) {
1968
- // 如果已存在但不是数组,删除并重新设置为数组
1969
- if (!Array.isArray(this._data[key])) {
1970
- this._data[key] = [stringValue];
1971
- this._types[key] = 'array';
1972
- } else {
1973
- // 向现有数组添加元素
1974
- this._data[key].push(stringValue);
1975
- }
1976
- } else {
1977
- // 键不存在或已过期,创建新数组
1978
- this._data[key] = [stringValue];
1979
- this._types[key] = 'array';
2809
+ const {
2810
+ key: finalKey,
2811
+ members: finalMembers
2812
+ } = ctx;
2813
+
2814
+ if (!(finalKey in this._data) || this._types[finalKey] !== 'zset') {
2815
+ return 0;
1980
2816
  }
1981
2817
 
1982
- // 设置过期时间
1983
- if (maxAge > 0) {
1984
- this._expires[key] = Date.now() + maxAge * 1000;
1985
- } else if (key in this._expires) {
1986
- delete this._expires[key];
2818
+ const map = this._data[finalKey];
2819
+ let removedCount = 0;
2820
+ for (const member of finalMembers) {
2821
+ const memberStr = String(member);
2822
+ if (map.has(memberStr)) {
2823
+ map.delete(memberStr);
2824
+ removedCount++;
2825
+ }
1987
2826
  }
1988
2827
 
1989
- const length = this._data[key].length;
2828
+ // 如果有序集合为空,删除整个键
2829
+ if (map.size === 0) {
2830
+ delete this._data[finalKey];
2831
+ delete this._types[finalKey];
2832
+ delete this._expires[finalKey];
2833
+ }
1990
2834
 
1991
- // 触发后置事件
1992
- this.emit('addForList_after', {
1993
- key,
1994
- value: stringValue,
1995
- maxAge,
1996
- result: length
2835
+ this.emit('zrem:after', {
2836
+ key: finalKey,
2837
+ members: finalMembers,
2838
+ result: removedCount
1997
2839
  });
1998
- return length;
2840
+ return removedCount;
1999
2841
  } catch (error) {
2000
- // 触发错误事件
2001
- this.emit('addForList_error', {
2002
- key,
2003
- value,
2004
- maxAge,
2005
- error
2006
- });
2007
- this.log('error', 'addForList操作失败:', error);
2842
+ this.log('error', `移除有序集合成员失败 [${key}]:`, error);
2843
+ this.emit('zrem:error', { key, members, error });
2008
2844
  return 0;
2009
2845
  }
2010
2846
  };
2011
2847
 
2012
2848
  /**
2013
- * 检查列表中是否包含指定元素
2014
- * @param {string} key - 键名
2015
- * @param {*} value - 要检查的元素
2016
- * @returns {Promise<boolean>} - 是否包含
2849
+ * 获取有序集合中成员的分数
2850
+ * @param {String} key 键
2851
+ * @param {String} member 成员
2852
+ * @returns {Promise<Number|null>} 分数,不存在返回null
2017
2853
  */
2018
- CacheBase.prototype.hasForList = async function (key, value) {
2854
+ CacheBase.prototype.zscore = async function (key, member) {
2019
2855
  try {
2020
- // 参数验证
2021
- if (!key || typeof key !== 'string') {
2022
- return false;
2856
+ if (this._isExpired(key)) {
2857
+ await this.del(key);
2858
+ return null;
2023
2859
  }
2024
2860
 
2025
- // 确保value是字符串
2026
- const stringValue = String(value);
2861
+ if (!(key in this._data) || this._types[key] !== 'zset') {
2862
+ return null;
2863
+ }
2027
2864
 
2028
- // 触发前置事件
2029
- const beforeEventData = {
2030
- key,
2031
- value,
2032
- cancel: false
2033
- };
2034
- this.emit('hasForList_before', beforeEventData);
2865
+ const memberStr = String(member);
2866
+ const map = this._data[key];
2867
+ if (!map.has(memberStr)) {
2868
+ return null;
2869
+ }
2870
+ return map.get(memberStr);
2871
+ } catch (error) {
2872
+ this.log('error', `获取有序集合成员分数失败 [${key}:${member}]:`, error);
2873
+ return null;
2874
+ }
2875
+ };
2035
2876
 
2036
- if (beforeEventData.cancel) {
2037
- this.log('debug', `hasForList操作被事件处理器取消 [${key}]`);
2038
- return false;
2877
+ /**
2878
+ * 获取有序集合中成员的排名(分数从小到大)
2879
+ * @param {String} key 键
2880
+ * @param {String} member 成员
2881
+ * @returns {Promise<Number|null>} 排名,不存在返回null
2882
+ */
2883
+ CacheBase.prototype.zrank = async function (key, member) {
2884
+ try {
2885
+ if (this._isExpired(key)) {
2886
+ await this.del(key);
2887
+ return null;
2039
2888
  }
2040
2889
 
2041
- // 检查键是否存在且未过期
2042
- if (key in this._data && !this._isExpired(key)) {
2043
- // 检查是否为数组
2044
- if (Array.isArray(this._data[key])) {
2045
- // 检查数组中是否包含指定元素
2046
- const result = this._data[key].includes(stringValue);
2047
- // 触发后置事件
2048
- this.emit('hasForList_after', {
2049
- key,
2050
- value,
2051
- result
2052
- });
2053
- return result;
2054
- }
2890
+ if (!(key in this._data) || this._types[key] !== 'zset') {
2891
+ return null;
2055
2892
  }
2056
2893
 
2057
- // 键不存在、已过期或不是数组
2058
- this.emit('hasForList_after', {
2059
- key,
2060
- value,
2061
- result: 0
2062
- });
2063
- return false;
2894
+ const memberStr = String(member);
2895
+ const map = this._data[key];
2896
+ if (!map.has(memberStr)) {
2897
+ return null;
2898
+ }
2899
+
2900
+ // 按分数从小到大排序
2901
+ const sorted = Array.from(map.entries()).sort((a, b) => a[1] - b[1]);
2902
+ const rank = sorted.findIndex(([m]) => m === memberStr);
2903
+ return rank >= 0 ? rank : null;
2064
2904
  } catch (error) {
2065
- // 触发错误事件
2066
- this.emit('hasForList_error', {
2067
- key,
2068
- value,
2069
- error
2070
- });
2071
- this.log('error', 'hasForList操作失败:', error);
2072
- return false;
2905
+ this.log('error', `获取有序集合成员排名失败 [${key}:${member}]:`, error);
2906
+ return null;
2073
2907
  }
2074
2908
  };
2075
2909
 
2076
2910
  /**
2077
- * 获取列表
2078
- * @param {string} key - 键名
2079
- * @param {number} [start=0] - 起始索引
2080
- * @param {number} [stop=-1] - 结束索引,-1表示全部
2081
- * @returns {Promise<Array>} - 列表数组
2911
+ * 获取有序集合中指定范围的成员
2912
+ * @param {String} key 键
2913
+ * @param {Number} start 起始索引
2914
+ * @param {Number} stop 结束索引
2915
+ * @param {Boolean} [withScores=false] 是否返回分数
2916
+ * @returns {Promise<Array>} 成员数组
2082
2917
  */
2083
- CacheBase.prototype.getForList = async function (key, start = 0, stop = -1) {
2918
+ CacheBase.prototype.zrange = async function (key, start, stop, withScores = false) {
2084
2919
  try {
2085
- // 参数验证
2086
- if (!key || typeof key !== 'string') {
2920
+ if (this._isExpired(key)) {
2921
+ await this.del(key);
2087
2922
  return [];
2088
2923
  }
2089
2924
 
2090
- // 触发前置事件
2091
- const beforeEventData = {
2092
- key,
2093
- start,
2094
- stop,
2095
- cancel: false
2096
- };
2097
- this.emit('getForList_before', beforeEventData);
2098
-
2099
- if (beforeEventData.cancel) {
2100
- this.log('debug', `getForList操作被事件处理器取消 [${key}]`);
2925
+ if (!(key in this._data) || this._types[key] !== 'zset') {
2101
2926
  return [];
2102
2927
  }
2103
2928
 
2104
- // 检查键是否存在且未过期
2105
- if (key in this._data && !this._isExpired(key)) {
2106
- // 检查是否为数组
2107
- if (Array.isArray(this._data[key])) {
2108
- const array = this._data[key];
2109
-
2110
- // 处理负索引
2111
- const startIndex = start < 0 ? Math.max(0, array.length + start) : start;
2112
- const endIndex = stop < 0 ? array.length : stop + 1;
2113
-
2114
- // 截取数组
2115
- const result = array.slice(startIndex, endIndex);
2116
-
2117
- // 触发后置事件
2118
- this.emit('getForList_after', {
2119
- key,
2120
- start,
2121
- stop,
2122
- result
2123
- });
2124
- return result;
2125
- }
2929
+ const map = this._data[key];
2930
+ // 按分数从小到大排序
2931
+ const sorted = Array.from(map.entries()).sort((a, b) => a[1] - b[1]);
2932
+ const len = sorted.length;
2933
+
2934
+ // 处理负索引
2935
+ let startIndex = start < 0 ? Math.max(0, len + start) : start;
2936
+ let endIndex = stop < 0 ? Math.max(0, len + stop + 1) : stop + 1;
2937
+
2938
+ if (startIndex >= len) {
2939
+ return [];
2126
2940
  }
2127
2941
 
2128
- // 键不存在、已过期或不是数组
2129
- this.emit('getForList_after', {
2130
- key,
2131
- start,
2132
- stop,
2133
- result: []
2134
- });
2135
- return [];
2942
+ const slice = sorted.slice(startIndex, endIndex);
2943
+ if (withScores) {
2944
+ return slice.map(([member, score]) => ({ member, score }));
2945
+ }
2946
+ return slice.map(([member]) => member);
2136
2947
  } catch (error) {
2137
- // 触发错误事件
2138
- this.emit('getForList_error', {
2139
- key,
2140
- start,
2141
- stop,
2142
- error
2143
- });
2144
- this.log('error', 'getForList操作失败:', error);
2948
+ this.log('error', `获取有序集合范围成员失败 [${key}]:`, error);
2145
2949
  return [];
2146
2950
  }
2147
2951
  };
2148
2952
 
2149
2953
  /**
2150
- * 清空列表
2151
- * @param {string} key - 键名
2152
- * @returns {Promise<boolean>} - 操作结果
2954
+ * 获取有序集合中的成员数量
2955
+ * @param {String} key 键
2956
+ * @returns {Promise<Number>} 成员数量
2153
2957
  */
2154
- CacheBase.prototype.clearForList = async function (key) {
2958
+ CacheBase.prototype.zcard = async function (key) {
2155
2959
  try {
2156
- // 参数验证
2157
- if (!key || typeof key !== 'string') {
2158
- return false;
2159
- }
2160
-
2161
- // 触发前置事件
2162
- const beforeEventData = {
2163
- key,
2164
- cancel: false
2165
- };
2166
- this.emit('clearForList_before', beforeEventData);
2167
-
2168
- if (beforeEventData.cancel) {
2169
- this.log('debug', `clearForList操作被事件处理器取消 [${key}]`);
2170
- return false;
2960
+ if (this._isExpired(key)) {
2961
+ await this.del(key);
2962
+ return 0;
2171
2963
  }
2172
2964
 
2173
- // 检查键是否存在且未过期
2174
- if (key in this._data && !this._isExpired(key)) {
2175
- // 检查是否为数组
2176
- if (Array.isArray(this._data[key])) {
2177
- // 清空数组
2178
- this._data[key] = [];
2179
-
2180
- // 触发后置事件
2181
- this.emit('clearForList_after', {
2182
- key,
2183
- result: true
2184
- });
2185
- return true;
2186
- } else {
2187
- // 如果不是数组,删除键
2188
- delete this._data[key];
2189
- delete this._types[key];
2190
- delete this._expires[key];
2191
-
2192
- // 触发后置事件
2193
- this.emit('clearForList_after', {
2194
- key,
2195
- result: false
2196
- });
2197
- return false;
2198
- }
2965
+ if (!(key in this._data) || this._types[key] !== 'zset') {
2966
+ return 0;
2199
2967
  }
2200
2968
 
2201
- // 键不存在或已过期,创建空数组
2202
- this._data[key] = [];
2203
- this._types[key] = 'array';
2204
-
2205
- // 触发后置事件
2206
- this.emit('clearForList_after', {
2207
- key,
2208
- result: true
2209
- });
2210
- return true;
2969
+ return this._data[key].size;
2211
2970
  } catch (error) {
2212
- // 触发错误事件
2213
- this.emit('clearForList_error', {
2214
- key,
2215
- error
2216
- });
2217
- this.log('error', 'clearForList操作失败:', error);
2218
- return false;
2971
+ this.log('error', `获取有序集合成员数量失败 [${key}]:`, error);
2972
+ return 0;
2219
2973
  }
2220
2974
  };
2221
2975
 
2222
2976
  /**
2223
- * 批量设置哈希表字段
2224
- * @param {string} key - 哈希表键名
2225
- * @param {Object} obj - 包含多个字段的对象
2226
- * @returns {Promise<boolean>} - 操作结果
2977
+ * 增加有序集合成员的分数
2978
+ * @param {String} key 键
2979
+ * @param {Number} increment 增量
2980
+ * @param {String} member 成员
2981
+ * @returns {Promise<Number>} 增加后的分数
2227
2982
  */
2228
- CacheBase.prototype.hmset = function (key, obj) {
2229
- return new Promise((resolve) => {
2230
- try {
2231
- // 参数验证
2232
- if (!key || typeof key !== 'string' || !obj || typeof obj !== 'object' || Array.isArray(obj)) {
2233
- resolve(false);
2234
- return;
2235
- }
2983
+ CacheBase.prototype.zincrby = async function (key, increment, member) {
2984
+ try {
2985
+ // 检查是否过期
2986
+ if (this._isExpired(key)) {
2987
+ await this.del(key);
2988
+ }
2236
2989
 
2237
- // 触发前置事件
2238
- const beforeEventData = {
2239
- key,
2240
- obj,
2241
- cancel: false
2242
- };
2243
- this.emit('hmset_before', beforeEventData);
2990
+ // 初始化有序集合
2991
+ if (!(key in this._data) || this._types[key] !== 'zset') {
2992
+ this._data[key] = new Map();
2993
+ this._types[key] = 'zset';
2994
+ }
2244
2995
 
2245
- if (beforeEventData.cancel) {
2246
- resolve(false);
2247
- return;
2248
- }
2996
+ const map = this._data[key];
2997
+ const memberStr = String(member);
2998
+ const currentScore = map.has(memberStr) ? map.get(memberStr) : 0;
2999
+ const newScore = currentScore + Number(increment);
3000
+ map.set(memberStr, newScore);
2249
3001
 
2250
- // 检查是否过期
2251
- if (this._isExpired(key)) {
2252
- this.del(key);
2253
- }
3002
+ return newScore;
3003
+ } catch (error) {
3004
+ this.log('error', `增加有序集合成员分数失败 [${key}:${member}]:`, error);
3005
+ return null;
3006
+ }
3007
+ };
2254
3008
 
2255
- // 初始化哈希表
2256
- if (!(key in this._data) || this._types[key] !== 'hash') {
2257
- this._data[key] = {};
2258
- this._types[key] = 'hash';
2259
- }
3009
+ // ==================== 发布订阅 ====================
2260
3010
 
2261
- // 批量设置字段
2262
- for (const field in obj) {
2263
- if (Object.prototype.hasOwnProperty.call(obj, field)) {
2264
- this._data[key][field] = obj[field];
2265
- }
2266
- }
3011
+ /**
3012
+ * 消息订阅
3013
+ * @param {String} channel 订阅的频道
3014
+ * @param {Function} func 操作回调函数
3015
+ * @returns {Promise<Boolean>} 订阅结果
3016
+ */
3017
+ CacheBase.prototype.subscribe = async function (channel, func) {
3018
+ if (!channel || typeof channel !== 'string') {
3019
+ throw new TypeError('channel必须是非空字符串');
3020
+ }
3021
+ if (typeof func !== 'function') {
3022
+ throw new TypeError('func必须是函数');
3023
+ }
2267
3024
 
2268
- // 触发后置事件
2269
- this.emit('hmset_after', {
2270
- key,
2271
- obj,
2272
- success: true
2273
- });
2274
- resolve(true);
2275
- } catch (error) {
2276
- this.emit('hmset_error', {
2277
- key,
2278
- obj,
2279
- error
2280
- });
2281
- this.log('error', 'Failed to hmset:', error);
2282
- resolve(false);
2283
- }
2284
- });
3025
+ try {
3026
+ this._channels[channel] = func;
3027
+ return true;
3028
+ } catch (err) {
3029
+ this.log('error', `订阅频道 ${channel} 失败:`, err);
3030
+ return false;
3031
+ }
2285
3032
  };
2286
3033
 
2287
3034
  /**
2288
- * 批量获取哈希表字段
2289
- * @param {string} key - 哈希表键名
2290
- * @param {Array} fields - 字段名数组
2291
- * @returns {Promise<Array>} - 字段值数组,不存在的字段返回null
3035
+ * 取消订阅
3036
+ * @param {String} channel 订阅的频道
3037
+ * @returns {Promise<Boolean>} 取消订阅结果
2292
3038
  */
2293
- CacheBase.prototype.hmget = function (key, fields) {
2294
- return new Promise((resolve) => {
2295
- try {
2296
- // 参数验证
2297
- if (!key || typeof key !== 'string' || !Array.isArray(fields)) {
2298
- resolve([]);
2299
- return;
2300
- }
3039
+ CacheBase.prototype.unsubscribe = async function (channel) {
3040
+ if (!channel || typeof channel !== 'string') {
3041
+ throw new TypeError('channel必须是非空字符串');
3042
+ }
2301
3043
 
2302
- // 检查是否过期
2303
- if (this._isExpired(key)) {
2304
- this.del(key);
2305
- resolve([]);
2306
- return;
2307
- }
3044
+ try {
3045
+ delete this._channels[channel];
3046
+ return true;
3047
+ } catch (err) {
3048
+ this.log('error', `取消订阅频道 ${channel} 失败:`, err);
3049
+ return false;
3050
+ }
3051
+ };
2308
3052
 
2309
- // 检查键是否存在且类型正确
2310
- if (!(key in this._data) || this._types[key] !== 'hash') {
2311
- resolve(fields.map(() => null));
2312
- return;
2313
- }
3053
+ /**
3054
+ * 发布消息
3055
+ * @param {String} channel 频道
3056
+ * @param {String} message 消息
3057
+ * @returns {Promise<Number>} 接收消息的订阅者数量
3058
+ */
3059
+ CacheBase.prototype.publish = async function (channel, message) {
3060
+ if (!channel || typeof channel !== 'string') {
3061
+ throw new TypeError('channel必须是非空字符串');
3062
+ }
3063
+ if (message === undefined || message === null) {
3064
+ throw new TypeError('message不能为undefined或null');
3065
+ }
2314
3066
 
2315
- // 批量获取字段值
2316
- const results = [];
2317
- for (const field of fields) {
2318
- results.push(this._data[key][field] !== undefined ? this._data[key][field] : null);
3067
+ try {
3068
+ const func = this._channels[channel];
3069
+ if (func) {
3070
+ try {
3071
+ func(message);
3072
+ } catch (err) {
3073
+ this.log('error', `处理频道 ${channel} 的消息时出错:`, err);
2319
3074
  }
2320
-
2321
- resolve(results);
2322
- } catch (error) {
2323
- this.emit('hmget_error', {
2324
- key,
2325
- fields,
2326
- error
2327
- });
2328
- this.log('error', 'Failed to hmget:', error);
2329
- resolve([]);
3075
+ return 1;
2330
3076
  }
2331
- });
3077
+ return 0;
3078
+ } catch (err) {
3079
+ this.log('error', `发布消息到频道 ${channel} 失败:`, err);
3080
+ return 0;
3081
+ }
2332
3082
  };
2333
3083
 
2334
3084
  /**
2335
- * 获取哈希表字段数量
2336
- * @param {string} key - 哈希表键名
2337
- * @returns {Promise<number>} - 字段数量,失败返回0
3085
+ * 订阅消息处理, 如果没有订阅函数,则其他的订阅消息会进入该函数
3086
+ * @param {String} channel 频道
3087
+ * @param {String} message 消息
2338
3088
  */
2339
- CacheBase.prototype.hlen = function (key) {
2340
- return new Promise((resolve) => {
2341
- try {
2342
- // 参数验证
2343
- if (!key || typeof key !== 'string') {
2344
- resolve(0);
2345
- return;
2346
- }
2347
-
2348
- // 检查是否过期
2349
- if (this._isExpired(key)) {
2350
- this.del(key);
2351
- resolve(0);
2352
- return;
2353
- }
2354
-
2355
- // 检查键是否存在且类型正确
2356
- if (!(key in this._data) || this._types[key] !== 'hash') {
2357
- resolve(0);
2358
- return;
2359
- }
3089
+ CacheBase.prototype.main = async function (channel, message) {
3090
+ // 默认实现,可由子类重写
3091
+ this.log('debug', `收到未处理的消息 [${channel}]:`, message);
3092
+ };
2360
3093
 
2361
- resolve(Object.keys(this._data[key]).length);
2362
- } catch (error) {
2363
- this.emit('hlen_error', {
2364
- key,
2365
- error
2366
- });
2367
- this.log('error', 'Failed to hlen:', error);
2368
- resolve(0);
3094
+ /**
3095
+ * 消息通知
3096
+ * @param {String} channel 频道
3097
+ * @param {String} message 消息
3098
+ */
3099
+ CacheBase.prototype.message = function (channel, message) {
3100
+ var func = this._channels[channel];
3101
+ if (func) {
3102
+ try {
3103
+ func(message);
3104
+ } catch (err) {
3105
+ this.log('error', `处理频道 ${channel} 的消息时出错:`, err);
2369
3106
  }
2370
- });
3107
+ } else {
3108
+ this.main(channel, message);
3109
+ }
2371
3110
  };
2372
3111
 
3112
+ // ==================== 连接池管理 ====================
3113
+
3114
+ /**
3115
+ * 确保连接池对象存在
3116
+ */
3117
+ global.$ = global.$ || {};
3118
+ if (!$.pool) {
3119
+ $.pool = {};
3120
+ }
3121
+ if (!$.pool.cachebase) {
3122
+ $.pool.cachebase = {};
3123
+ }
2373
3124
 
2374
3125
  /**
2375
3126
  * @description CacheBase管理器,用于创建缓存
@@ -2406,4 +3157,4 @@ function cachebaseAdmin(scope, config) {
2406
3157
  module.exports = {
2407
3158
  CacheBase,
2408
3159
  cachebaseAdmin
2409
- };
3160
+ };