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.mjs
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/
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
import { flatten, isNil, union, isUndefined } from 'lodash';
|
|
9
9
|
import * as helper from 'koatty_lib';
|
|
10
10
|
import { EventEmitter } from 'events';
|
|
11
|
+
import { LRUCache } from 'lru-cache';
|
|
12
|
+
import AsyncLock from 'async-lock';
|
|
11
13
|
import { DefaultLogger } from 'koatty_logger';
|
|
12
14
|
import { Cluster, Redis } from 'ioredis';
|
|
13
15
|
import genericPool from 'generic-pool';
|
|
@@ -49,26 +51,100 @@ var messages;
|
|
|
49
51
|
messages["mutuallyExclusiveNXXX"] = "ERR XX and NX options at the same time are not compatible";
|
|
50
52
|
})(messages || (messages = {}));
|
|
51
53
|
class MemoryCache extends EventEmitter {
|
|
52
|
-
databases = Object.create({});
|
|
53
|
-
options;
|
|
54
|
-
currentDBIndex;
|
|
55
|
-
connected;
|
|
56
|
-
lastSave;
|
|
57
|
-
multiMode;
|
|
58
|
-
cache;
|
|
59
|
-
responseMessages;
|
|
60
54
|
/**
|
|
61
55
|
* Creates an instance of MemoryCache.
|
|
62
|
-
* @param {
|
|
56
|
+
* @param {MemoryCacheOptions} options
|
|
63
57
|
* @memberof MemoryCache
|
|
64
58
|
*/
|
|
65
59
|
constructor(options) {
|
|
66
60
|
super();
|
|
67
|
-
this.
|
|
68
|
-
this.
|
|
61
|
+
this.databases = new Map();
|
|
62
|
+
this.ttlCheckTimer = null;
|
|
63
|
+
this.options = {
|
|
64
|
+
database: 0,
|
|
65
|
+
maxKeys: 1000,
|
|
66
|
+
evictionPolicy: 'lru',
|
|
67
|
+
ttlCheckInterval: 60000, // 1分钟检查一次过期键
|
|
68
|
+
maxAge: 1000 * 60 * 60, // 默认1小时过期
|
|
69
|
+
...options
|
|
70
|
+
};
|
|
71
|
+
this.currentDBIndex = options.database || 0;
|
|
69
72
|
this.connected = false;
|
|
70
73
|
this.lastSave = Date.now();
|
|
71
74
|
this.multiMode = false;
|
|
75
|
+
this.responseMessages = [];
|
|
76
|
+
this.lock = new AsyncLock();
|
|
77
|
+
// 初始化数据库和缓存
|
|
78
|
+
if (!this.databases.has(this.currentDBIndex)) {
|
|
79
|
+
this.databases.set(this.currentDBIndex, this.createLRUCache());
|
|
80
|
+
}
|
|
81
|
+
this.cache = this.databases.get(this.currentDBIndex);
|
|
82
|
+
// 启动TTL检查定时器
|
|
83
|
+
this.startTTLCheck();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 创建LRU缓存实例
|
|
87
|
+
*/
|
|
88
|
+
createLRUCache() {
|
|
89
|
+
return new LRUCache({
|
|
90
|
+
max: this.options.maxKeys || 1000,
|
|
91
|
+
ttl: this.options.maxAge || 1000 * 60 * 60, // 1小时默认
|
|
92
|
+
updateAgeOnGet: true, // 访问时更新age
|
|
93
|
+
dispose: (value, key, reason) => {
|
|
94
|
+
// 键被淘汰时的回调 - 直接使用lru-cache的事件机制
|
|
95
|
+
this.emit('evict', key, value, reason);
|
|
96
|
+
},
|
|
97
|
+
onInsert: (value, key) => {
|
|
98
|
+
// 键被插入时的回调
|
|
99
|
+
this.emit('insert', key, value);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 启动TTL检查定时器
|
|
105
|
+
*/
|
|
106
|
+
startTTLCheck() {
|
|
107
|
+
if (this.ttlCheckTimer) {
|
|
108
|
+
clearInterval(this.ttlCheckTimer);
|
|
109
|
+
}
|
|
110
|
+
this.ttlCheckTimer = setInterval(() => {
|
|
111
|
+
this.cleanExpiredKeys();
|
|
112
|
+
}, this.options.ttlCheckInterval || 60000);
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* 清理过期键
|
|
116
|
+
*/
|
|
117
|
+
cleanExpiredKeys() {
|
|
118
|
+
for (const [_dbIndex, cache] of this.databases) {
|
|
119
|
+
const keysToDelete = [];
|
|
120
|
+
cache.forEach((item, key) => {
|
|
121
|
+
if (item.timeout && item.timeout <= Date.now()) {
|
|
122
|
+
keysToDelete.push(key);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
keysToDelete.forEach(key => {
|
|
126
|
+
cache.delete(key);
|
|
127
|
+
this.emit('expire', key);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 停止TTL检查
|
|
133
|
+
*/
|
|
134
|
+
stopTTLCheck() {
|
|
135
|
+
if (this.ttlCheckTimer) {
|
|
136
|
+
clearInterval(this.ttlCheckTimer);
|
|
137
|
+
this.ttlCheckTimer = null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* 清理所有资源
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
cleanup() {
|
|
145
|
+
this.stopTTLCheck();
|
|
146
|
+
this.databases.clear();
|
|
147
|
+
this.removeAllListeners();
|
|
72
148
|
}
|
|
73
149
|
/**
|
|
74
150
|
*
|
|
@@ -77,8 +153,10 @@ class MemoryCache extends EventEmitter {
|
|
|
77
153
|
* @memberof MemoryCache
|
|
78
154
|
*/
|
|
79
155
|
createClient() {
|
|
80
|
-
this.databases
|
|
81
|
-
|
|
156
|
+
if (!this.databases.has(this.options.database)) {
|
|
157
|
+
this.databases.set(this.options.database, this.createLRUCache());
|
|
158
|
+
}
|
|
159
|
+
this.cache = this.databases.get(this.options.database);
|
|
82
160
|
this.connected = true;
|
|
83
161
|
// exit multi mode if we are in it
|
|
84
162
|
this.discard(null, true);
|
|
@@ -93,6 +171,7 @@ class MemoryCache extends EventEmitter {
|
|
|
93
171
|
* @memberof MemoryCache
|
|
94
172
|
*/
|
|
95
173
|
quit() {
|
|
174
|
+
this.cleanup(); // 调用清理方法
|
|
96
175
|
this.connected = false;
|
|
97
176
|
// exit multi mode if we are in it
|
|
98
177
|
this.discard(null, true);
|
|
@@ -108,6 +187,40 @@ class MemoryCache extends EventEmitter {
|
|
|
108
187
|
end() {
|
|
109
188
|
return this.quit();
|
|
110
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* 获取缓存统计信息
|
|
192
|
+
*/
|
|
193
|
+
info() {
|
|
194
|
+
const stats = {
|
|
195
|
+
databases: this.databases.size,
|
|
196
|
+
currentDB: this.currentDBIndex,
|
|
197
|
+
keys: this.cache ? this.cache.length : 0,
|
|
198
|
+
maxKeys: this.options.maxKeys,
|
|
199
|
+
hits: 0,
|
|
200
|
+
misses: 0,
|
|
201
|
+
memory: this.getMemoryUsage()
|
|
202
|
+
};
|
|
203
|
+
// 如果缓存支持统计信息
|
|
204
|
+
if (this.cache && typeof this.cache.dump === 'function') {
|
|
205
|
+
const dump = this.cache.dump();
|
|
206
|
+
stats.keys = dump.length;
|
|
207
|
+
}
|
|
208
|
+
return stats;
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* 估算内存使用量
|
|
212
|
+
*/
|
|
213
|
+
getMemoryUsage() {
|
|
214
|
+
let totalSize = 0;
|
|
215
|
+
for (const [, cache] of this.databases) {
|
|
216
|
+
cache.forEach((item, key) => {
|
|
217
|
+
// 粗略估算:key长度 + JSON序列化后的大小
|
|
218
|
+
totalSize += key.length * 2; // Unicode字符占2字节
|
|
219
|
+
totalSize += JSON.stringify(item).length * 2;
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
return totalSize;
|
|
223
|
+
}
|
|
111
224
|
/**
|
|
112
225
|
*
|
|
113
226
|
*
|
|
@@ -154,12 +267,12 @@ class MemoryCache extends EventEmitter {
|
|
|
154
267
|
if (!helper.isNumber(dbIndex)) {
|
|
155
268
|
return this._handleCallback(callback, null, messages.invalidDBIndex);
|
|
156
269
|
}
|
|
157
|
-
if (!this.databases.
|
|
158
|
-
this.databases
|
|
270
|
+
if (!this.databases.has(dbIndex)) {
|
|
271
|
+
this.databases.set(dbIndex, this.createLRUCache());
|
|
159
272
|
}
|
|
160
273
|
this.multiMode = false;
|
|
161
274
|
this.currentDBIndex = dbIndex;
|
|
162
|
-
this.cache = this.databases
|
|
275
|
+
this.cache = this.databases.get(dbIndex);
|
|
163
276
|
return this._handleCallback(callback, messages.ok);
|
|
164
277
|
}
|
|
165
278
|
// ---------------------------------------
|
|
@@ -238,7 +351,7 @@ class MemoryCache extends EventEmitter {
|
|
|
238
351
|
else if (onlyexist) {
|
|
239
352
|
return this._handleCallback(callback, retVal);
|
|
240
353
|
}
|
|
241
|
-
this.cache
|
|
354
|
+
this.cache.set(key, this._makeKey(value.toString(), 'string', pttl));
|
|
242
355
|
return this._handleCallback(callback, messages.ok);
|
|
243
356
|
}
|
|
244
357
|
/**
|
|
@@ -268,7 +381,8 @@ class MemoryCache extends EventEmitter {
|
|
|
268
381
|
expire(key, seconds, callback) {
|
|
269
382
|
let retVal = 0;
|
|
270
383
|
if (this._hasKey(key)) {
|
|
271
|
-
|
|
384
|
+
const pttl = seconds * 1000;
|
|
385
|
+
this.cache.set(key, { ...this.cache.get(key), timeout: Date.now() + pttl });
|
|
272
386
|
retVal = 1;
|
|
273
387
|
}
|
|
274
388
|
return this._handleCallback(callback, retVal);
|
|
@@ -288,7 +402,7 @@ class MemoryCache extends EventEmitter {
|
|
|
288
402
|
for (let itr = 0; itr < keys.length; itr++) {
|
|
289
403
|
const key = keys[itr];
|
|
290
404
|
if (this._hasKey(key)) {
|
|
291
|
-
|
|
405
|
+
this.cache.delete(key);
|
|
292
406
|
retVal++;
|
|
293
407
|
}
|
|
294
408
|
}
|
|
@@ -321,14 +435,18 @@ class MemoryCache extends EventEmitter {
|
|
|
321
435
|
* @memberof MemoryCache
|
|
322
436
|
*/
|
|
323
437
|
incr(key, callback) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
438
|
+
// 使用锁保护原子操作
|
|
439
|
+
const lockKey = `incr:${key}`;
|
|
440
|
+
return this.lock.acquire(lockKey, () => {
|
|
441
|
+
let retVal = null;
|
|
442
|
+
try {
|
|
443
|
+
retVal = this._addToKey(key, 1);
|
|
444
|
+
}
|
|
445
|
+
catch (err) {
|
|
446
|
+
return this._handleCallback(callback, null, err);
|
|
447
|
+
}
|
|
448
|
+
return this._handleCallback(callback, retVal);
|
|
449
|
+
});
|
|
332
450
|
}
|
|
333
451
|
/**
|
|
334
452
|
*
|
|
@@ -340,14 +458,18 @@ class MemoryCache extends EventEmitter {
|
|
|
340
458
|
* @memberof MemoryCache
|
|
341
459
|
*/
|
|
342
460
|
incrby(key, amount, callback) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
461
|
+
// 使用锁保护原子操作
|
|
462
|
+
const lockKey = `incrby:${key}`;
|
|
463
|
+
return this.lock.acquire(lockKey, () => {
|
|
464
|
+
let retVal = null;
|
|
465
|
+
try {
|
|
466
|
+
retVal = this._addToKey(key, amount);
|
|
467
|
+
}
|
|
468
|
+
catch (err) {
|
|
469
|
+
return this._handleCallback(callback, null, err);
|
|
470
|
+
}
|
|
471
|
+
return this._handleCallback(callback, retVal);
|
|
472
|
+
});
|
|
351
473
|
}
|
|
352
474
|
/**
|
|
353
475
|
*
|
|
@@ -358,14 +480,18 @@ class MemoryCache extends EventEmitter {
|
|
|
358
480
|
* @memberof MemoryCache
|
|
359
481
|
*/
|
|
360
482
|
decr(key, callback) {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
483
|
+
// 使用锁保护原子操作
|
|
484
|
+
const lockKey = `decr:${key}`;
|
|
485
|
+
return this.lock.acquire(lockKey, () => {
|
|
486
|
+
let retVal = null;
|
|
487
|
+
try {
|
|
488
|
+
retVal = this._addToKey(key, -1);
|
|
489
|
+
}
|
|
490
|
+
catch (err) {
|
|
491
|
+
return this._handleCallback(callback, null, err);
|
|
492
|
+
}
|
|
493
|
+
return this._handleCallback(callback, retVal);
|
|
494
|
+
});
|
|
369
495
|
}
|
|
370
496
|
/**
|
|
371
497
|
*
|
|
@@ -377,30 +503,42 @@ class MemoryCache extends EventEmitter {
|
|
|
377
503
|
* @memberof MemoryCache
|
|
378
504
|
*/
|
|
379
505
|
decrby(key, amount, callback) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
506
|
+
// 使用锁保护原子操作
|
|
507
|
+
const lockKey = `decrby:${key}`;
|
|
508
|
+
return this.lock.acquire(lockKey, () => {
|
|
509
|
+
let retVal = null;
|
|
510
|
+
try {
|
|
511
|
+
retVal = this._addToKey(key, 0 - amount);
|
|
512
|
+
}
|
|
513
|
+
catch (err) {
|
|
514
|
+
return this._handleCallback(callback, null, err);
|
|
515
|
+
}
|
|
516
|
+
return this._handleCallback(callback, retVal);
|
|
517
|
+
});
|
|
388
518
|
}
|
|
389
519
|
// ---------------------------------------
|
|
390
520
|
// ## Hash ##
|
|
391
521
|
// ---------------------------------------
|
|
392
|
-
hset(key, field, value, callback) {
|
|
522
|
+
hset(key, field, value, timeout, callback) {
|
|
393
523
|
let retVal = 0;
|
|
394
524
|
if (this._hasKey(key)) {
|
|
395
525
|
this._testType(key, 'hash', true, callback);
|
|
396
526
|
}
|
|
397
527
|
else {
|
|
398
|
-
this.cache
|
|
528
|
+
this.cache.set(key, this._makeKey({}, 'hash'));
|
|
399
529
|
}
|
|
400
530
|
if (!this._hasField(key, field)) {
|
|
401
531
|
retVal = 1;
|
|
402
532
|
}
|
|
403
533
|
this._setField(key, field, value.toString());
|
|
534
|
+
// 如果指定了 timeout,存储字段级别的过期时间
|
|
535
|
+
if (typeof timeout === 'number') {
|
|
536
|
+
const hashObj = this.cache.get(key).value;
|
|
537
|
+
if (!hashObj._fieldTTL) {
|
|
538
|
+
hashObj._fieldTTL = {};
|
|
539
|
+
}
|
|
540
|
+
hashObj._fieldTTL[field] = Date.now() + (timeout * 1000);
|
|
541
|
+
}
|
|
404
542
|
this.persist(key);
|
|
405
543
|
return this._handleCallback(callback, retVal);
|
|
406
544
|
}
|
|
@@ -417,8 +555,17 @@ class MemoryCache extends EventEmitter {
|
|
|
417
555
|
let retVal = null;
|
|
418
556
|
if (this._hasKey(key)) {
|
|
419
557
|
this._testType(key, 'hash', true, callback);
|
|
558
|
+
// 检查字段级别的过期时间
|
|
559
|
+
const hashObj = this._getKey(key);
|
|
560
|
+
if (hashObj && hashObj._fieldTTL && hashObj._fieldTTL[field]) {
|
|
561
|
+
if (hashObj._fieldTTL[field] <= Date.now()) {
|
|
562
|
+
// 过期,删除字段
|
|
563
|
+
this.hdel(key, field);
|
|
564
|
+
return this._handleCallback(callback, null);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
420
567
|
if (this._hasField(key, field)) {
|
|
421
|
-
retVal =
|
|
568
|
+
retVal = hashObj[field];
|
|
422
569
|
}
|
|
423
570
|
}
|
|
424
571
|
return this._handleCallback(callback, retVal);
|
|
@@ -436,6 +583,15 @@ class MemoryCache extends EventEmitter {
|
|
|
436
583
|
let retVal = 0;
|
|
437
584
|
if (this._hasKey(key)) {
|
|
438
585
|
this._testType(key, 'hash', true, callback);
|
|
586
|
+
// 检查字段级别的过期时间
|
|
587
|
+
const hashObj = this._getKey(key);
|
|
588
|
+
if (hashObj && hashObj._fieldTTL && hashObj._fieldTTL[field]) {
|
|
589
|
+
if (hashObj._fieldTTL[field] <= Date.now()) {
|
|
590
|
+
// 过期,删除字段
|
|
591
|
+
this.hdel(key, field);
|
|
592
|
+
return this._handleCallback(callback, 0);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
439
595
|
if (this._hasField(key, field)) {
|
|
440
596
|
retVal = 1;
|
|
441
597
|
}
|
|
@@ -455,10 +611,15 @@ class MemoryCache extends EventEmitter {
|
|
|
455
611
|
const callback = this._retrieveCallback(fields);
|
|
456
612
|
if (this._hasKey(key)) {
|
|
457
613
|
this._testType(key, 'hash', true, callback);
|
|
614
|
+
const hashObj = this.cache.get(key).value;
|
|
458
615
|
for (let itr = 0; itr < fields.length; itr++) {
|
|
459
616
|
const field = fields[itr];
|
|
460
617
|
if (this._hasField(key, field)) {
|
|
461
|
-
delete
|
|
618
|
+
delete hashObj[field];
|
|
619
|
+
// 清理过期时间记录
|
|
620
|
+
if (hashObj._fieldTTL && hashObj._fieldTTL[field]) {
|
|
621
|
+
delete hashObj._fieldTTL[field];
|
|
622
|
+
}
|
|
462
623
|
retVal++;
|
|
463
624
|
}
|
|
464
625
|
}
|
|
@@ -488,14 +649,18 @@ class MemoryCache extends EventEmitter {
|
|
|
488
649
|
* @memberof MemoryCache
|
|
489
650
|
*/
|
|
490
651
|
hincrby(key, field, value, callback) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
652
|
+
// 使用锁保护原子操作,使用组合 key
|
|
653
|
+
const lockKey = `hincrby:${key}:${field}`;
|
|
654
|
+
return this.lock.acquire(lockKey, () => {
|
|
655
|
+
let retVal;
|
|
656
|
+
try {
|
|
657
|
+
retVal = this._addToField(key, field, value, false);
|
|
658
|
+
}
|
|
659
|
+
catch (err) {
|
|
660
|
+
return this._handleCallback(callback, null, err);
|
|
661
|
+
}
|
|
662
|
+
return this._handleCallback(callback, retVal);
|
|
663
|
+
});
|
|
499
664
|
}
|
|
500
665
|
/**
|
|
501
666
|
*
|
|
@@ -509,7 +674,11 @@ class MemoryCache extends EventEmitter {
|
|
|
509
674
|
let retVals = {};
|
|
510
675
|
if (this._hasKey(key)) {
|
|
511
676
|
this._testType(key, 'hash', true, callback);
|
|
512
|
-
|
|
677
|
+
const hashObj = this._getKey(key);
|
|
678
|
+
// 排除内部属性 _fieldTTL
|
|
679
|
+
retVals = Object.entries(hashObj)
|
|
680
|
+
.filter(([k]) => k !== '_fieldTTL')
|
|
681
|
+
.reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {});
|
|
513
682
|
}
|
|
514
683
|
return this._handleCallback(callback, retVals);
|
|
515
684
|
}
|
|
@@ -525,7 +694,9 @@ class MemoryCache extends EventEmitter {
|
|
|
525
694
|
let retVals = [];
|
|
526
695
|
if (this._hasKey(key)) {
|
|
527
696
|
this._testType(key, 'hash', true, callback);
|
|
528
|
-
|
|
697
|
+
const hashObj = this._getKey(key);
|
|
698
|
+
// 排除内部属性 _fieldTTL
|
|
699
|
+
retVals = Object.keys(hashObj).filter(k => k !== '_fieldTTL');
|
|
529
700
|
}
|
|
530
701
|
return this._handleCallback(callback, retVals);
|
|
531
702
|
}
|
|
@@ -541,7 +712,11 @@ class MemoryCache extends EventEmitter {
|
|
|
541
712
|
let retVals = [];
|
|
542
713
|
if (this._hasKey(key)) {
|
|
543
714
|
this._testType(key, 'hash', true, callback);
|
|
544
|
-
|
|
715
|
+
const hashObj = this._getKey(key);
|
|
716
|
+
// 排除内部属性 _fieldTTL 的值
|
|
717
|
+
retVals = Object.entries(hashObj)
|
|
718
|
+
.filter(([k]) => k !== '_fieldTTL')
|
|
719
|
+
.map(([, v]) => v);
|
|
545
720
|
}
|
|
546
721
|
return this._handleCallback(callback, retVals);
|
|
547
722
|
}
|
|
@@ -579,22 +754,18 @@ class MemoryCache extends EventEmitter {
|
|
|
579
754
|
this._testType(key, 'list', true, callback);
|
|
580
755
|
}
|
|
581
756
|
else {
|
|
582
|
-
this.cache
|
|
757
|
+
this.cache.set(key, this._makeKey([], 'list'));
|
|
583
758
|
}
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
this.
|
|
587
|
-
retVal = val.length;
|
|
759
|
+
this._getKey(key).push(value.toString());
|
|
760
|
+
retVal = this._getKey(key).length;
|
|
761
|
+
this.persist(key);
|
|
588
762
|
return this._handleCallback(callback, retVal);
|
|
589
763
|
}
|
|
590
764
|
/**
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
* @param
|
|
594
|
-
* @param
|
|
595
|
-
* @param {Function} [callback]
|
|
596
|
-
* @returns {*}
|
|
597
|
-
* @memberof MemoryCache
|
|
765
|
+
* List:从左侧推入
|
|
766
|
+
* @param key
|
|
767
|
+
* @param value
|
|
768
|
+
* @param callback
|
|
598
769
|
*/
|
|
599
770
|
lpush(key, value, callback) {
|
|
600
771
|
let retVal = 0;
|
|
@@ -602,14 +773,31 @@ class MemoryCache extends EventEmitter {
|
|
|
602
773
|
this._testType(key, 'list', true, callback);
|
|
603
774
|
}
|
|
604
775
|
else {
|
|
605
|
-
this.cache
|
|
776
|
+
this.cache.set(key, this._makeKey([], 'list'));
|
|
606
777
|
}
|
|
607
|
-
const
|
|
608
|
-
|
|
609
|
-
this._setKey(key,
|
|
610
|
-
retVal = val.length;
|
|
778
|
+
const list = this._getKey(key);
|
|
779
|
+
retVal = list.unshift(value);
|
|
780
|
+
this._setKey(key, list);
|
|
611
781
|
return this._handleCallback(callback, retVal);
|
|
612
782
|
}
|
|
783
|
+
/**
|
|
784
|
+
* List:获取指定索引的元素
|
|
785
|
+
* @param key
|
|
786
|
+
* @param index
|
|
787
|
+
* @param callback
|
|
788
|
+
*/
|
|
789
|
+
lindex(key, index, callback) {
|
|
790
|
+
if (!this._hasKey(key)) {
|
|
791
|
+
return this._handleCallback(callback, null);
|
|
792
|
+
}
|
|
793
|
+
this._testType(key, 'list', true, callback);
|
|
794
|
+
const list = this._getKey(key);
|
|
795
|
+
if (index < 0) {
|
|
796
|
+
index = list.length + index;
|
|
797
|
+
}
|
|
798
|
+
const value = index >= 0 && index < list.length ? list[index] : null;
|
|
799
|
+
return this._handleCallback(callback, value);
|
|
800
|
+
}
|
|
613
801
|
/**
|
|
614
802
|
*
|
|
615
803
|
*
|
|
@@ -622,9 +810,11 @@ class MemoryCache extends EventEmitter {
|
|
|
622
810
|
let retVal = null;
|
|
623
811
|
if (this._hasKey(key)) {
|
|
624
812
|
this._testType(key, 'list', true, callback);
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
813
|
+
const list = this._getKey(key);
|
|
814
|
+
if (list.length > 0) {
|
|
815
|
+
retVal = list.shift();
|
|
816
|
+
this.persist(key);
|
|
817
|
+
}
|
|
628
818
|
}
|
|
629
819
|
return this._handleCallback(callback, retVal);
|
|
630
820
|
}
|
|
@@ -640,9 +830,11 @@ class MemoryCache extends EventEmitter {
|
|
|
640
830
|
let retVal = null;
|
|
641
831
|
if (this._hasKey(key)) {
|
|
642
832
|
this._testType(key, 'list', true, callback);
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
833
|
+
const list = this._getKey(key);
|
|
834
|
+
if (list.length > 0) {
|
|
835
|
+
retVal = list.pop();
|
|
836
|
+
this.persist(key);
|
|
837
|
+
}
|
|
646
838
|
}
|
|
647
839
|
return this._handleCallback(callback, retVal);
|
|
648
840
|
}
|
|
@@ -660,8 +852,8 @@ class MemoryCache extends EventEmitter {
|
|
|
660
852
|
const retVal = [];
|
|
661
853
|
if (this._hasKey(key)) {
|
|
662
854
|
this._testType(key, 'list', true, callback);
|
|
663
|
-
const
|
|
664
|
-
const length =
|
|
855
|
+
const list = this._getKey(key);
|
|
856
|
+
const length = list.length;
|
|
665
857
|
if (stop < 0) {
|
|
666
858
|
stop = length + stop;
|
|
667
859
|
}
|
|
@@ -675,9 +867,8 @@ class MemoryCache extends EventEmitter {
|
|
|
675
867
|
stop = length - 1;
|
|
676
868
|
}
|
|
677
869
|
if (stop >= 0 && stop >= start) {
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
retVal.push(val[itr]);
|
|
870
|
+
for (let itr = start; itr <= stop; itr++) {
|
|
871
|
+
retVal.push(list[itr]);
|
|
681
872
|
}
|
|
682
873
|
}
|
|
683
874
|
}
|
|
@@ -701,7 +892,7 @@ class MemoryCache extends EventEmitter {
|
|
|
701
892
|
this._testType(key, 'set', true, callback);
|
|
702
893
|
}
|
|
703
894
|
else {
|
|
704
|
-
this.cache
|
|
895
|
+
this.cache.set(key, this._makeKey([], 'set'));
|
|
705
896
|
}
|
|
706
897
|
const val = this._getKey(key);
|
|
707
898
|
const length = val.length;
|
|
@@ -773,19 +964,29 @@ class MemoryCache extends EventEmitter {
|
|
|
773
964
|
* @memberof MemoryCache
|
|
774
965
|
*/
|
|
775
966
|
spop(key, count, callback) {
|
|
776
|
-
let retVal =
|
|
967
|
+
let retVal = [];
|
|
777
968
|
count = count || 1;
|
|
778
|
-
if (
|
|
779
|
-
|
|
969
|
+
if (typeof count === 'function') {
|
|
970
|
+
callback = count;
|
|
971
|
+
count = 1;
|
|
780
972
|
}
|
|
781
973
|
if (this._hasKey(key)) {
|
|
782
|
-
retVal = [];
|
|
783
974
|
this._testType(key, 'set', true, callback);
|
|
784
975
|
const val = this._getKey(key);
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
976
|
+
const keys = Object.keys(val);
|
|
977
|
+
const keysLength = keys.length;
|
|
978
|
+
if (keysLength) {
|
|
979
|
+
if (count >= keysLength) {
|
|
980
|
+
retVal = keys;
|
|
981
|
+
this.del(key);
|
|
982
|
+
}
|
|
983
|
+
else {
|
|
984
|
+
for (let itr = 0; itr < count; itr++) {
|
|
985
|
+
const randomNum = Math.floor(Math.random() * keys.length);
|
|
986
|
+
retVal.push(keys[randomNum]);
|
|
987
|
+
this.srem(key, keys[randomNum]);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
789
990
|
}
|
|
790
991
|
}
|
|
791
992
|
return this._handleCallback(callback, retVal);
|
|
@@ -852,14 +1053,14 @@ class MemoryCache extends EventEmitter {
|
|
|
852
1053
|
discard(callback, silent) {
|
|
853
1054
|
// Clear the queue mode, drain the queue, empty the watch list
|
|
854
1055
|
if (this.multiMode) {
|
|
855
|
-
this.cache = this.databases
|
|
1056
|
+
this.cache = this.databases.get(this.currentDBIndex);
|
|
856
1057
|
this.multiMode = false;
|
|
857
1058
|
this.responseMessages = [];
|
|
858
1059
|
}
|
|
859
|
-
|
|
860
|
-
return this._handleCallback(callback,
|
|
1060
|
+
if (!silent) {
|
|
1061
|
+
return this._handleCallback(callback, messages.ok);
|
|
861
1062
|
}
|
|
862
|
-
return
|
|
1063
|
+
return null;
|
|
863
1064
|
}
|
|
864
1065
|
// ---------------------------------------
|
|
865
1066
|
// ## Internal - Key ##
|
|
@@ -872,11 +1073,12 @@ class MemoryCache extends EventEmitter {
|
|
|
872
1073
|
* @returns {*}
|
|
873
1074
|
* @memberof MemoryCache
|
|
874
1075
|
*/
|
|
875
|
-
pttl(key,
|
|
1076
|
+
pttl(key, _callback) {
|
|
1077
|
+
var _a;
|
|
876
1078
|
let retVal = -2;
|
|
877
1079
|
if (this._hasKey(key)) {
|
|
878
|
-
if (!isNil(this.cache
|
|
879
|
-
retVal = this.cache
|
|
1080
|
+
if (!isNil((_a = this.cache.get(key)) === null || _a === void 0 ? void 0 : _a.timeout)) {
|
|
1081
|
+
retVal = this.cache.get(key).timeout - Date.now();
|
|
880
1082
|
// Prevent unexpected errors if the actual ttl just happens to be -2 or -1
|
|
881
1083
|
if (retVal < 0 && retVal > -3) {
|
|
882
1084
|
retVal = -3;
|
|
@@ -886,7 +1088,7 @@ class MemoryCache extends EventEmitter {
|
|
|
886
1088
|
retVal = -1;
|
|
887
1089
|
}
|
|
888
1090
|
}
|
|
889
|
-
return
|
|
1091
|
+
return retVal;
|
|
890
1092
|
}
|
|
891
1093
|
/**
|
|
892
1094
|
*
|
|
@@ -901,7 +1103,7 @@ class MemoryCache extends EventEmitter {
|
|
|
901
1103
|
let retVal = 0;
|
|
902
1104
|
if (this._hasKey(key)) {
|
|
903
1105
|
if (!isNil(this._key(key).timeout)) {
|
|
904
|
-
this.
|
|
1106
|
+
this.cache.set(key, { ...this.cache.get(key), timeout: null });
|
|
905
1107
|
retVal = 1;
|
|
906
1108
|
}
|
|
907
1109
|
}
|
|
@@ -916,7 +1118,7 @@ class MemoryCache extends EventEmitter {
|
|
|
916
1118
|
* @memberof MemoryCache
|
|
917
1119
|
*/
|
|
918
1120
|
_hasKey(key) {
|
|
919
|
-
return this.cache.
|
|
1121
|
+
return this.cache.has(key);
|
|
920
1122
|
}
|
|
921
1123
|
/**
|
|
922
1124
|
*
|
|
@@ -940,8 +1142,8 @@ class MemoryCache extends EventEmitter {
|
|
|
940
1142
|
* @memberof MemoryCache
|
|
941
1143
|
*/
|
|
942
1144
|
_key(key) {
|
|
943
|
-
this.cache
|
|
944
|
-
return this.cache
|
|
1145
|
+
this.cache.get(key).lastAccess = Date.now();
|
|
1146
|
+
return this.cache.get(key);
|
|
945
1147
|
}
|
|
946
1148
|
/**
|
|
947
1149
|
*
|
|
@@ -966,7 +1168,7 @@ class MemoryCache extends EventEmitter {
|
|
|
966
1168
|
}
|
|
967
1169
|
}
|
|
968
1170
|
else {
|
|
969
|
-
this.cache
|
|
1171
|
+
this.cache.set(key, this._makeKey('0', 'string'));
|
|
970
1172
|
}
|
|
971
1173
|
const val = keyValue + amount;
|
|
972
1174
|
this._setKey(key, val.toString());
|
|
@@ -1019,8 +1221,7 @@ class MemoryCache extends EventEmitter {
|
|
|
1019
1221
|
* @memberof MemoryCache
|
|
1020
1222
|
*/
|
|
1021
1223
|
_setKey(key, value) {
|
|
1022
|
-
this.cache
|
|
1023
|
-
this.cache[key].lastAccess = Date.now();
|
|
1224
|
+
this.cache.set(key, { ...this.cache.get(key), value: value, lastAccess: Date.now() });
|
|
1024
1225
|
}
|
|
1025
1226
|
/**
|
|
1026
1227
|
*
|
|
@@ -1048,7 +1249,7 @@ class MemoryCache extends EventEmitter {
|
|
|
1048
1249
|
}
|
|
1049
1250
|
}
|
|
1050
1251
|
else {
|
|
1051
|
-
this.cache
|
|
1252
|
+
this.cache.set(key, this._makeKey({}, 'hash'));
|
|
1052
1253
|
}
|
|
1053
1254
|
fieldValue = useFloat ? parseFloat(`${value}`) : parseInt(`${value}`);
|
|
1054
1255
|
amount = useFloat ? parseFloat(`${amount}`) : parseInt(`${amount}`);
|
|
@@ -1085,7 +1286,8 @@ class MemoryCache extends EventEmitter {
|
|
|
1085
1286
|
if (key && field) {
|
|
1086
1287
|
const ky = this._getKey(key);
|
|
1087
1288
|
if (ky) {
|
|
1088
|
-
|
|
1289
|
+
// 排除内部属性 _fieldTTL
|
|
1290
|
+
retVal = field !== '_fieldTTL' && ky.hasOwnProperty(field);
|
|
1089
1291
|
}
|
|
1090
1292
|
}
|
|
1091
1293
|
return retVal;
|
|
@@ -1157,6 +1359,333 @@ class MemoryCache extends EventEmitter {
|
|
|
1157
1359
|
}
|
|
1158
1360
|
return;
|
|
1159
1361
|
}
|
|
1362
|
+
/**
|
|
1363
|
+
* 字符串追加操作
|
|
1364
|
+
* @param key
|
|
1365
|
+
* @param value
|
|
1366
|
+
* @param callback
|
|
1367
|
+
*/
|
|
1368
|
+
append(key, value, callback) {
|
|
1369
|
+
let retVal = 0;
|
|
1370
|
+
if (this._hasKey(key)) {
|
|
1371
|
+
this._testType(key, 'string', true, callback);
|
|
1372
|
+
const existingValue = this._getKey(key);
|
|
1373
|
+
const newValue = existingValue + value;
|
|
1374
|
+
this._setKey(key, newValue);
|
|
1375
|
+
retVal = newValue.length;
|
|
1376
|
+
}
|
|
1377
|
+
else {
|
|
1378
|
+
this.cache.set(key, this._makeKey(value, 'string'));
|
|
1379
|
+
retVal = value.length;
|
|
1380
|
+
}
|
|
1381
|
+
return this._handleCallback(callback, retVal);
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* 获取字符串长度
|
|
1385
|
+
* @param key
|
|
1386
|
+
* @param callback
|
|
1387
|
+
*/
|
|
1388
|
+
strlen(key, callback) {
|
|
1389
|
+
let retVal = 0;
|
|
1390
|
+
if (this._hasKey(key)) {
|
|
1391
|
+
this._testType(key, 'string', true, callback);
|
|
1392
|
+
retVal = this._getKey(key).length;
|
|
1393
|
+
}
|
|
1394
|
+
return this._handleCallback(callback, retVal);
|
|
1395
|
+
}
|
|
1396
|
+
/**
|
|
1397
|
+
* 获取子字符串
|
|
1398
|
+
* @param key
|
|
1399
|
+
* @param start
|
|
1400
|
+
* @param end
|
|
1401
|
+
* @param callback
|
|
1402
|
+
*/
|
|
1403
|
+
getrange(key, start, end, callback) {
|
|
1404
|
+
let retVal = '';
|
|
1405
|
+
if (this._hasKey(key)) {
|
|
1406
|
+
this._testType(key, 'string', true, callback);
|
|
1407
|
+
const value = this._getKey(key);
|
|
1408
|
+
retVal = value.substring(start, end + 1);
|
|
1409
|
+
}
|
|
1410
|
+
return this._handleCallback(callback, retVal);
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* 设置子字符串
|
|
1414
|
+
* @param key
|
|
1415
|
+
* @param offset
|
|
1416
|
+
* @param value
|
|
1417
|
+
* @param callback
|
|
1418
|
+
*/
|
|
1419
|
+
setrange(key, offset, value, callback) {
|
|
1420
|
+
let retVal = 0;
|
|
1421
|
+
if (this._hasKey(key)) {
|
|
1422
|
+
this._testType(key, 'string', true, callback);
|
|
1423
|
+
const existingValue = this._getKey(key);
|
|
1424
|
+
const newValue = existingValue.substring(0, offset) + value + existingValue.substring(offset + value.length);
|
|
1425
|
+
this._setKey(key, newValue);
|
|
1426
|
+
retVal = newValue.length;
|
|
1427
|
+
}
|
|
1428
|
+
else {
|
|
1429
|
+
// 如果键不存在,创建一个足够长的字符串
|
|
1430
|
+
const newValue = ''.padEnd(offset, '\0') + value;
|
|
1431
|
+
this.cache.set(key, this._makeKey(newValue, 'string'));
|
|
1432
|
+
retVal = newValue.length;
|
|
1433
|
+
}
|
|
1434
|
+
return this._handleCallback(callback, retVal);
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* 批量获取
|
|
1438
|
+
* @param keys
|
|
1439
|
+
* @param callback
|
|
1440
|
+
*/
|
|
1441
|
+
mget(...keys) {
|
|
1442
|
+
const callback = this._retrieveCallback(keys);
|
|
1443
|
+
const retVal = [];
|
|
1444
|
+
for (const key of keys) {
|
|
1445
|
+
if (this._hasKey(key)) {
|
|
1446
|
+
this._testType(key, 'string', false, callback);
|
|
1447
|
+
retVal.push(this._getKey(key));
|
|
1448
|
+
}
|
|
1449
|
+
else {
|
|
1450
|
+
retVal.push(null);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
return this._handleCallback(callback, retVal);
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* 批量设置
|
|
1457
|
+
* @param keyValuePairs
|
|
1458
|
+
* @param callback
|
|
1459
|
+
*/
|
|
1460
|
+
mset(...keyValuePairs) {
|
|
1461
|
+
const callback = this._retrieveCallback(keyValuePairs);
|
|
1462
|
+
// 确保参数是偶数个
|
|
1463
|
+
if (keyValuePairs.length % 2 !== 0) {
|
|
1464
|
+
return this._handleCallback(callback, null, messages.wrongArgCount.replace('%0', 'mset'));
|
|
1465
|
+
}
|
|
1466
|
+
for (let i = 0; i < keyValuePairs.length; i += 2) {
|
|
1467
|
+
const key = keyValuePairs[i];
|
|
1468
|
+
const value = keyValuePairs[i + 1];
|
|
1469
|
+
this.cache.set(key, this._makeKey(value.toString(), 'string'));
|
|
1470
|
+
}
|
|
1471
|
+
return this._handleCallback(callback, messages.ok);
|
|
1472
|
+
}
|
|
1473
|
+
/**
|
|
1474
|
+
* 获取所有键
|
|
1475
|
+
* @param pattern
|
|
1476
|
+
* @param callback
|
|
1477
|
+
*/
|
|
1478
|
+
keys(pattern = '*', callback) {
|
|
1479
|
+
const retVal = [];
|
|
1480
|
+
this.cache.forEach((_item, key) => {
|
|
1481
|
+
if (pattern === '*' || this.matchPattern(key, pattern)) {
|
|
1482
|
+
retVal.push(key);
|
|
1483
|
+
}
|
|
1484
|
+
});
|
|
1485
|
+
return this._handleCallback(callback, retVal);
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* 简单的模式匹配
|
|
1489
|
+
* @param key
|
|
1490
|
+
* @param pattern
|
|
1491
|
+
*/
|
|
1492
|
+
matchPattern(key, pattern) {
|
|
1493
|
+
if (pattern === '*')
|
|
1494
|
+
return true;
|
|
1495
|
+
// 转换glob模式为正则表达式
|
|
1496
|
+
const regexPattern = pattern
|
|
1497
|
+
.replace(/\*/g, '.*')
|
|
1498
|
+
.replace(/\?/g, '.')
|
|
1499
|
+
.replace(/\[([^\]]*)\]/g, '[$1]');
|
|
1500
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
1501
|
+
return regex.test(key);
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* 获取随机键
|
|
1505
|
+
* @param callback
|
|
1506
|
+
*/
|
|
1507
|
+
randomkey(callback) {
|
|
1508
|
+
const keys = [];
|
|
1509
|
+
this.cache.forEach((_item, key) => {
|
|
1510
|
+
keys.push(key);
|
|
1511
|
+
});
|
|
1512
|
+
if (keys.length === 0) {
|
|
1513
|
+
return this._handleCallback(callback, null);
|
|
1514
|
+
}
|
|
1515
|
+
const randomIndex = Math.floor(Math.random() * keys.length);
|
|
1516
|
+
return this._handleCallback(callback, keys[randomIndex]);
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* 重命名键
|
|
1520
|
+
* @param oldKey
|
|
1521
|
+
* @param newKey
|
|
1522
|
+
* @param callback
|
|
1523
|
+
*/
|
|
1524
|
+
rename(oldKey, newKey, callback) {
|
|
1525
|
+
if (!this._hasKey(oldKey)) {
|
|
1526
|
+
return this._handleCallback(callback, null, messages.nokey);
|
|
1527
|
+
}
|
|
1528
|
+
const value = this.cache.get(oldKey);
|
|
1529
|
+
this.cache.set(newKey, value);
|
|
1530
|
+
this.cache.delete(oldKey);
|
|
1531
|
+
return this._handleCallback(callback, messages.ok);
|
|
1532
|
+
}
|
|
1533
|
+
/**
|
|
1534
|
+
* 安全重命名键(目标键不存在时才重命名)
|
|
1535
|
+
* @param oldKey
|
|
1536
|
+
* @param newKey
|
|
1537
|
+
* @param callback
|
|
1538
|
+
*/
|
|
1539
|
+
renamenx(oldKey, newKey, callback) {
|
|
1540
|
+
if (!this._hasKey(oldKey)) {
|
|
1541
|
+
return this._handleCallback(callback, null, messages.nokey);
|
|
1542
|
+
}
|
|
1543
|
+
if (this._hasKey(newKey)) {
|
|
1544
|
+
return this._handleCallback(callback, 0);
|
|
1545
|
+
}
|
|
1546
|
+
const value = this.cache.get(oldKey);
|
|
1547
|
+
this.cache.set(newKey, value);
|
|
1548
|
+
this.cache.delete(oldKey);
|
|
1549
|
+
return this._handleCallback(callback, 1);
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* 获取键的类型
|
|
1553
|
+
* @param key
|
|
1554
|
+
* @param callback
|
|
1555
|
+
*/
|
|
1556
|
+
type(key, callback) {
|
|
1557
|
+
if (!this._hasKey(key)) {
|
|
1558
|
+
return this._handleCallback(callback, 'none');
|
|
1559
|
+
}
|
|
1560
|
+
const item = this.cache.get(key);
|
|
1561
|
+
return this._handleCallback(callback, item.type);
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* 清空当前数据库
|
|
1565
|
+
* @param callback
|
|
1566
|
+
*/
|
|
1567
|
+
flushdb(callback) {
|
|
1568
|
+
this.cache.clear();
|
|
1569
|
+
return this._handleCallback(callback, messages.ok);
|
|
1570
|
+
}
|
|
1571
|
+
/**
|
|
1572
|
+
* 清空所有数据库
|
|
1573
|
+
* @param callback
|
|
1574
|
+
*/
|
|
1575
|
+
flushall(callback) {
|
|
1576
|
+
this.databases.clear();
|
|
1577
|
+
this.cache = this.createLRUCache();
|
|
1578
|
+
this.databases.set(this.currentDBIndex, this.cache);
|
|
1579
|
+
return this._handleCallback(callback, messages.ok);
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* 获取数据库大小
|
|
1583
|
+
* @param callback
|
|
1584
|
+
*/
|
|
1585
|
+
dbsize(callback) {
|
|
1586
|
+
const size = this.cache.size || 0;
|
|
1587
|
+
return this._handleCallback(callback, size);
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Sorted Set基础实现 - 添加成员
|
|
1591
|
+
* @param key
|
|
1592
|
+
* @param score
|
|
1593
|
+
* @param member
|
|
1594
|
+
* @param callback
|
|
1595
|
+
*/
|
|
1596
|
+
zadd(key, score, member, callback) {
|
|
1597
|
+
let retVal = 0;
|
|
1598
|
+
if (this._hasKey(key)) {
|
|
1599
|
+
this._testType(key, 'zset', true, callback);
|
|
1600
|
+
}
|
|
1601
|
+
else {
|
|
1602
|
+
this.cache.set(key, this._makeKey([], 'zset'));
|
|
1603
|
+
}
|
|
1604
|
+
const zset = this._getKey(key);
|
|
1605
|
+
const existing = zset.find((item) => item.member === member);
|
|
1606
|
+
if (existing) {
|
|
1607
|
+
existing.score = score;
|
|
1608
|
+
}
|
|
1609
|
+
else {
|
|
1610
|
+
zset.push({ score, member });
|
|
1611
|
+
retVal = 1;
|
|
1612
|
+
}
|
|
1613
|
+
// 按分数排序
|
|
1614
|
+
zset.sort((a, b) => a.score - b.score);
|
|
1615
|
+
this._setKey(key, zset);
|
|
1616
|
+
return this._handleCallback(callback, retVal);
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Sorted Set - 获取成员分数
|
|
1620
|
+
* @param key
|
|
1621
|
+
* @param member
|
|
1622
|
+
* @param callback
|
|
1623
|
+
*/
|
|
1624
|
+
zscore(key, member, callback) {
|
|
1625
|
+
if (!this._hasKey(key)) {
|
|
1626
|
+
return this._handleCallback(callback, null);
|
|
1627
|
+
}
|
|
1628
|
+
this._testType(key, 'zset', true, callback);
|
|
1629
|
+
const zset = this._getKey(key);
|
|
1630
|
+
const item = zset.find((item) => item.member === member);
|
|
1631
|
+
return this._handleCallback(callback, item ? item.score : null);
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* Sorted Set - 获取范围内的成员
|
|
1635
|
+
* @param key
|
|
1636
|
+
* @param start
|
|
1637
|
+
* @param stop
|
|
1638
|
+
* @param callback
|
|
1639
|
+
*/
|
|
1640
|
+
zrange(key, start, stop, callback) {
|
|
1641
|
+
if (!this._hasKey(key)) {
|
|
1642
|
+
return this._handleCallback(callback, []);
|
|
1643
|
+
}
|
|
1644
|
+
this._testType(key, 'zset', true, callback);
|
|
1645
|
+
const zset = this._getKey(key);
|
|
1646
|
+
const length = zset.length;
|
|
1647
|
+
if (stop < 0) {
|
|
1648
|
+
stop = length + stop;
|
|
1649
|
+
}
|
|
1650
|
+
if (start < 0) {
|
|
1651
|
+
start = length + start;
|
|
1652
|
+
}
|
|
1653
|
+
const retVal = zset.slice(start, stop + 1).map((item) => item.member);
|
|
1654
|
+
return this._handleCallback(callback, retVal);
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* Sorted Set - 获取成员数量
|
|
1658
|
+
* @param key
|
|
1659
|
+
* @param callback
|
|
1660
|
+
*/
|
|
1661
|
+
zcard(key, callback) {
|
|
1662
|
+
if (!this._hasKey(key)) {
|
|
1663
|
+
return this._handleCallback(callback, 0);
|
|
1664
|
+
}
|
|
1665
|
+
this._testType(key, 'zset', true, callback);
|
|
1666
|
+
const zset = this._getKey(key);
|
|
1667
|
+
return this._handleCallback(callback, zset.length);
|
|
1668
|
+
}
|
|
1669
|
+
/**
|
|
1670
|
+
* Sorted Set - 删除成员
|
|
1671
|
+
* @param key
|
|
1672
|
+
* @param member
|
|
1673
|
+
* @param callback
|
|
1674
|
+
*/
|
|
1675
|
+
zrem(key, member, callback) {
|
|
1676
|
+
let retVal = 0;
|
|
1677
|
+
if (this._hasKey(key)) {
|
|
1678
|
+
this._testType(key, 'zset', true, callback);
|
|
1679
|
+
const zset = this._getKey(key);
|
|
1680
|
+
const index = zset.findIndex((item) => item.member === member);
|
|
1681
|
+
if (index !== -1) {
|
|
1682
|
+
zset.splice(index, 1);
|
|
1683
|
+
retVal = 1;
|
|
1684
|
+
this._setKey(key, zset);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
return this._handleCallback(callback, retVal);
|
|
1688
|
+
}
|
|
1160
1689
|
}
|
|
1161
1690
|
|
|
1162
1691
|
/*
|
|
@@ -1167,17 +1696,27 @@ class MemoryCache extends EventEmitter {
|
|
|
1167
1696
|
* @LastEditTime: 2023-02-18 23:52:47
|
|
1168
1697
|
*/
|
|
1169
1698
|
class MemoryStore {
|
|
1170
|
-
client;
|
|
1171
|
-
pool;
|
|
1172
|
-
options;
|
|
1173
1699
|
/**
|
|
1174
1700
|
* Creates an instance of MemoryStore.
|
|
1175
1701
|
* @param {MemoryStoreOpt} options
|
|
1176
1702
|
* @memberof MemoryStore
|
|
1177
1703
|
*/
|
|
1178
1704
|
constructor(options) {
|
|
1179
|
-
this.options =
|
|
1180
|
-
|
|
1705
|
+
this.options = {
|
|
1706
|
+
maxKeys: 1000,
|
|
1707
|
+
evictionPolicy: 'lru',
|
|
1708
|
+
ttlCheckInterval: 60000, // 1分钟
|
|
1709
|
+
...options
|
|
1710
|
+
};
|
|
1711
|
+
// 直接创建 MemoryCache 实例
|
|
1712
|
+
this.client = new MemoryCache({
|
|
1713
|
+
database: this.options.db || 0,
|
|
1714
|
+
maxKeys: this.options.maxKeys,
|
|
1715
|
+
maxMemory: this.options.maxMemory,
|
|
1716
|
+
evictionPolicy: this.options.evictionPolicy,
|
|
1717
|
+
ttlCheckInterval: this.options.ttlCheckInterval
|
|
1718
|
+
});
|
|
1719
|
+
this.client.createClient();
|
|
1181
1720
|
}
|
|
1182
1721
|
/**
|
|
1183
1722
|
* getConnection
|
|
@@ -1186,15 +1725,7 @@ class MemoryStore {
|
|
|
1186
1725
|
* @memberof MemoryStore
|
|
1187
1726
|
*/
|
|
1188
1727
|
getConnection() {
|
|
1189
|
-
|
|
1190
|
-
this.pool = new MemoryCache({
|
|
1191
|
-
database: this.options.db
|
|
1192
|
-
});
|
|
1193
|
-
}
|
|
1194
|
-
if (!this.client) {
|
|
1195
|
-
this.client = this.pool.createClient();
|
|
1196
|
-
this.client.status = "ready";
|
|
1197
|
-
}
|
|
1728
|
+
// 直接返回 MemoryCache 实例
|
|
1198
1729
|
return this.client;
|
|
1199
1730
|
}
|
|
1200
1731
|
/**
|
|
@@ -1204,8 +1735,10 @@ class MemoryStore {
|
|
|
1204
1735
|
* @memberof MemoryStore
|
|
1205
1736
|
*/
|
|
1206
1737
|
async close() {
|
|
1207
|
-
this.client
|
|
1208
|
-
|
|
1738
|
+
if (this.client) {
|
|
1739
|
+
this.client.end();
|
|
1740
|
+
this.client = null;
|
|
1741
|
+
}
|
|
1209
1742
|
}
|
|
1210
1743
|
/**
|
|
1211
1744
|
* release
|
|
@@ -1215,7 +1748,8 @@ class MemoryStore {
|
|
|
1215
1748
|
* @memberof MemoryStore
|
|
1216
1749
|
*/
|
|
1217
1750
|
async release(_conn) {
|
|
1218
|
-
|
|
1751
|
+
// 对于内存存储,不需要释放连接
|
|
1752
|
+
return Promise.resolve();
|
|
1219
1753
|
}
|
|
1220
1754
|
/**
|
|
1221
1755
|
* defineCommand
|
|
@@ -1248,6 +1782,20 @@ class MemoryStore {
|
|
|
1248
1782
|
return -1;
|
|
1249
1783
|
}
|
|
1250
1784
|
}
|
|
1785
|
+
/**
|
|
1786
|
+
* 获取缓存统计信息
|
|
1787
|
+
*/
|
|
1788
|
+
getStats() {
|
|
1789
|
+
if (this.client) {
|
|
1790
|
+
return this.client.info();
|
|
1791
|
+
}
|
|
1792
|
+
return {
|
|
1793
|
+
keys: 0,
|
|
1794
|
+
memory: 0,
|
|
1795
|
+
hits: 0,
|
|
1796
|
+
misses: 0
|
|
1797
|
+
};
|
|
1798
|
+
}
|
|
1251
1799
|
}
|
|
1252
1800
|
|
|
1253
1801
|
/*
|
|
@@ -1265,15 +1813,15 @@ class MemoryStore {
|
|
|
1265
1813
|
* @class RedisStore
|
|
1266
1814
|
*/
|
|
1267
1815
|
class RedisStore {
|
|
1268
|
-
options;
|
|
1269
|
-
pool;
|
|
1270
|
-
client;
|
|
1271
1816
|
/**
|
|
1272
1817
|
* Creates an instance of RedisStore.
|
|
1273
1818
|
* @param {RedisStoreOpt} options
|
|
1274
1819
|
* @memberof RedisStore
|
|
1275
1820
|
*/
|
|
1276
1821
|
constructor(options) {
|
|
1822
|
+
this.reconnectAttempts = 0;
|
|
1823
|
+
this.maxReconnectAttempts = 5;
|
|
1824
|
+
this.reconnectDelay = 1000; // 初始重连延迟1秒
|
|
1277
1825
|
this.options = this.parseOpt(options);
|
|
1278
1826
|
this.pool = null;
|
|
1279
1827
|
}
|
|
@@ -1327,7 +1875,7 @@ class RedisStore {
|
|
|
1327
1875
|
return opt;
|
|
1328
1876
|
}
|
|
1329
1877
|
/**
|
|
1330
|
-
* create connection by native
|
|
1878
|
+
* create connection by native with improved error handling
|
|
1331
1879
|
*
|
|
1332
1880
|
* @param {number} [connNum=0]
|
|
1333
1881
|
* @returns {*} {Promise<Redis | Cluster>}
|
|
@@ -1339,32 +1887,77 @@ class RedisStore {
|
|
|
1339
1887
|
}
|
|
1340
1888
|
const defer = helper.getDefer();
|
|
1341
1889
|
let connection;
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
this.options.keyPrefix = "";
|
|
1350
|
-
connection.on('end', () => {
|
|
1351
|
-
if (connNum < 3) {
|
|
1352
|
-
connNum++;
|
|
1353
|
-
defer.resolve(this.connect(connNum));
|
|
1890
|
+
try {
|
|
1891
|
+
if (!helper.isEmpty(this.options.clusters)) {
|
|
1892
|
+
connection = new Cluster([...this.options.clusters], {
|
|
1893
|
+
redisOptions: this.options,
|
|
1894
|
+
enableOfflineQueue: false,
|
|
1895
|
+
retryDelayOnFailover: 100
|
|
1896
|
+
});
|
|
1354
1897
|
}
|
|
1355
1898
|
else {
|
|
1356
|
-
|
|
1357
|
-
|
|
1899
|
+
connection = new Redis({
|
|
1900
|
+
...this.options,
|
|
1901
|
+
enableOfflineQueue: false,
|
|
1902
|
+
retryDelayOnFailover: 100,
|
|
1903
|
+
lazyConnect: true
|
|
1904
|
+
});
|
|
1358
1905
|
}
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1906
|
+
// 去除prefix, 防止重复
|
|
1907
|
+
this.options.keyPrefix = "";
|
|
1908
|
+
connection.on('error', (err) => {
|
|
1909
|
+
DefaultLogger.Error(`Redis connection error: ${err.message}`);
|
|
1910
|
+
if (this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
1911
|
+
this.scheduleReconnect(connNum);
|
|
1912
|
+
}
|
|
1913
|
+
else {
|
|
1914
|
+
defer.reject(new Error(`Redis connection failed after ${this.maxReconnectAttempts} attempts`));
|
|
1915
|
+
}
|
|
1916
|
+
});
|
|
1917
|
+
connection.on('end', () => {
|
|
1918
|
+
DefaultLogger.Warn('Redis connection ended');
|
|
1919
|
+
if (connNum < 3) {
|
|
1920
|
+
this.scheduleReconnect(connNum + 1);
|
|
1921
|
+
}
|
|
1922
|
+
else {
|
|
1923
|
+
this.close();
|
|
1924
|
+
defer.reject(new Error('Redis connection end after 3 attempts'));
|
|
1925
|
+
}
|
|
1926
|
+
});
|
|
1927
|
+
connection.on('ready', () => {
|
|
1928
|
+
DefaultLogger.Info('Redis connection ready');
|
|
1929
|
+
this.client = connection;
|
|
1930
|
+
this.reconnectAttempts = 0; // 重置重连计数
|
|
1931
|
+
defer.resolve(connection);
|
|
1932
|
+
});
|
|
1933
|
+
// 主动连接
|
|
1934
|
+
if (connection instanceof Redis) {
|
|
1935
|
+
await connection.connect();
|
|
1936
|
+
}
|
|
1937
|
+
}
|
|
1938
|
+
catch (error) {
|
|
1939
|
+
DefaultLogger.Error(`Failed to create Redis connection: ${error.message}`);
|
|
1940
|
+
defer.reject(error);
|
|
1941
|
+
}
|
|
1364
1942
|
return defer.promise;
|
|
1365
1943
|
}
|
|
1366
1944
|
/**
|
|
1367
|
-
*
|
|
1945
|
+
* 计划重连,使用指数退避策略
|
|
1946
|
+
* @private
|
|
1947
|
+
* @param {number} connNum
|
|
1948
|
+
*/
|
|
1949
|
+
scheduleReconnect(connNum) {
|
|
1950
|
+
this.reconnectAttempts++;
|
|
1951
|
+
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
1952
|
+
DefaultLogger.Info(`Scheduling Redis reconnect attempt ${this.reconnectAttempts} in ${delay}ms`);
|
|
1953
|
+
setTimeout(() => {
|
|
1954
|
+
this.connect(connNum).catch(err => {
|
|
1955
|
+
DefaultLogger.Error(`Reconnect attempt ${this.reconnectAttempts} failed: ${err.message}`);
|
|
1956
|
+
});
|
|
1957
|
+
}, delay);
|
|
1958
|
+
}
|
|
1959
|
+
/**
|
|
1960
|
+
* get connection from pool with improved configuration
|
|
1368
1961
|
*
|
|
1369
1962
|
* @returns {*}
|
|
1370
1963
|
* @memberof RedisStore
|
|
@@ -1375,38 +1968,57 @@ class RedisStore {
|
|
|
1375
1968
|
create: () => {
|
|
1376
1969
|
return this.connect();
|
|
1377
1970
|
},
|
|
1378
|
-
destroy: () => {
|
|
1379
|
-
|
|
1971
|
+
destroy: (resource) => {
|
|
1972
|
+
if (resource && typeof resource.disconnect === 'function') {
|
|
1973
|
+
resource.disconnect();
|
|
1974
|
+
}
|
|
1975
|
+
return Promise.resolve();
|
|
1380
1976
|
},
|
|
1381
1977
|
validate: (resource) => {
|
|
1382
|
-
return Promise.resolve(resource.status === 'ready');
|
|
1978
|
+
return Promise.resolve(resource && resource.status === 'ready');
|
|
1383
1979
|
}
|
|
1384
1980
|
};
|
|
1385
1981
|
this.pool = genericPool.createPool(factory, {
|
|
1386
1982
|
max: this.options.poolSize || 10, // maximum size of the pool
|
|
1387
|
-
min: 2 // minimum size of the pool
|
|
1983
|
+
min: Math.min(2, this.options.poolSize || 2), // minimum size of the pool
|
|
1984
|
+
acquireTimeoutMillis: 30000, // 30秒获取连接超时
|
|
1985
|
+
testOnBorrow: true, // 借用时测试连接
|
|
1986
|
+
evictionRunIntervalMillis: 30000, // 30秒检查一次空闲连接
|
|
1987
|
+
idleTimeoutMillis: 300000, // 5分钟空闲超时
|
|
1988
|
+
softIdleTimeoutMillis: 180000 // 3分钟软空闲超时
|
|
1388
1989
|
});
|
|
1389
1990
|
this.pool.on('factoryCreateError', function (err) {
|
|
1390
|
-
DefaultLogger.Error(err);
|
|
1991
|
+
DefaultLogger.Error(`Redis pool create error: ${err.message}`);
|
|
1391
1992
|
});
|
|
1392
1993
|
this.pool.on('factoryDestroyError', function (err) {
|
|
1393
|
-
DefaultLogger.Error(err);
|
|
1994
|
+
DefaultLogger.Error(`Redis pool destroy error: ${err.message}`);
|
|
1394
1995
|
});
|
|
1395
1996
|
}
|
|
1396
1997
|
return this.pool.acquire();
|
|
1397
1998
|
}
|
|
1398
1999
|
/**
|
|
1399
|
-
* close connection
|
|
2000
|
+
* close connection with proper cleanup
|
|
1400
2001
|
*
|
|
1401
2002
|
* @returns {*}
|
|
1402
2003
|
* @memberof RedisStore
|
|
1403
2004
|
*/
|
|
1404
2005
|
async close() {
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
2006
|
+
try {
|
|
2007
|
+
if (this.pool) {
|
|
2008
|
+
await this.pool.drain();
|
|
2009
|
+
await this.pool.clear();
|
|
2010
|
+
this.pool = null;
|
|
2011
|
+
}
|
|
2012
|
+
if (this.client) {
|
|
2013
|
+
if (typeof this.client.disconnect === 'function') {
|
|
2014
|
+
this.client.disconnect();
|
|
2015
|
+
}
|
|
2016
|
+
this.client = null;
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
2019
|
+
catch (error) {
|
|
2020
|
+
DefaultLogger.Error(`Error closing Redis connection: ${error.message}`);
|
|
2021
|
+
}
|
|
1410
2022
|
}
|
|
1411
2023
|
/**
|
|
1412
2024
|
*
|
|
@@ -1449,16 +2061,16 @@ class RedisStore {
|
|
|
1449
2061
|
try {
|
|
1450
2062
|
conn = await this.defineCommand("getCompare", {
|
|
1451
2063
|
numberOfKeys: 1,
|
|
1452
|
-
lua: `
|
|
1453
|
-
local remote_value = redis.call("get",KEYS[1])
|
|
1454
|
-
|
|
1455
|
-
if (not remote_value) then
|
|
1456
|
-
return 0
|
|
1457
|
-
elseif (remote_value == ARGV[1]) then
|
|
1458
|
-
return redis.call("del",KEYS[1])
|
|
1459
|
-
else
|
|
1460
|
-
return -1
|
|
1461
|
-
end
|
|
2064
|
+
lua: `
|
|
2065
|
+
local remote_value = redis.call("get",KEYS[1])
|
|
2066
|
+
|
|
2067
|
+
if (not remote_value) then
|
|
2068
|
+
return 0
|
|
2069
|
+
elseif (remote_value == ARGV[1]) then
|
|
2070
|
+
return redis.call("del",KEYS[1])
|
|
2071
|
+
else
|
|
2072
|
+
return -1
|
|
2073
|
+
end
|
|
1462
2074
|
`
|
|
1463
2075
|
});
|
|
1464
2076
|
return conn.getCompare(name, value);
|
|
@@ -1496,9 +2108,6 @@ const defaultOptions = {
|
|
|
1496
2108
|
* @class Store
|
|
1497
2109
|
*/
|
|
1498
2110
|
class CacheStore {
|
|
1499
|
-
client;
|
|
1500
|
-
options;
|
|
1501
|
-
static instance;
|
|
1502
2111
|
/**
|
|
1503
2112
|
* Creates an instance of CacheStore.
|
|
1504
2113
|
* @param {StoreOptions} options
|
|
@@ -1518,17 +2127,70 @@ class CacheStore {
|
|
|
1518
2127
|
}
|
|
1519
2128
|
}
|
|
1520
2129
|
/**
|
|
1521
|
-
*
|
|
1522
|
-
*
|
|
2130
|
+
* 获取单例实例,支持多配置实例管理
|
|
1523
2131
|
* @static
|
|
1524
|
-
* @
|
|
2132
|
+
* @param {StoreOptions} [options]
|
|
2133
|
+
* @param {string} [instanceKey='default'] 实例键名,用于区分不同配置的实例
|
|
2134
|
+
* @returns {CacheStore}
|
|
1525
2135
|
*/
|
|
1526
|
-
static getInstance(options) {
|
|
1527
|
-
|
|
1528
|
-
|
|
2136
|
+
static getInstance(options, instanceKey = 'default') {
|
|
2137
|
+
// 生成配置哈希作为实例键的一部分
|
|
2138
|
+
const configHash = options ? this.generateConfigHash(options) : 'default';
|
|
2139
|
+
const fullKey = `${instanceKey}_${configHash}`;
|
|
2140
|
+
if (this.instances.has(fullKey)) {
|
|
2141
|
+
return this.instances.get(fullKey);
|
|
2142
|
+
}
|
|
2143
|
+
const instance = new CacheStore(options);
|
|
2144
|
+
this.instances.set(fullKey, instance);
|
|
2145
|
+
return instance;
|
|
2146
|
+
}
|
|
2147
|
+
/**
|
|
2148
|
+
* 生成配置哈希
|
|
2149
|
+
* @private
|
|
2150
|
+
* @static
|
|
2151
|
+
* @param {StoreOptions} options
|
|
2152
|
+
* @returns {string}
|
|
2153
|
+
*/
|
|
2154
|
+
static generateConfigHash(options) {
|
|
2155
|
+
const configStr = JSON.stringify({
|
|
2156
|
+
type: options.type,
|
|
2157
|
+
host: options.host,
|
|
2158
|
+
port: options.port,
|
|
2159
|
+
db: options.db,
|
|
2160
|
+
keyPrefix: options.keyPrefix
|
|
2161
|
+
});
|
|
2162
|
+
// 简单哈希函数
|
|
2163
|
+
let hash = 0;
|
|
2164
|
+
for (let i = 0; i < configStr.length; i++) {
|
|
2165
|
+
const char = configStr.charCodeAt(i);
|
|
2166
|
+
hash = ((hash << 5) - hash) + char;
|
|
2167
|
+
hash = hash & hash; // 转换为32位整数
|
|
2168
|
+
}
|
|
2169
|
+
return Math.abs(hash).toString(36);
|
|
2170
|
+
}
|
|
2171
|
+
/**
|
|
2172
|
+
* 清理指定实例
|
|
2173
|
+
* @static
|
|
2174
|
+
* @param {string} [instanceKey='default']
|
|
2175
|
+
*/
|
|
2176
|
+
static async clearInstance(instanceKey = 'default') {
|
|
2177
|
+
const keysToRemove = Array.from(this.instances.keys()).filter(key => key.startsWith(`${instanceKey}_`));
|
|
2178
|
+
for (const key of keysToRemove) {
|
|
2179
|
+
const instance = this.instances.get(key);
|
|
2180
|
+
if (instance) {
|
|
2181
|
+
await instance.close();
|
|
2182
|
+
this.instances.delete(key);
|
|
2183
|
+
}
|
|
1529
2184
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
2185
|
+
}
|
|
2186
|
+
/**
|
|
2187
|
+
* 清理所有实例
|
|
2188
|
+
* @static
|
|
2189
|
+
*/
|
|
2190
|
+
static async clearAllInstances() {
|
|
2191
|
+
const promises = Array.from(this.instances.values()).map(instance => instance.close());
|
|
2192
|
+
await Promise.all(promises);
|
|
2193
|
+
this.instances.clear();
|
|
1532
2194
|
}
|
|
1533
2195
|
getConnection() {
|
|
1534
2196
|
return this.client.getConnection();
|
|
@@ -1539,11 +2201,13 @@ class CacheStore {
|
|
|
1539
2201
|
release(conn) {
|
|
1540
2202
|
return this.client.release(conn);
|
|
1541
2203
|
}
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
2204
|
+
/**
|
|
2205
|
+
* 获取底层实现客户端,用于访问特定实现的功能
|
|
2206
|
+
* 例如:Redis的defineCommand, getCompare等
|
|
2207
|
+
* @returns {MemoryStore | RedisStore}
|
|
2208
|
+
*/
|
|
2209
|
+
getRawClient() {
|
|
2210
|
+
return this.client;
|
|
1547
2211
|
}
|
|
1548
2212
|
/**
|
|
1549
2213
|
* handler for native client
|
|
@@ -1561,10 +2225,22 @@ class CacheStore {
|
|
|
1561
2225
|
return res;
|
|
1562
2226
|
}
|
|
1563
2227
|
catch (err) {
|
|
1564
|
-
|
|
2228
|
+
// 添加详细的错误信息
|
|
2229
|
+
const error = new Error(`Cache operation failed: ${name}(${data.slice(0, 2).join(', ')}${data.length > 2 ? '...' : ''}) - ${err.message}`);
|
|
2230
|
+
error.stack = err.stack;
|
|
2231
|
+
throw error;
|
|
1565
2232
|
}
|
|
1566
2233
|
finally {
|
|
1567
|
-
|
|
2234
|
+
// 安全地释放连接
|
|
2235
|
+
if (conn) {
|
|
2236
|
+
try {
|
|
2237
|
+
await this.release(conn);
|
|
2238
|
+
}
|
|
2239
|
+
catch (releaseErr) {
|
|
2240
|
+
// 记录但不抛出,避免掩盖原始错误
|
|
2241
|
+
DefaultLogger.Error(`Failed to release connection: ${releaseErr.message}`);
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
1568
2244
|
}
|
|
1569
2245
|
}
|
|
1570
2246
|
/**
|
|
@@ -1603,13 +2279,6 @@ class CacheStore {
|
|
|
1603
2279
|
expire(name, timeout) {
|
|
1604
2280
|
return this.wrap('expire', [`${this.options.keyPrefix || ""}${name}`, timeout]);
|
|
1605
2281
|
}
|
|
1606
|
-
/**
|
|
1607
|
-
* 删除key
|
|
1608
|
-
* @param name
|
|
1609
|
-
*/
|
|
1610
|
-
rm(name) {
|
|
1611
|
-
return this.wrap('del', [`${this.options.keyPrefix || ""}${name}`]);
|
|
1612
|
-
}
|
|
1613
2282
|
/**
|
|
1614
2283
|
*
|
|
1615
2284
|
*
|
|
@@ -1647,8 +2316,8 @@ class CacheStore {
|
|
|
1647
2316
|
* @param incr
|
|
1648
2317
|
* @returns {*}
|
|
1649
2318
|
*/
|
|
1650
|
-
incrby(name,
|
|
1651
|
-
return this.wrap('incrby', [`${this.options.keyPrefix || ""}${name}`,
|
|
2319
|
+
incrby(name, increment) {
|
|
2320
|
+
return this.wrap('incrby', [`${this.options.keyPrefix || ""}${name}`, increment]);
|
|
1652
2321
|
}
|
|
1653
2322
|
/**
|
|
1654
2323
|
* 将 key 所储存的值减去减量
|
|
@@ -1656,8 +2325,8 @@ class CacheStore {
|
|
|
1656
2325
|
* @param {any} name
|
|
1657
2326
|
* @param {any} decr
|
|
1658
2327
|
*/
|
|
1659
|
-
decrby(name,
|
|
1660
|
-
return this.wrap('decrby', [`${this.options.keyPrefix || ""}${name}`,
|
|
2328
|
+
decrby(name, decrement) {
|
|
2329
|
+
return this.wrap('decrby', [`${this.options.keyPrefix || ""}${name}`, decrement]);
|
|
1661
2330
|
}
|
|
1662
2331
|
/**
|
|
1663
2332
|
* 哈希写入
|
|
@@ -1666,13 +2335,9 @@ class CacheStore {
|
|
|
1666
2335
|
* @param value
|
|
1667
2336
|
* @param timeout
|
|
1668
2337
|
*/
|
|
1669
|
-
hset(name, key, value, timeout) {
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
timeout = this.options.timeout;
|
|
1673
|
-
}
|
|
1674
|
-
setP.push(this.set(`${name}:${key}_ex`, 1, timeout));
|
|
1675
|
-
return Promise.all(setP);
|
|
2338
|
+
async hset(name, key, value, timeout) {
|
|
2339
|
+
// MemoryStore 直接支持字段级 TTL,传递 timeout 参数
|
|
2340
|
+
return this.wrap('hset', [`${this.options.keyPrefix || ""}${name}`, key, value, timeout]);
|
|
1676
2341
|
}
|
|
1677
2342
|
/**
|
|
1678
2343
|
* 哈希获取
|
|
@@ -1681,15 +2346,8 @@ class CacheStore {
|
|
|
1681
2346
|
* @returns {*}
|
|
1682
2347
|
*/
|
|
1683
2348
|
hget(name, key) {
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
return Promise.all(setP).then(dataArr => {
|
|
1687
|
-
if (dataArr[0] === null) {
|
|
1688
|
-
this.hdel(name, key);
|
|
1689
|
-
return null;
|
|
1690
|
-
}
|
|
1691
|
-
return dataArr[1] || null;
|
|
1692
|
-
});
|
|
2349
|
+
// 直接调用底层实现,TTL 检查在 MemoryCache 中处理
|
|
2350
|
+
return this.wrap('hget', [`${this.options.keyPrefix || ""}${name}`, key]);
|
|
1693
2351
|
}
|
|
1694
2352
|
/**
|
|
1695
2353
|
* 查看哈希表 hashKey 中,给定域 key 是否存在
|
|
@@ -1698,15 +2356,8 @@ class CacheStore {
|
|
|
1698
2356
|
* @returns {*}
|
|
1699
2357
|
*/
|
|
1700
2358
|
hexists(name, key) {
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
return Promise.all(setP).then(dataArr => {
|
|
1704
|
-
if (dataArr[0] === null) {
|
|
1705
|
-
this.hdel(name, key);
|
|
1706
|
-
return 0;
|
|
1707
|
-
}
|
|
1708
|
-
return dataArr[1] || 0;
|
|
1709
|
-
});
|
|
2359
|
+
// 直接调用底层实现,TTL 检查在 MemoryCache 中处理
|
|
2360
|
+
return this.wrap('hexists', [`${this.options.keyPrefix || ""}${name}`, key]);
|
|
1710
2361
|
}
|
|
1711
2362
|
/**
|
|
1712
2363
|
* 哈希删除
|
|
@@ -1714,10 +2365,9 @@ class CacheStore {
|
|
|
1714
2365
|
* @param key
|
|
1715
2366
|
* @returns {*}
|
|
1716
2367
|
*/
|
|
1717
|
-
hdel(name, key) {
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
return Promise.all(setP);
|
|
2368
|
+
async hdel(name, key) {
|
|
2369
|
+
// 直接调用底层实现,TTL 清理在 MemoryCache 中处理
|
|
2370
|
+
return this.wrap('hdel', [`${this.options.keyPrefix || ""}${name}`, key]);
|
|
1721
2371
|
}
|
|
1722
2372
|
/**
|
|
1723
2373
|
* 返回哈希表 key 中域的数量
|
|
@@ -1731,11 +2381,11 @@ class CacheStore {
|
|
|
1731
2381
|
* 给哈希表指定key,增加increment
|
|
1732
2382
|
* @param name
|
|
1733
2383
|
* @param key
|
|
1734
|
-
* @param
|
|
2384
|
+
* @param increment
|
|
1735
2385
|
* @returns {*}
|
|
1736
2386
|
*/
|
|
1737
|
-
hincrby(name, key,
|
|
1738
|
-
return this.wrap('hincrby', [`${this.options.keyPrefix || ""}${name}`, key,
|
|
2387
|
+
hincrby(name, key, increment) {
|
|
2388
|
+
return this.wrap('hincrby', [`${this.options.keyPrefix || ""}${name}`, key, increment]);
|
|
1739
2389
|
}
|
|
1740
2390
|
/**
|
|
1741
2391
|
* 返回哈希表所有key-value
|
|
@@ -1824,12 +2474,12 @@ class CacheStore {
|
|
|
1824
2474
|
* @param timeout
|
|
1825
2475
|
* @returns {*}
|
|
1826
2476
|
*/
|
|
1827
|
-
sadd(name, value, timeout) {
|
|
1828
|
-
const
|
|
1829
|
-
if (typeof timeout
|
|
1830
|
-
|
|
2477
|
+
async sadd(name, value, timeout) {
|
|
2478
|
+
const result = await this.wrap('sadd', [`${this.options.keyPrefix || ""}${name}`, value]);
|
|
2479
|
+
if (typeof timeout === 'number') {
|
|
2480
|
+
await this.wrap('expire', [`${this.options.keyPrefix || ""}${name}`, timeout]);
|
|
1831
2481
|
}
|
|
1832
|
-
return
|
|
2482
|
+
return result;
|
|
1833
2483
|
}
|
|
1834
2484
|
/**
|
|
1835
2485
|
* 返回集合的基数(集合中元素的数量)
|
|
@@ -1881,8 +2531,9 @@ class CacheStore {
|
|
|
1881
2531
|
* @returns {*}
|
|
1882
2532
|
*/
|
|
1883
2533
|
smove(source, destination, member) {
|
|
1884
|
-
return this.wrap('smove', [`${this.options.keyPrefix || ""}${source}`, `${this.options.keyPrefix}${destination}`, member]);
|
|
2534
|
+
return this.wrap('smove', [`${this.options.keyPrefix || ""}${source}`, `${this.options.keyPrefix || ""}${destination}`, member]);
|
|
1885
2535
|
}
|
|
1886
2536
|
}
|
|
2537
|
+
CacheStore.instances = new Map();
|
|
1887
2538
|
|
|
1888
2539
|
export { CacheStore };
|