mm_cachebase 2.0.4 → 2.0.5

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