koatty_store 1.7.0 → 1.9.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.
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @Author: richen
3
- * @Date: 2024-11-07 14:38:56
3
+ * @Date: 2025-11-02 08:37:33
4
4
  * @License: BSD (3-Clause)
5
5
  * @Copyright (c) - <richenlin(at)gmail.com>
6
6
  * @HomePage: https://koatty.org/
@@ -10,6 +10,8 @@
10
10
  var lodash = require('lodash');
11
11
  var helper = require('koatty_lib');
12
12
  var events = require('events');
13
+ var lruCache = require('lru-cache');
14
+ var AsyncLock = require('async-lock');
13
15
  var koatty_logger = require('koatty_logger');
14
16
  var ioredis = require('ioredis');
15
17
  var genericPool = require('generic-pool');
@@ -70,26 +72,100 @@ var messages;
70
72
  messages["mutuallyExclusiveNXXX"] = "ERR XX and NX options at the same time are not compatible";
71
73
  })(messages || (messages = {}));
72
74
  class MemoryCache extends events.EventEmitter {
73
- databases = Object.create({});
74
- options;
75
- currentDBIndex;
76
- connected;
77
- lastSave;
78
- multiMode;
79
- cache;
80
- responseMessages;
81
75
  /**
82
76
  * Creates an instance of MemoryCache.
83
- * @param {*} options
77
+ * @param {MemoryCacheOptions} options
84
78
  * @memberof MemoryCache
85
79
  */
86
80
  constructor(options) {
87
81
  super();
88
- this.options = { ...{ database: "0" }, ...options };
89
- this.currentDBIndex = 0;
82
+ this.databases = new Map();
83
+ this.ttlCheckTimer = null;
84
+ this.options = {
85
+ database: 0,
86
+ maxKeys: 1000,
87
+ evictionPolicy: 'lru',
88
+ ttlCheckInterval: 60000, // 1分钟检查一次过期键
89
+ maxAge: 1000 * 60 * 60, // 默认1小时过期
90
+ ...options
91
+ };
92
+ this.currentDBIndex = options.database || 0;
90
93
  this.connected = false;
91
94
  this.lastSave = Date.now();
92
95
  this.multiMode = false;
96
+ this.responseMessages = [];
97
+ this.lock = new AsyncLock();
98
+ // 初始化数据库和缓存
99
+ if (!this.databases.has(this.currentDBIndex)) {
100
+ this.databases.set(this.currentDBIndex, this.createLRUCache());
101
+ }
102
+ this.cache = this.databases.get(this.currentDBIndex);
103
+ // 启动TTL检查定时器
104
+ this.startTTLCheck();
105
+ }
106
+ /**
107
+ * 创建LRU缓存实例
108
+ */
109
+ createLRUCache() {
110
+ return new lruCache.LRUCache({
111
+ max: this.options.maxKeys || 1000,
112
+ ttl: this.options.maxAge || 1000 * 60 * 60, // 1小时默认
113
+ updateAgeOnGet: true, // 访问时更新age
114
+ dispose: (value, key, reason) => {
115
+ // 键被淘汰时的回调 - 直接使用lru-cache的事件机制
116
+ this.emit('evict', key, value, reason);
117
+ },
118
+ onInsert: (value, key) => {
119
+ // 键被插入时的回调
120
+ this.emit('insert', key, value);
121
+ }
122
+ });
123
+ }
124
+ /**
125
+ * 启动TTL检查定时器
126
+ */
127
+ startTTLCheck() {
128
+ if (this.ttlCheckTimer) {
129
+ clearInterval(this.ttlCheckTimer);
130
+ }
131
+ this.ttlCheckTimer = setInterval(() => {
132
+ this.cleanExpiredKeys();
133
+ }, this.options.ttlCheckInterval || 60000);
134
+ }
135
+ /**
136
+ * 清理过期键
137
+ */
138
+ cleanExpiredKeys() {
139
+ for (const [_dbIndex, cache] of this.databases) {
140
+ const keysToDelete = [];
141
+ cache.forEach((item, key) => {
142
+ if (item.timeout && item.timeout <= Date.now()) {
143
+ keysToDelete.push(key);
144
+ }
145
+ });
146
+ keysToDelete.forEach(key => {
147
+ cache.delete(key);
148
+ this.emit('expire', key);
149
+ });
150
+ }
151
+ }
152
+ /**
153
+ * 停止TTL检查
154
+ */
155
+ stopTTLCheck() {
156
+ if (this.ttlCheckTimer) {
157
+ clearInterval(this.ttlCheckTimer);
158
+ this.ttlCheckTimer = null;
159
+ }
160
+ }
161
+ /**
162
+ * 清理所有资源
163
+ * @private
164
+ */
165
+ cleanup() {
166
+ this.stopTTLCheck();
167
+ this.databases.clear();
168
+ this.removeAllListeners();
93
169
  }
94
170
  /**
95
171
  *
@@ -98,8 +174,10 @@ class MemoryCache extends events.EventEmitter {
98
174
  * @memberof MemoryCache
99
175
  */
100
176
  createClient() {
101
- this.databases[this.options.database] = Object.create({});
102
- this.cache = this.databases[this.options.database];
177
+ if (!this.databases.has(this.options.database)) {
178
+ this.databases.set(this.options.database, this.createLRUCache());
179
+ }
180
+ this.cache = this.databases.get(this.options.database);
103
181
  this.connected = true;
104
182
  // exit multi mode if we are in it
105
183
  this.discard(null, true);
@@ -114,6 +192,7 @@ class MemoryCache extends events.EventEmitter {
114
192
  * @memberof MemoryCache
115
193
  */
116
194
  quit() {
195
+ this.cleanup(); // 调用清理方法
117
196
  this.connected = false;
118
197
  // exit multi mode if we are in it
119
198
  this.discard(null, true);
@@ -129,6 +208,40 @@ class MemoryCache extends events.EventEmitter {
129
208
  end() {
130
209
  return this.quit();
131
210
  }
211
+ /**
212
+ * 获取缓存统计信息
213
+ */
214
+ info() {
215
+ const stats = {
216
+ databases: this.databases.size,
217
+ currentDB: this.currentDBIndex,
218
+ keys: this.cache ? this.cache.length : 0,
219
+ maxKeys: this.options.maxKeys,
220
+ hits: 0,
221
+ misses: 0,
222
+ memory: this.getMemoryUsage()
223
+ };
224
+ // 如果缓存支持统计信息
225
+ if (this.cache && typeof this.cache.dump === 'function') {
226
+ const dump = this.cache.dump();
227
+ stats.keys = dump.length;
228
+ }
229
+ return stats;
230
+ }
231
+ /**
232
+ * 估算内存使用量
233
+ */
234
+ getMemoryUsage() {
235
+ let totalSize = 0;
236
+ for (const [, cache] of this.databases) {
237
+ cache.forEach((item, key) => {
238
+ // 粗略估算:key长度 + JSON序列化后的大小
239
+ totalSize += key.length * 2; // Unicode字符占2字节
240
+ totalSize += JSON.stringify(item).length * 2;
241
+ });
242
+ }
243
+ return totalSize;
244
+ }
132
245
  /**
133
246
  *
134
247
  *
@@ -175,12 +288,12 @@ class MemoryCache extends events.EventEmitter {
175
288
  if (!helper__namespace.isNumber(dbIndex)) {
176
289
  return this._handleCallback(callback, null, messages.invalidDBIndex);
177
290
  }
178
- if (!this.databases.hasOwnProperty(dbIndex)) {
179
- this.databases[dbIndex] = Object.create({});
291
+ if (!this.databases.has(dbIndex)) {
292
+ this.databases.set(dbIndex, this.createLRUCache());
180
293
  }
181
294
  this.multiMode = false;
182
295
  this.currentDBIndex = dbIndex;
183
- this.cache = this.databases[dbIndex];
296
+ this.cache = this.databases.get(dbIndex);
184
297
  return this._handleCallback(callback, messages.ok);
185
298
  }
186
299
  // ---------------------------------------
@@ -259,7 +372,7 @@ class MemoryCache extends events.EventEmitter {
259
372
  else if (onlyexist) {
260
373
  return this._handleCallback(callback, retVal);
261
374
  }
262
- this.cache[key] = this._makeKey(value.toString(), 'string', pttl);
375
+ this.cache.set(key, this._makeKey(value.toString(), 'string', pttl));
263
376
  return this._handleCallback(callback, messages.ok);
264
377
  }
265
378
  /**
@@ -289,7 +402,8 @@ class MemoryCache extends events.EventEmitter {
289
402
  expire(key, seconds, callback) {
290
403
  let retVal = 0;
291
404
  if (this._hasKey(key)) {
292
- this.cache[key].timeout = Date.now() + seconds * 1000;
405
+ const pttl = seconds * 1000;
406
+ this.cache.set(key, { ...this.cache.get(key), timeout: Date.now() + pttl });
293
407
  retVal = 1;
294
408
  }
295
409
  return this._handleCallback(callback, retVal);
@@ -309,7 +423,7 @@ class MemoryCache extends events.EventEmitter {
309
423
  for (let itr = 0; itr < keys.length; itr++) {
310
424
  const key = keys[itr];
311
425
  if (this._hasKey(key)) {
312
- delete this.cache[key];
426
+ this.cache.delete(key);
313
427
  retVal++;
314
428
  }
315
429
  }
@@ -342,14 +456,18 @@ class MemoryCache extends events.EventEmitter {
342
456
  * @memberof MemoryCache
343
457
  */
344
458
  incr(key, callback) {
345
- let retVal = null;
346
- try {
347
- retVal = this._addToKey(key, 1);
348
- }
349
- catch (err) {
350
- return this._handleCallback(callback, null, err);
351
- }
352
- return this._handleCallback(callback, retVal);
459
+ // 使用锁保护原子操作
460
+ const lockKey = `incr:${key}`;
461
+ return this.lock.acquire(lockKey, () => {
462
+ let retVal = null;
463
+ try {
464
+ retVal = this._addToKey(key, 1);
465
+ }
466
+ catch (err) {
467
+ return this._handleCallback(callback, null, err);
468
+ }
469
+ return this._handleCallback(callback, retVal);
470
+ });
353
471
  }
354
472
  /**
355
473
  *
@@ -361,14 +479,18 @@ class MemoryCache extends events.EventEmitter {
361
479
  * @memberof MemoryCache
362
480
  */
363
481
  incrby(key, amount, callback) {
364
- let retVal = null;
365
- try {
366
- retVal = this._addToKey(key, amount);
367
- }
368
- catch (err) {
369
- return this._handleCallback(callback, null, err);
370
- }
371
- return this._handleCallback(callback, retVal);
482
+ // 使用锁保护原子操作
483
+ const lockKey = `incrby:${key}`;
484
+ return this.lock.acquire(lockKey, () => {
485
+ let retVal = null;
486
+ try {
487
+ retVal = this._addToKey(key, amount);
488
+ }
489
+ catch (err) {
490
+ return this._handleCallback(callback, null, err);
491
+ }
492
+ return this._handleCallback(callback, retVal);
493
+ });
372
494
  }
373
495
  /**
374
496
  *
@@ -379,14 +501,18 @@ class MemoryCache extends events.EventEmitter {
379
501
  * @memberof MemoryCache
380
502
  */
381
503
  decr(key, callback) {
382
- let retVal = null;
383
- try {
384
- retVal = this._addToKey(key, -1);
385
- }
386
- catch (err) {
387
- return this._handleCallback(callback, null, err);
388
- }
389
- return this._handleCallback(callback, retVal);
504
+ // 使用锁保护原子操作
505
+ const lockKey = `decr:${key}`;
506
+ return this.lock.acquire(lockKey, () => {
507
+ let retVal = null;
508
+ try {
509
+ retVal = this._addToKey(key, -1);
510
+ }
511
+ catch (err) {
512
+ return this._handleCallback(callback, null, err);
513
+ }
514
+ return this._handleCallback(callback, retVal);
515
+ });
390
516
  }
391
517
  /**
392
518
  *
@@ -398,30 +524,42 @@ class MemoryCache extends events.EventEmitter {
398
524
  * @memberof MemoryCache
399
525
  */
400
526
  decrby(key, amount, callback) {
401
- let retVal = null;
402
- try {
403
- retVal = this._addToKey(key, -amount);
404
- }
405
- catch (err) {
406
- return this._handleCallback(callback, null, err);
407
- }
408
- return this._handleCallback(callback, retVal);
527
+ // 使用锁保护原子操作
528
+ const lockKey = `decrby:${key}`;
529
+ return this.lock.acquire(lockKey, () => {
530
+ let retVal = null;
531
+ try {
532
+ retVal = this._addToKey(key, 0 - amount);
533
+ }
534
+ catch (err) {
535
+ return this._handleCallback(callback, null, err);
536
+ }
537
+ return this._handleCallback(callback, retVal);
538
+ });
409
539
  }
410
540
  // ---------------------------------------
411
541
  // ## Hash ##
412
542
  // ---------------------------------------
413
- hset(key, field, value, callback) {
543
+ hset(key, field, value, timeout, callback) {
414
544
  let retVal = 0;
415
545
  if (this._hasKey(key)) {
416
546
  this._testType(key, 'hash', true, callback);
417
547
  }
418
548
  else {
419
- this.cache[key] = this._makeKey({}, 'hash');
549
+ this.cache.set(key, this._makeKey({}, 'hash'));
420
550
  }
421
551
  if (!this._hasField(key, field)) {
422
552
  retVal = 1;
423
553
  }
424
554
  this._setField(key, field, value.toString());
555
+ // 如果指定了 timeout,存储字段级别的过期时间
556
+ if (typeof timeout === 'number') {
557
+ const hashObj = this.cache.get(key).value;
558
+ if (!hashObj._fieldTTL) {
559
+ hashObj._fieldTTL = {};
560
+ }
561
+ hashObj._fieldTTL[field] = Date.now() + (timeout * 1000);
562
+ }
425
563
  this.persist(key);
426
564
  return this._handleCallback(callback, retVal);
427
565
  }
@@ -438,8 +576,17 @@ class MemoryCache extends events.EventEmitter {
438
576
  let retVal = null;
439
577
  if (this._hasKey(key)) {
440
578
  this._testType(key, 'hash', true, callback);
579
+ // 检查字段级别的过期时间
580
+ const hashObj = this._getKey(key);
581
+ if (hashObj && hashObj._fieldTTL && hashObj._fieldTTL[field]) {
582
+ if (hashObj._fieldTTL[field] <= Date.now()) {
583
+ // 过期,删除字段
584
+ this.hdel(key, field);
585
+ return this._handleCallback(callback, null);
586
+ }
587
+ }
441
588
  if (this._hasField(key, field)) {
442
- retVal = this._getKey(key)[field];
589
+ retVal = hashObj[field];
443
590
  }
444
591
  }
445
592
  return this._handleCallback(callback, retVal);
@@ -457,6 +604,15 @@ class MemoryCache extends events.EventEmitter {
457
604
  let retVal = 0;
458
605
  if (this._hasKey(key)) {
459
606
  this._testType(key, 'hash', true, callback);
607
+ // 检查字段级别的过期时间
608
+ const hashObj = this._getKey(key);
609
+ if (hashObj && hashObj._fieldTTL && hashObj._fieldTTL[field]) {
610
+ if (hashObj._fieldTTL[field] <= Date.now()) {
611
+ // 过期,删除字段
612
+ this.hdel(key, field);
613
+ return this._handleCallback(callback, 0);
614
+ }
615
+ }
460
616
  if (this._hasField(key, field)) {
461
617
  retVal = 1;
462
618
  }
@@ -476,10 +632,15 @@ class MemoryCache extends events.EventEmitter {
476
632
  const callback = this._retrieveCallback(fields);
477
633
  if (this._hasKey(key)) {
478
634
  this._testType(key, 'hash', true, callback);
635
+ const hashObj = this.cache.get(key).value;
479
636
  for (let itr = 0; itr < fields.length; itr++) {
480
637
  const field = fields[itr];
481
638
  if (this._hasField(key, field)) {
482
- delete this.cache[key].value[field];
639
+ delete hashObj[field];
640
+ // 清理过期时间记录
641
+ if (hashObj._fieldTTL && hashObj._fieldTTL[field]) {
642
+ delete hashObj._fieldTTL[field];
643
+ }
483
644
  retVal++;
484
645
  }
485
646
  }
@@ -509,14 +670,18 @@ class MemoryCache extends events.EventEmitter {
509
670
  * @memberof MemoryCache
510
671
  */
511
672
  hincrby(key, field, value, callback) {
512
- let retVal;
513
- try {
514
- retVal = this._addToField(key, field, value, false);
515
- }
516
- catch (err) {
517
- return this._handleCallback(callback, null, err);
518
- }
519
- return this._handleCallback(callback, retVal);
673
+ // 使用锁保护原子操作,使用组合 key
674
+ const lockKey = `hincrby:${key}:${field}`;
675
+ return this.lock.acquire(lockKey, () => {
676
+ let retVal;
677
+ try {
678
+ retVal = this._addToField(key, field, value, false);
679
+ }
680
+ catch (err) {
681
+ return this._handleCallback(callback, null, err);
682
+ }
683
+ return this._handleCallback(callback, retVal);
684
+ });
520
685
  }
521
686
  /**
522
687
  *
@@ -530,7 +695,11 @@ class MemoryCache extends events.EventEmitter {
530
695
  let retVals = {};
531
696
  if (this._hasKey(key)) {
532
697
  this._testType(key, 'hash', true, callback);
533
- retVals = this._getKey(key);
698
+ const hashObj = this._getKey(key);
699
+ // 排除内部属性 _fieldTTL
700
+ retVals = Object.entries(hashObj)
701
+ .filter(([k]) => k !== '_fieldTTL')
702
+ .reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {});
534
703
  }
535
704
  return this._handleCallback(callback, retVals);
536
705
  }
@@ -546,7 +715,9 @@ class MemoryCache extends events.EventEmitter {
546
715
  let retVals = [];
547
716
  if (this._hasKey(key)) {
548
717
  this._testType(key, 'hash', true, callback);
549
- retVals = Object.keys(this._getKey(key));
718
+ const hashObj = this._getKey(key);
719
+ // 排除内部属性 _fieldTTL
720
+ retVals = Object.keys(hashObj).filter(k => k !== '_fieldTTL');
550
721
  }
551
722
  return this._handleCallback(callback, retVals);
552
723
  }
@@ -562,7 +733,11 @@ class MemoryCache extends events.EventEmitter {
562
733
  let retVals = [];
563
734
  if (this._hasKey(key)) {
564
735
  this._testType(key, 'hash', true, callback);
565
- retVals = Object.values(this._getKey(key));
736
+ const hashObj = this._getKey(key);
737
+ // 排除内部属性 _fieldTTL 的值
738
+ retVals = Object.entries(hashObj)
739
+ .filter(([k]) => k !== '_fieldTTL')
740
+ .map(([, v]) => v);
566
741
  }
567
742
  return this._handleCallback(callback, retVals);
568
743
  }
@@ -600,22 +775,18 @@ class MemoryCache extends events.EventEmitter {
600
775
  this._testType(key, 'list', true, callback);
601
776
  }
602
777
  else {
603
- this.cache[key] = this._makeKey([], 'list');
778
+ this.cache.set(key, this._makeKey([], 'list'));
604
779
  }
605
- const val = this._getKey(key);
606
- val.push(value);
607
- this._setKey(key, val);
608
- retVal = val.length;
780
+ this._getKey(key).push(value.toString());
781
+ retVal = this._getKey(key).length;
782
+ this.persist(key);
609
783
  return this._handleCallback(callback, retVal);
610
784
  }
611
785
  /**
612
- *
613
- *
614
- * @param {string} key
615
- * @param {(string | number)} value
616
- * @param {Function} [callback]
617
- * @returns {*}
618
- * @memberof MemoryCache
786
+ * List:从左侧推入
787
+ * @param key
788
+ * @param value
789
+ * @param callback
619
790
  */
620
791
  lpush(key, value, callback) {
621
792
  let retVal = 0;
@@ -623,14 +794,31 @@ class MemoryCache extends events.EventEmitter {
623
794
  this._testType(key, 'list', true, callback);
624
795
  }
625
796
  else {
626
- this.cache[key] = this._makeKey([], 'list');
797
+ this.cache.set(key, this._makeKey([], 'list'));
627
798
  }
628
- const val = this._getKey(key);
629
- val.splice(0, 0, value);
630
- this._setKey(key, val);
631
- retVal = val.length;
799
+ const list = this._getKey(key);
800
+ retVal = list.unshift(value);
801
+ this._setKey(key, list);
632
802
  return this._handleCallback(callback, retVal);
633
803
  }
804
+ /**
805
+ * List:获取指定索引的元素
806
+ * @param key
807
+ * @param index
808
+ * @param callback
809
+ */
810
+ lindex(key, index, callback) {
811
+ if (!this._hasKey(key)) {
812
+ return this._handleCallback(callback, null);
813
+ }
814
+ this._testType(key, 'list', true, callback);
815
+ const list = this._getKey(key);
816
+ if (index < 0) {
817
+ index = list.length + index;
818
+ }
819
+ const value = index >= 0 && index < list.length ? list[index] : null;
820
+ return this._handleCallback(callback, value);
821
+ }
634
822
  /**
635
823
  *
636
824
  *
@@ -643,9 +831,11 @@ class MemoryCache extends events.EventEmitter {
643
831
  let retVal = null;
644
832
  if (this._hasKey(key)) {
645
833
  this._testType(key, 'list', true, callback);
646
- const val = this._getKey(key);
647
- retVal = val.shift();
648
- this._setKey(key, val);
834
+ const list = this._getKey(key);
835
+ if (list.length > 0) {
836
+ retVal = list.shift();
837
+ this.persist(key);
838
+ }
649
839
  }
650
840
  return this._handleCallback(callback, retVal);
651
841
  }
@@ -661,9 +851,11 @@ class MemoryCache extends events.EventEmitter {
661
851
  let retVal = null;
662
852
  if (this._hasKey(key)) {
663
853
  this._testType(key, 'list', true, callback);
664
- const val = this._getKey(key);
665
- retVal = val.pop();
666
- this._setKey(key, val);
854
+ const list = this._getKey(key);
855
+ if (list.length > 0) {
856
+ retVal = list.pop();
857
+ this.persist(key);
858
+ }
667
859
  }
668
860
  return this._handleCallback(callback, retVal);
669
861
  }
@@ -681,8 +873,8 @@ class MemoryCache extends events.EventEmitter {
681
873
  const retVal = [];
682
874
  if (this._hasKey(key)) {
683
875
  this._testType(key, 'list', true, callback);
684
- const val = this._getKey(key);
685
- const length = val.length;
876
+ const list = this._getKey(key);
877
+ const length = list.length;
686
878
  if (stop < 0) {
687
879
  stop = length + stop;
688
880
  }
@@ -696,9 +888,8 @@ class MemoryCache extends events.EventEmitter {
696
888
  stop = length - 1;
697
889
  }
698
890
  if (stop >= 0 && stop >= start) {
699
- const size = stop - start + 1;
700
- for (let itr = start; itr < size; itr++) {
701
- retVal.push(val[itr]);
891
+ for (let itr = start; itr <= stop; itr++) {
892
+ retVal.push(list[itr]);
702
893
  }
703
894
  }
704
895
  }
@@ -722,7 +913,7 @@ class MemoryCache extends events.EventEmitter {
722
913
  this._testType(key, 'set', true, callback);
723
914
  }
724
915
  else {
725
- this.cache[key] = this._makeKey([], 'set');
916
+ this.cache.set(key, this._makeKey([], 'set'));
726
917
  }
727
918
  const val = this._getKey(key);
728
919
  const length = val.length;
@@ -794,19 +985,29 @@ class MemoryCache extends events.EventEmitter {
794
985
  * @memberof MemoryCache
795
986
  */
796
987
  spop(key, count, callback) {
797
- let retVal = null;
988
+ let retVal = [];
798
989
  count = count || 1;
799
- if (isNaN(count)) {
800
- return this._handleCallback(callback, null, messages.noint);
990
+ if (typeof count === 'function') {
991
+ callback = count;
992
+ count = 1;
801
993
  }
802
994
  if (this._hasKey(key)) {
803
- retVal = [];
804
995
  this._testType(key, 'set', true, callback);
805
996
  const val = this._getKey(key);
806
- const length = val.length;
807
- count = count > length ? length : count;
808
- for (let itr = 0; itr < count; itr++) {
809
- retVal.push(val.pop());
997
+ const keys = Object.keys(val);
998
+ const keysLength = keys.length;
999
+ if (keysLength) {
1000
+ if (count >= keysLength) {
1001
+ retVal = keys;
1002
+ this.del(key);
1003
+ }
1004
+ else {
1005
+ for (let itr = 0; itr < count; itr++) {
1006
+ const randomNum = Math.floor(Math.random() * keys.length);
1007
+ retVal.push(keys[randomNum]);
1008
+ this.srem(key, keys[randomNum]);
1009
+ }
1010
+ }
810
1011
  }
811
1012
  }
812
1013
  return this._handleCallback(callback, retVal);
@@ -873,14 +1074,14 @@ class MemoryCache extends events.EventEmitter {
873
1074
  discard(callback, silent) {
874
1075
  // Clear the queue mode, drain the queue, empty the watch list
875
1076
  if (this.multiMode) {
876
- this.cache = this.databases[this.currentDBIndex];
1077
+ this.cache = this.databases.get(this.currentDBIndex);
877
1078
  this.multiMode = false;
878
1079
  this.responseMessages = [];
879
1080
  }
880
- else if (!silent) {
881
- return this._handleCallback(callback, null, messages.nomultidiscard);
1081
+ if (!silent) {
1082
+ return this._handleCallback(callback, messages.ok);
882
1083
  }
883
- return this._handleCallback(callback, messages.ok);
1084
+ return null;
884
1085
  }
885
1086
  // ---------------------------------------
886
1087
  // ## Internal - Key ##
@@ -893,11 +1094,12 @@ class MemoryCache extends events.EventEmitter {
893
1094
  * @returns {*}
894
1095
  * @memberof MemoryCache
895
1096
  */
896
- pttl(key, callback) {
1097
+ pttl(key, _callback) {
1098
+ var _a;
897
1099
  let retVal = -2;
898
1100
  if (this._hasKey(key)) {
899
- if (!lodash.isNil(this.cache[key].timeout)) {
900
- retVal = this.cache[key].timeout - Date.now();
1101
+ if (!lodash.isNil((_a = this.cache.get(key)) === null || _a === void 0 ? void 0 : _a.timeout)) {
1102
+ retVal = this.cache.get(key).timeout - Date.now();
901
1103
  // Prevent unexpected errors if the actual ttl just happens to be -2 or -1
902
1104
  if (retVal < 0 && retVal > -3) {
903
1105
  retVal = -3;
@@ -907,7 +1109,7 @@ class MemoryCache extends events.EventEmitter {
907
1109
  retVal = -1;
908
1110
  }
909
1111
  }
910
- return this._handleCallback(callback, retVal);
1112
+ return retVal;
911
1113
  }
912
1114
  /**
913
1115
  *
@@ -922,7 +1124,7 @@ class MemoryCache extends events.EventEmitter {
922
1124
  let retVal = 0;
923
1125
  if (this._hasKey(key)) {
924
1126
  if (!lodash.isNil(this._key(key).timeout)) {
925
- this._key(key).timeout = null;
1127
+ this.cache.set(key, { ...this.cache.get(key), timeout: null });
926
1128
  retVal = 1;
927
1129
  }
928
1130
  }
@@ -937,7 +1139,7 @@ class MemoryCache extends events.EventEmitter {
937
1139
  * @memberof MemoryCache
938
1140
  */
939
1141
  _hasKey(key) {
940
- return this.cache.hasOwnProperty(key);
1142
+ return this.cache.has(key);
941
1143
  }
942
1144
  /**
943
1145
  *
@@ -961,8 +1163,8 @@ class MemoryCache extends events.EventEmitter {
961
1163
  * @memberof MemoryCache
962
1164
  */
963
1165
  _key(key) {
964
- this.cache[key].lastAccess = Date.now();
965
- return this.cache[key];
1166
+ this.cache.get(key).lastAccess = Date.now();
1167
+ return this.cache.get(key);
966
1168
  }
967
1169
  /**
968
1170
  *
@@ -987,7 +1189,7 @@ class MemoryCache extends events.EventEmitter {
987
1189
  }
988
1190
  }
989
1191
  else {
990
- this.cache[key] = this._makeKey('0', 'string');
1192
+ this.cache.set(key, this._makeKey('0', 'string'));
991
1193
  }
992
1194
  const val = keyValue + amount;
993
1195
  this._setKey(key, val.toString());
@@ -1040,8 +1242,7 @@ class MemoryCache extends events.EventEmitter {
1040
1242
  * @memberof MemoryCache
1041
1243
  */
1042
1244
  _setKey(key, value) {
1043
- this.cache[key].value = value;
1044
- this.cache[key].lastAccess = Date.now();
1245
+ this.cache.set(key, { ...this.cache.get(key), value: value, lastAccess: Date.now() });
1045
1246
  }
1046
1247
  /**
1047
1248
  *
@@ -1069,7 +1270,7 @@ class MemoryCache extends events.EventEmitter {
1069
1270
  }
1070
1271
  }
1071
1272
  else {
1072
- this.cache[key] = this._makeKey({}, 'hash');
1273
+ this.cache.set(key, this._makeKey({}, 'hash'));
1073
1274
  }
1074
1275
  fieldValue = useFloat ? parseFloat(`${value}`) : parseInt(`${value}`);
1075
1276
  amount = useFloat ? parseFloat(`${amount}`) : parseInt(`${amount}`);
@@ -1106,7 +1307,8 @@ class MemoryCache extends events.EventEmitter {
1106
1307
  if (key && field) {
1107
1308
  const ky = this._getKey(key);
1108
1309
  if (ky) {
1109
- retVal = ky.hasOwnProperty(field);
1310
+ // 排除内部属性 _fieldTTL
1311
+ retVal = field !== '_fieldTTL' && ky.hasOwnProperty(field);
1110
1312
  }
1111
1313
  }
1112
1314
  return retVal;
@@ -1178,6 +1380,333 @@ class MemoryCache extends events.EventEmitter {
1178
1380
  }
1179
1381
  return;
1180
1382
  }
1383
+ /**
1384
+ * 字符串追加操作
1385
+ * @param key
1386
+ * @param value
1387
+ * @param callback
1388
+ */
1389
+ append(key, value, callback) {
1390
+ let retVal = 0;
1391
+ if (this._hasKey(key)) {
1392
+ this._testType(key, 'string', true, callback);
1393
+ const existingValue = this._getKey(key);
1394
+ const newValue = existingValue + value;
1395
+ this._setKey(key, newValue);
1396
+ retVal = newValue.length;
1397
+ }
1398
+ else {
1399
+ this.cache.set(key, this._makeKey(value, 'string'));
1400
+ retVal = value.length;
1401
+ }
1402
+ return this._handleCallback(callback, retVal);
1403
+ }
1404
+ /**
1405
+ * 获取字符串长度
1406
+ * @param key
1407
+ * @param callback
1408
+ */
1409
+ strlen(key, callback) {
1410
+ let retVal = 0;
1411
+ if (this._hasKey(key)) {
1412
+ this._testType(key, 'string', true, callback);
1413
+ retVal = this._getKey(key).length;
1414
+ }
1415
+ return this._handleCallback(callback, retVal);
1416
+ }
1417
+ /**
1418
+ * 获取子字符串
1419
+ * @param key
1420
+ * @param start
1421
+ * @param end
1422
+ * @param callback
1423
+ */
1424
+ getrange(key, start, end, callback) {
1425
+ let retVal = '';
1426
+ if (this._hasKey(key)) {
1427
+ this._testType(key, 'string', true, callback);
1428
+ const value = this._getKey(key);
1429
+ retVal = value.substring(start, end + 1);
1430
+ }
1431
+ return this._handleCallback(callback, retVal);
1432
+ }
1433
+ /**
1434
+ * 设置子字符串
1435
+ * @param key
1436
+ * @param offset
1437
+ * @param value
1438
+ * @param callback
1439
+ */
1440
+ setrange(key, offset, value, callback) {
1441
+ let retVal = 0;
1442
+ if (this._hasKey(key)) {
1443
+ this._testType(key, 'string', true, callback);
1444
+ const existingValue = this._getKey(key);
1445
+ const newValue = existingValue.substring(0, offset) + value + existingValue.substring(offset + value.length);
1446
+ this._setKey(key, newValue);
1447
+ retVal = newValue.length;
1448
+ }
1449
+ else {
1450
+ // 如果键不存在,创建一个足够长的字符串
1451
+ const newValue = ''.padEnd(offset, '\0') + value;
1452
+ this.cache.set(key, this._makeKey(newValue, 'string'));
1453
+ retVal = newValue.length;
1454
+ }
1455
+ return this._handleCallback(callback, retVal);
1456
+ }
1457
+ /**
1458
+ * 批量获取
1459
+ * @param keys
1460
+ * @param callback
1461
+ */
1462
+ mget(...keys) {
1463
+ const callback = this._retrieveCallback(keys);
1464
+ const retVal = [];
1465
+ for (const key of keys) {
1466
+ if (this._hasKey(key)) {
1467
+ this._testType(key, 'string', false, callback);
1468
+ retVal.push(this._getKey(key));
1469
+ }
1470
+ else {
1471
+ retVal.push(null);
1472
+ }
1473
+ }
1474
+ return this._handleCallback(callback, retVal);
1475
+ }
1476
+ /**
1477
+ * 批量设置
1478
+ * @param keyValuePairs
1479
+ * @param callback
1480
+ */
1481
+ mset(...keyValuePairs) {
1482
+ const callback = this._retrieveCallback(keyValuePairs);
1483
+ // 确保参数是偶数个
1484
+ if (keyValuePairs.length % 2 !== 0) {
1485
+ return this._handleCallback(callback, null, messages.wrongArgCount.replace('%0', 'mset'));
1486
+ }
1487
+ for (let i = 0; i < keyValuePairs.length; i += 2) {
1488
+ const key = keyValuePairs[i];
1489
+ const value = keyValuePairs[i + 1];
1490
+ this.cache.set(key, this._makeKey(value.toString(), 'string'));
1491
+ }
1492
+ return this._handleCallback(callback, messages.ok);
1493
+ }
1494
+ /**
1495
+ * 获取所有键
1496
+ * @param pattern
1497
+ * @param callback
1498
+ */
1499
+ keys(pattern = '*', callback) {
1500
+ const retVal = [];
1501
+ this.cache.forEach((_item, key) => {
1502
+ if (pattern === '*' || this.matchPattern(key, pattern)) {
1503
+ retVal.push(key);
1504
+ }
1505
+ });
1506
+ return this._handleCallback(callback, retVal);
1507
+ }
1508
+ /**
1509
+ * 简单的模式匹配
1510
+ * @param key
1511
+ * @param pattern
1512
+ */
1513
+ matchPattern(key, pattern) {
1514
+ if (pattern === '*')
1515
+ return true;
1516
+ // 转换glob模式为正则表达式
1517
+ const regexPattern = pattern
1518
+ .replace(/\*/g, '.*')
1519
+ .replace(/\?/g, '.')
1520
+ .replace(/\[([^\]]*)\]/g, '[$1]');
1521
+ const regex = new RegExp(`^${regexPattern}$`);
1522
+ return regex.test(key);
1523
+ }
1524
+ /**
1525
+ * 获取随机键
1526
+ * @param callback
1527
+ */
1528
+ randomkey(callback) {
1529
+ const keys = [];
1530
+ this.cache.forEach((_item, key) => {
1531
+ keys.push(key);
1532
+ });
1533
+ if (keys.length === 0) {
1534
+ return this._handleCallback(callback, null);
1535
+ }
1536
+ const randomIndex = Math.floor(Math.random() * keys.length);
1537
+ return this._handleCallback(callback, keys[randomIndex]);
1538
+ }
1539
+ /**
1540
+ * 重命名键
1541
+ * @param oldKey
1542
+ * @param newKey
1543
+ * @param callback
1544
+ */
1545
+ rename(oldKey, newKey, callback) {
1546
+ if (!this._hasKey(oldKey)) {
1547
+ return this._handleCallback(callback, null, messages.nokey);
1548
+ }
1549
+ const value = this.cache.get(oldKey);
1550
+ this.cache.set(newKey, value);
1551
+ this.cache.delete(oldKey);
1552
+ return this._handleCallback(callback, messages.ok);
1553
+ }
1554
+ /**
1555
+ * 安全重命名键(目标键不存在时才重命名)
1556
+ * @param oldKey
1557
+ * @param newKey
1558
+ * @param callback
1559
+ */
1560
+ renamenx(oldKey, newKey, callback) {
1561
+ if (!this._hasKey(oldKey)) {
1562
+ return this._handleCallback(callback, null, messages.nokey);
1563
+ }
1564
+ if (this._hasKey(newKey)) {
1565
+ return this._handleCallback(callback, 0);
1566
+ }
1567
+ const value = this.cache.get(oldKey);
1568
+ this.cache.set(newKey, value);
1569
+ this.cache.delete(oldKey);
1570
+ return this._handleCallback(callback, 1);
1571
+ }
1572
+ /**
1573
+ * 获取键的类型
1574
+ * @param key
1575
+ * @param callback
1576
+ */
1577
+ type(key, callback) {
1578
+ if (!this._hasKey(key)) {
1579
+ return this._handleCallback(callback, 'none');
1580
+ }
1581
+ const item = this.cache.get(key);
1582
+ return this._handleCallback(callback, item.type);
1583
+ }
1584
+ /**
1585
+ * 清空当前数据库
1586
+ * @param callback
1587
+ */
1588
+ flushdb(callback) {
1589
+ this.cache.clear();
1590
+ return this._handleCallback(callback, messages.ok);
1591
+ }
1592
+ /**
1593
+ * 清空所有数据库
1594
+ * @param callback
1595
+ */
1596
+ flushall(callback) {
1597
+ this.databases.clear();
1598
+ this.cache = this.createLRUCache();
1599
+ this.databases.set(this.currentDBIndex, this.cache);
1600
+ return this._handleCallback(callback, messages.ok);
1601
+ }
1602
+ /**
1603
+ * 获取数据库大小
1604
+ * @param callback
1605
+ */
1606
+ dbsize(callback) {
1607
+ const size = this.cache.size || 0;
1608
+ return this._handleCallback(callback, size);
1609
+ }
1610
+ /**
1611
+ * Sorted Set基础实现 - 添加成员
1612
+ * @param key
1613
+ * @param score
1614
+ * @param member
1615
+ * @param callback
1616
+ */
1617
+ zadd(key, score, member, callback) {
1618
+ let retVal = 0;
1619
+ if (this._hasKey(key)) {
1620
+ this._testType(key, 'zset', true, callback);
1621
+ }
1622
+ else {
1623
+ this.cache.set(key, this._makeKey([], 'zset'));
1624
+ }
1625
+ const zset = this._getKey(key);
1626
+ const existing = zset.find((item) => item.member === member);
1627
+ if (existing) {
1628
+ existing.score = score;
1629
+ }
1630
+ else {
1631
+ zset.push({ score, member });
1632
+ retVal = 1;
1633
+ }
1634
+ // 按分数排序
1635
+ zset.sort((a, b) => a.score - b.score);
1636
+ this._setKey(key, zset);
1637
+ return this._handleCallback(callback, retVal);
1638
+ }
1639
+ /**
1640
+ * Sorted Set - 获取成员分数
1641
+ * @param key
1642
+ * @param member
1643
+ * @param callback
1644
+ */
1645
+ zscore(key, member, callback) {
1646
+ if (!this._hasKey(key)) {
1647
+ return this._handleCallback(callback, null);
1648
+ }
1649
+ this._testType(key, 'zset', true, callback);
1650
+ const zset = this._getKey(key);
1651
+ const item = zset.find((item) => item.member === member);
1652
+ return this._handleCallback(callback, item ? item.score : null);
1653
+ }
1654
+ /**
1655
+ * Sorted Set - 获取范围内的成员
1656
+ * @param key
1657
+ * @param start
1658
+ * @param stop
1659
+ * @param callback
1660
+ */
1661
+ zrange(key, start, stop, callback) {
1662
+ if (!this._hasKey(key)) {
1663
+ return this._handleCallback(callback, []);
1664
+ }
1665
+ this._testType(key, 'zset', true, callback);
1666
+ const zset = this._getKey(key);
1667
+ const length = zset.length;
1668
+ if (stop < 0) {
1669
+ stop = length + stop;
1670
+ }
1671
+ if (start < 0) {
1672
+ start = length + start;
1673
+ }
1674
+ const retVal = zset.slice(start, stop + 1).map((item) => item.member);
1675
+ return this._handleCallback(callback, retVal);
1676
+ }
1677
+ /**
1678
+ * Sorted Set - 获取成员数量
1679
+ * @param key
1680
+ * @param callback
1681
+ */
1682
+ zcard(key, callback) {
1683
+ if (!this._hasKey(key)) {
1684
+ return this._handleCallback(callback, 0);
1685
+ }
1686
+ this._testType(key, 'zset', true, callback);
1687
+ const zset = this._getKey(key);
1688
+ return this._handleCallback(callback, zset.length);
1689
+ }
1690
+ /**
1691
+ * Sorted Set - 删除成员
1692
+ * @param key
1693
+ * @param member
1694
+ * @param callback
1695
+ */
1696
+ zrem(key, member, callback) {
1697
+ let retVal = 0;
1698
+ if (this._hasKey(key)) {
1699
+ this._testType(key, 'zset', true, callback);
1700
+ const zset = this._getKey(key);
1701
+ const index = zset.findIndex((item) => item.member === member);
1702
+ if (index !== -1) {
1703
+ zset.splice(index, 1);
1704
+ retVal = 1;
1705
+ this._setKey(key, zset);
1706
+ }
1707
+ }
1708
+ return this._handleCallback(callback, retVal);
1709
+ }
1181
1710
  }
1182
1711
 
1183
1712
  /*
@@ -1188,17 +1717,27 @@ class MemoryCache extends events.EventEmitter {
1188
1717
  * @LastEditTime: 2023-02-18 23:52:47
1189
1718
  */
1190
1719
  class MemoryStore {
1191
- client;
1192
- pool;
1193
- options;
1194
1720
  /**
1195
1721
  * Creates an instance of MemoryStore.
1196
1722
  * @param {MemoryStoreOpt} options
1197
1723
  * @memberof MemoryStore
1198
1724
  */
1199
1725
  constructor(options) {
1200
- this.options = options;
1201
- this.client = null;
1726
+ this.options = {
1727
+ maxKeys: 1000,
1728
+ evictionPolicy: 'lru',
1729
+ ttlCheckInterval: 60000, // 1分钟
1730
+ ...options
1731
+ };
1732
+ // 直接创建 MemoryCache 实例
1733
+ this.client = new MemoryCache({
1734
+ database: this.options.db || 0,
1735
+ maxKeys: this.options.maxKeys,
1736
+ maxMemory: this.options.maxMemory,
1737
+ evictionPolicy: this.options.evictionPolicy,
1738
+ ttlCheckInterval: this.options.ttlCheckInterval
1739
+ });
1740
+ this.client.createClient();
1202
1741
  }
1203
1742
  /**
1204
1743
  * getConnection
@@ -1207,15 +1746,7 @@ class MemoryStore {
1207
1746
  * @memberof MemoryStore
1208
1747
  */
1209
1748
  getConnection() {
1210
- if (!this.pool) {
1211
- this.pool = new MemoryCache({
1212
- database: this.options.db
1213
- });
1214
- }
1215
- if (!this.client) {
1216
- this.client = this.pool.createClient();
1217
- this.client.status = "ready";
1218
- }
1749
+ // 直接返回 MemoryCache 实例
1219
1750
  return this.client;
1220
1751
  }
1221
1752
  /**
@@ -1225,8 +1756,10 @@ class MemoryStore {
1225
1756
  * @memberof MemoryStore
1226
1757
  */
1227
1758
  async close() {
1228
- this.client.end();
1229
- this.client = null;
1759
+ if (this.client) {
1760
+ this.client.end();
1761
+ this.client = null;
1762
+ }
1230
1763
  }
1231
1764
  /**
1232
1765
  * release
@@ -1236,7 +1769,8 @@ class MemoryStore {
1236
1769
  * @memberof MemoryStore
1237
1770
  */
1238
1771
  async release(_conn) {
1239
- return;
1772
+ // 对于内存存储,不需要释放连接
1773
+ return Promise.resolve();
1240
1774
  }
1241
1775
  /**
1242
1776
  * defineCommand
@@ -1269,6 +1803,20 @@ class MemoryStore {
1269
1803
  return -1;
1270
1804
  }
1271
1805
  }
1806
+ /**
1807
+ * 获取缓存统计信息
1808
+ */
1809
+ getStats() {
1810
+ if (this.client) {
1811
+ return this.client.info();
1812
+ }
1813
+ return {
1814
+ keys: 0,
1815
+ memory: 0,
1816
+ hits: 0,
1817
+ misses: 0
1818
+ };
1819
+ }
1272
1820
  }
1273
1821
 
1274
1822
  /*
@@ -1286,15 +1834,15 @@ class MemoryStore {
1286
1834
  * @class RedisStore
1287
1835
  */
1288
1836
  class RedisStore {
1289
- options;
1290
- pool;
1291
- client;
1292
1837
  /**
1293
1838
  * Creates an instance of RedisStore.
1294
1839
  * @param {RedisStoreOpt} options
1295
1840
  * @memberof RedisStore
1296
1841
  */
1297
1842
  constructor(options) {
1843
+ this.reconnectAttempts = 0;
1844
+ this.maxReconnectAttempts = 5;
1845
+ this.reconnectDelay = 1000; // 初始重连延迟1秒
1298
1846
  this.options = this.parseOpt(options);
1299
1847
  this.pool = null;
1300
1848
  }
@@ -1348,7 +1896,7 @@ class RedisStore {
1348
1896
  return opt;
1349
1897
  }
1350
1898
  /**
1351
- * create connection by native
1899
+ * create connection by native with improved error handling
1352
1900
  *
1353
1901
  * @param {number} [connNum=0]
1354
1902
  * @returns {*} {Promise<Redis | Cluster>}
@@ -1360,32 +1908,77 @@ class RedisStore {
1360
1908
  }
1361
1909
  const defer = helper__namespace.getDefer();
1362
1910
  let connection;
1363
- if (!helper__namespace.isEmpty(this.options.clusters)) {
1364
- connection = new ioredis.Cluster([...this.options.clusters], { redisOptions: this.options });
1365
- }
1366
- else {
1367
- connection = new ioredis.Redis(this.options);
1368
- }
1369
- // 去除prefix, 防止重复
1370
- this.options.keyPrefix = "";
1371
- connection.on('end', () => {
1372
- if (connNum < 3) {
1373
- connNum++;
1374
- defer.resolve(this.connect(connNum));
1911
+ try {
1912
+ if (!helper__namespace.isEmpty(this.options.clusters)) {
1913
+ connection = new ioredis.Cluster([...this.options.clusters], {
1914
+ redisOptions: this.options,
1915
+ enableOfflineQueue: false,
1916
+ retryDelayOnFailover: 100
1917
+ });
1375
1918
  }
1376
1919
  else {
1377
- this.close();
1378
- defer.reject('redis connection end');
1920
+ connection = new ioredis.Redis({
1921
+ ...this.options,
1922
+ enableOfflineQueue: false,
1923
+ retryDelayOnFailover: 100,
1924
+ lazyConnect: true
1925
+ });
1379
1926
  }
1380
- });
1381
- connection.on('ready', () => {
1382
- this.client = connection;
1383
- defer.resolve(connection);
1384
- });
1927
+ // 去除prefix, 防止重复
1928
+ this.options.keyPrefix = "";
1929
+ connection.on('error', (err) => {
1930
+ koatty_logger.DefaultLogger.Error(`Redis connection error: ${err.message}`);
1931
+ if (this.reconnectAttempts < this.maxReconnectAttempts) {
1932
+ this.scheduleReconnect(connNum);
1933
+ }
1934
+ else {
1935
+ defer.reject(new Error(`Redis connection failed after ${this.maxReconnectAttempts} attempts`));
1936
+ }
1937
+ });
1938
+ connection.on('end', () => {
1939
+ koatty_logger.DefaultLogger.Warn('Redis connection ended');
1940
+ if (connNum < 3) {
1941
+ this.scheduleReconnect(connNum + 1);
1942
+ }
1943
+ else {
1944
+ this.close();
1945
+ defer.reject(new Error('Redis connection end after 3 attempts'));
1946
+ }
1947
+ });
1948
+ connection.on('ready', () => {
1949
+ koatty_logger.DefaultLogger.Info('Redis connection ready');
1950
+ this.client = connection;
1951
+ this.reconnectAttempts = 0; // 重置重连计数
1952
+ defer.resolve(connection);
1953
+ });
1954
+ // 主动连接
1955
+ if (connection instanceof ioredis.Redis) {
1956
+ await connection.connect();
1957
+ }
1958
+ }
1959
+ catch (error) {
1960
+ koatty_logger.DefaultLogger.Error(`Failed to create Redis connection: ${error.message}`);
1961
+ defer.reject(error);
1962
+ }
1385
1963
  return defer.promise;
1386
1964
  }
1387
1965
  /**
1388
- * get connection from pool
1966
+ * 计划重连,使用指数退避策略
1967
+ * @private
1968
+ * @param {number} connNum
1969
+ */
1970
+ scheduleReconnect(connNum) {
1971
+ this.reconnectAttempts++;
1972
+ const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
1973
+ koatty_logger.DefaultLogger.Info(`Scheduling Redis reconnect attempt ${this.reconnectAttempts} in ${delay}ms`);
1974
+ setTimeout(() => {
1975
+ this.connect(connNum).catch(err => {
1976
+ koatty_logger.DefaultLogger.Error(`Reconnect attempt ${this.reconnectAttempts} failed: ${err.message}`);
1977
+ });
1978
+ }, delay);
1979
+ }
1980
+ /**
1981
+ * get connection from pool with improved configuration
1389
1982
  *
1390
1983
  * @returns {*}
1391
1984
  * @memberof RedisStore
@@ -1396,38 +1989,57 @@ class RedisStore {
1396
1989
  create: () => {
1397
1990
  return this.connect();
1398
1991
  },
1399
- destroy: () => {
1400
- return this.close();
1992
+ destroy: (resource) => {
1993
+ if (resource && typeof resource.disconnect === 'function') {
1994
+ resource.disconnect();
1995
+ }
1996
+ return Promise.resolve();
1401
1997
  },
1402
1998
  validate: (resource) => {
1403
- return Promise.resolve(resource.status === 'ready');
1999
+ return Promise.resolve(resource && resource.status === 'ready');
1404
2000
  }
1405
2001
  };
1406
2002
  this.pool = genericPool.createPool(factory, {
1407
2003
  max: this.options.poolSize || 10, // maximum size of the pool
1408
- min: 2 // minimum size of the pool
2004
+ min: Math.min(2, this.options.poolSize || 2), // minimum size of the pool
2005
+ acquireTimeoutMillis: 30000, // 30秒获取连接超时
2006
+ testOnBorrow: true, // 借用时测试连接
2007
+ evictionRunIntervalMillis: 30000, // 30秒检查一次空闲连接
2008
+ idleTimeoutMillis: 300000, // 5分钟空闲超时
2009
+ softIdleTimeoutMillis: 180000 // 3分钟软空闲超时
1409
2010
  });
1410
2011
  this.pool.on('factoryCreateError', function (err) {
1411
- koatty_logger.DefaultLogger.Error(err);
2012
+ koatty_logger.DefaultLogger.Error(`Redis pool create error: ${err.message}`);
1412
2013
  });
1413
2014
  this.pool.on('factoryDestroyError', function (err) {
1414
- koatty_logger.DefaultLogger.Error(err);
2015
+ koatty_logger.DefaultLogger.Error(`Redis pool destroy error: ${err.message}`);
1415
2016
  });
1416
2017
  }
1417
2018
  return this.pool.acquire();
1418
2019
  }
1419
2020
  /**
1420
- * close connection
2021
+ * close connection with proper cleanup
1421
2022
  *
1422
2023
  * @returns {*}
1423
2024
  * @memberof RedisStore
1424
2025
  */
1425
2026
  async close() {
1426
- this.client.disconnect();
1427
- this.client = null;
1428
- this.pool.destroy(this.client);
1429
- this.pool = null;
1430
- return;
2027
+ try {
2028
+ if (this.pool) {
2029
+ await this.pool.drain();
2030
+ await this.pool.clear();
2031
+ this.pool = null;
2032
+ }
2033
+ if (this.client) {
2034
+ if (typeof this.client.disconnect === 'function') {
2035
+ this.client.disconnect();
2036
+ }
2037
+ this.client = null;
2038
+ }
2039
+ }
2040
+ catch (error) {
2041
+ koatty_logger.DefaultLogger.Error(`Error closing Redis connection: ${error.message}`);
2042
+ }
1431
2043
  }
1432
2044
  /**
1433
2045
  *
@@ -1470,16 +2082,16 @@ class RedisStore {
1470
2082
  try {
1471
2083
  conn = await this.defineCommand("getCompare", {
1472
2084
  numberOfKeys: 1,
1473
- lua: `
1474
- local remote_value = redis.call("get",KEYS[1])
1475
-
1476
- if (not remote_value) then
1477
- return 0
1478
- elseif (remote_value == ARGV[1]) then
1479
- return redis.call("del",KEYS[1])
1480
- else
1481
- return -1
1482
- end
2085
+ lua: `
2086
+ local remote_value = redis.call("get",KEYS[1])
2087
+
2088
+ if (not remote_value) then
2089
+ return 0
2090
+ elseif (remote_value == ARGV[1]) then
2091
+ return redis.call("del",KEYS[1])
2092
+ else
2093
+ return -1
2094
+ end
1483
2095
  `
1484
2096
  });
1485
2097
  return conn.getCompare(name, value);
@@ -1517,9 +2129,6 @@ const defaultOptions = {
1517
2129
  * @class Store
1518
2130
  */
1519
2131
  class CacheStore {
1520
- client;
1521
- options;
1522
- static instance;
1523
2132
  /**
1524
2133
  * Creates an instance of CacheStore.
1525
2134
  * @param {StoreOptions} options
@@ -1539,17 +2148,70 @@ class CacheStore {
1539
2148
  }
1540
2149
  }
1541
2150
  /**
1542
- *
1543
- *
2151
+ * 获取单例实例,支持多配置实例管理
1544
2152
  * @static
1545
- * @returns
2153
+ * @param {StoreOptions} [options]
2154
+ * @param {string} [instanceKey='default'] 实例键名,用于区分不同配置的实例
2155
+ * @returns {CacheStore}
1546
2156
  */
1547
- static getInstance(options) {
1548
- if (this.instance) {
1549
- return this.instance;
2157
+ static getInstance(options, instanceKey = 'default') {
2158
+ // 生成配置哈希作为实例键的一部分
2159
+ const configHash = options ? this.generateConfigHash(options) : 'default';
2160
+ const fullKey = `${instanceKey}_${configHash}`;
2161
+ if (this.instances.has(fullKey)) {
2162
+ return this.instances.get(fullKey);
2163
+ }
2164
+ const instance = new CacheStore(options);
2165
+ this.instances.set(fullKey, instance);
2166
+ return instance;
2167
+ }
2168
+ /**
2169
+ * 生成配置哈希
2170
+ * @private
2171
+ * @static
2172
+ * @param {StoreOptions} options
2173
+ * @returns {string}
2174
+ */
2175
+ static generateConfigHash(options) {
2176
+ const configStr = JSON.stringify({
2177
+ type: options.type,
2178
+ host: options.host,
2179
+ port: options.port,
2180
+ db: options.db,
2181
+ keyPrefix: options.keyPrefix
2182
+ });
2183
+ // 简单哈希函数
2184
+ let hash = 0;
2185
+ for (let i = 0; i < configStr.length; i++) {
2186
+ const char = configStr.charCodeAt(i);
2187
+ hash = ((hash << 5) - hash) + char;
2188
+ hash = hash & hash; // 转换为32位整数
2189
+ }
2190
+ return Math.abs(hash).toString(36);
2191
+ }
2192
+ /**
2193
+ * 清理指定实例
2194
+ * @static
2195
+ * @param {string} [instanceKey='default']
2196
+ */
2197
+ static async clearInstance(instanceKey = 'default') {
2198
+ const keysToRemove = Array.from(this.instances.keys()).filter(key => key.startsWith(`${instanceKey}_`));
2199
+ for (const key of keysToRemove) {
2200
+ const instance = this.instances.get(key);
2201
+ if (instance) {
2202
+ await instance.close();
2203
+ this.instances.delete(key);
2204
+ }
1550
2205
  }
1551
- this.instance = new CacheStore(options);
1552
- return this.instance;
2206
+ }
2207
+ /**
2208
+ * 清理所有实例
2209
+ * @static
2210
+ */
2211
+ static async clearAllInstances() {
2212
+ const promises = Array.from(this.instances.values()).map(instance => instance.close());
2213
+ await Promise.all(promises);
2214
+ this.instances.clear();
1553
2215
  }
1554
2216
  getConnection() {
1555
2217
  return this.client.getConnection();
@@ -1560,11 +2222,13 @@ class CacheStore {
1560
2222
  release(conn) {
1561
2223
  return this.client.release(conn);
1562
2224
  }
1563
- defineCommand(name, scripts) {
1564
- return this.client.defineCommand(name, scripts);
1565
- }
1566
- getCompare(name, value) {
1567
- return this.client.getCompare(name, value);
2225
+ /**
2226
+ * 获取底层实现客户端,用于访问特定实现的功能
2227
+ * 例如:Redis的defineCommand, getCompare等
2228
+ * @returns {MemoryStore | RedisStore}
2229
+ */
2230
+ getRawClient() {
2231
+ return this.client;
1568
2232
  }
1569
2233
  /**
1570
2234
  * handler for native client
@@ -1582,10 +2246,22 @@ class CacheStore {
1582
2246
  return res;
1583
2247
  }
1584
2248
  catch (err) {
1585
- throw err;
2249
+ // 添加详细的错误信息
2250
+ const error = new Error(`Cache operation failed: ${name}(${data.slice(0, 2).join(', ')}${data.length > 2 ? '...' : ''}) - ${err.message}`);
2251
+ error.stack = err.stack;
2252
+ throw error;
1586
2253
  }
1587
2254
  finally {
1588
- this.release(conn);
2255
+ // 安全地释放连接
2256
+ if (conn) {
2257
+ try {
2258
+ await this.release(conn);
2259
+ }
2260
+ catch (releaseErr) {
2261
+ // 记录但不抛出,避免掩盖原始错误
2262
+ koatty_logger.DefaultLogger.Error(`Failed to release connection: ${releaseErr.message}`);
2263
+ }
2264
+ }
1589
2265
  }
1590
2266
  }
1591
2267
  /**
@@ -1624,13 +2300,6 @@ class CacheStore {
1624
2300
  expire(name, timeout) {
1625
2301
  return this.wrap('expire', [`${this.options.keyPrefix || ""}${name}`, timeout]);
1626
2302
  }
1627
- /**
1628
- * 删除key
1629
- * @param name
1630
- */
1631
- rm(name) {
1632
- return this.wrap('del', [`${this.options.keyPrefix || ""}${name}`]);
1633
- }
1634
2303
  /**
1635
2304
  *
1636
2305
  *
@@ -1668,8 +2337,8 @@ class CacheStore {
1668
2337
  * @param incr
1669
2338
  * @returns {*}
1670
2339
  */
1671
- incrby(name, incr = 1) {
1672
- return this.wrap('incrby', [`${this.options.keyPrefix || ""}${name}`, incr]);
2340
+ incrby(name, increment) {
2341
+ return this.wrap('incrby', [`${this.options.keyPrefix || ""}${name}`, increment]);
1673
2342
  }
1674
2343
  /**
1675
2344
  * 将 key 所储存的值减去减量
@@ -1677,8 +2346,8 @@ class CacheStore {
1677
2346
  * @param {any} name
1678
2347
  * @param {any} decr
1679
2348
  */
1680
- decrby(name, decr = 1) {
1681
- return this.wrap('decrby', [`${this.options.keyPrefix || ""}${name}`, decr]);
2349
+ decrby(name, decrement) {
2350
+ return this.wrap('decrby', [`${this.options.keyPrefix || ""}${name}`, decrement]);
1682
2351
  }
1683
2352
  /**
1684
2353
  * 哈希写入
@@ -1687,13 +2356,9 @@ class CacheStore {
1687
2356
  * @param value
1688
2357
  * @param timeout
1689
2358
  */
1690
- hset(name, key, value, timeout) {
1691
- const setP = [this.wrap('hset', [`${this.options.keyPrefix || ""}${name}`, key, value])];
1692
- if (typeof timeout !== 'number') {
1693
- timeout = this.options.timeout;
1694
- }
1695
- setP.push(this.set(`${name}:${key}_ex`, 1, timeout));
1696
- return Promise.all(setP);
2359
+ async hset(name, key, value, timeout) {
2360
+ // MemoryStore 直接支持字段级 TTL,传递 timeout 参数
2361
+ return this.wrap('hset', [`${this.options.keyPrefix || ""}${name}`, key, value, timeout]);
1697
2362
  }
1698
2363
  /**
1699
2364
  * 哈希获取
@@ -1702,15 +2367,8 @@ class CacheStore {
1702
2367
  * @returns {*}
1703
2368
  */
1704
2369
  hget(name, key) {
1705
- const setP = [this.get(`${name}:${key}_ex`)];
1706
- setP.push(this.wrap('hget', [`${this.options.keyPrefix || ""}${name}`, key]));
1707
- return Promise.all(setP).then(dataArr => {
1708
- if (dataArr[0] === null) {
1709
- this.hdel(name, key);
1710
- return null;
1711
- }
1712
- return dataArr[1] || null;
1713
- });
2370
+ // 直接调用底层实现,TTL 检查在 MemoryCache 中处理
2371
+ return this.wrap('hget', [`${this.options.keyPrefix || ""}${name}`, key]);
1714
2372
  }
1715
2373
  /**
1716
2374
  * 查看哈希表 hashKey 中,给定域 key 是否存在
@@ -1719,15 +2377,8 @@ class CacheStore {
1719
2377
  * @returns {*}
1720
2378
  */
1721
2379
  hexists(name, key) {
1722
- const setP = [this.get(`${name}:${key}_ex`)];
1723
- setP.push(this.wrap('hexists', [`${this.options.keyPrefix || ""}${name}`, key]));
1724
- return Promise.all(setP).then(dataArr => {
1725
- if (dataArr[0] === null) {
1726
- this.hdel(name, key);
1727
- return 0;
1728
- }
1729
- return dataArr[1] || 0;
1730
- });
2380
+ // 直接调用底层实现,TTL 检查在 MemoryCache 中处理
2381
+ return this.wrap('hexists', [`${this.options.keyPrefix || ""}${name}`, key]);
1731
2382
  }
1732
2383
  /**
1733
2384
  * 哈希删除
@@ -1735,10 +2386,9 @@ class CacheStore {
1735
2386
  * @param key
1736
2387
  * @returns {*}
1737
2388
  */
1738
- hdel(name, key) {
1739
- const setP = [this.del(`${name}:${key}_ex`)];
1740
- setP.push(this.wrap('hdel', [`${this.options.keyPrefix || ""}${name}`, key]));
1741
- return Promise.all(setP);
2389
+ async hdel(name, key) {
2390
+ // 直接调用底层实现,TTL 清理在 MemoryCache 中处理
2391
+ return this.wrap('hdel', [`${this.options.keyPrefix || ""}${name}`, key]);
1742
2392
  }
1743
2393
  /**
1744
2394
  * 返回哈希表 key 中域的数量
@@ -1752,11 +2402,11 @@ class CacheStore {
1752
2402
  * 给哈希表指定key,增加increment
1753
2403
  * @param name
1754
2404
  * @param key
1755
- * @param incr
2405
+ * @param increment
1756
2406
  * @returns {*}
1757
2407
  */
1758
- hincrby(name, key, incr = 1) {
1759
- return this.wrap('hincrby', [`${this.options.keyPrefix || ""}${name}`, key, incr]);
2408
+ hincrby(name, key, increment) {
2409
+ return this.wrap('hincrby', [`${this.options.keyPrefix || ""}${name}`, key, increment]);
1760
2410
  }
1761
2411
  /**
1762
2412
  * 返回哈希表所有key-value
@@ -1845,12 +2495,12 @@ class CacheStore {
1845
2495
  * @param timeout
1846
2496
  * @returns {*}
1847
2497
  */
1848
- sadd(name, value, timeout) {
1849
- const setP = [this.wrap('sadd', [`${this.options.keyPrefix || ""}${name}`, value])];
1850
- if (typeof timeout !== 'number') {
1851
- setP.push(this.wrap('expire', [`${this.options.keyPrefix || ""}${name}`, timeout]));
2498
+ async sadd(name, value, timeout) {
2499
+ const result = await this.wrap('sadd', [`${this.options.keyPrefix || ""}${name}`, value]);
2500
+ if (typeof timeout === 'number') {
2501
+ await this.wrap('expire', [`${this.options.keyPrefix || ""}${name}`, timeout]);
1852
2502
  }
1853
- return Promise.all(setP);
2503
+ return result;
1854
2504
  }
1855
2505
  /**
1856
2506
  * 返回集合的基数(集合中元素的数量)
@@ -1902,8 +2552,9 @@ class CacheStore {
1902
2552
  * @returns {*}
1903
2553
  */
1904
2554
  smove(source, destination, member) {
1905
- return this.wrap('smove', [`${this.options.keyPrefix || ""}${source}`, `${this.options.keyPrefix}${destination}`, member]);
2555
+ return this.wrap('smove', [`${this.options.keyPrefix || ""}${source}`, `${this.options.keyPrefix || ""}${destination}`, member]);
1906
2556
  }
1907
2557
  }
2558
+ CacheStore.instances = new Map();
1908
2559
 
1909
2560
  exports.CacheStore = CacheStore;