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