mm_cachebase 2.0.4 → 2.0.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/index.js CHANGED
@@ -1,3141 +1,11 @@
1
- /**
2
- * CacheBase 是一个通用缓存基类,支持内存缓存和持久化缓存
3
- * 接口与 mm_redis 完全对齐:全异步、冒号风格事件、统一签名与返回值
4
- * @class CacheBase
5
- * @extends Base
6
- */
7
- const {
8
- Base
9
- } = require('mm_expand');
10
-
11
- class CacheBase extends Base {
12
- /**
13
- * 默认配置静态变量
14
- */
15
- static config = {
16
- enabled: true,
17
- persist: true,
18
- scope: 'sys',
19
- dir: './cache',
20
- debug: false,
21
- auto_save: true,
22
- save_interval: 30000,
23
- expire_check_interval: 1000,
24
- max_size: null,
25
- storage_type: 'memory'
26
- };
27
-
28
- /**
29
- * 构造函数
30
- * @param {Object} config - 配置对象
31
- */
32
- constructor(config = {}) {
33
- // 先调用父类构造函数
34
- super(CacheBase.config);
35
-
36
- // 存储实际数据
37
- this._data = {};
38
-
39
- // 存储过期时间
40
- this._expires = {};
41
-
42
- // 存储数据类型
43
- this._types = {};
44
-
45
- // 存储持久化数据的定时器
46
- this._save_timer = null;
47
-
48
- // 存储过期检查的定时器
49
- this._check_timer = null;
50
-
51
- // 初始化服务状态
52
- this.is_inited = false;
53
- this.is_destroyed = false;
54
-
55
- // 连接状态(与 mm_redis 保持一致)
56
- this.connected = false;
57
-
58
- // 发布订阅频道回调
59
- this._channels = {};
60
-
61
- this.setConfig(config);
62
- // 初始化服务(内存缓存特性:构造即初始化)
63
- this.init();
64
- }
65
- }
66
-
67
- /**
68
- * 设置配置
69
- * @param {Object} config - 配置对象
70
- */
71
- CacheBase.prototype.setConfig = function (config) {
72
- // 加载外部配置文件
73
- const external_config = this._loadExternalConfig();
74
- // 更新配置
75
- Object.assign(this.config, external_config, config);
76
- };
77
-
78
- /**
79
- * 初始化服务
80
- * @returns {Promise<void>}
81
- */
82
- CacheBase.prototype.init = async function () {
83
- if (this.is_inited || this.is_destroyed) {
84
- return;
85
- }
86
-
87
- try {
88
- await this._initService();
89
- this.is_inited = true;
90
- this.connected = true;
91
- } catch (error) {
92
- this.log('error', 'Failed to initialize CacheBase service:', error);
93
- }
94
- };
95
-
96
- /**
97
- * 加载外部配置文件
98
- * @private
99
- * @returns {Object} 外部配置对象
100
- */
101
- CacheBase.prototype._loadExternalConfig = function () {
102
- try {
103
- const config_path = './config/cachebase.config.json'.fullname(__dirname);
104
- if (config_path.hasFile()) {
105
- const config = config_path.loadJson();
106
- return config;
107
- }
108
- } catch (error) {
109
- this.log('warn', '外部配置文件加载失败,使用默认配置:', error.message);
110
- }
111
- return {};
112
- };
113
-
114
- /**
115
- * 设置文件路径
116
- * @private
117
- */
118
- CacheBase.prototype._setFilePath = function () {
119
- const scope = this.config.scope || 'sys';
120
- const dir = this.config.dir || './cache';
121
- this.file_path = `./${scope}.json`.fullname(dir);
122
- };
123
-
124
- /**
125
- * 初始化服务
126
- * @private
127
- * @returns {Promise<void>}
128
- */
129
- CacheBase.prototype._initService = async function () {
130
- try {
131
- // 设置文件路径
132
- this._setFilePath();
133
- // 初始化过期检查定时器
134
- this._check_timer = setInterval(() => {
135
- this.clearExpire();
136
- }, this.config.expire_check_interval || 1000);
137
-
138
- // 设置最大大小限制
139
- if (this.config.max_size) {
140
- this._max_size = this.config.max_size;
141
- }
142
-
143
- // 加载数据
144
- this._loadData();
145
-
146
- // 设置定时保存
147
- this._connect();
148
-
149
- // 设置初始化完成状态
150
- this.is_inited = true;
151
- } catch (error) {
152
- this.log('error', 'Failed to initialize service:', error);
153
- }
154
- };
155
-
156
- /**
157
- * 设置定时保存(内部方法)
158
- * @private
159
- */
160
- CacheBase.prototype._connect = function () {
161
- // 如果启用了自动保存
162
- if (this.config.auto_save) {
163
- this._save_timer = setInterval(() => {
164
- this.save();
165
- }, this.config.save_interval || 30000);
166
- }
167
- };
168
-
169
- /**
170
- * 从文件加载数据
171
- * @private
172
- */
173
- CacheBase.prototype._loadData = function () {
174
- try {
175
- if (this.config.persist && this.file_path) {
176
- if (this.file_path.hasFile()) {
177
- // 使用mm_expand的原型函数读取JSON文件
178
- const parsed_data = this.file_path.loadJson();
179
- if (parsed_data && parsed_data.data) {
180
- this._data = parsed_data.data || {};
181
- } else {
182
- this.log('warn', `[_loadData] 解析后的数据中没有data字段`);
183
- }
184
-
185
- if (parsed_data && parsed_data.expires) {
186
- this._expires = parsed_data.expires || {};
187
- }
188
-
189
- if (parsed_data && parsed_data.types) {
190
- this._types = parsed_data.types || {};
191
- }
192
- }
193
- }
194
- } catch (error) {
195
- this.log('error', '加载数据失败:', error);
196
- }
197
- };
198
-
199
- /**
200
- * 保存数据到文件
201
- */
202
- CacheBase.prototype.save = function () {
203
- // 检查服务状态
204
- if (!this.is_inited || this.is_destroyed) {
205
- this.log('error', 'Service is not initialized or has been destroyed');
206
- return;
207
- }
208
-
209
- try {
210
- this.emit('save:before');
211
- this._saveData();
212
- this.emit('save:after');
213
- } catch (error) {
214
- this.log('error', 'Failed to save data:', error);
215
- }
216
- };
217
-
218
- /**
219
- * 保存数据到文件
220
- * @private
221
- */
222
- CacheBase.prototype._saveData = function () {
223
- try {
224
- if (this.config.persist && this.file_path) {
225
- // 确保目录存在 - 使用目录路径的原型函数
226
- this.file_path.addDir();
227
-
228
- const data_to_save = {
229
- data: this._data,
230
- expires: this._expires,
231
- types: this._types,
232
- timestamp: Date.now()
233
- };
234
-
235
- // 使用mm_expand的原型函数写入JSON文件
236
- this.file_path.saveJson(data_to_save, null, 2);
237
- }
238
- } catch (error) {
239
- this.log('error', 'Failed to save data:', error);
240
- }
241
- };
242
-
243
- /**
244
- * 销毁服务
245
- * @returns {Promise<void>}
246
- */
247
- CacheBase.prototype.destroy = async function () {
248
- if (this.is_destroyed) {
249
- return;
250
- }
251
-
252
- try {
253
- // 保存数据
254
- this.save();
255
-
256
- // 清除定时器
257
- if (this._save_timer) {
258
- clearInterval(this._save_timer);
259
- this._save_timer = null;
260
- }
261
-
262
- if (this._check_timer) {
263
- clearInterval(this._check_timer);
264
- this._check_timer = null;
265
- }
266
-
267
- // 清空数据
268
- this._data = {};
269
- this._expires = {};
270
- this._types = {};
271
- this._channels = {};
272
-
273
- // 设置销毁状态
274
- this.is_inited = false;
275
- this.is_destroyed = true;
276
- this.connected = false;
277
-
278
- // 触发销毁事件(注意:不可使用 'destroy:after' 冒号格式,
279
- // mm_expand 的 Base.emit 会将冒号格式解释为 do 流程的阶段,触发 _destroyCore)
280
- this.emit('destroy', { success: true });
281
- } catch (error) {
282
- this.log('error', 'Failed to destroy service:', error);
283
- }
284
- };
285
-
286
- /**
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} 操作结果
327
- */
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
- }
367
- };
368
-
369
- // ==================== 键值操作 ====================
370
-
371
- /**
372
- * 设置键值对
373
- * @param {String} key 键
374
- * @param {*} value 值
375
- * @param {Number} [seconds=0] 过期时间(秒),0表示永不过期
376
- * @returns {Promise<Boolean>} 成功返回true,失败返回false
377
- */
378
- CacheBase.prototype.set = async function (key, value, seconds = 0) {
379
- // 检查服务状态
380
- if (!this.is_inited || this.is_destroyed) {
381
- this.log('error', 'Service is not initialized or has been destroyed');
382
- return false;
383
- }
384
-
385
- // 参数验证
386
- if (!key || typeof key !== 'string') {
387
- throw new TypeError('key必须是非空字符串');
388
- }
389
-
390
- try {
391
- // 触发前置事件
392
- const ctx = {
393
- key,
394
- value,
395
- seconds,
396
- cancel: false
397
- };
398
- this.emit('set:before', ctx);
399
-
400
- if (ctx.cancel) {
401
- this.log('debug', `set操作被事件处理器取消 [${key}]`);
402
- return false;
403
- }
404
-
405
- // 使用可能被修改的值
406
- const {
407
- key: finalKey,
408
- value: finalValue,
409
- seconds: finalSeconds
410
- } = ctx;
411
-
412
- // 保存数据
413
- this._data[finalKey] = finalValue;
414
-
415
- // 设置类型
416
- this._types[finalKey] = this._getDataTypes(finalValue);
417
-
418
- // 设置过期时间
419
- if (finalSeconds && typeof finalSeconds === 'number' && finalSeconds > 0) {
420
- this._expires[finalKey] = Date.now() + finalSeconds * 1000;
421
- } else {
422
- delete this._expires[finalKey];
423
- }
424
-
425
- // 触发后置事件
426
- this.emit('set:after', {
427
- key,
428
- value,
429
- seconds,
430
- success: true
431
- });
432
-
433
- return true;
434
- } catch (error) {
435
- this.log('error', `Failed to set key ${key}:`, error);
436
- this.emit('set:error', { key, value, seconds, error });
437
- return false;
438
- }
439
- };
440
-
441
- /**
442
- * 获取键值
443
- * @param {String} key 键
444
- * @returns {Promise<Object|null>} 值,不存在或错误返回null
445
- */
446
- CacheBase.prototype.get = async function (key) {
447
- // 检查服务状态
448
- if (!this.is_inited || this.is_destroyed) {
449
- this.log('error', 'Service is not initialized or has been destroyed');
450
- return null;
451
- }
452
-
453
- try {
454
- this.emit('get:before', { key });
455
-
456
- // 检查是否过期
457
- if (this._isExpired(key)) {
458
- await this.del(key);
459
- this.emit('get:after', { key, value: null });
460
- return null;
461
- }
462
-
463
- const value = this._data[key];
464
- this.emit('get:after', { key, value });
465
- return value !== undefined ? value : null;
466
- } catch (error) {
467
- this.log('error', `Failed to get key ${key}:`, error);
468
- this.emit('get:error', { key, error });
469
- return null;
470
- }
471
- };
472
-
473
- /**
474
- * 删除键
475
- * @param {String|Array<String>} key 键名或键名数组
476
- * @returns {Promise<Number>} 删除的键数量
477
- */
478
- CacheBase.prototype.del = async function (key) {
479
- // 参数验证
480
- if (!key) {
481
- throw new TypeError('key必须是非空字符串或非空字符串数组');
482
- }
483
-
484
- if (typeof key === 'string') {
485
- if (!key) {
486
- throw new TypeError('key必须是非空字符串');
487
- }
488
- } else if (Array.isArray(key)) {
489
- for (const k of key) {
490
- if (!k || typeof k !== 'string') {
491
- throw new TypeError('key必须是非空字符串或非空字符串数组');
492
- }
493
- }
494
- } else {
495
- throw new TypeError('key必须是非空字符串或非空字符串数组');
496
- }
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
-
504
- try {
505
- // 触发前置事件
506
- const ctx = {
507
- key,
508
- cancel: false
509
- };
510
- this.emit('del:before', ctx);
511
-
512
- if (ctx.cancel) {
513
- return 0;
514
- }
515
-
516
- const finalKey = ctx.key;
517
- let deletedCount = 0;
518
-
519
- if (Array.isArray(finalKey)) {
520
- // 批量删除
521
- for (const k of finalKey) {
522
- if (k in this._data) {
523
- delete this._data[k];
524
- delete this._types[k];
525
- delete this._expires[k];
526
- deletedCount++;
527
- }
528
- }
529
- } else {
530
- // 单个删除
531
- if (finalKey in this._data) {
532
- delete this._data[finalKey];
533
- delete this._types[finalKey];
534
- delete this._expires[finalKey];
535
- deletedCount++;
536
- }
537
- }
538
-
539
- // 触发后置事件
540
- this.emit('del:after', {
541
- key: finalKey,
542
- success: deletedCount > 0,
543
- deleted: deletedCount
544
- });
545
-
546
- return deletedCount;
547
- } catch (error) {
548
- this.log('error', 'Failed to del:', error);
549
- this.emit('del:error', { key, error });
550
- return 0;
551
- }
552
- };
553
-
554
- /**
555
- * 批量删除键
556
- * @param {Array} keys 键名数组
557
- * @returns {Promise<Number>} 删除的键数量
558
- */
559
- CacheBase.prototype.delBatch = async function (keys) {
560
- try {
561
- if (!Array.isArray(keys)) {
562
- return 0;
563
- }
564
- return await this.del(keys);
565
- } catch (error) {
566
- this.log('error', 'Failed to delBatch:', error);
567
- return 0;
568
- }
569
- };
570
-
571
- /**
572
- * 检查键是否存在
573
- * @param {String} key 键
574
- * @returns {Promise<Number>} 存在返回1,不存在返回0
575
- */
576
- CacheBase.prototype.exists = async function (key) {
577
- try {
578
- this.emit('exists:before', { key });
579
-
580
- if (this._isExpired(key)) {
581
- await this.del(key);
582
- this.emit('exists:after', { key, result: 0 });
583
- return 0;
584
- }
585
-
586
- const result = key in this._data ? 1 : 0;
587
- this.emit('exists:after', { key, result });
588
- return result;
589
- } catch (error) {
590
- this.log('error', `Failed to check key ${key}:`, error);
591
- return 0;
592
- }
593
- };
594
-
595
- /**
596
- * 检查键是否存在
597
- * @param {String} key 键名
598
- * @returns {Promise<Boolean>} 是否存在
599
- */
600
- CacheBase.prototype.has = async function (key) {
601
- const result = await this.exists(key);
602
- return result > 0;
603
- };
604
-
605
- /**
606
- * 清空过期数据
607
- * @returns {Promise<void>}
608
- */
609
- CacheBase.prototype.clearExpire = async function () {
610
- try {
611
- const now = Date.now();
612
- const expiredKeys = [];
613
- for (const key in this._expires) {
614
- if (this._expires[key] < now) {
615
- expiredKeys.push(key);
616
- }
617
- }
618
- if (expiredKeys.length > 0) {
619
- await this.del(expiredKeys);
620
- }
621
- } catch (error) {
622
- this.log('error', 'Failed to clear expired data:', error);
623
- }
624
- };
625
-
626
- /**
627
- * 检查键是否过期
628
- * @private
629
- * @param {String} key 键名
630
- * @returns {Boolean} 是否过期
631
- */
632
- CacheBase.prototype._isExpired = function (key) {
633
- // 先检查键是否存在
634
- if (!(key in this._data)) {
635
- return true;
636
- }
637
-
638
- // 如果没有设置过期时间,则未过期
639
- if (!(key in this._expires)) {
640
- return false;
641
- }
642
-
643
- const now = Date.now();
644
- const is_expired = now > this._expires[key];
645
-
646
- // 仅返回过期状态,不在此处做惰性删除。
647
- // 删除统一由 del 方法完成,避免事件不成对。
648
- return is_expired;
649
- };
650
-
651
- /**
652
- * 获取数据类型
653
- * @private
654
- * @param {*} value 值
655
- * @returns {String} 数据类型
656
- */
657
- CacheBase.prototype._getDataTypes = function (value) {
658
- if (value === null) return 'null';
659
- if (value instanceof Set) return 'set';
660
- if (value instanceof Map) return 'zset';
661
- if (Array.isArray(value)) return 'array';
662
- return typeof value;
663
- };
664
-
665
- // ==================== 过期时间管理 ====================
666
-
667
- /**
668
- * 设置键的过期时间
669
- * @param {String} key 键名
670
- * @param {Number} seconds 过期时间(秒)
671
- * @returns {Promise<Boolean>} 是否成功
672
- */
673
- CacheBase.prototype.expire = async function (key, seconds) {
674
- try {
675
- if (!key || typeof key !== 'string') {
676
- return false;
677
- }
678
-
679
- if (typeof seconds !== 'number' || seconds <= 0) {
680
- return false;
681
- }
682
-
683
- // 触发前置事件
684
- const ctx = {
685
- key,
686
- ttl: seconds,
687
- cancel: false
688
- };
689
- this.emit('expire:before', ctx);
690
-
691
- if (ctx.cancel) {
692
- return false;
693
- }
694
-
695
- const {
696
- key: finalKey,
697
- ttl: finalSeconds
698
- } = ctx;
699
-
700
- // 检查键是否存在
701
- if (!(finalKey in this._data)) {
702
- return false;
703
- }
704
-
705
- // 设置过期时间
706
- this._expires[finalKey] = Date.now() + finalSeconds * 1000;
707
-
708
- // 触发后置事件
709
- this.emit('expire:after', {
710
- key: finalKey,
711
- ttl: finalSeconds,
712
- success: true
713
- });
714
-
715
- return true;
716
- } catch (error) {
717
- this.log('error', `Failed to set expire for key ${key}:`, error);
718
- this.emit('expire:error', { key, ttl: seconds, error });
719
- return false;
720
- }
721
- };
722
-
723
- /**
724
- * 设置键的过期时间(毫秒)
725
- * @param {String} key 键名
726
- * @param {Number} milliseconds 过期时间(毫秒)
727
- * @returns {Promise<Boolean>} 是否成功
728
- */
729
- CacheBase.prototype.pexpire = async function (key, milliseconds) {
730
- try {
731
- if (!key || typeof key !== 'string') {
732
- throw new TypeError('key必须是非空字符串');
733
- }
734
- if (typeof milliseconds !== 'number' || milliseconds <= 0) {
735
- throw new TypeError('milliseconds必须是正数');
736
- }
737
-
738
- // 触发前置事件
739
- const ctx = {
740
- key,
741
- ttl: milliseconds,
742
- cancel: false
743
- };
744
- this.emit('pexpire:before', ctx);
745
-
746
- if (ctx.cancel) {
747
- return false;
748
- }
749
-
750
- const {
751
- key: finalKey,
752
- ttl: finalMilliseconds
753
- } = ctx;
754
-
755
- // 检查键是否存在
756
- if (!(finalKey in this._data)) {
757
- return false;
758
- }
759
-
760
- // 设置过期时间(毫秒)
761
- this._expires[finalKey] = Date.now() + finalMilliseconds;
762
-
763
- // 触发后置事件
764
- this.emit('pexpire:after', {
765
- key: finalKey,
766
- ttl: finalMilliseconds,
767
- success: true
768
- });
769
-
770
- return true;
771
- } catch (error) {
772
- this.log('error', `Failed to set pexpire for key ${key}:`, error);
773
- this.emit('pexpire:error', { key, ttl: milliseconds, error });
774
- return false;
775
- }
776
- };
777
-
778
- /**
779
- * 移除键的过期时间
780
- * @param {String} key 键名
781
- * @returns {Promise<Boolean>} 是否成功移除
782
- */
783
- CacheBase.prototype.persist = async function (key) {
784
- try {
785
- if (!key || typeof key !== 'string') {
786
- return false;
787
- }
788
-
789
- // 触发前置事件
790
- const ctx = {
791
- key,
792
- cancel: false
793
- };
794
- this.emit('persist:before', ctx);
795
-
796
- if (ctx.cancel) {
797
- return false;
798
- }
799
-
800
- const {
801
- key: finalKey
802
- } = ctx;
803
-
804
- // 检查键是否存在
805
- if (!(finalKey in this._data)) {
806
- return false;
807
- }
808
-
809
- // 检查是否过期
810
- if (this._isExpired(finalKey)) {
811
- await this.del(finalKey);
812
- return false;
813
- }
814
-
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;
824
- }
825
-
826
- return false;
827
- } catch (error) {
828
- this.log('error', `Failed to persist key ${key}:`, error);
829
- this.emit('persist:error', { key, error });
830
- return false;
831
- }
832
- };
833
-
834
- /**
835
- * 获取或设置键的过期时间
836
- * @param {String} key 键名
837
- * @param {Number} [seconds] 过期时间(秒),不传则获取剩余过期时间
838
- * @returns {Promise<Number|Boolean>} 不传seconds返回剩余秒数(-1无过期,-2已过期),传seconds返回是否设置成功
839
- */
840
- CacheBase.prototype.ttl = async function (key, seconds) {
841
- const isSetMode = arguments.length > 1 && seconds !== undefined;
842
- try {
843
- this.emit('ttl:before', { key, seconds });
844
-
845
- if (!key || typeof key !== 'string') {
846
- return isSetMode ? false : -2;
847
- }
848
-
849
- if (isSetMode) {
850
- // 设置过期时间模式
851
- if (typeof seconds !== 'number' || seconds < 0) {
852
- return false;
853
- }
854
-
855
- if (!(key in this._data)) {
856
- return false;
857
- }
858
-
859
- if (this._isExpired(key)) {
860
- await this.del(key);
861
- return false;
862
- }
863
-
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
- }
1006
-
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
- // 处理字段参数
1065
- const fields = Array.isArray(field) ? field : [field];
1066
- let deletedCount = 0;
1067
-
1068
- for (const f of fields) {
1069
- if (typeof f === 'string' && f && f in this._data[key]) {
1070
- delete this._data[key][f];
1071
- deletedCount++;
1072
- }
1073
- }
1074
-
1075
- // 如果哈希表为空,删除整个键
1076
- if (deletedCount > 0 && Object.keys(this._data[key]).length === 0) {
1077
- delete this._data[key];
1078
- delete this._types[key];
1079
- delete this._expires[key];
1080
- }
1081
-
1082
- this.emit('hdel:after', {
1083
- key,
1084
- field,
1085
- success: deletedCount > 0,
1086
- deleted: deletedCount
1087
- });
1088
-
1089
- return deletedCount;
1090
- } catch (error) {
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 = {
1111
- key,
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
1148
- });
1149
-
1150
- return true;
1151
- } catch (error) {
1152
- this.emit('hmset:error', { key, value: obj, error });
1153
- this.log('error', 'Failed to hmset:', error);
1154
- return false;
1155
- }
1156
- };
1157
-
1158
- /**
1159
- * 批量获取哈希表字段
1160
- * @param {String} key 哈希表键名
1161
- * @param {Array} fields 字段名数组
1162
- * @returns {Promise<Array>} 字段值数组,不存在的字段返回null
1163
- */
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);
1229
- return 0;
1230
- }
1231
- };
1232
-
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;
1355
- }
1356
- };
1357
-
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;
1370
- }
1371
-
1372
- if (!key || typeof key !== 'string' || typeof increment !== 'number') {
1373
- this.log('error', 'incrby参数错误: key必须是非空字符串,increment必须是数字');
1374
- return null;
1375
- }
1376
-
1377
- try {
1378
- const ctx = {
1379
- key,
1380
- increment,
1381
- seconds,
1382
- cancel: false
1383
- };
1384
- this.emit('incrby:before', ctx);
1385
-
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;
1444
- }
1445
-
1446
- if (!key || typeof key !== 'string' || typeof decrement !== 'number') {
1447
- this.log('error', 'decrby参数错误: key必须是非空字符串,decrement必须是数字');
1448
- return null;
1449
- }
1450
-
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
- }
1503
- };
1504
-
1505
- /**
1506
- * 将键的值增加指定浮点数
1507
- * @param {String} key 键
1508
- * @param {Number} [increment=1.0] 增量
1509
- * @param {Number} [seconds=0] 过期时间(秒)
1510
- * @returns {Promise<Number>} 增加后的值,失败返回null
1511
- */
1512
- CacheBase.prototype.incrbyfloat = async function (key, increment = 1.0, seconds = 0) {
1513
- try {
1514
- if (!key || typeof key !== 'string' || typeof increment !== 'number') {
1515
- this.log('error', 'incrbyfloat参数错误: key必须是非空字符串,increment必须是数字');
1516
- return null;
1517
- }
1518
-
1519
- const ctx = {
1520
- key,
1521
- increment,
1522
- ttl: seconds,
1523
- cancel: false
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;
1536
-
1537
- if (!this._data) {
1538
- this._data = {};
1539
- }
1540
-
1541
- let current = 0;
1542
- if (finalKey in this._data && !this._isExpired(finalKey)) {
1543
- current = Number(this._data[finalKey]) || 0;
1544
- }
1545
-
1546
- const newValue = current + finalIncrement;
1547
- this._data[finalKey] = newValue;
1548
- this._types[finalKey] = 'number';
1549
-
1550
- if (finalSeconds > 0) {
1551
- this._expires[finalKey] = Date.now() + finalSeconds * 1000;
1552
- } else {
1553
- delete this._expires[finalKey];
1554
- }
1555
-
1556
- this.emit('incrbyfloat:after', {
1557
- key: finalKey,
1558
- increment: finalIncrement,
1559
- ttl: finalSeconds,
1560
- success: true,
1561
- result: newValue
1562
- });
1563
- return newValue;
1564
- } catch (error) {
1565
- this.emit('incrbyfloat:error', { key, increment, ttl: seconds, error });
1566
- this.log('error', 'incrbyfloat操作失败:', error);
1567
- return null;
1568
- }
1569
- };
1570
-
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
- }
1586
-
1587
- const ctx = {
1588
- key,
1589
- value,
1590
- ttl: seconds,
1591
- cancel: false
1592
- };
1593
- this.emit('addInt:before', ctx);
1594
-
1595
- if (ctx.cancel) {
1596
- return null;
1597
- }
1598
-
1599
- const {
1600
- key: finalKey,
1601
- value: finalValue,
1602
- ttl: finalSeconds
1603
- } = ctx;
1604
-
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));
1610
-
1611
- this._data[finalKey] = result;
1612
- this._types[finalKey] = 'number';
1613
-
1614
- if (finalSeconds > 0) {
1615
- this._expires[finalKey] = Date.now() + finalSeconds * 1000;
1616
- } else {
1617
- delete this._expires[finalKey];
1618
- }
1619
-
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
- }
1633
- };
1634
-
1635
- /**
1636
- * 增加浮点数(兼容方法)
1637
- * @param {String} key 键名
1638
- * @param {Number} num 要增加的浮点数值
1639
- * @param {Number} [seconds=0] 过期时间(秒)
1640
- * @returns {Promise<Number>} 增加后的值,失败返回null
1641
- */
1642
- CacheBase.prototype.addFloat = async function (key, num, seconds = 0) {
1643
- try {
1644
- if (!key || typeof key !== 'string' || typeof num !== 'number') {
1645
- throw new TypeError('addFloat参数错误: key必须是非空字符串,num必须是数字');
1646
- }
1647
-
1648
- const ctx = {
1649
- key,
1650
- num,
1651
- ttl: seconds,
1652
- cancel: false
1653
- };
1654
- this.emit('addFloat:before', ctx);
1655
-
1656
- if (ctx.cancel) {
1657
- return null;
1658
- }
1659
-
1660
- const {
1661
- key: finalKey,
1662
- num: finalNum,
1663
- ttl: finalSeconds
1664
- } = ctx;
1665
-
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);
1671
-
1672
- this._data[finalKey] = result;
1673
- this._types[finalKey] = 'number';
1674
-
1675
- if (finalSeconds > 0) {
1676
- this._expires[finalKey] = Date.now() + finalSeconds * 1000;
1677
- } else {
1678
- delete this._expires[finalKey];
1679
- }
1680
-
1681
- this.emit('addFloat:after', {
1682
- key: finalKey,
1683
- num: finalNum,
1684
- ttl: finalSeconds,
1685
- success: true,
1686
- result
1687
- });
1688
- return result;
1689
- } catch (error) {
1690
- this.emit('addFloat:error', { key, num, ttl: seconds, error });
1691
- this.log('error', 'addFloat操作失败:', error);
1692
- return null;
1693
- }
1694
- };
1695
-
1696
- // ==================== 字符串操作 ====================
1697
-
1698
- /**
1699
- * 追加字符串(兼容方法,对齐 mm_redis 的 setrange)
1700
- * @param {String} key 键名
1701
- * @param {String} value 要追加的字符串
1702
- * @param {Number} [seconds=0] 过期时间(秒)
1703
- * @returns {Promise<Number>} 追加后字符串的长度,失败返回null
1704
- */
1705
- CacheBase.prototype.setrange = async function (key, value, seconds = 0) {
1706
- try {
1707
- if (!key || typeof key !== 'string' || typeof value !== 'string') {
1708
- throw new TypeError('setrange参数错误: key和value必须是非空字符串');
1709
- }
1710
-
1711
- const ctx = {
1712
- key,
1713
- value,
1714
- ttl: seconds,
1715
- cancel: false
1716
- };
1717
- this.emit('setrange:before', ctx);
1718
-
1719
- if (ctx.cancel) {
1720
- return null;
1721
- }
1722
-
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;
1735
-
1736
- this._data[finalKey] = result;
1737
- this._types[finalKey] = 'string';
1738
-
1739
- if (finalSeconds > 0) {
1740
- this._expires[finalKey] = Date.now() + finalSeconds * 1000;
1741
- } else {
1742
- delete this._expires[finalKey];
1743
- }
1744
-
1745
- this.emit('setrange:after', {
1746
- key: finalKey,
1747
- value: finalValue,
1748
- ttl: finalSeconds,
1749
- result,
1750
- length,
1751
- success: true
1752
- });
1753
- return length;
1754
- } catch (error) {
1755
- this.emit('setrange:error', { key, value, ttl: seconds, error });
1756
- this.log('error', 'setrange操作失败:', error);
1757
- return null;
1758
- }
1759
- };
1760
-
1761
- /**
1762
- * 向字符串末尾追加内容
1763
- * @param {String} key 键名
1764
- * @param {String} append 要追加的内容
1765
- * @returns {Promise<Number>} 追加后的字符串长度,失败返回null
1766
- */
1767
- CacheBase.prototype.append = async function (key, append) {
1768
- try {
1769
- if (!key || typeof key !== 'string' || typeof append !== 'string') {
1770
- return null;
1771
- }
1772
-
1773
- // 检查是否过期
1774
- if (this._isExpired(key)) {
1775
- await this.del(key);
1776
- return null;
1777
- }
1778
-
1779
- let current = '';
1780
- if (key in this._data && typeof this._data[key] === 'string') {
1781
- current = this._data[key];
1782
- }
1783
-
1784
- current += append;
1785
- this._data[key] = current;
1786
- this._types[key] = 'string';
1787
-
1788
- return current.length;
1789
- } catch (error) {
1790
- this.log('error', `Failed to append for key ${key}:`, error);
1791
- return null;
1792
- }
1793
- };
1794
-
1795
- /**
1796
- * 获取字符串长度
1797
- * @param {String} key 键名
1798
- * @returns {Promise<Number>} 字符串长度,不存在返回0
1799
- */
1800
- CacheBase.prototype.strlen = async function (key) {
1801
- try {
1802
- if (!key || typeof key !== 'string') {
1803
- return 0;
1804
- }
1805
-
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
- // 检查服务状态
1929
- if (!this.is_inited || this.is_destroyed) {
1930
- this.log('error', 'Service is not initialized or has been destroyed');
1931
- return false;
1932
- }
1933
-
1934
- try {
1935
- // 参数验证:必须是对象
1936
- if (!obj || typeof obj !== 'object' || Array.isArray(obj)) {
1937
- throw new TypeError('参数必须是键值对对象');
1938
- }
1939
-
1940
- // 触发前置事件
1941
- const ctx = {
1942
- keyValuePairs: obj,
1943
- seconds,
1944
- cancel: false
1945
- };
1946
- this.emit('mset:before', ctx);
1947
-
1948
- if (ctx.cancel) {
1949
- this.log('debug', '[mset] 操作被事件处理器取消');
1950
- return false;
1951
- }
1952
-
1953
- const {
1954
- keyValuePairs: finalKeyValuePairs,
1955
- seconds: finalSeconds
1956
- } = ctx;
1957
-
1958
- // 先校验所有键的合法性,避免批量写入中途失败导致部分写入(保证原子性)
1959
- const entries = Object.entries(finalKeyValuePairs);
1960
- for (const [key] of entries) {
1961
- if (typeof key !== 'string' || !key) {
1962
- throw new TypeError('键必须是非空字符串');
1963
- }
1964
- }
1965
-
1966
- // 批量设置键值对
1967
- for (const [key, value] of entries) {
1968
- // 检查键是否过期
1969
- if (this._isExpired(key)) {
1970
- await this.del(key);
1971
- }
1972
-
1973
- this._data[key] = value;
1974
- this._types[key] = this._getDataTypes(value);
1975
-
1976
- if (finalSeconds > 0) {
1977
- this._expires[key] = Date.now() + finalSeconds * 1000;
1978
- } else {
1979
- delete this._expires[key];
1980
- }
1981
- }
1982
-
1983
- this.emit('mset:after', {
1984
- keyValuePairs: finalKeyValuePairs,
1985
- seconds: finalSeconds,
1986
- result: true,
1987
- success: true
1988
- });
1989
- return true;
1990
- } catch (error) {
1991
- this.emit('mset:error', { keyValuePairs: obj, error });
1992
- // 参数校验类错误属预期内,用 debug 级别避免误导;运行时错误保留 error 级别
1993
- if (error instanceof TypeError) {
1994
- this.log('debug', `[mset] 参数错误: ${error.message}`);
1995
- } else {
1996
- this.log('error', 'Failed to mset:', error);
1997
- }
1998
- return false;
1999
- }
2000
- };
2001
-
2002
- /**
2003
- * 批量获取多个键的值
2004
- * @param {Array} keys 键名数组
2005
- * @returns {Promise<Array>} 值数组,按键的顺序返回
2006
- */
2007
- CacheBase.prototype.mget = async function (keys) {
2008
- try {
2009
- this.emit('mget:before', { keys });
2010
-
2011
- if (!Array.isArray(keys)) {
2012
- return [];
2013
- }
2014
-
2015
- const results = [];
2016
- for (const key of keys) {
2017
- if (typeof key === 'string') {
2018
- results.push(await this.get(key));
2019
- } else {
2020
- results.push(null);
2021
- }
2022
- }
2023
-
2024
- this.emit('mget:after', { keys, results });
2025
- return results;
2026
- } catch (error) {
2027
- this.log('error', 'Failed to mget:', error);
2028
- return [];
2029
- }
2030
- };
2031
-
2032
- // ==================== 键管理 ====================
2033
-
2034
- /**
2035
- * 获取所有键名
2036
- * @param {String} [pattern] 键名匹配模式,支持*通配符
2037
- * @returns {Promise<Array>} 所有键名数组
2038
- */
2039
- CacheBase.prototype.keys = async function (pattern) {
2040
- try {
2041
- this.emit('keys:before', { pattern });
2042
-
2043
- const result = [];
2044
- for (const key in this._data) {
2045
- if (!this._isExpired(key)) {
2046
- if (!pattern || this._matchPattern(key, pattern)) {
2047
- result.push(key);
2048
- }
2049
- }
2050
- }
2051
-
2052
- this.emit('keys:after', { pattern, result });
2053
- return result;
2054
- } catch (error) {
2055
- this.log('error', 'Failed to get keys:', error);
2056
- return [];
2057
- }
2058
- };
2059
-
2060
- /**
2061
- * 检查键是否匹配指定模式
2062
- * @private
2063
- * @param {String} key 要检查的键名
2064
- * @param {String} pattern 匹配模式,支持*通配符
2065
- * @returns {Boolean} 是否匹配
2066
- */
2067
- CacheBase.prototype._matchPattern = function (key, pattern) {
2068
- try {
2069
- // 转义正则元字符(包含 ])
2070
- var regexPattern = pattern
2071
- .replace(/[.+?^${}()|[\]\\]/g, '\\$&')
2072
- .replace(/\*/g, '.*');
2073
- var regex = new RegExp('^' + regexPattern + '$');
2074
- return regex.test(key);
2075
- } catch (error) {
2076
- return false;
2077
- }
2078
- };
2079
-
2080
- /**
2081
- * 获取键的数量
2082
- * @returns {Promise<Number>} 键的数量
2083
- */
2084
- CacheBase.prototype.dbsize = async function () {
2085
- try {
2086
- let count = 0;
2087
- for (const key in this._data) {
2088
- if (!this._isExpired(key)) {
2089
- count++;
2090
- }
2091
- }
2092
- return count;
2093
- } catch (error) {
2094
- this.log('error', 'Failed to get dbsize:', error);
2095
- return 0;
2096
- }
2097
- };
2098
-
2099
- /**
2100
- * 清空所有数据库键
2101
- * @returns {Promise<Boolean>} 操作结果
2102
- */
2103
- CacheBase.prototype.flushdb = async function () {
2104
- try {
2105
- const ctx = {
2106
- cancel: false
2107
- };
2108
- this.emit('clear:before', ctx);
2109
-
2110
- if (ctx.cancel) {
2111
- return false;
2112
- }
2113
-
2114
- this._data = {};
2115
- this._expires = {};
2116
- this._types = {};
2117
-
2118
- this.emit('clear:after', { success: true });
2119
- return true;
2120
- } catch (error) {
2121
- this.log('error', 'Failed to flushdb:', error);
2122
- return false;
2123
- }
2124
- };
2125
-
2126
- /**
2127
- * 清空所有数据(flushdb 别名)
2128
- * @returns {Promise<Boolean>} 操作结果
2129
- */
2130
- CacheBase.prototype.clear = async function () {
2131
- return this.flushdb();
2132
- };
2133
-
2134
- // ==================== 列表操作 ====================
2135
-
2136
- /**
2137
- * 设置列表
2138
- * @param {String} key 键名
2139
- * @param {Array} value 列表值
2140
- * @param {Number} [seconds=0] 过期时间(秒)
2141
- * @returns {Promise<Number>} 列表长度
2142
- */
2143
- CacheBase.prototype.setForList = async function (key, value, seconds = 0) {
2144
- try {
2145
- if (!key || typeof key !== 'string') {
2146
- return 0;
2147
- }
2148
-
2149
- // 确保value是数组
2150
- let stringValues = [];
2151
- if (Array.isArray(value)) {
2152
- stringValues = value.map(item => String(item));
2153
- } else {
2154
- stringValues = [String(value)];
2155
- }
2156
-
2157
- const ctx = {
2158
- key,
2159
- value,
2160
- seconds,
2161
- cancel: false
2162
- };
2163
- this.emit('setForList:before', ctx);
2164
-
2165
- if (ctx.cancel) {
2166
- this.log('debug', `setForList操作被事件处理器取消 [${key}]`);
2167
- return 0;
2168
- }
2169
-
2170
- const {
2171
- key: finalKey,
2172
- value: finalValue,
2173
- seconds: finalSeconds
2174
- } = ctx;
2175
-
2176
- // 重新处理 finalValue
2177
- let finalStringValues = [];
2178
- if (Array.isArray(finalValue)) {
2179
- finalStringValues = finalValue.map(item => String(item));
2180
- } else {
2181
- finalStringValues = [String(finalValue)];
2182
- }
2183
-
2184
- // 检查键是否存在且未过期
2185
- if (finalKey in this._data && !this._isExpired(finalKey)) {
2186
- if (!Array.isArray(this._data[finalKey])) {
2187
- delete this._data[finalKey];
2188
- delete this._types[finalKey];
2189
- delete this._expires[finalKey];
2190
- }
2191
- }
2192
-
2193
- this._data[finalKey] = finalStringValues;
2194
- this._types[finalKey] = 'array';
2195
-
2196
- if (finalSeconds > 0) {
2197
- this._expires[finalKey] = Date.now() + finalSeconds * 1000;
2198
- } else {
2199
- delete this._expires[finalKey];
2200
- }
2201
-
2202
- const length = finalStringValues.length;
2203
- this.emit('setForList:after', {
2204
- key: finalKey,
2205
- value: finalStringValues,
2206
- seconds: finalSeconds,
2207
- result: length
2208
- });
2209
- return length;
2210
- } catch (error) {
2211
- this.emit('setForList:error', { key, value, seconds, error });
2212
- this.log('error', 'setForList操作失败:', error);
2213
- return 0;
2214
- }
2215
- };
2216
-
2217
- /**
2218
- * 向列表添加元素
2219
- * @param {String} key 键名
2220
- * @param {*} value 要添加的元素
2221
- * @param {Number} [seconds=0] 过期时间(秒)
2222
- * @returns {Promise<Number>} 添加后列表长度
2223
- */
2224
- CacheBase.prototype.addForList = async function (key, value, seconds = 0) {
2225
- try {
2226
- if (!key || typeof key !== 'string') {
2227
- return 0;
2228
- }
2229
-
2230
- const ctx = {
2231
- key,
2232
- value,
2233
- seconds,
2234
- cancel: false
2235
- };
2236
- this.emit('addForList:before', ctx);
2237
-
2238
- if (ctx.cancel) {
2239
- this.log('debug', `addForList操作被事件处理器取消 [${key}]`);
2240
- return 0;
2241
- }
2242
-
2243
- const {
2244
- key: finalKey,
2245
- value: finalValue,
2246
- seconds: finalSeconds
2247
- } = ctx;
2248
-
2249
- const stringValue = String(finalValue);
2250
-
2251
- // 检查键是否存在且未过期
2252
- if (finalKey in this._data && !this._isExpired(finalKey)) {
2253
- if (!Array.isArray(this._data[finalKey])) {
2254
- this._data[finalKey] = [stringValue];
2255
- this._types[finalKey] = 'array';
2256
- } else {
2257
- this._data[finalKey].push(stringValue);
2258
- }
2259
- } else {
2260
- this._data[finalKey] = [stringValue];
2261
- this._types[finalKey] = 'array';
2262
- }
2263
-
2264
- if (finalSeconds > 0) {
2265
- this._expires[finalKey] = Date.now() + finalSeconds * 1000;
2266
- } else if (finalKey in this._expires) {
2267
- delete this._expires[finalKey];
2268
- }
2269
-
2270
- const length = this._data[finalKey].length;
2271
- this.emit('addForList:after', {
2272
- key: finalKey,
2273
- value: stringValue,
2274
- seconds: finalSeconds,
2275
- result: length
2276
- });
2277
- return length;
2278
- } catch (error) {
2279
- this.emit('addForList:error', { key, value, seconds, error });
2280
- this.log('error', 'addForList操作失败:', error);
2281
- return 0;
2282
- }
2283
- };
2284
-
2285
- /**
2286
- * 检查列表中是否包含指定元素
2287
- * @param {String} key 键名
2288
- * @param {*} value 要检查的元素
2289
- * @returns {Promise<Boolean>} 是否包含
2290
- */
2291
- CacheBase.prototype.hasForList = async function (key, value) {
2292
- try {
2293
- if (!key || typeof key !== 'string') {
2294
- return false;
2295
- }
2296
-
2297
- const ctx = {
2298
- key,
2299
- value,
2300
- cancel: false
2301
- };
2302
- this.emit('hasForList:before', ctx);
2303
-
2304
- if (ctx.cancel) {
2305
- this.log('debug', `hasForList操作被事件处理器取消 [${key}]`);
2306
- return false;
2307
- }
2308
-
2309
- const {
2310
- key: finalKey,
2311
- value: finalValue
2312
- } = ctx;
2313
-
2314
- const stringValue = String(finalValue);
2315
-
2316
- if (finalKey in this._data && !this._isExpired(finalKey)) {
2317
- if (Array.isArray(this._data[finalKey])) {
2318
- const result = this._data[finalKey].includes(stringValue);
2319
- this.emit('hasForList:after', { key: finalKey, value: finalValue, result });
2320
- return result;
2321
- }
2322
- }
2323
-
2324
- this.emit('hasForList:after', { key: finalKey, value: finalValue, result: false });
2325
- return false;
2326
- } catch (error) {
2327
- this.emit('hasForList:error', { key, value, error });
2328
- this.log('error', 'hasForList操作失败:', error);
2329
- return false;
2330
- }
2331
- };
2332
-
2333
- /**
2334
- * 获取列表缓存
2335
- * @param {String} key 键名
2336
- * @param {Number} [start=0] 起始索引
2337
- * @param {Number} [end=-1] 结束索引
2338
- * @returns {Promise<Array>} 列表数组
2339
- */
2340
- CacheBase.prototype.getForList = async function (key, start = 0, end = -1) {
2341
- try {
2342
- this.emit('getForList:before', { key, start, end });
2343
-
2344
- if (!key || typeof key !== 'string') {
2345
- return [];
2346
- }
2347
-
2348
- // 检查是否过期
2349
- if (this._isExpired(key)) {
2350
- await this.del(key);
2351
- return [];
2352
- }
2353
-
2354
- // 检查键是否存在
2355
- if (!(key in this._data)) {
2356
- return [];
2357
- }
2358
-
2359
- // 检查是否为数组
2360
- if (Array.isArray(this._data[key])) {
2361
- const array = this._data[key];
2362
-
2363
- // 处理负索引,符合 Redis LRANGE 语义
2364
- const startIndex = start < 0
2365
- ? Math.max(0, array.length + start)
2366
- : start;
2367
- const endIndex = end < 0
2368
- ? Math.max(0, array.length + end + 1)
2369
- : end + 1;
2370
-
2371
- const result = array.slice(startIndex, endIndex);
2372
- this.emit('getForList:after', { key, start, end, result });
2373
- return result;
2374
- }
2375
-
2376
- return [];
2377
- } catch (error) {
2378
- this.log('error', 'getForList操作失败:', error);
2379
- return [];
2380
- }
2381
- };
2382
-
2383
- /**
2384
- * 清空列表
2385
- * @param {String} key 键名
2386
- * @returns {Promise<Boolean>} 操作结果
2387
- */
2388
- CacheBase.prototype.clearForList = async function (key) {
2389
- try {
2390
- const ctx = {
2391
- key,
2392
- cancel: false
2393
- };
2394
- this.emit('clearForList:before', ctx);
2395
-
2396
- if (ctx.cancel) {
2397
- return false;
2398
- }
2399
-
2400
- const {
2401
- key: finalKey
2402
- } = ctx;
2403
-
2404
- if (!finalKey || typeof finalKey !== 'string') {
2405
- return false;
2406
- }
2407
-
2408
- if (finalKey in this._data && !this._isExpired(finalKey)) {
2409
- if (Array.isArray(this._data[finalKey])) {
2410
- this._data[finalKey] = [];
2411
- this.emit('clearForList:after', { key: finalKey, result: true });
2412
- return true;
2413
- } else {
2414
- delete this._data[finalKey];
2415
- delete this._types[finalKey];
2416
- delete this._expires[finalKey];
2417
- this.emit('clearForList:after', { key: finalKey, result: false });
2418
- return false;
2419
- }
2420
- }
2421
-
2422
- this._data[finalKey] = [];
2423
- this._types[finalKey] = 'array';
2424
- this.emit('clearForList:after', { key: finalKey, result: true });
2425
- return true;
2426
- } catch (error) {
2427
- this.emit('clearForList:error', { key, error });
2428
- this.log('error', 'clearForList操作失败:', error);
2429
- return false;
2430
- }
2431
- };
2432
-
2433
- // ==================== 集合操作 ====================
2434
-
2435
- /**
2436
- * 将成员添加到集合
2437
- * @param {String} key 键
2438
- * @param {String|Array} members 成员或成员数组
2439
- * @returns {Promise<Number>} 添加的成员数量
2440
- */
2441
- CacheBase.prototype.sadd = async function (key, members) {
2442
- try {
2443
- const ctx = {
2444
- key,
2445
- members: Array.isArray(members) ? members : [members],
2446
- cancel: false
2447
- };
2448
- this.emit('sadd:before', ctx);
2449
-
2450
- if (ctx.cancel) {
2451
- return 0;
2452
- }
2453
-
2454
- const {
2455
- key: finalKey,
2456
- members: finalMembers
2457
- } = ctx;
2458
-
2459
- // 检查是否过期
2460
- if (this._isExpired(finalKey)) {
2461
- await this.del(finalKey);
2462
- }
2463
-
2464
- // 初始化集合
2465
- if (!(finalKey in this._data) || this._types[finalKey] !== 'set') {
2466
- this._data[finalKey] = new Set();
2467
- this._types[finalKey] = 'set';
2468
- }
2469
-
2470
- const set = this._data[finalKey];
2471
- let addedCount = 0;
2472
- for (const member of finalMembers) {
2473
- const memberStr = String(member);
2474
- if (!set.has(memberStr)) {
2475
- set.add(memberStr);
2476
- addedCount++;
2477
- }
2478
- }
2479
-
2480
- this.emit('sadd:after', {
2481
- key: finalKey,
2482
- members: finalMembers,
2483
- result: addedCount
2484
- });
2485
- return addedCount;
2486
- } catch (error) {
2487
- this.log('error', `添加集合成员失败 [${key}]:`, error);
2488
- this.emit('sadd:error', { key, members, error });
2489
- return 0;
2490
- }
2491
- };
2492
-
2493
- /**
2494
- * 从集合中移除成员
2495
- * @param {String} key 键
2496
- * @param {String|Array} members 成员或成员数组
2497
- * @returns {Promise<Number>} 移除的成员数量
2498
- */
2499
- CacheBase.prototype.srem = async function (key, members) {
2500
- try {
2501
- const ctx = {
2502
- key,
2503
- members: Array.isArray(members) ? members : [members],
2504
- cancel: false
2505
- };
2506
- this.emit('srem:before', ctx);
2507
-
2508
- if (ctx.cancel) {
2509
- return 0;
2510
- }
2511
-
2512
- const {
2513
- key: finalKey,
2514
- members: finalMembers
2515
- } = ctx;
2516
-
2517
- if (!(finalKey in this._data) || this._types[finalKey] !== 'set') {
2518
- return 0;
2519
- }
2520
-
2521
- const set = this._data[finalKey];
2522
- let removedCount = 0;
2523
- for (const member of finalMembers) {
2524
- const memberStr = String(member);
2525
- if (set.has(memberStr)) {
2526
- set.delete(memberStr);
2527
- removedCount++;
2528
- }
2529
- }
2530
-
2531
- // 如果集合为空,删除整个键
2532
- if (set.size === 0) {
2533
- delete this._data[finalKey];
2534
- delete this._types[finalKey];
2535
- delete this._expires[finalKey];
2536
- }
2537
-
2538
- this.emit('srem:after', {
2539
- key: finalKey,
2540
- members: finalMembers,
2541
- result: removedCount
2542
- });
2543
- return removedCount;
2544
- } catch (error) {
2545
- this.log('error', `移除集合成员失败 [${key}]:`, error);
2546
- this.emit('srem:error', { key, members, error });
2547
- return 0;
2548
- }
2549
- };
2550
-
2551
- /**
2552
- * 检查成员是否在集合中
2553
- * @param {String} key 键
2554
- * @param {String} member 成员
2555
- * @returns {Promise<Boolean>} 是否存在
2556
- */
2557
- CacheBase.prototype.sismember = async function (key, member) {
2558
- try {
2559
- this.emit('sismember:before', { key, member });
2560
-
2561
- if (!key || typeof key !== 'string') {
2562
- throw new TypeError('key必须是非空字符串');
2563
- }
2564
- if (member === undefined || member === null) {
2565
- throw new TypeError('member不能为undefined或null');
2566
- }
2567
-
2568
- if (this._isExpired(key)) {
2569
- await this.del(key);
2570
- return false;
2571
- }
2572
-
2573
- if (!(key in this._data) || this._types[key] !== 'set') {
2574
- return false;
2575
- }
2576
-
2577
- const memberStr = String(member);
2578
- const result = this._data[key].has(memberStr);
2579
- this.emit('sismember:after', { key, member, result });
2580
- return result;
2581
- } catch (error) {
2582
- this.emit('sismember:error', { key, member, error });
2583
- this.log('error', `检查集合成员失败 [${key}:${member}]:`, error);
2584
- return false;
2585
- }
2586
- };
2587
-
2588
- /**
2589
- * 获取集合中的所有成员
2590
- * @param {String} key 键
2591
- * @returns {Promise<Array>} 成员数组
2592
- */
2593
- CacheBase.prototype.smembers = async function (key) {
2594
- try {
2595
- this.emit('smembers:before', { key });
2596
-
2597
- if (this._isExpired(key)) {
2598
- await this.del(key);
2599
- return [];
2600
- }
2601
-
2602
- if (!(key in this._data) || this._types[key] !== 'set') {
2603
- return [];
2604
- }
2605
-
2606
- const result = Array.from(this._data[key]);
2607
- this.emit('smembers:after', { key, result });
2608
- return result;
2609
- } catch (error) {
2610
- this.log('error', `获取集合所有成员失败 [${key}]:`, error);
2611
- this.emit('smembers:error', { key, error });
2612
- return [];
2613
- }
2614
- };
2615
-
2616
- /**
2617
- * 获取集合中的成员数量
2618
- * @param {String} key 键
2619
- * @returns {Promise<Number>} 成员数量
2620
- */
2621
- CacheBase.prototype.scard = async function (key) {
2622
- try {
2623
- if (this._isExpired(key)) {
2624
- await this.del(key);
2625
- return 0;
2626
- }
2627
-
2628
- if (!(key in this._data) || this._types[key] !== 'set') {
2629
- return 0;
2630
- }
2631
-
2632
- return this._data[key].size;
2633
- } catch (error) {
2634
- this.log('error', `获取集合成员数量失败 [${key}]:`, error);
2635
- return 0;
2636
- }
2637
- };
2638
-
2639
- /**
2640
- * 获取多个集合的交集
2641
- * @param {...String} keys 键名列表
2642
- * @returns {Promise<Array>} 交集成员数组
2643
- */
2644
- CacheBase.prototype.sinter = async function (...keys) {
2645
- try {
2646
- if (keys.length === 0) {
2647
- return [];
2648
- }
2649
-
2650
- const sets = [];
2651
- for (const key of keys) {
2652
- if (this._isExpired(key)) {
2653
- await this.del(key);
2654
- return [];
2655
- }
2656
- if (!(key in this._data) || this._types[key] !== 'set') {
2657
- return [];
2658
- }
2659
- sets.push(this._data[key]);
2660
- }
2661
-
2662
- const result = Array.from(sets[0]).filter(member =>
2663
- sets.every(set => set.has(member))
2664
- );
2665
- return result;
2666
- } catch (error) {
2667
- this.log('error', '获取集合交集失败:', error);
2668
- return [];
2669
- }
2670
- };
2671
-
2672
- /**
2673
- * 获取多个集合的并集
2674
- * @param {...String} keys 键名列表
2675
- * @returns {Promise<Array>} 并集成员数组
2676
- */
2677
- CacheBase.prototype.sunion = async function (...keys) {
2678
- try {
2679
- if (keys.length === 0) {
2680
- return [];
2681
- }
2682
-
2683
- const resultSet = new Set();
2684
- for (const key of keys) {
2685
- if (this._isExpired(key)) {
2686
- await this.del(key);
2687
- continue;
2688
- }
2689
- if (key in this._data && this._types[key] === 'set') {
2690
- for (const member of this._data[key]) {
2691
- resultSet.add(member);
2692
- }
2693
- }
2694
- }
2695
-
2696
- return Array.from(resultSet);
2697
- } catch (error) {
2698
- this.log('error', '获取集合并集失败:', error);
2699
- return [];
2700
- }
2701
- };
2702
-
2703
- /**
2704
- * 获取多个集合的差集(第一个集合减去其余集合)
2705
- * @param {...String} keys 键名列表
2706
- * @returns {Promise<Array>} 差集成员数组
2707
- */
2708
- CacheBase.prototype.sdiff = async function (...keys) {
2709
- try {
2710
- if (keys.length === 0) {
2711
- return [];
2712
- }
2713
-
2714
- const firstKey = keys[0];
2715
- if (this._isExpired(firstKey)) {
2716
- await this.del(firstKey);
2717
- return [];
2718
- }
2719
- if (!(firstKey in this._data) || this._types[firstKey] !== 'set') {
2720
- return [];
2721
- }
2722
-
2723
- const firstSet = this._data[firstKey];
2724
- const otherSets = [];
2725
- for (let i = 1; i < keys.length; i++) {
2726
- const key = keys[i];
2727
- if (this._isExpired(key)) {
2728
- await this.del(key);
2729
- continue;
2730
- }
2731
- if (key in this._data && this._types[key] === 'set') {
2732
- otherSets.push(this._data[key]);
2733
- }
2734
- }
2735
-
2736
- const result = Array.from(firstSet).filter(member =>
2737
- !otherSets.some(set => set.has(member))
2738
- );
2739
- return result;
2740
- } catch (error) {
2741
- this.log('error', '获取集合差集失败:', error);
2742
- return [];
2743
- }
2744
- };
2745
-
2746
- // ==================== 有序集合操作 ====================
2747
-
2748
- /**
2749
- * 添加成员到有序集合
2750
- * @param {String} key 键
2751
- * @param {Number} score 分数
2752
- * @param {String} member 成员
2753
- * @returns {Promise<Boolean>} 是否成功(新成员返回true,更新返回false)
2754
- */
2755
- CacheBase.prototype.zadd = async function (key, score, member) {
2756
- try {
2757
- const ctx = {
2758
- key,
2759
- score,
2760
- member,
2761
- cancel: false
2762
- };
2763
- this.emit('zadd:before', ctx);
2764
-
2765
- if (ctx.cancel) {
2766
- return false;
2767
- }
2768
-
2769
- const {
2770
- key: finalKey,
2771
- score: finalScore,
2772
- member: finalMember
2773
- } = ctx;
2774
-
2775
- // 检查是否过期
2776
- if (this._isExpired(finalKey)) {
2777
- await this.del(finalKey);
2778
- }
2779
-
2780
- // 初始化有序集合
2781
- if (!(finalKey in this._data) || this._types[finalKey] !== 'zset') {
2782
- this._data[finalKey] = new Map();
2783
- this._types[finalKey] = 'zset';
2784
- }
2785
-
2786
- const map = this._data[finalKey];
2787
- const memberStr = String(finalMember);
2788
- const isNew = !map.has(memberStr);
2789
- map.set(memberStr, Number(finalScore));
2790
-
2791
- this.emit('zadd:after', {
2792
- key: finalKey,
2793
- score: finalScore,
2794
- member: finalMember,
2795
- success: isNew
2796
- });
2797
- return isNew;
2798
- } catch (error) {
2799
- this.log('error', `添加有序集合成员失败 [${key}]:`, error);
2800
- this.emit('zadd:error', { key, score, member, error });
2801
- return false;
2802
- }
2803
- };
2804
-
2805
- /**
2806
- * 从有序集合中移除成员
2807
- * @param {String} key 键
2808
- * @param {String|Array} members 成员或成员数组
2809
- * @returns {Promise<Number>} 移除的成员数量
2810
- */
2811
- CacheBase.prototype.zrem = async function (key, members) {
2812
- try {
2813
- const ctx = {
2814
- key,
2815
- members: Array.isArray(members) ? members : [members],
2816
- cancel: false
2817
- };
2818
- this.emit('zrem:before', ctx);
2819
-
2820
- if (ctx.cancel) {
2821
- return 0;
2822
- }
2823
-
2824
- const {
2825
- key: finalKey,
2826
- members: finalMembers
2827
- } = ctx;
2828
-
2829
- if (!(finalKey in this._data) || this._types[finalKey] !== 'zset') {
2830
- return 0;
2831
- }
2832
-
2833
- const map = this._data[finalKey];
2834
- let removedCount = 0;
2835
- for (const member of finalMembers) {
2836
- const memberStr = String(member);
2837
- if (map.has(memberStr)) {
2838
- map.delete(memberStr);
2839
- removedCount++;
2840
- }
2841
- }
2842
-
2843
- // 如果有序集合为空,删除整个键
2844
- if (map.size === 0) {
2845
- delete this._data[finalKey];
2846
- delete this._types[finalKey];
2847
- delete this._expires[finalKey];
2848
- }
2849
-
2850
- this.emit('zrem:after', {
2851
- key: finalKey,
2852
- members: finalMembers,
2853
- result: removedCount
2854
- });
2855
- return removedCount;
2856
- } catch (error) {
2857
- this.log('error', `移除有序集合成员失败 [${key}]:`, error);
2858
- this.emit('zrem:error', { key, members, error });
2859
- return 0;
2860
- }
2861
- };
2862
-
2863
- /**
2864
- * 获取有序集合中成员的分数
2865
- * @param {String} key 键
2866
- * @param {String} member 成员
2867
- * @returns {Promise<Number|null>} 分数,不存在返回null
2868
- */
2869
- CacheBase.prototype.zscore = async function (key, member) {
2870
- try {
2871
- if (this._isExpired(key)) {
2872
- await this.del(key);
2873
- return null;
2874
- }
2875
-
2876
- if (!(key in this._data) || this._types[key] !== 'zset') {
2877
- return null;
2878
- }
2879
-
2880
- const memberStr = String(member);
2881
- const map = this._data[key];
2882
- if (!map.has(memberStr)) {
2883
- return null;
2884
- }
2885
- return map.get(memberStr);
2886
- } catch (error) {
2887
- this.log('error', `获取有序集合成员分数失败 [${key}:${member}]:`, error);
2888
- return null;
2889
- }
2890
- };
1
+ const CacheBase = require('./lib/cachebase');
2
+ const Admin = require('mm_corejs').Admin;
2891
3
 
2892
4
  /**
2893
- * 获取有序集合中成员的排名(分数从小到大)
2894
- * @param {String} key 键
2895
- * @param {String} member 成员
2896
- * @returns {Promise<Number|null>} 排名,不存在返回null
5
+ * 缓存管理器实例(Admin 工厂)
2897
6
  */
2898
- CacheBase.prototype.zrank = async function (key, member) {
2899
- try {
2900
- if (this._isExpired(key)) {
2901
- await this.del(key);
2902
- return null;
2903
- }
2904
-
2905
- if (!(key in this._data) || this._types[key] !== 'zset') {
2906
- return null;
2907
- }
2908
-
2909
- const memberStr = String(member);
2910
- const map = this._data[key];
2911
- if (!map.has(memberStr)) {
2912
- return null;
2913
- }
2914
-
2915
- // 按分数从小到大排序
2916
- const sorted = Array.from(map.entries()).sort((a, b) => a[1] - b[1]);
2917
- const rank = sorted.findIndex(([m]) => m === memberStr);
2918
- return rank >= 0 ? rank : null;
2919
- } catch (error) {
2920
- this.log('error', `获取有序集合成员排名失败 [${key}:${member}]:`, error);
2921
- return null;
2922
- }
2923
- };
2924
-
2925
- /**
2926
- * 获取有序集合中指定范围的成员
2927
- * @param {String} key 键
2928
- * @param {Number} start 起始索引
2929
- * @param {Number} stop 结束索引
2930
- * @param {Boolean} [withScores=false] 是否返回分数
2931
- * @returns {Promise<Array>} 成员数组
2932
- */
2933
- CacheBase.prototype.zrange = async function (key, start, stop, withScores = false) {
2934
- try {
2935
- if (this._isExpired(key)) {
2936
- await this.del(key);
2937
- return [];
2938
- }
2939
-
2940
- if (!(key in this._data) || this._types[key] !== 'zset') {
2941
- return [];
2942
- }
2943
-
2944
- const map = this._data[key];
2945
- // 按分数从小到大排序
2946
- const sorted = Array.from(map.entries()).sort((a, b) => a[1] - b[1]);
2947
- const len = sorted.length;
2948
-
2949
- // 处理负索引
2950
- let startIndex = start < 0 ? Math.max(0, len + start) : start;
2951
- let endIndex = stop < 0 ? Math.max(0, len + stop + 1) : stop + 1;
2952
-
2953
- if (startIndex >= len) {
2954
- return [];
2955
- }
2956
-
2957
- const slice = sorted.slice(startIndex, endIndex);
2958
- if (withScores) {
2959
- return slice.map(([member, score]) => ({ member, score }));
2960
- }
2961
- return slice.map(([member]) => member);
2962
- } catch (error) {
2963
- this.log('error', `获取有序集合范围成员失败 [${key}]:`, error);
2964
- return [];
2965
- }
2966
- };
2967
-
2968
- /**
2969
- * 获取有序集合中的成员数量
2970
- * @param {String} key 键
2971
- * @returns {Promise<Number>} 成员数量
2972
- */
2973
- CacheBase.prototype.zcard = async function (key) {
2974
- try {
2975
- if (this._isExpired(key)) {
2976
- await this.del(key);
2977
- return 0;
2978
- }
2979
-
2980
- if (!(key in this._data) || this._types[key] !== 'zset') {
2981
- return 0;
2982
- }
2983
-
2984
- return this._data[key].size;
2985
- } catch (error) {
2986
- this.log('error', `获取有序集合成员数量失败 [${key}]:`, error);
2987
- return 0;
2988
- }
2989
- };
2990
-
2991
- /**
2992
- * 增加有序集合成员的分数
2993
- * @param {String} key 键
2994
- * @param {Number} increment 增量
2995
- * @param {String} member 成员
2996
- * @returns {Promise<Number>} 增加后的分数
2997
- */
2998
- CacheBase.prototype.zincrby = async function (key, increment, member) {
2999
- try {
3000
- // 检查是否过期
3001
- if (this._isExpired(key)) {
3002
- await this.del(key);
3003
- }
3004
-
3005
- // 初始化有序集合
3006
- if (!(key in this._data) || this._types[key] !== 'zset') {
3007
- this._data[key] = new Map();
3008
- this._types[key] = 'zset';
3009
- }
3010
-
3011
- const map = this._data[key];
3012
- const memberStr = String(member);
3013
- const currentScore = map.has(memberStr) ? map.get(memberStr) : 0;
3014
- const newScore = currentScore + Number(increment);
3015
- map.set(memberStr, newScore);
3016
-
3017
- return newScore;
3018
- } catch (error) {
3019
- this.log('error', `增加有序集合成员分数失败 [${key}:${member}]:`, error);
3020
- return null;
3021
- }
3022
- };
3023
-
3024
- // ==================== 发布订阅 ====================
3025
-
3026
- /**
3027
- * 消息订阅
3028
- * @param {String} channel 订阅的频道
3029
- * @param {Function} func 操作回调函数
3030
- * @returns {Promise<Boolean>} 订阅结果
3031
- */
3032
- CacheBase.prototype.subscribe = async function (channel, func) {
3033
- if (!channel || typeof channel !== 'string') {
3034
- throw new TypeError('channel必须是非空字符串');
3035
- }
3036
- if (typeof func !== 'function') {
3037
- throw new TypeError('func必须是函数');
3038
- }
3039
-
3040
- try {
3041
- this._channels[channel] = func;
3042
- return true;
3043
- } catch (err) {
3044
- this.log('error', `订阅频道 ${channel} 失败:`, err);
3045
- return false;
3046
- }
3047
- };
3048
-
3049
- /**
3050
- * 取消订阅
3051
- * @param {String} channel 订阅的频道
3052
- * @returns {Promise<Boolean>} 取消订阅结果
3053
- */
3054
- CacheBase.prototype.unsubscribe = async function (channel) {
3055
- if (!channel || typeof channel !== 'string') {
3056
- throw new TypeError('channel必须是非空字符串');
3057
- }
3058
-
3059
- try {
3060
- delete this._channels[channel];
3061
- return true;
3062
- } catch (err) {
3063
- this.log('error', `取消订阅频道 ${channel} 失败:`, err);
3064
- return false;
3065
- }
3066
- };
3067
-
3068
- /**
3069
- * 发布消息
3070
- * @param {String} channel 频道
3071
- * @param {String} message 消息
3072
- * @returns {Promise<Number>} 接收消息的订阅者数量
3073
- */
3074
- CacheBase.prototype.publish = async function (channel, message) {
3075
- if (!channel || typeof channel !== 'string') {
3076
- throw new TypeError('channel必须是非空字符串');
3077
- }
3078
- if (message === undefined || message === null) {
3079
- throw new TypeError('message不能为undefined或null');
3080
- }
3081
-
3082
- try {
3083
- const func = this._channels[channel];
3084
- if (func) {
3085
- try {
3086
- func(message);
3087
- } catch (err) {
3088
- this.log('error', `处理频道 ${channel} 的消息时出错:`, err);
3089
- }
3090
- return 1;
3091
- }
3092
- return 0;
3093
- } catch (err) {
3094
- this.log('error', `发布消息到频道 ${channel} 失败:`, err);
3095
- return 0;
3096
- }
3097
- };
3098
-
3099
- /**
3100
- * 订阅消息处理, 如果没有订阅函数,则其他的订阅消息会进入该函数
3101
- * @param {String} channel 频道
3102
- * @param {String} message 消息
3103
- */
3104
- CacheBase.prototype.main = async function (channel, message) {
3105
- // 默认实现,可由子类重写
3106
- this.log('debug', `收到未处理的消息 [${channel}]:`, message);
3107
- };
3108
-
3109
- /**
3110
- * 消息通知
3111
- * @param {String} channel 频道
3112
- * @param {String} message 消息
3113
- */
3114
- CacheBase.prototype.message = function (channel, message) {
3115
- var func = this._channels[channel];
3116
- if (func) {
3117
- try {
3118
- func(message);
3119
- } catch (err) {
3120
- this.log('error', `处理频道 ${channel} 的消息时出错:`, err);
3121
- }
3122
- } else {
3123
- this.main(channel, message);
3124
- }
3125
- };
3126
-
3127
- // ==================== 连接池管理 ====================
3128
-
3129
- /**
3130
- * 确保连接池对象存在
3131
- */
3132
- global.$ = global.$ || {};
3133
- if (!$.pool) {
3134
- $.pool = {};
3135
- }
3136
- if (!$.pool.cachebase) {
3137
- $.pool.cachebase = {};
3138
- }
7
+ const admin = new Admin();
8
+ admin.setClass(CacheBase);
3139
9
 
3140
10
  /**
3141
11
  * @description CacheBase管理器,用于创建缓存
@@ -3144,32 +14,15 @@ if (!$.pool.cachebase) {
3144
14
  * @return {Object} 返回一个CacheBase类实例
3145
15
  */
3146
16
  function cachebaseAdmin(scope, config) {
3147
- if (!global.$) {
3148
- global.$ = {};
3149
- }
3150
- if (!$.pool) {
3151
- $.pool = {};
3152
- }
3153
- if (!$.pool.cachebase) {
3154
- $.pool.cachebase = {};
3155
- }
3156
17
  if (!scope) {
3157
- scope = 'sys';
3158
- }
3159
- var obj = $.pool.cachebase[scope];
3160
- if (!obj) {
3161
- const dbConfig = {
3162
- scope: scope,
3163
- ...(typeof config === 'object' && config ? config : {})
3164
- };
3165
- $.pool.cachebase[scope] = new CacheBase(dbConfig);
3166
- obj = $.pool.cachebase[scope];
18
+ scope = 'os';
3167
19
  }
3168
- return obj;
20
+ return admin.get(scope, config);
3169
21
  }
3170
22
 
3171
23
  // 导出类
3172
24
  module.exports = {
3173
25
  CacheBase,
26
+ admin,
3174
27
  cachebaseAdmin
3175
- };
28
+ };