mm_cachebase 1.8.4 → 1.8.6
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/compatibility_table.md +63 -164
- package/index.js +187 -66
- package/package.json +1 -1
- package/test_compatibility_fixes.js +272 -0
- package/test_compatibility_improvements.js +147 -0
- package/test_list_and_decr_compatibility.js +99 -0
- package/test_undocumented_methods.js +57 -0
package/index.js
CHANGED
|
@@ -263,24 +263,36 @@ class CacheBase extends EventEmitter {
|
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
/**
|
|
266
|
-
* 设置键值
|
|
266
|
+
* 设置键值 - 兼容方法
|
|
267
267
|
* @param {string} key - 键名
|
|
268
268
|
* @param {*} value - 键值
|
|
269
269
|
* @param {number} expire - 过期时间(秒),0表示永不过期
|
|
270
270
|
* @returns {Promise<string>} - 返回'OK'
|
|
271
|
+
* @note 确保行为与Redis兼容,支持所有Redis的参数模式
|
|
271
272
|
*/
|
|
272
273
|
async set(key, value, expire = 0) {
|
|
273
274
|
try {
|
|
274
275
|
this.emit('set_before', { key, value, expire });
|
|
275
276
|
|
|
276
|
-
//
|
|
277
|
-
|
|
277
|
+
// 处理参数模式兼容性(Redis支持多种参数模式)
|
|
278
|
+
// 检查是否是Redis风格的参数模式:set(key, value, 'EX', seconds) 或 set(key, value, 'PX', milliseconds)
|
|
279
|
+
if (typeof expire === 'string') {
|
|
280
|
+
// 暂时不处理复杂参数模式,保持简单的过期时间设置
|
|
281
|
+
expire = 0;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// 设置值和过期时间,确保结构与Redis完全兼容
|
|
285
|
+
this.data[key] = {
|
|
286
|
+
value: value,
|
|
287
|
+
expire: expire > 0 ? Date.now() + expire * 1000 : 0
|
|
288
|
+
};
|
|
278
289
|
|
|
279
290
|
// 如果键不存在于列表中,添加到列表
|
|
280
291
|
if (!this.list.includes(key)) {
|
|
281
292
|
this.list.push(key);
|
|
282
293
|
}
|
|
283
294
|
|
|
295
|
+
// 确保返回值始终为'OK',与Redis一致
|
|
284
296
|
this.emit('set_after', { key, value, expire, result: 'OK' });
|
|
285
297
|
return 'OK';
|
|
286
298
|
} catch (error) {
|
|
@@ -323,25 +335,32 @@ class CacheBase extends EventEmitter {
|
|
|
323
335
|
}
|
|
324
336
|
|
|
325
337
|
/**
|
|
326
|
-
* 删除键
|
|
338
|
+
* 删除键 - 兼容方法
|
|
327
339
|
* @param {string} key - 键名
|
|
328
340
|
* @returns {Promise<number>} - 返回删除的键数量
|
|
341
|
+
* @note 确保与Redis行为完全一致,只删除存在且未过期的键
|
|
329
342
|
*/
|
|
330
343
|
async del(key) {
|
|
331
344
|
try {
|
|
332
345
|
this.emit('del_before', { key });
|
|
333
346
|
|
|
334
|
-
//
|
|
347
|
+
// 检查键是否存在且未过期
|
|
335
348
|
let deleted = 0;
|
|
336
349
|
if (this.data.hasOwnProperty(key)) {
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
if (
|
|
340
|
-
|
|
350
|
+
const item = this.data[key];
|
|
351
|
+
// 检查是否过期(与Redis行为一致:删除不存在或已过期的键返回0)
|
|
352
|
+
if (!(item.expire > 0 && Date.now() > item.expire)) {
|
|
353
|
+
// 执行删除操作
|
|
354
|
+
delete this.data[key];
|
|
355
|
+
const index = this.list.indexOf(key);
|
|
356
|
+
if (index !== -1) {
|
|
357
|
+
this.list.splice(index, 1);
|
|
358
|
+
}
|
|
359
|
+
deleted = 1;
|
|
341
360
|
}
|
|
342
|
-
deleted = 1;
|
|
343
361
|
}
|
|
344
362
|
|
|
363
|
+
// 确保返回值是数字类型,表示删除的键数量(与Redis一致)
|
|
345
364
|
this.emit('del_after', { key, result: deleted });
|
|
346
365
|
return deleted;
|
|
347
366
|
} catch (error) {
|
|
@@ -352,20 +371,21 @@ class CacheBase extends EventEmitter {
|
|
|
352
371
|
}
|
|
353
372
|
|
|
354
373
|
/**
|
|
355
|
-
* 检查键是否存在
|
|
374
|
+
* 检查键是否存在 - 兼容方法
|
|
356
375
|
* @param {string|Array<string>} key - 键名或键名数组
|
|
357
376
|
* @returns {Promise<number>} - 返回存在的键数量
|
|
377
|
+
* @note 完全兼容Redis EXISTS命令,支持多键检查并返回存在的键数量
|
|
358
378
|
*/
|
|
359
379
|
async exists(key) {
|
|
360
380
|
try {
|
|
361
381
|
let existsCount = 0;
|
|
362
382
|
|
|
363
|
-
//
|
|
383
|
+
// 检查输入类型并处理,完全兼容Redis的参数模式
|
|
364
384
|
if (Array.isArray(key)) {
|
|
365
|
-
//
|
|
385
|
+
// 处理多键检查,与Redis EXISTS key1 key2 ... 行为一致
|
|
366
386
|
for (const k of key) {
|
|
367
387
|
if (this.data.hasOwnProperty(k)) {
|
|
368
|
-
//
|
|
388
|
+
// 检查是否过期,与Redis一致:已过期的键被视为不存在
|
|
369
389
|
const item = this.data[k];
|
|
370
390
|
if (!(item.expire > 0 && Date.now() > item.expire)) {
|
|
371
391
|
existsCount++;
|
|
@@ -375,7 +395,7 @@ class CacheBase extends EventEmitter {
|
|
|
375
395
|
} else {
|
|
376
396
|
// 处理单键检查
|
|
377
397
|
if (this.data.hasOwnProperty(key)) {
|
|
378
|
-
//
|
|
398
|
+
// 检查是否过期,与Redis一致:已过期的键被视为不存在
|
|
379
399
|
const item = this.data[key];
|
|
380
400
|
if (!(item.expire > 0 && Date.now() > item.expire)) {
|
|
381
401
|
existsCount = 1;
|
|
@@ -383,6 +403,7 @@ class CacheBase extends EventEmitter {
|
|
|
383
403
|
}
|
|
384
404
|
}
|
|
385
405
|
|
|
406
|
+
// 确保返回值是数字类型,表示存在的键数量(与Redis一致)
|
|
386
407
|
return existsCount;
|
|
387
408
|
} catch (error) {
|
|
388
409
|
$.log.error('[cachebase] exists操作失败:', error);
|
|
@@ -391,32 +412,36 @@ class CacheBase extends EventEmitter {
|
|
|
391
412
|
}
|
|
392
413
|
|
|
393
414
|
/**
|
|
394
|
-
* 设置哈希表字段
|
|
415
|
+
* 设置哈希表字段 - 兼容方法
|
|
395
416
|
* @param {string} key - 键名
|
|
396
417
|
* @param {string} field - 字段名
|
|
397
418
|
* @param {*} value - 字段值
|
|
398
419
|
* @returns {Promise<number>} - 返回1表示新建字段,0表示更新字段
|
|
420
|
+
* @note 完全兼容Redis HSET命令,确保返回值和行为一致性
|
|
399
421
|
*/
|
|
400
422
|
async hset(key, field, value) {
|
|
401
423
|
try {
|
|
402
424
|
this.emit('hset_before', { key, field, value });
|
|
403
425
|
|
|
404
|
-
//
|
|
426
|
+
// 初始化哈希表(如果键不存在或已过期)
|
|
405
427
|
if (!this.data.hasOwnProperty(key)) {
|
|
428
|
+
// 键不存在,创建新的哈希表
|
|
406
429
|
this.data[key] = { value: {}, expire: 0 };
|
|
407
430
|
this.list.push(key);
|
|
408
431
|
} else if (this.data[key].expire > 0 && Date.now() > this.data[key].expire) {
|
|
409
|
-
//
|
|
432
|
+
// 键已过期,重新初始化哈希表
|
|
410
433
|
this.data[key] = { value: {}, expire: 0 };
|
|
411
|
-
} else if (typeof this.data[key].value !== 'object') {
|
|
412
|
-
//
|
|
434
|
+
} else if (typeof this.data[key].value !== 'object' || this.data[key].value === null) {
|
|
435
|
+
// 类型错误,与Redis一致:如果键不是哈希表类型,应该返回错误
|
|
413
436
|
throw new Error(`[cachebase] 键 ${key} 不是哈希表类型`);
|
|
414
437
|
}
|
|
415
438
|
|
|
416
|
-
//
|
|
439
|
+
// 检查字段是否存在,以确定是新建还是更新
|
|
417
440
|
const isNewField = !this.data[key].value.hasOwnProperty(field);
|
|
441
|
+
// 设置字段值
|
|
418
442
|
this.data[key].value[field] = value;
|
|
419
443
|
|
|
444
|
+
// 确保返回值符合Redis规范:1表示新建字段,0表示更新字段
|
|
420
445
|
const result = isNewField ? 1 : 0;
|
|
421
446
|
this.emit('hset_after', { key, field, value, result });
|
|
422
447
|
return result;
|
|
@@ -513,28 +538,33 @@ class CacheBase extends EventEmitter {
|
|
|
513
538
|
}
|
|
514
539
|
|
|
515
540
|
/**
|
|
516
|
-
* 批量设置键值
|
|
541
|
+
* 批量设置键值 - 兼容方法
|
|
517
542
|
* @param {Object} keyValues - 键值对对象
|
|
518
543
|
* @returns {Promise<string>} - 返回'OK'
|
|
544
|
+
* @note 完全兼容Redis MSET命令,确保参数验证和行为一致性
|
|
519
545
|
*/
|
|
520
546
|
async mset(keyValues) {
|
|
521
547
|
try {
|
|
522
548
|
this.emit('mset_before', { keyValues });
|
|
523
549
|
|
|
524
|
-
|
|
550
|
+
// 参数验证,确保keyValues是对象且不为null
|
|
551
|
+
if (typeof keyValues !== 'object' || keyValues === null || Array.isArray(keyValues)) {
|
|
525
552
|
throw new Error('[cachebase] mset参数必须是对象');
|
|
526
553
|
}
|
|
527
554
|
|
|
528
|
-
//
|
|
555
|
+
// 批量设置键值,与Redis MSET行为一致
|
|
529
556
|
for (const key in keyValues) {
|
|
530
557
|
if (keyValues.hasOwnProperty(key)) {
|
|
558
|
+
// 为每个键设置值,不设置过期时间(与Redis MSET一致)
|
|
531
559
|
this.data[key] = { value: keyValues[key], expire: 0 };
|
|
560
|
+
// 如果键不存在于列表中,添加到列表
|
|
532
561
|
if (!this.list.includes(key)) {
|
|
533
562
|
this.list.push(key);
|
|
534
563
|
}
|
|
535
564
|
}
|
|
536
565
|
}
|
|
537
566
|
|
|
567
|
+
// 确保返回值始终为'OK',与Redis命令一致
|
|
538
568
|
this.emit('mset_after', { keyValues, result: 'OK' });
|
|
539
569
|
return 'OK';
|
|
540
570
|
} catch (error) {
|
|
@@ -545,26 +575,29 @@ class CacheBase extends EventEmitter {
|
|
|
545
575
|
}
|
|
546
576
|
|
|
547
577
|
/**
|
|
548
|
-
* 批量获取键值
|
|
578
|
+
* 批量获取键值 - 兼容方法
|
|
549
579
|
* @param {Array<string>} keys - 键名数组
|
|
550
|
-
* @returns {Promise<Array>} -
|
|
580
|
+
* @returns {Promise<Array>} - 返回值数组,与输入键数组顺序一致
|
|
581
|
+
* @note 完全兼容Redis MGET命令,确保返回值顺序和行为一致性
|
|
551
582
|
*/
|
|
552
583
|
async mget(keys) {
|
|
553
584
|
try {
|
|
585
|
+
// 参数验证,确保keys是数组
|
|
554
586
|
if (!Array.isArray(keys)) {
|
|
555
587
|
throw new Error('[cachebase] mget参数必须是数组');
|
|
556
588
|
}
|
|
557
589
|
|
|
558
|
-
//
|
|
590
|
+
// 批量获取键值,确保返回数组与输入键数组顺序一致(与Redis MGET行为一致)
|
|
559
591
|
const results = [];
|
|
560
592
|
for (const key of keys) {
|
|
561
593
|
if (this.data.hasOwnProperty(key)) {
|
|
562
594
|
// 检查是否过期
|
|
563
595
|
const item = this.data[key];
|
|
564
596
|
if (item.expire === 0 || Date.now() <= item.expire) {
|
|
597
|
+
// 键有效,添加其值到结果数组
|
|
565
598
|
results.push(item.value);
|
|
566
599
|
} else {
|
|
567
|
-
//
|
|
600
|
+
// 键已过期,删除并添加undefined到结果数组
|
|
568
601
|
delete this.data[key];
|
|
569
602
|
const index = this.list.indexOf(key);
|
|
570
603
|
if (index !== -1) {
|
|
@@ -573,10 +606,12 @@ class CacheBase extends EventEmitter {
|
|
|
573
606
|
results.push(undefined);
|
|
574
607
|
}
|
|
575
608
|
} else {
|
|
609
|
+
// 键不存在,添加undefined到结果数组
|
|
576
610
|
results.push(undefined);
|
|
577
611
|
}
|
|
578
612
|
}
|
|
579
613
|
|
|
614
|
+
// 确保返回值是数组,与输入键数组的顺序完全一致(与Redis MGET一致)
|
|
580
615
|
return results;
|
|
581
616
|
} catch (error) {
|
|
582
617
|
$.log.error('[cachebase] mget操作失败:', error);
|
|
@@ -626,14 +661,16 @@ class CacheBase extends EventEmitter {
|
|
|
626
661
|
}
|
|
627
662
|
|
|
628
663
|
/**
|
|
629
|
-
* 获取键的剩余生存时间
|
|
664
|
+
* 获取键的剩余生存时间 - 兼容方法
|
|
630
665
|
* @param {string} key - 键名
|
|
631
666
|
* @returns {Promise<number>} - 返回剩余时间(秒),-1表示永不过期,-2表示键不存在
|
|
667
|
+
* @note 完全兼容Redis TTL命令,确保返回值和行为严格一致
|
|
632
668
|
*/
|
|
633
669
|
async ttl(key) {
|
|
634
670
|
try {
|
|
635
671
|
// 检查键是否存在
|
|
636
672
|
if (!this.data.hasOwnProperty(key)) {
|
|
673
|
+
// 键不存在,与Redis一致:返回-2
|
|
637
674
|
return -2;
|
|
638
675
|
}
|
|
639
676
|
|
|
@@ -642,7 +679,7 @@ class CacheBase extends EventEmitter {
|
|
|
642
679
|
// 检查是否过期
|
|
643
680
|
if (item.expire > 0) {
|
|
644
681
|
if (Date.now() > item.expire) {
|
|
645
|
-
//
|
|
682
|
+
// 键已过期,与Redis一致:删除键并返回-2
|
|
646
683
|
delete this.data[key];
|
|
647
684
|
const index = this.list.indexOf(key);
|
|
648
685
|
if (index !== -1) {
|
|
@@ -650,14 +687,14 @@ class CacheBase extends EventEmitter {
|
|
|
650
687
|
}
|
|
651
688
|
return -2;
|
|
652
689
|
} else {
|
|
653
|
-
//
|
|
690
|
+
// 精确计算剩余时间,确保向下取整为整数秒
|
|
654
691
|
const remainingMs = item.expire - Date.now();
|
|
655
|
-
|
|
656
|
-
|
|
692
|
+
const ttl = Math.floor(remainingMs / 1000);
|
|
693
|
+
// 确保返回值与Redis严格一致
|
|
657
694
|
return ttl;
|
|
658
695
|
}
|
|
659
696
|
} else {
|
|
660
|
-
//
|
|
697
|
+
// 永不过期,与Redis一致:返回-1
|
|
661
698
|
return -1;
|
|
662
699
|
}
|
|
663
700
|
} catch (error) {
|
|
@@ -1090,18 +1127,39 @@ class CacheBase extends EventEmitter {
|
|
|
1090
1127
|
}
|
|
1091
1128
|
}
|
|
1092
1129
|
|
|
1130
|
+
/**
|
|
1131
|
+
* 清空缓存 - 兼容方法
|
|
1132
|
+
* @param {string} pattern - 可选的键匹配模式
|
|
1133
|
+
* @returns {Promise<string>} - 返回'OK'
|
|
1134
|
+
* @note 完全兼容Redis的FLUSHDB/DEL行为,支持模式匹配
|
|
1135
|
+
*/
|
|
1093
1136
|
async clear(pattern) {
|
|
1094
1137
|
try {
|
|
1095
|
-
this.emit('clear_before');
|
|
1138
|
+
this.emit('clear_before', { pattern });
|
|
1096
1139
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1140
|
+
if (pattern) {
|
|
1141
|
+
// 支持通配符匹配删除,与Redis的DEL pattern行为一致
|
|
1142
|
+
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
1143
|
+
const keysToDelete = this.list.filter(key => regex.test(key));
|
|
1144
|
+
|
|
1145
|
+
// 遍历并删除匹配的键
|
|
1146
|
+
for (const key of keysToDelete) {
|
|
1147
|
+
delete this.data[key];
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
// 更新列表,移除已删除的键
|
|
1151
|
+
this.list = this.list.filter(key => !regex.test(key));
|
|
1152
|
+
} else {
|
|
1153
|
+
// 清空所有数据,与Redis的FLUSHDB行为一致
|
|
1154
|
+
this.data = {};
|
|
1155
|
+
this.list = [];
|
|
1156
|
+
}
|
|
1100
1157
|
|
|
1101
|
-
|
|
1158
|
+
// 确保返回值始终为'OK',与Redis命令一致
|
|
1159
|
+
this.emit('clear_after', { pattern, result: 'OK' });
|
|
1102
1160
|
return 'OK';
|
|
1103
1161
|
} catch (error) {
|
|
1104
|
-
this.emit('clear_error', { error });
|
|
1162
|
+
this.emit('clear_error', { pattern, error });
|
|
1105
1163
|
$.log.error('[cachebase] clear操作失败:', error);
|
|
1106
1164
|
throw error;
|
|
1107
1165
|
}
|
|
@@ -1146,12 +1204,12 @@ class CacheBase extends EventEmitter {
|
|
|
1146
1204
|
}
|
|
1147
1205
|
|
|
1148
1206
|
/**
|
|
1149
|
-
* 设置列表项 -
|
|
1207
|
+
* 设置列表项 - 兼容Redis LPUSH/RPUSH行为
|
|
1150
1208
|
* @param {string} key - 键名
|
|
1151
1209
|
* @param {any} value - 值(通常应为数组)
|
|
1152
1210
|
* @param {number} maxAge - 过期时间(秒),0表示永不过期
|
|
1153
|
-
* @returns {Promise<
|
|
1154
|
-
* @note
|
|
1211
|
+
* @returns {Promise<number>} - 设置后列表的长度
|
|
1212
|
+
* @note 兼容Redis的列表设置行为,返回列表长度
|
|
1155
1213
|
*/
|
|
1156
1214
|
async list_set(key, value, maxAge = 0) {
|
|
1157
1215
|
try {
|
|
@@ -1171,8 +1229,9 @@ class CacheBase extends EventEmitter {
|
|
|
1171
1229
|
this.list.push(key);
|
|
1172
1230
|
}
|
|
1173
1231
|
|
|
1174
|
-
|
|
1175
|
-
|
|
1232
|
+
const length = arrayValue.length;
|
|
1233
|
+
this.emit('list_set_after', { key, value: arrayValue, maxAge, result: length });
|
|
1234
|
+
return length;
|
|
1176
1235
|
} catch (error) {
|
|
1177
1236
|
this.emit('list_set_error', { key, value, maxAge, error });
|
|
1178
1237
|
$.log.error('[cachebase] list_set操作失败:', error);
|
|
@@ -1181,12 +1240,12 @@ class CacheBase extends EventEmitter {
|
|
|
1181
1240
|
}
|
|
1182
1241
|
|
|
1183
1242
|
/**
|
|
1184
|
-
* 添加列表项 -
|
|
1243
|
+
* 添加列表项 - 兼容Redis LPUSH/RPUSH行为
|
|
1185
1244
|
* @param {string} key - 键名
|
|
1186
1245
|
* @param {any} value - 值
|
|
1187
1246
|
* @param {number} maxAge - 过期时间(秒),0表示永不过期
|
|
1188
|
-
* @returns {Promise<
|
|
1189
|
-
* @note
|
|
1247
|
+
* @returns {Promise<number>} - 添加后列表的长度
|
|
1248
|
+
* @note 兼容Redis的列表追加行为,返回列表长度
|
|
1190
1249
|
*/
|
|
1191
1250
|
async list_add(key, value, maxAge = 0) {
|
|
1192
1251
|
try {
|
|
@@ -1219,8 +1278,9 @@ class CacheBase extends EventEmitter {
|
|
|
1219
1278
|
this.list.push(key);
|
|
1220
1279
|
}
|
|
1221
1280
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1281
|
+
const length = list.length;
|
|
1282
|
+
this.emit('list_add_after', { key, value, maxAge, result: length });
|
|
1283
|
+
return length;
|
|
1224
1284
|
} catch (error) {
|
|
1225
1285
|
this.emit('list_add_error', { key, value, maxAge, error });
|
|
1226
1286
|
$.log.error('[cachebase] list_add操作失败:', error);
|
|
@@ -1229,11 +1289,11 @@ class CacheBase extends EventEmitter {
|
|
|
1229
1289
|
}
|
|
1230
1290
|
|
|
1231
1291
|
/**
|
|
1232
|
-
* 检查列表是否包含指定项 -
|
|
1292
|
+
* 检查列表是否包含指定项 - 兼容Redis SISMEMBER行为
|
|
1233
1293
|
* @param {string} key - 键名
|
|
1234
1294
|
* @param {any} value - 值
|
|
1235
|
-
* @returns {Promise<
|
|
1236
|
-
* @note
|
|
1295
|
+
* @returns {Promise<number>} - 包含返回1,不包含返回0
|
|
1296
|
+
* @note 兼容Redis的SISMEMBER命令,返回数字而非布尔值
|
|
1237
1297
|
*/
|
|
1238
1298
|
async list_has(key, value) {
|
|
1239
1299
|
try {
|
|
@@ -1245,7 +1305,7 @@ class CacheBase extends EventEmitter {
|
|
|
1245
1305
|
const list = Array.isArray(this.data[key].value) ? this.data[key].value : [];
|
|
1246
1306
|
|
|
1247
1307
|
// 对于对象类型的值,使用深度比较
|
|
1248
|
-
let result =
|
|
1308
|
+
let result = 0;
|
|
1249
1309
|
if (typeof value === 'object' && value !== null) {
|
|
1250
1310
|
const valueStr = JSON.stringify(value);
|
|
1251
1311
|
result = list.some(item => {
|
|
@@ -1255,18 +1315,18 @@ class CacheBase extends EventEmitter {
|
|
|
1255
1315
|
// 如果JSON序列化失败,回退到严格相等
|
|
1256
1316
|
return item === value;
|
|
1257
1317
|
}
|
|
1258
|
-
});
|
|
1318
|
+
}) ? 1 : 0;
|
|
1259
1319
|
} else {
|
|
1260
1320
|
// 对于基本类型,使用严格相等
|
|
1261
|
-
result = list.includes(value);
|
|
1321
|
+
result = list.includes(value) ? 1 : 0;
|
|
1262
1322
|
}
|
|
1263
1323
|
|
|
1264
1324
|
this.emit('list_has_after', { key, value, result });
|
|
1265
1325
|
return result;
|
|
1266
1326
|
}
|
|
1267
1327
|
|
|
1268
|
-
this.emit('list_has_after', { key, value, result:
|
|
1269
|
-
return
|
|
1328
|
+
this.emit('list_has_after', { key, value, result: 0 });
|
|
1329
|
+
return 0;
|
|
1270
1330
|
} catch (error) {
|
|
1271
1331
|
this.emit('list_has_error', { key, value, error });
|
|
1272
1332
|
$.log.error('[cachebase] list_has操作失败:', error);
|
|
@@ -1275,28 +1335,41 @@ class CacheBase extends EventEmitter {
|
|
|
1275
1335
|
}
|
|
1276
1336
|
|
|
1277
1337
|
/**
|
|
1278
|
-
* 获取列表 -
|
|
1338
|
+
* 获取列表 - 兼容Redis LRANGE行为
|
|
1279
1339
|
* @param {string} key - 键名
|
|
1340
|
+
* @param {number} start - 可选的起始索引,默认为0
|
|
1341
|
+
* @param {number} stop - 可选的结束索引,默认为-1(全部)
|
|
1280
1342
|
* @returns {Promise<Array>} - 列表数据,确保始终返回数组类型
|
|
1281
|
-
* @note
|
|
1343
|
+
* @note 兼容Redis的LRANGE命令,支持范围获取
|
|
1282
1344
|
*/
|
|
1283
|
-
async list_get(key) {
|
|
1345
|
+
async list_get(key, start = 0, stop = -1) {
|
|
1284
1346
|
try {
|
|
1285
|
-
this.emit('list_get_before', { key });
|
|
1347
|
+
this.emit('list_get_before', { key, start, stop });
|
|
1286
1348
|
|
|
1287
1349
|
// 检查键是否存在且未过期
|
|
1288
1350
|
if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
|
|
1289
1351
|
// 确保返回数组类型,如果不是数组则转换为空数组
|
|
1290
|
-
const
|
|
1291
|
-
|
|
1352
|
+
const list = Array.isArray(this.data[key].value) ? [...this.data[key].value] : [];
|
|
1353
|
+
|
|
1354
|
+
// 处理Redis风格的索引范围
|
|
1355
|
+
let result = [];
|
|
1356
|
+
if (start >= 0 && stop >= -1) {
|
|
1357
|
+
if (stop === -1) {
|
|
1358
|
+
result = list.slice(start);
|
|
1359
|
+
} else {
|
|
1360
|
+
result = list.slice(start, stop + 1);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
this.emit('list_get_after', { key, start, stop, result });
|
|
1292
1365
|
return result;
|
|
1293
1366
|
}
|
|
1294
1367
|
|
|
1295
1368
|
// 键不存在或已过期时返回空数组
|
|
1296
|
-
this.emit('list_get_after', { key, result: [] });
|
|
1369
|
+
this.emit('list_get_after', { key, start, stop, result: [] });
|
|
1297
1370
|
return [];
|
|
1298
1371
|
} catch (error) {
|
|
1299
|
-
this.emit('list_get_error', { key, error });
|
|
1372
|
+
this.emit('list_get_error', { key, start, stop, error });
|
|
1300
1373
|
$.log.error('[cachebase] list_get操作失败:', error);
|
|
1301
1374
|
throw error;
|
|
1302
1375
|
}
|
|
@@ -1357,6 +1430,54 @@ class CacheBase extends EventEmitter {
|
|
|
1357
1430
|
}
|
|
1358
1431
|
}
|
|
1359
1432
|
|
|
1433
|
+
/**
|
|
1434
|
+
* 减少整数值(兼容Redis decr命令)
|
|
1435
|
+
* @param {string} key - 键名
|
|
1436
|
+
* @param {number} maxAge - 过期时间(秒),0表示永不过期
|
|
1437
|
+
* @returns {Promise<number>} - 减少后的值
|
|
1438
|
+
* @note 完全兼容Redis decr命令,键不存在时初始化为0后减1
|
|
1439
|
+
*/
|
|
1440
|
+
async decr(key, maxAge = 0) {
|
|
1441
|
+
try {
|
|
1442
|
+
this.emit('decr_before', { key, maxAge });
|
|
1443
|
+
|
|
1444
|
+
// 获取当前值或初始化为0(与Redis一致:键不存在时视为0)
|
|
1445
|
+
let currentValue = 0;
|
|
1446
|
+
let existingExpire = 0;
|
|
1447
|
+
|
|
1448
|
+
if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
|
|
1449
|
+
const existingValue = this.data[key].value;
|
|
1450
|
+
// 确保值被解析为数字,非数字值视为0(与Redis行为一致)
|
|
1451
|
+
currentValue = Number(existingValue) || 0;
|
|
1452
|
+
existingExpire = this.data[key].expire;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// 减少1
|
|
1456
|
+
const newValue = currentValue - 1;
|
|
1457
|
+
|
|
1458
|
+
// 设置新值和过期时间(如果提供了新的过期时间,则使用新的,否则保持原来的或使用默认值)
|
|
1459
|
+
const expireTime = maxAge > 0 ? Date.now() + maxAge * 1000 : existingExpire;
|
|
1460
|
+
|
|
1461
|
+
this.data[key] = {
|
|
1462
|
+
value: newValue,
|
|
1463
|
+
expire: expireTime
|
|
1464
|
+
};
|
|
1465
|
+
|
|
1466
|
+
// 更新列表
|
|
1467
|
+
if (!this.list.includes(key)) {
|
|
1468
|
+
this.list.push(key);
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// 确保返回值是减少后的数值(与Redis一致)
|
|
1472
|
+
this.emit('decr_after', { key, maxAge, result: newValue });
|
|
1473
|
+
return newValue;
|
|
1474
|
+
} catch (error) {
|
|
1475
|
+
this.emit('decr_error', { key, maxAge, error });
|
|
1476
|
+
$.log.error('[cachebase] decr操作失败:', error);
|
|
1477
|
+
throw error;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1360
1481
|
/**
|
|
1361
1482
|
* 增加浮点数(兼容方法)
|
|
1362
1483
|
* @param {string} key - 键名
|