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/.rollup.config.js +59 -59
- package/CHANGELOG.md +94 -27
- package/LICENSE +29 -29
- package/README.md +373 -2
- package/dist/LICENSE +29 -29
- package/dist/README.md +373 -2
- package/dist/index.d.ts +318 -99
- package/dist/index.js +902 -251
- package/dist/index.mjs +902 -251
- package/dist/package.json +107 -103
- package/package.json +107 -103
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* @Author: richen
|
|
3
|
-
* @Date:
|
|
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 {
|
|
77
|
+
* @param {MemoryCacheOptions} options
|
|
84
78
|
* @memberof MemoryCache
|
|
85
79
|
*/
|
|
86
80
|
constructor(options) {
|
|
87
81
|
super();
|
|
88
|
-
this.
|
|
89
|
-
this.
|
|
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
|
|
102
|
-
|
|
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.
|
|
179
|
-
this.databases
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
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
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
778
|
+
this.cache.set(key, this._makeKey([], 'list'));
|
|
604
779
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
this.
|
|
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
|
|
615
|
-
* @param
|
|
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
|
|
797
|
+
this.cache.set(key, this._makeKey([], 'list'));
|
|
627
798
|
}
|
|
628
|
-
const
|
|
629
|
-
|
|
630
|
-
this._setKey(key,
|
|
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
|
|
647
|
-
|
|
648
|
-
|
|
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
|
|
665
|
-
|
|
666
|
-
|
|
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
|
|
685
|
-
const 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
|
-
|
|
700
|
-
|
|
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
|
|
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 =
|
|
988
|
+
let retVal = [];
|
|
798
989
|
count = count || 1;
|
|
799
|
-
if (
|
|
800
|
-
|
|
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
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
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
|
|
1077
|
+
this.cache = this.databases.get(this.currentDBIndex);
|
|
877
1078
|
this.multiMode = false;
|
|
878
1079
|
this.responseMessages = [];
|
|
879
1080
|
}
|
|
880
|
-
|
|
881
|
-
return this._handleCallback(callback,
|
|
1081
|
+
if (!silent) {
|
|
1082
|
+
return this._handleCallback(callback, messages.ok);
|
|
882
1083
|
}
|
|
883
|
-
return
|
|
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,
|
|
1097
|
+
pttl(key, _callback) {
|
|
1098
|
+
var _a;
|
|
897
1099
|
let retVal = -2;
|
|
898
1100
|
if (this._hasKey(key)) {
|
|
899
|
-
if (!lodash.isNil(this.cache
|
|
900
|
-
retVal = this.cache
|
|
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
|
|
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.
|
|
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.
|
|
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
|
|
965
|
-
return this.cache
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 =
|
|
1201
|
-
|
|
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
|
-
|
|
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
|
|
1229
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
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
|
-
|
|
1378
|
-
|
|
1920
|
+
connection = new ioredis.Redis({
|
|
1921
|
+
...this.options,
|
|
1922
|
+
enableOfflineQueue: false,
|
|
1923
|
+
retryDelayOnFailover: 100,
|
|
1924
|
+
lazyConnect: true
|
|
1925
|
+
});
|
|
1379
1926
|
}
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
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
|
-
* @
|
|
2153
|
+
* @param {StoreOptions} [options]
|
|
2154
|
+
* @param {string} [instanceKey='default'] 实例键名,用于区分不同配置的实例
|
|
2155
|
+
* @returns {CacheStore}
|
|
1546
2156
|
*/
|
|
1547
|
-
static getInstance(options) {
|
|
1548
|
-
|
|
1549
|
-
|
|
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
|
-
|
|
1552
|
-
|
|
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
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
1672
|
-
return this.wrap('incrby', [`${this.options.keyPrefix || ""}${name}`,
|
|
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,
|
|
1681
|
-
return this.wrap('decrby', [`${this.options.keyPrefix || ""}${name}`,
|
|
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
|
-
|
|
1692
|
-
|
|
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
|
-
|
|
1706
|
-
|
|
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
|
-
|
|
1723
|
-
|
|
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
|
-
|
|
1740
|
-
|
|
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
|
|
2405
|
+
* @param increment
|
|
1756
2406
|
* @returns {*}
|
|
1757
2407
|
*/
|
|
1758
|
-
hincrby(name, key,
|
|
1759
|
-
return this.wrap('hincrby', [`${this.options.keyPrefix || ""}${name}`, key,
|
|
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
|
|
1850
|
-
if (typeof timeout
|
|
1851
|
-
|
|
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
|
|
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;
|