mm_cachebase 1.8.5 → 1.8.7

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,1632 +1,1765 @@
1
- const EventEmitter = require('events');
2
- require('mm_logs');
3
-
4
- class CacheBase extends EventEmitter {
5
- /**
6
- * 构造函数,初始化缓存
7
- * @param {Object} config - 配置选项
8
- */
9
- constructor(scope = 'sys', config = {}) {
10
- super();
11
-
12
- // 持久化相关属性
13
- this.scope = scope;
14
- // 配置选项
15
- this.config = {
16
- dir: './cache/'.fullname(),
17
- expire: 0,
18
- max: 0,
19
- persist: false, // 是否启用持久化
20
- saveInterval: 60000 // 保存间隔(毫秒)
21
- };
22
- // 数据存储对象
23
- this.data = {};
24
- // 键列表,用于存储所有的键名
25
- this.list = [];
26
- this.saveTimer = null;
27
- this.dir = this.config.dir;
28
- this.setConfig(config);
29
- this.init();
30
- }
31
-
32
- /**
33
- * 初始化方法
34
- * @private
35
- */
36
- init() {
37
- // 如果启用了持久化,尝试加载数据
38
- if (this.config.persist) {
39
- this._loadData().catch(error => {
40
- $.log.error('[cachebase] 数据加载失败:', error);
41
- });
42
- }
43
- }
44
-
45
- /**
46
- * 启动定时保存功能
47
- * @returns {Promise<boolean>} 是否成功连接
48
- */
49
- async connect() {
50
- try {
51
- this.emit('connect_before');
52
-
53
- // 只有在启用持久化时才启动定时保存
54
- if (this.config.persist) {
55
- // 先立即保存一次
56
- await this._saveData();
57
-
58
- // 清除已存在的定时器(如果有)
59
- if (this.saveTimer) {
60
- clearInterval(this.saveTimer);
61
- }
62
-
63
- // 设置新的定时器
64
- const interval = this.config.saveInterval || 60000;
65
- this.saveTimer = setInterval(() => {
66
- this._saveData().catch(error => {
67
- $.log.error('[cachebase] 定时保存失败:', error);
68
- });
69
- }, interval);
70
-
71
- $.log.info(`[cachebase] 定时保存已启动,间隔 ${interval}ms`);
72
- }
73
-
74
- this.emit('connect_after');
75
- return true;
76
- } catch (error) {
77
- this.emit('connect_error', { error });
78
- $.log.error('[cachebase] 连接失败:', error);
79
- return false;
80
- }
81
- }
82
-
83
- /**
84
- * 手动保存数据到文件
85
- * @returns {Promise<boolean>} 是否保存成功
86
- */
87
- async save() {
88
- try {
89
- this.emit('save_before');
90
- await this._saveData();
91
- this.emit('save_after');
92
- return true;
93
- } catch (error) {
94
- this.emit('save_error', { error });
95
- $.log.error('[cachebase] 保存失败:', error);
96
- return false;
97
- }
98
- }
99
-
100
- /**
101
- * 保存数据到文件
102
- * @private
103
- * @returns {Promise<void>}
104
- */
105
- async _saveData() {
106
- if (!this.config.persist) return;
107
-
108
- try {
109
- // 确保目录存在
110
- this.dir.addDir();
111
-
112
- // 过滤过期数据
113
- const now = Date.now();
114
- const validData = {};
115
- const validList = [];
116
-
117
- for (const key of this.list) {
118
- if (this.data.hasOwnProperty(key)) {
119
- const item = this.data[key];
120
- // 检查是否过期
121
- if (!item.expire || item.expire === 0 || item.expire > now) {
122
- validData[key] = item;
123
- validList.push(key);
124
- }
125
- }
126
- }
127
-
128
- // 准备保存的数据
129
- const saveData = {
130
- data: validData,
131
- list: validList,
132
- timestamp: now
133
- };
134
-
135
- // 保存到文件
136
- const file = `./${this.scope}.cache.json`.fullname(this.dir);
137
- file.saveJson(saveData);
138
- $.log.debug(`[cachebase] 数据已保存到 ${file},共 ${validList.length} 个键`);
139
- } catch (error) {
140
- $.log.error('[cachebase] 数据保存失败:', error);
141
- throw error;
142
- }
143
- }
144
-
145
- /**
146
- * 从文件加载数据
147
- * @private
148
- * @returns {Promise<void>}
149
- */
150
- async _loadData() {
151
- if (!this.config.persist) return;
152
-
153
- try {
154
- // 确保目录存在
155
- this.dir.addDir();
156
-
157
- // 只加载当前scope的缓存文件
158
- const file = `./${this.scope}.cache.json`.fullname(this.dir);
159
-
160
- if (!file.hasFile()) {
161
- $.log.debug(`[cachebase] 未找到当前scope的缓存文件: ${file}`);
162
- return;
163
- }
164
-
165
- $.log.debug(`[cachebase] 尝试从 ${file} 加载数据`);
166
-
167
- try {
168
- try {
169
- const savedData = file.loadJson();
170
- $.log.debug(`[cachebase] 解析数据成功,包含data: ${savedData.data ? '是' : '否'}, list: ${savedData.list ? '是' : '否'}`);
171
-
172
- if (savedData.data && savedData.list && Array.isArray(savedData.list)) {
173
- $.log.debug(`[cachebase] 数据格式正确,list长度: ${savedData.list.length}`);
174
-
175
- // 验证数据并过滤过期项
176
- const now = Date.now();
177
- const validData = {};
178
- const validList = [];
179
-
180
- for (const key of savedData.list) {
181
- if (savedData.data.hasOwnProperty(key)) {
182
- const item = savedData.data[key];
183
- $.log.debug(`[cachebase] 检查键 ${key},过期时间: ${item.expire || 0}`);
184
- // 检查是否过期
185
- if (!item.expire || item.expire === 0 || item.expire > now) {
186
- validData[key] = item;
187
- validList.push(key);
188
- } else {
189
- $.log.debug(`[cachebase] 键 ${key} 已过期,跳过`);
190
- }
191
- }
192
- }
193
-
194
- // 直接使用加载的数据替换当前数据
195
- this.data = validData;
196
- this.list = validList;
197
- this.dict = this.data; // 确保dict指向最新的data
198
-
199
- $.log.info(`[cachebase] 从 ${this.scope}.cache.json 加载数据成功,共 ${validList.length} 个键`);
200
- if (validList.length > 0) {
201
- $.log.debug(`[cachebase] 加载的键: ${validList.join(', ')}`);
202
- }
203
- } else {
204
- $.log.warn(`[cachebase] 缓存文件格式不正确,缺少必要字段`);
205
- }
206
- } catch (parseError) {
207
- $.log.error(`[cachebase] 文件JSON解析失败:`, parseError);
208
- }
209
- } catch (readError) {
210
- $.log.error(`[cachebase] 读取文件失败:`, readError);
211
- }
212
- } catch (error) {
213
- $.log.error('[cachebase] 数据加载失败:', error);
214
- throw error;
215
- }
216
- }
217
-
218
- /**
219
- * 设置缓存配置
220
- * @param {Object} config
221
- */
222
- setConfig(config) {
223
- if (config) {
224
- Object.assign(this.config, config);
225
- this.dir = this.config.dir.fullname(__dirname)
226
- }
227
- }
228
-
229
- /**
230
- * 检查键是否过期
231
- * @private
232
- * @param {string} key - 键名
233
- * @returns {boolean} - 是否已过期
234
- */
235
- _checkExpire(key) {
236
- if (this.data.hasOwnProperty(key)) {
237
- const item = this.data[key];
238
- if (item && typeof item === 'object' && item.expire > 0) {
239
- // 检查是否过期
240
- if (Date.now() > item.expire) {
241
- // 删除过期键
242
- this.del(key);
243
- return true;
244
- }
245
- }
246
- }
247
- return false;
248
- }
249
-
250
- /**
251
- * 设置键的过期时间
252
- * @private
253
- * @param {string} key - 键名
254
- * @param {number} seconds - 过期时间(秒)
255
- */
256
- _setExpire(key, seconds) {
257
- if (this.data.hasOwnProperty(key)) {
258
- const item = this.data[key];
259
- if (item && typeof item === 'object') {
260
- item.expire = seconds > 0 ? Date.now() + seconds * 1000 : 0;
261
- }
262
- }
263
- }
264
-
265
- /**
266
- * 设置键值 - 兼容方法
267
- * @param {string} key - 键名
268
- * @param {*} value - 键值
269
- * @param {number} expire - 过期时间(秒),0表示永不过期
270
- * @returns {Promise<string>} - 返回'OK'
271
- * @note 确保行为与Redis兼容,支持所有Redis的参数模式
272
- */
273
- async set(key, value, expire = 0) {
274
- try {
275
- this.emit('set_before', { key, value, expire });
276
-
277
- // 设置值和过期时间
278
- this.data[key] = { value, expire: expire > 0 ? Date.now() + expire * 1000 : 0 };
279
-
280
- // 如果键不存在于列表中,添加到列表
281
- if (!this.list.includes(key)) {
282
- this.list.push(key);
283
- }
284
-
285
- this.emit('set_after', { key, value, expire, result: 'OK' });
286
- return 'OK';
287
- } catch (error) {
288
- this.emit('set_error', { key, value, expire, error });
289
- $.log.error('[cachebase] set操作失败:', error);
290
- throw error;
291
- }
292
- }
293
-
294
- /**
295
- * 获取键值
296
- * @param {string} key - 键名
297
- * @returns {Promise<*>} - 返回键值,不存在返回undefined
298
- */
299
- async get(key) {
300
- try {
301
- // 检查键是否存在
302
- if (!this.data.hasOwnProperty(key)) {
303
- return undefined;
304
- }
305
-
306
- const item = this.data[key];
307
-
308
- // 检查是否过期
309
- if (item.expire > 0 && Date.now() > item.expire) {
310
- // 删除过期键
311
- delete this.data[key];
312
- const index = this.list.indexOf(key);
313
- if (index !== -1) {
314
- this.list.splice(index, 1);
315
- }
316
- return undefined;
317
- }
318
-
319
- return item.value;
320
- } catch (error) {
321
- $.log.error('[cachebase] get操作失败:', error);
322
- throw error;
323
- }
324
- }
325
-
326
- /**
327
- * 删除键 - 兼容方法
328
- * @param {string} key - 键名
329
- * @returns {Promise<number>} - 返回删除的键数量
330
- * @note 确保与Redis行为一致,只删除存在且未过期的键
331
- */
332
- async del(key) {
333
- try {
334
- this.emit('del_before', { key });
335
-
336
- // 检查键是否存在且未过期
337
- let deleted = 0;
338
- if (this.data.hasOwnProperty(key)) {
339
- const item = this.data[key];
340
- // 检查是否过期
341
- if (!(item.expire > 0 && Date.now() > item.expire)) {
342
- delete this.data[key];
343
- const index = this.list.indexOf(key);
344
- if (index !== -1) {
345
- this.list.splice(index, 1);
346
- }
347
- deleted = 1;
348
- }
349
- }
350
-
351
- this.emit('del_after', { key, result: deleted });
352
- return deleted;
353
- } catch (error) {
354
- this.emit('del_error', { key, error });
355
- $.log.error('[cachebase] del操作失败:', error);
356
- throw error;
357
- }
358
- }
359
-
360
- /**
361
- * 检查键是否存在 - 兼容方法
362
- * @param {string|Array<string>} key - 键名或键名数组
363
- * @returns {Promise<number>} - 返回存在的键数量
364
- * @note 确保与Redis行为一致,只计算存在且未过期的键
365
- */
366
- async exists(key) {
367
- try {
368
- let existsCount = 0;
369
-
370
- // 检查输入类型并处理
371
- if (Array.isArray(key)) {
372
- // 处理多键检查
373
- for (const k of key) {
374
- if (this.data.hasOwnProperty(k)) {
375
- // 检查是否过期
376
- const item = this.data[k];
377
- if (!(item.expire > 0 && Date.now() > item.expire)) {
378
- existsCount++;
379
- }
380
- }
381
- }
382
- } else {
383
- // 处理单键检查
384
- if (this.data.hasOwnProperty(key)) {
385
- // 检查是否过期
386
- const item = this.data[key];
387
- if (!(item.expire > 0 && Date.now() > item.expire)) {
388
- existsCount = 1;
389
- }
390
- }
391
- }
392
-
393
- return existsCount;
394
- } catch (error) {
395
- $.log.error('[cachebase] exists操作失败:', error);
396
- throw error;
397
- }
398
- }
399
-
400
- /**
401
- * 设置哈希表字段
402
- * @param {string} key - 键名
403
- * @param {string} field - 字段名
404
- * @param {*} value - 字段值
405
- * @returns {Promise<number>} - 返回1表示新建字段,0表示更新字段
406
- */
407
- async hset(key, field, value) {
408
- try {
409
- this.emit('hset_before', { key, field, value });
410
-
411
- // 初始化哈希表
412
- if (!this.data.hasOwnProperty(key)) {
413
- this.data[key] = { value: {}, expire: 0 };
414
- this.list.push(key);
415
- } else if (this.data[key].expire > 0 && Date.now() > this.data[key].expire) {
416
- // 键已过期,重新初始化
417
- this.data[key] = { value: {}, expire: 0 };
418
- } else if (typeof this.data[key].value !== 'object') {
419
- // 类型错误
420
- throw new Error(`[cachebase] 键 ${key} 不是哈希表类型`);
421
- }
422
-
423
- // 检查字段是否存在
424
- const isNewField = !this.data[key].value.hasOwnProperty(field);
425
- this.data[key].value[field] = value;
426
-
427
- const result = isNewField ? 1 : 0;
428
- this.emit('hset_after', { key, field, value, result });
429
- return result;
430
- } catch (error) {
431
- this.emit('hset_error', { key, field, value, error });
432
- $.log.error('[cachebase] hset操作失败:', error);
433
- throw error;
434
- }
435
- }
436
-
437
- /**
438
- * 获取哈希表字段
439
- * @param {string} key - 键名
440
- * @param {string} field - 字段名
441
- * @returns {Promise<*>} - 返回字段值,不存在返回undefined
442
- */
443
- async hget(key, field) {
444
- try {
445
- // 检查键是否存在且未过期
446
- if (!this.data.hasOwnProperty(key) ||
447
- (this.data[key].expire > 0 && Date.now() > this.data[key].expire) ||
448
- typeof this.data[key].value !== 'object') {
449
- return undefined;
450
- }
451
-
452
- return this.data[key].value[field];
453
- } catch (error) {
454
- $.log.error('[cachebase] hget操作失败:', error);
455
- throw error;
456
- }
457
- }
458
-
459
- /**
460
- * 获取哈希表所有字段
461
- * @param {string} key - 键名
462
- * @returns {Promise<Object>} - 返回哈希表对象,不存在或不是哈希表返回{}
463
- */
464
- async hgetall(key) {
465
- try {
466
- // 检查键是否存在且未过期
467
- if (!this.data.hasOwnProperty(key) ||
468
- (this.data[key].expire > 0 && Date.now() > this.data[key].expire) ||
469
- typeof this.data[key].value !== 'object') {
470
- return {};
471
- }
472
-
473
- // 返回哈希表的副本
474
- return { ...this.data[key].value };
475
- } catch (error) {
476
- $.log.error('[cachebase] hgetall操作失败:', error);
477
- throw error;
478
- }
479
- }
480
-
481
- /**
482
- * 删除哈希表字段
483
- * @param {string} key - 键名
484
- * @param {string} field - 字段名
485
- * @returns {Promise<number>} - 始终返回0以保持兼容性,但实际会删除字段
486
- */
487
- async hdel(key, field) {
488
- try {
489
- this.emit('hdel_before', { key, field });
490
-
491
- // 检查键是否存在且未过期
492
- if (this.data.hasOwnProperty(key) &&
493
- !(this.data[key].expire > 0 && Date.now() > this.data[key].expire) &&
494
- typeof this.data[key].value === 'object' &&
495
- this.data[key].value.hasOwnProperty(field)) {
496
-
497
- // 删除字段
498
- delete this.data[key].value[field];
499
-
500
- // 检查哈希表是否为空
501
- const hashKeys = Object.keys(this.data[key].value);
502
- if (hashKeys.length === 0) {
503
- // 删除空哈希表
504
- delete this.data[key];
505
- const index = this.list.indexOf(key);
506
- if (index !== -1) {
507
- this.list.splice(index, 1);
508
- }
509
- }
510
- }
511
-
512
- // 始终返回0以保持兼容性
513
- this.emit('hdel_after', { key, field, result: 0 });
514
- return 0;
515
- } catch (error) {
516
- this.emit('hdel_error', { key, field, error });
517
- $.log.error('[cachebase] hdel操作失败:', error);
518
- throw error;
519
- }
520
- }
521
-
522
- /**
523
- * 批量设置键值
524
- * @param {Object} keyValues - 键值对对象
525
- * @returns {Promise<string>} - 返回'OK'
526
- */
527
- async mset(keyValues) {
528
- try {
529
- this.emit('mset_before', { keyValues });
530
-
531
- if (typeof keyValues !== 'object' || keyValues === null) {
532
- throw new Error('[cachebase] mset参数必须是对象');
533
- }
534
-
535
- // 批量设置键值
536
- for (const key in keyValues) {
537
- if (keyValues.hasOwnProperty(key)) {
538
- this.data[key] = { value: keyValues[key], expire: 0 };
539
- if (!this.list.includes(key)) {
540
- this.list.push(key);
541
- }
542
- }
543
- }
544
-
545
- this.emit('mset_after', { keyValues, result: 'OK' });
546
- return 'OK';
547
- } catch (error) {
548
- this.emit('mset_error', { keyValues, error });
549
- $.log.error('[cachebase] mset操作失败:', error);
550
- throw error;
551
- }
552
- }
553
-
554
- /**
555
- * 批量获取键值
556
- * @param {Array<string>} keys - 键名数组
557
- * @returns {Promise<Array>} - 返回值数组
558
- */
559
- async mget(keys) {
560
- try {
561
- if (!Array.isArray(keys)) {
562
- throw new Error('[cachebase] mget参数必须是数组');
563
- }
564
-
565
- // 批量获取键值
566
- const results = [];
567
- for (const key of keys) {
568
- if (this.data.hasOwnProperty(key)) {
569
- // 检查是否过期
570
- const item = this.data[key];
571
- if (item.expire === 0 || Date.now() <= item.expire) {
572
- results.push(item.value);
573
- } else {
574
- // 删除过期键
575
- delete this.data[key];
576
- const index = this.list.indexOf(key);
577
- if (index !== -1) {
578
- this.list.splice(index, 1);
579
- }
580
- results.push(undefined);
581
- }
582
- } else {
583
- results.push(undefined);
584
- }
585
- }
586
-
587
- return results;
588
- } catch (error) {
589
- $.log.error('[cachebase] mget操作失败:', error);
590
- throw error;
591
- }
592
- }
593
-
594
- /**
595
- * 移除键的过期时间,使其永不过期
596
- * @param {string} key - 键名
597
- * @returns {Promise<number>} - 返回1表示成功移除过期时间,0表示键不存在
598
- */
599
- async persist(key) {
600
- try {
601
- this.emit('persist_before', { key });
602
-
603
- // 检查键是否存在
604
- if (!this.data.hasOwnProperty(key)) {
605
- this.emit('persist_after', { key, result: 0 });
606
- return 0;
607
- }
608
-
609
- const item = this.data[key];
610
-
611
- // 检查是否已过期
612
- if (item.expire > 0 && Date.now() > item.expire) {
613
- // 键已过期,删除并返回0
614
- delete this.data[key];
615
- const index = this.list.indexOf(key);
616
- if (index !== -1) {
617
- this.list.splice(index, 1);
618
- }
619
- this.emit('persist_after', { key, result: 0 });
620
- return 0;
621
- }
622
-
623
- // 设置为永不过期
624
- item.expire = 0;
625
-
626
- this.emit('persist_after', { key, result: 1 });
627
- return 1;
628
- } catch (error) {
629
- this.emit('persist_error', { key, error });
630
- $.log.error('[cachebase] persist操作失败:', error);
631
- throw error;
632
- }
633
- }
634
-
635
- /**
636
- * 获取键的剩余生存时间 - 兼容方法
637
- * @param {string} key - 键名
638
- * @returns {Promise<number>} - 返回剩余时间(秒),-1表示永不过期,-2表示键不存在
639
- * @note 确保与Redis行为完全一致,精确计算剩余时间
640
- */
641
- async ttl(key) {
642
- try {
643
- // 检查键是否存在
644
- if (!this.data.hasOwnProperty(key)) {
645
- return -2;
646
- }
647
-
648
- const item = this.data[key];
649
-
650
- // 检查是否过期
651
- if (item.expire > 0) {
652
- if (Date.now() > item.expire) {
653
- // 键已过期,删除并返回-2
654
- delete this.data[key];
655
- const index = this.list.indexOf(key);
656
- if (index !== -1) {
657
- this.list.splice(index, 1);
658
- }
659
- return -2;
660
- } else {
661
- // 精确计算剩余时间
662
- const remainingMs = item.expire - Date.now();
663
- // 确保返回整数秒,向下取整
664
- const ttl = Math.floor(remainingMs / 1000);
665
- return ttl;
666
- }
667
- } else {
668
- // 永不过期
669
- return -1;
670
- }
671
- } catch (error) {
672
- $.log.error('[cachebase] ttl操作失败:', error);
673
- throw error;
674
- }
675
- }
676
-
677
- /**
678
- * 设置键的过期时间
679
- * @param {string} key - 键名
680
- * @param {number} seconds - 过期时间(秒)
681
- * @returns {Promise<number>} - 返回1表示设置成功,0表示键不存在
682
- */
683
- async expire(key, seconds) {
684
- try {
685
- this.emit('expire_before', { key, seconds });
686
-
687
- // 检查键是否存在
688
- if (!this.data.hasOwnProperty(key)) {
689
- this.emit('expire_after', { key, seconds, result: 0 });
690
- return 0;
691
- }
692
-
693
- // 检查是否已过期
694
- const item = this.data[key];
695
- if (item.expire > 0 && Date.now() > item.expire) {
696
- // 键已过期,删除并返回0
697
- delete this.data[key];
698
- const index = this.list.indexOf(key);
699
- if (index !== -1) {
700
- this.list.splice(index, 1);
701
- }
702
- this.emit('expire_after', { key, seconds, result: 0 });
703
- return 0;
704
- }
705
-
706
- // 设置过期时间
707
- this.data[key].expire = Date.now() + seconds * 1000;
708
-
709
- this.emit('expire_after', { key, seconds, result: 1 });
710
- return 1;
711
- } catch (error) {
712
- this.emit('expire_error', { key, seconds, error });
713
- $.log.error('[cachebase] expire操作失败:', error);
714
- throw error;
715
- }
716
- }
717
-
718
- /**
719
- * 移除键的过期时间
720
- * @param {string} key - 键名
721
- * @returns {Promise<number>} - 返回1表示移除成功,0表示键不存在或没有过期时间
722
- */
723
- async persist(key) {
724
- try {
725
- this.emit('persist_before', { key });
726
-
727
- // 检查键是否存在
728
- if (!this.data.hasOwnProperty(key)) {
729
- this.emit('persist_after', { key, result: 0 });
730
- return 0;
731
- }
732
-
733
- // 检查是否已过期
734
- const item = this.data[key];
735
- if (item.expire > 0 && Date.now() > item.expire) {
736
- // 键已过期,删除并返回0
737
- delete this.data[key];
738
- const index = this.list.indexOf(key);
739
- if (index !== -1) {
740
- this.list.splice(index, 1);
741
- }
742
- this.emit('persist_after', { key, result: 0 });
743
- return 0;
744
- }
745
-
746
- // 检查是否有过期时间
747
- if (item.expire === 0) {
748
- // 没有过期时间,返回0
749
- this.emit('persist_after', { key, result: 0 });
750
- return 0;
751
- }
752
-
753
- // 移除过期时间
754
- this.data[key].expire = 0;
755
-
756
- this.emit('persist_after', { key, result: 1 });
757
- return 1;
758
- } catch (error) {
759
- this.emit('persist_error', { key, error });
760
- $.log.error('[cachebase] persist操作失败:', error);
761
- throw error;
762
- }
763
- }
764
-
765
- /**
766
- * 获取所有键名
767
- * @param {string} pattern - 模式匹配
768
- * @returns {Promise<Array>} - 返回键名数组
769
- */
770
- async keys(pattern = '*') {
771
- try {
772
- // 支持通配符匹配
773
- let filteredKeys = [];
774
- if (pattern === '*') {
775
- // 通配符匹配所有
776
- filteredKeys = [...this.list];
777
- } else {
778
- // 简单的通配符匹配
779
- const regex = new RegExp(pattern.replace(/\*/g, '.*'));
780
- filteredKeys = this.list.filter(key => regex.test(key));
781
- }
782
-
783
- return filteredKeys;
784
- } catch (error) {
785
- $.log.error('[cachebase] keys操作失败:', error);
786
- throw error;
787
- }
788
- }
789
-
790
- /**
791
- * 获取键数量
792
- * @returns {Promise<number>} - 返回键数量
793
- */
794
- async dbsize() {
795
- try {
796
- // 过滤掉已过期的键
797
- let count = 0;
798
- for (const key of this.list) {
799
- if (this.data.hasOwnProperty(key)) {
800
- const item = this.data[key];
801
- if (item.expire === 0 || Date.now() <= item.expire) {
802
- count++;
803
- }
804
- }
805
- }
806
-
807
- return count;
808
- } catch (error) {
809
- $.log.error('[cachebase] dbsize操作失败:', error);
810
- throw error;
811
- }
812
- }
813
-
814
- /**
815
- * 设置键值并返回旧值
816
- * @param {string} key - 键名
817
- * @param {*} value - 键值
818
- * @returns {Promise<*>} - 返回旧值
819
- */
820
- async getset(key, value) {
821
- try {
822
- this.emit('getset_before', { key, value });
823
-
824
- // 获取旧值
825
- let oldValue = undefined;
826
- if (this.data.hasOwnProperty(key)) {
827
- const item = this.data[key];
828
- if (!(item.expire > 0 && Date.now() > item.expire)) {
829
- oldValue = item.value;
830
- }
831
- }
832
-
833
- // 设置新值
834
- this.data[key] = { value, expire: 0 };
835
-
836
- // 添加到键列表
837
- if (!this.list.includes(key)) {
838
- this.list.push(key);
839
- }
840
-
841
- this.emit('getset_after', { key, value, result: oldValue });
842
- return oldValue;
843
- } catch (error) {
844
- this.emit('getset_error', { key, value, error });
845
- $.log.error('[cachebase] getset操作失败:', error);
846
- throw error;
847
- }
848
- }
849
-
850
- /**
851
- * 数值递增
852
- * @param {string} key - 键名
853
- * @returns {Promise<number>} - 返回递增后的值
854
- */
855
- async incr(key) {
856
- try {
857
- this.emit('incr_before', { key });
858
-
859
- // 获取当前值或默认为0
860
- let currentValue = 0;
861
- if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
862
- currentValue = parseFloat(this.data[key].value) || 0;
863
- }
864
-
865
- // 递增
866
- const result = currentValue + 1;
867
-
868
- // 保存结果
869
- this.data[key] = { value: result, expire: 0 };
870
-
871
- // 如果键不存在于列表中,添加到列表
872
- if (!this.list.includes(key)) {
873
- this.list.push(key);
874
- }
875
-
876
- this.emit('incr_after', { key, result });
877
- return result;
878
- } catch (error) {
879
- this.emit('incr_error', { key, error });
880
- $.log.error('[cachebase] incr操作失败:', error);
881
- throw error;
882
- }
883
- }
884
-
885
- /**
886
- * 数值递减
887
- * @param {string} key - 键名
888
- * @returns {Promise<number>} - 返回递减后的值
889
- */
890
- async decr(key) {
891
- try {
892
- this.emit('decr_before', { key });
893
-
894
- // 获取当前值或默认为0
895
- let currentValue = 0;
896
- if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
897
- currentValue = parseFloat(this.data[key].value) || 0;
898
- }
899
-
900
- // 递减
901
- const result = currentValue - 1;
902
-
903
- // 保存结果
904
- this.data[key] = { value: result, expire: 0 };
905
-
906
- // 如果键不存在于列表中,添加到列表
907
- if (!this.list.includes(key)) {
908
- this.list.push(key);
909
- }
910
-
911
- this.emit('decr_after', { key, result });
912
- return result;
913
- } catch (error) {
914
- this.emit('decr_error', { key, error });
915
- $.log.error('[cachebase] decr操作失败:', error);
916
- throw error;
917
- }
918
- }
919
-
920
- /**
921
- * 数值增加指定值
922
- * @param {string} key - 键名
923
- * @param {number} increment - 增加的值
924
- * @returns {Promise<number>} - 返回增加后的值
925
- */
926
- async incrby(key, increment) {
927
- try {
928
- this.emit('incrby_before', { key, increment });
929
-
930
- // 获取当前值或默认为0
931
- let currentValue = 0;
932
- if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
933
- currentValue = parseFloat(this.data[key].value) || 0;
934
- }
935
-
936
- // 增加指定值
937
- const result = currentValue + parseFloat(increment);
938
-
939
- // 保存结果
940
- this.data[key] = { value: result, expire: 0 };
941
-
942
- // 如果键不存在于列表中,添加到列表
943
- if (!this.list.includes(key)) {
944
- this.list.push(key);
945
- }
946
-
947
- this.emit('incrby_after', { key, increment, result });
948
- return result;
949
- } catch (error) {
950
- this.emit('incrby_error', { key, increment, error });
951
- $.log.error('[cachebase] incrby操作失败:', error);
952
- throw error;
953
- }
954
- }
955
-
956
- /**
957
- * 数值减少指定值
958
- * @param {string} key - 键名
959
- * @param {number} decrement - 减少的值
960
- * @returns {Promise<number>} - 返回减少后的值
961
- */
962
- async decrby(key, decrement) {
963
- try {
964
- this.emit('decrby_before', { key, decrement });
965
-
966
- // 获取当前值或默认为0
967
- let currentValue = 0;
968
- if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
969
- currentValue = parseFloat(this.data[key].value) || 0;
970
- }
971
-
972
- // 减少指定值
973
- const result = currentValue - parseFloat(decrement);
974
-
975
- // 保存结果
976
- this.data[key] = { value: result, expire: 0 };
977
-
978
- // 如果键不存在于列表中,添加到列表
979
- if (!this.list.includes(key)) {
980
- this.list.push(key);
981
- }
982
-
983
- this.emit('decrby_after', { key, decrement, result });
984
- return result;
985
- } catch (error) {
986
- this.emit('decrby_error', { key, decrement, error });
987
- $.log.error('[cachebase] decrby操作失败:', error);
988
- throw error;
989
- }
990
- }
991
-
992
- /**
993
- * 浮点数增加指定值
994
- * @param {string} key - 键名
995
- * @param {number} increment - 增加的值
996
- * @returns {Promise<number>} - 返回增加后的值
997
- */
998
- async incrbyfloat(key, increment) {
999
- try {
1000
- this.emit('incrbyfloat_before', { key, increment });
1001
-
1002
- // 获取当前值或默认为0
1003
- let currentValue = 0;
1004
- if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
1005
- currentValue = parseFloat(this.data[key].value) || 0;
1006
- }
1007
-
1008
- // 增加指定值
1009
- const result = currentValue + parseFloat(increment);
1010
-
1011
- // 保存结果
1012
- this.data[key] = { value: result, expire: 0 };
1013
-
1014
- // 如果键不存在于列表中,添加到列表
1015
- if (!this.list.includes(key)) {
1016
- this.list.push(key);
1017
- }
1018
-
1019
- this.emit('incrbyfloat_after', { key, increment, result });
1020
- return result;
1021
- } catch (error) {
1022
- this.emit('incrbyfloat_error', { key, increment, error });
1023
- $.log.error('[cachebase] incrbyfloat操作失败:', error);
1024
- throw error;
1025
- }
1026
- }
1027
-
1028
- /**
1029
- * 字符串追加
1030
- * @param {string} key - 键名
1031
- * @param {string} value - 追加的字符串
1032
- * @returns {Promise<number>} - 返回追加后的字符串长度
1033
- */
1034
- async append(key, value) {
1035
- try {
1036
- this.emit('append_before', { key, value });
1037
-
1038
- // 获取当前值或默认为空字符串
1039
- let currentValue = '';
1040
- if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
1041
- currentValue = String(this.data[key].value);
1042
- }
1043
-
1044
- // 执行字符串拼接
1045
- const newValue = currentValue + String(value);
1046
- const result = newValue.length;
1047
-
1048
- // 保存结果
1049
- this.data[key] = { value: newValue, expire: 0 };
1050
-
1051
- // 如果键不存在于列表中,添加到列表
1052
- if (!this.list.includes(key)) {
1053
- this.list.push(key);
1054
- }
1055
-
1056
- this.emit('append_after', { key, value, result });
1057
- return result;
1058
- } catch (error) {
1059
- this.emit('append_error', { key, value, error });
1060
- $.log.error('[cachebase] append操作失败:', error);
1061
- throw error;
1062
- }
1063
- }
1064
-
1065
- /**
1066
- * 获取字符串长度
1067
- * @param {string} key - 键名
1068
- * @returns {Promise<number>} - 返回字符串长度
1069
- */
1070
- async strlen(key) {
1071
- try {
1072
- this.emit('strlen_before', { key });
1073
-
1074
- // 检查键是否存在且未过期
1075
- if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
1076
- const result = String(this.data[key].value).length;
1077
- this.emit('strlen_after', { key, result });
1078
- return result;
1079
- }
1080
-
1081
- this.emit('strlen_after', { key, result: 0 });
1082
- return 0;
1083
- } catch (error) {
1084
- this.emit('strlen_error', { key, error });
1085
- $.log.error('[cachebase] strlen操作失败:', error);
1086
- throw error;
1087
- }
1088
- }
1089
-
1090
- // 保持兼容性的方法
1091
- async has(key) {
1092
- try {
1093
- const exists = await this.exists(key);
1094
- return exists > 0;
1095
- } catch (error) {
1096
- $.log.error('[cachebase] has操作失败:', error);
1097
- throw error;
1098
- }
1099
- }
1100
-
1101
- /**
1102
- * 清空缓存 - 兼容方法
1103
- * @param {string} pattern - 可选的键匹配模式
1104
- * @returns {Promise<string>} - 返回'OK'
1105
- * @note 支持模式匹配,与Redis行为一致
1106
- */
1107
- async clear(pattern) {
1108
- try {
1109
- this.emit('clear_before', { pattern });
1110
-
1111
- if (pattern) {
1112
- // 支持通配符匹配删除
1113
- const regex = new RegExp(pattern.replace(/\*/g, '.*'));
1114
- const keysToDelete = this.list.filter(key => regex.test(key));
1115
-
1116
- for (const key of keysToDelete) {
1117
- delete this.data[key];
1118
- }
1119
-
1120
- // 更新列表
1121
- this.list = this.list.filter(key => !regex.test(key));
1122
- } else {
1123
- // 清空所有数据
1124
- this.data = {};
1125
- this.list = [];
1126
- }
1127
-
1128
- this.emit('clear_after', { pattern, result: 'OK' });
1129
- return 'OK';
1130
- } catch (error) {
1131
- this.emit('clear_error', { pattern, error });
1132
- $.log.error('[cachebase] clear操作失败:', error);
1133
- throw error;
1134
- }
1135
- }
1136
-
1137
- /**
1138
- * 添加缓存(如果不存在则添加)- 兼容方法
1139
- * @param {string} key - 键名
1140
- * @param {any} value - 要添加的值(不限定类型)
1141
- * @param {number} maxAge - 过期时间(秒),0表示永不过期
1142
- * @returns {Promise<boolean>} - 键不存在并添加成功返回true,键已存在返回false
1143
- */
1144
- async add(key, value, maxAge = 0) {
1145
- try {
1146
- this.emit('add_before', { key, value, maxAge });
1147
-
1148
- // 检查key是否已存在且未过期
1149
- if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
1150
- // 键已存在,不添加
1151
- this.emit('add_after', { key, value, maxAge, result: false });
1152
- return false;
1153
- }
1154
-
1155
- // 键不存在或已过期,添加值
1156
- this.data[key] = {
1157
- value,
1158
- expire: maxAge > 0 ? Date.now() + maxAge * 1000 : 0
1159
- };
1160
-
1161
- // 更新列表
1162
- if (!this.list.includes(key)) {
1163
- this.list.push(key);
1164
- }
1165
-
1166
- this.emit('add_after', { key, value, maxAge, result: true });
1167
- return true;
1168
- } catch (error) {
1169
- this.emit('add_error', { key, value, maxAge, error });
1170
- $.log.error('[cachebase] add操作失败:', error);
1171
- throw error;
1172
- }
1173
- }
1174
-
1175
- /**
1176
- * 设置列表项 - 兼容Redis LPUSH/RPUSH行为
1177
- * @param {string} key - 键名
1178
- * @param {any} value - 值(通常应为数组)
1179
- * @param {number} maxAge - 过期时间(秒),0表示永不过期
1180
- * @returns {Promise<number>} - 设置后列表的长度
1181
- * @note 兼容Redis的列表设置行为,返回列表长度
1182
- */
1183
- async list_set(key, value, maxAge = 0) {
1184
- try {
1185
- this.emit('list_set_before', { key, value, maxAge });
1186
-
1187
- // 确保值是数组类型
1188
- const arrayValue = Array.isArray(value) ? value : [value];
1189
-
1190
- // 设置值和过期时间
1191
- this.data[key] = {
1192
- value: arrayValue,
1193
- expire: maxAge > 0 ? Date.now() + maxAge * 1000 : 0
1194
- };
1195
-
1196
- // 更新列表
1197
- if (!this.list.includes(key)) {
1198
- this.list.push(key);
1199
- }
1200
-
1201
- const length = arrayValue.length;
1202
- this.emit('list_set_after', { key, value: arrayValue, maxAge, result: length });
1203
- return length;
1204
- } catch (error) {
1205
- this.emit('list_set_error', { key, value, maxAge, error });
1206
- $.log.error('[cachebase] list_set操作失败:', error);
1207
- throw error;
1208
- }
1209
- }
1210
-
1211
- /**
1212
- * 添加列表项 - 兼容Redis LPUSH/RPUSH行为
1213
- * @param {string} key - 键名
1214
- * @param {any} value - 值
1215
- * @param {number} maxAge - 过期时间(秒),0表示永不过期
1216
- * @returns {Promise<number>} - 添加后列表的长度
1217
- * @note 兼容Redis的列表追加行为,返回列表长度
1218
- */
1219
- async list_add(key, value, maxAge = 0) {
1220
- try {
1221
- this.emit('list_add_before', { key, value, maxAge });
1222
-
1223
- // 获取现有列表或创建新列表
1224
- let list = [];
1225
- let existingExpire = 0;
1226
-
1227
- if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
1228
- // 确保现有值是数组类型
1229
- list = Array.isArray(this.data[key].value) ? this.data[key].value : [];
1230
- existingExpire = this.data[key].expire;
1231
- }
1232
-
1233
- // 添加值到列表
1234
- list.push(value);
1235
-
1236
- // 设置值和过期时间
1237
- // 如果提供了新的过期时间,则使用新的,否则保持原来的
1238
- const expireTime = maxAge > 0 ? Date.now() + maxAge * 1000 : existingExpire;
1239
-
1240
- this.data[key] = {
1241
- value: list,
1242
- expire: expireTime
1243
- };
1244
-
1245
- // 更新列表
1246
- if (!this.list.includes(key)) {
1247
- this.list.push(key);
1248
- }
1249
-
1250
- const length = list.length;
1251
- this.emit('list_add_after', { key, value, maxAge, result: length });
1252
- return length;
1253
- } catch (error) {
1254
- this.emit('list_add_error', { key, value, maxAge, error });
1255
- $.log.error('[cachebase] list_add操作失败:', error);
1256
- throw error;
1257
- }
1258
- }
1259
-
1260
- /**
1261
- * 检查列表是否包含指定项 - 兼容Redis SISMEMBER行为
1262
- * @param {string} key - 键名
1263
- * @param {any} value - 值
1264
- * @returns {Promise<number>} - 包含返回1,不包含返回0
1265
- * @note 兼容Redis的SISMEMBER命令,返回数字而非布尔值
1266
- */
1267
- async list_has(key, value) {
1268
- try {
1269
- this.emit('list_has_before', { key, value });
1270
-
1271
- // 检查键是否存在且未过期
1272
- if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
1273
- // 确保将值视为数组
1274
- const list = Array.isArray(this.data[key].value) ? this.data[key].value : [];
1275
-
1276
- // 对于对象类型的值,使用深度比较
1277
- let result = 0;
1278
- if (typeof value === 'object' && value !== null) {
1279
- const valueStr = JSON.stringify(value);
1280
- result = list.some(item => {
1281
- try {
1282
- return typeof item === 'object' && item !== null && JSON.stringify(item) === valueStr;
1283
- } catch (e) {
1284
- // 如果JSON序列化失败,回退到严格相等
1285
- return item === value;
1286
- }
1287
- }) ? 1 : 0;
1288
- } else {
1289
- // 对于基本类型,使用严格相等
1290
- result = list.includes(value) ? 1 : 0;
1291
- }
1292
-
1293
- this.emit('list_has_after', { key, value, result });
1294
- return result;
1295
- }
1296
-
1297
- this.emit('list_has_after', { key, value, result: 0 });
1298
- return 0;
1299
- } catch (error) {
1300
- this.emit('list_has_error', { key, value, error });
1301
- $.log.error('[cachebase] list_has操作失败:', error);
1302
- throw error;
1303
- }
1304
- }
1305
-
1306
- /**
1307
- * 获取列表 - 兼容Redis LRANGE行为
1308
- * @param {string} key - 键名
1309
- * @param {number} start - 可选的起始索引,默认为0
1310
- * @param {number} stop - 可选的结束索引,默认为-1(全部)
1311
- * @returns {Promise<Array>} - 列表数据,确保始终返回数组类型
1312
- * @note 兼容Redis的LRANGE命令,支持范围获取
1313
- */
1314
- async list_get(key, start = 0, stop = -1) {
1315
- try {
1316
- this.emit('list_get_before', { key, start, stop });
1317
-
1318
- // 检查键是否存在且未过期
1319
- if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
1320
- // 确保返回数组类型,如果不是数组则转换为空数组
1321
- const list = Array.isArray(this.data[key].value) ? [...this.data[key].value] : [];
1322
-
1323
- // 处理Redis风格的索引范围
1324
- let result = [];
1325
- if (start >= 0 && stop >= -1) {
1326
- if (stop === -1) {
1327
- result = list.slice(start);
1328
- } else {
1329
- result = list.slice(start, stop + 1);
1330
- }
1331
- }
1332
-
1333
- this.emit('list_get_after', { key, start, stop, result });
1334
- return result;
1335
- }
1336
-
1337
- // 键不存在或已过期时返回空数组
1338
- this.emit('list_get_after', { key, start, stop, result: [] });
1339
- return [];
1340
- } catch (error) {
1341
- this.emit('list_get_error', { key, start, stop, error });
1342
- $.log.error('[cachebase] list_get操作失败:', error);
1343
- throw error;
1344
- }
1345
- }
1346
-
1347
- /**
1348
- * 清空列表 - 兼容方法
1349
- * @param {string} key - 键名
1350
- * @returns {Promise<boolean>} - 是否成功
1351
- * @note 确保操作对象始终被视为数组以保持兼容性
1352
- */
1353
- async list_clear(key) {
1354
- try {
1355
- this.emit('list_clear_before', { key });
1356
-
1357
- // 检查键是否存在
1358
- if (this.data.hasOwnProperty(key)) {
1359
- // 检查是否过期
1360
- if (this.data[key].expire > 0 && Date.now() > this.data[key].expire) {
1361
- // 键已过期,删除键
1362
- delete this.data[key];
1363
- const index = this.list.indexOf(key);
1364
- if (index !== -1) {
1365
- this.list.splice(index, 1);
1366
- }
1367
- this.emit('list_clear_after', { key, result: false });
1368
- return false;
1369
- }
1370
-
1371
- // 保持过期时间不变,将列表清空为[]
1372
- const expire = this.data[key].expire;
1373
- this.data[key] = {
1374
- value: [],
1375
- expire: expire
1376
- };
1377
-
1378
- this.emit('list_clear_after', { key, result: true });
1379
- return true;
1380
- }
1381
-
1382
- // 键不存在,创建一个新的空列表键
1383
- this.data[key] = {
1384
- value: [],
1385
- expire: 0
1386
- };
1387
-
1388
- // 更新列表
1389
- if (!this.list.includes(key)) {
1390
- this.list.push(key);
1391
- }
1392
-
1393
- this.emit('list_clear_after', { key, result: true });
1394
- return true;
1395
- } catch (error) {
1396
- this.emit('list_clear_error', { key, error });
1397
- $.log.error('[cachebase] list_clear操作失败:', error);
1398
- throw error;
1399
- }
1400
- }
1401
-
1402
- /**
1403
- * 减少整数值(兼容Redis decr命令)
1404
- * @param {string} key - 键名
1405
- * @param {number} maxAge - 过期时间(秒),0表示永不过期
1406
- * @returns {Promise<number>} - 减少后的值
1407
- * @note 兼容Redis decr命令,初始值为0
1408
- */
1409
- async decr(key, maxAge = 0) {
1410
- try {
1411
- this.emit('decr_before', { key, maxAge });
1412
-
1413
- // 获取当前值或初始化为0
1414
- let currentValue = 0;
1415
- if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
1416
- const existingValue = this.data[key].value;
1417
- if (typeof existingValue === 'number') {
1418
- currentValue = existingValue;
1419
- }
1420
- }
1421
-
1422
- // 减少1
1423
- const newValue = currentValue - 1;
1424
-
1425
- // 设置新值和过期时间
1426
- this.data[key] = {
1427
- value: newValue,
1428
- expire: maxAge > 0 ? Date.now() + maxAge * 1000 : (this.data.hasOwnProperty(key) ? this.data[key].expire : 0)
1429
- };
1430
-
1431
- // 更新列表
1432
- if (!this.list.includes(key)) {
1433
- this.list.push(key);
1434
- }
1435
-
1436
- this.emit('decr_after', { key, maxAge, result: newValue });
1437
- return newValue;
1438
- } catch (error) {
1439
- this.emit('decr_error', { key, maxAge, error });
1440
- $.log.error('[cachebase] decr操作失败:', error);
1441
- throw error;
1442
- }
1443
- }
1444
-
1445
- /**
1446
- * 增加浮点数(兼容方法)
1447
- * @param {string} key - 键名
1448
- * @param {number} num - 要增加的浮点数
1449
- * @param {number} maxAge - 过期时间(秒),0表示永不过期
1450
- * @returns {Promise<number>} - 计算后的结果
1451
- */
1452
- async addFloat(key, num, maxAge = 0) {
1453
- try {
1454
- this.emit('addFloat_before', { key, num, maxAge });
1455
-
1456
- // 原生实现浮点数递增逻辑
1457
- let currentValue = 0;
1458
- if (this.data.hasOwnProperty(key)) {
1459
- const item = this.data[key];
1460
- if (item.expire === 0 || Date.now() < item.expire) {
1461
- currentValue = Number(item.value) || 0;
1462
- }
1463
- }
1464
- const result = currentValue + Number(num);
1465
-
1466
- // 设置值和过期时间
1467
- this.data[key] = {
1468
- value: result,
1469
- expire: maxAge > 0 ? Date.now() + maxAge * 1000 : 0
1470
- };
1471
-
1472
- // 更新列表
1473
- if (!this.list.includes(key)) {
1474
- this.list.push(key);
1475
- }
1476
-
1477
- this.emit('addFloat_after', { key, num, maxAge, result });
1478
- return result;
1479
- } catch (error) {
1480
- this.emit('addFloat_error', { key, num, maxAge, error });
1481
- $.log.error('[cachebase] addFloat操作失败:', error);
1482
- throw error;
1483
- }
1484
- }
1485
-
1486
- /**
1487
- * 增加整数(兼容方法)
1488
- * @param {string} key - 键名
1489
- * @param {number} value - 要增加的整数值
1490
- * @param {number} maxAge - 过期时间(秒),0表示永不过期
1491
- * @returns {Promise<number>} - 计算后的整数结果
1492
- */
1493
- async addInt(key, value, maxAge = 0) {
1494
- try {
1495
- this.emit('addInt_before', { key, value, maxAge });
1496
-
1497
- // 原生实现整数递增逻辑
1498
- let currentValue = 0;
1499
- if (this.data.hasOwnProperty(key)) {
1500
- const item = this.data[key];
1501
- if (item.expire === 0 || Date.now() < item.expire) {
1502
- currentValue = parseInt(item.value, 10) || 0;
1503
- }
1504
- }
1505
- const result = currentValue + parseInt(value, 10);
1506
-
1507
- // 设置值和过期时间
1508
- this.data[key] = {
1509
- value: result,
1510
- expire: maxAge > 0 ? Date.now() + maxAge * 1000 : 0
1511
- };
1512
-
1513
- // 更新列表
1514
- if (!this.list.includes(key)) {
1515
- this.list.push(key);
1516
- }
1517
-
1518
- this.emit('addInt_after', { key, value, maxAge, result });
1519
- return result;
1520
- } catch (error) {
1521
- this.emit('addInt_error', { key, value, maxAge, error });
1522
- $.log.error('[cachebase] addInt操作失败:', error);
1523
- throw error;
1524
- }
1525
- }
1526
- /**
1527
- * 追加字符串(兼容方法)
1528
- * @param {string} key - 键名
1529
- * @param {string} value - 要追加的字符串
1530
- * @param {number} maxAge - 过期时间(秒),0表示永不过期
1531
- * @returns {Promise<number>} - 追加后的字符串长度
1532
- */
1533
- async addStr(key, value, maxAge = 0) {
1534
- try {
1535
- this.emit('addStr_before', { key, value, maxAge });
1536
-
1537
- // 原生实现字符串追加逻辑
1538
- let currentValue = '';
1539
- let originalExpire = 0;
1540
-
1541
- if (this.data.hasOwnProperty(key)) {
1542
- const item = this.data[key];
1543
- if (item.expire === 0 || Date.now() < item.expire) {
1544
- currentValue = String(item.value) || '';
1545
- originalExpire = item.expire;
1546
- }
1547
- }
1548
-
1549
- const newValue = currentValue + String(value);
1550
- const result = newValue.length;
1551
-
1552
- // 设置值和过期时间(如果提供了新的过期时间,则使用新的,否则保持原来的)
1553
- const expireTime = maxAge > 0 ? Date.now() + maxAge * 1000 : originalExpire;
1554
-
1555
- this.data[key] = {
1556
- value: newValue,
1557
- expire: expireTime
1558
- };
1559
-
1560
- // 更新列表
1561
- if (!this.list.includes(key)) {
1562
- this.list.push(key);
1563
- }
1564
-
1565
- this.emit('addStr_after', { key, value, maxAge, result });
1566
- return result;
1567
- } catch (error) {
1568
- this.emit('addStr_error', { key, value, maxAge, error });
1569
- $.log.error('[cachebase] addStr操作失败:', error);
1570
- throw error;
1571
- }
1572
- }
1573
-
1574
- /**
1575
- * 销毁缓存实例
1576
- * @returns {Promise<void>}
1577
- */
1578
- async dispose() {
1579
- try {
1580
- this.emit('dispose_before');
1581
-
1582
- // 清除定时器
1583
- if (this.saveTimer) {
1584
- clearInterval(this.saveTimer);
1585
- this.saveTimer = null;
1586
- $.log.info('[cachebase] 定时保存已停止');
1587
- }
1588
-
1589
- // 如果启用了持久化,在销毁前保存一次
1590
- if (this.config.persist) {
1591
- await this._saveData().catch(error => {
1592
- $.log.error('[cachebase] 销毁前保存数据失败:', error);
1593
- });
1594
- }
1595
-
1596
- // 清空数据
1597
- this.data = {};
1598
- this.list = [];
1599
- this.dict = this.data; // 确保dict也被清空
1600
-
1601
- this.emit('dispose_after');
1602
-
1603
- // 移除所有监听器
1604
- this.removeAllListeners();
1605
- } catch (error) {
1606
- this.emit('dispose_error', { error });
1607
- $.log.error('[cachebase] dispose操作失败:', error);
1608
- }
1609
- }
1610
- /**
1611
- * 关闭缓存实例
1612
- */
1613
- async close() {
1614
- await this.dispose()
1615
- }
1
+ /**
2
+ * CacheBase 是一个通用缓存基类,支持内存缓存和持久化缓存
3
+ * @class CacheBase
4
+ * @extends BaseService
5
+ */
6
+ const {
7
+ BaseService
8
+ } = require('mm_base_service');
9
+
10
+ class CacheBase extends BaseService {
11
+ /**
12
+ * 默认配置静态变量
13
+ */
14
+ static default_config = {
15
+ enabled: true,
16
+ persist: true,
17
+ file_path: './test_cache/undefined.cache.json',
18
+ debug: false,
19
+ auto_save: true,
20
+ save_interval: 30000,
21
+ expire_check_interval: 1000,
22
+ max_size: null,
23
+ storage_type: 'memory'
24
+ };
25
+
26
+ /**
27
+ * 构造函数
28
+ * @param {Object} config - 配置对象
29
+ */
30
+ constructor(config = {}) {
31
+ super(config);
32
+ // 存储实际数据
33
+ this._data = {};
34
+
35
+ // 存储过期时间
36
+ this._expires = {};
37
+
38
+ // 存储数据类型
39
+ this._types = {};
40
+
41
+ // 存储持久化数据的定时器
42
+ this._save_timer = null;
43
+
44
+ // 存储过期检查的定时器
45
+ this._check_timer = null;
46
+
47
+ // 初始化服务状态
48
+ this.is_inited = false;
49
+ this.is_destroyed = false;
50
+
51
+ // 初始化服务
52
+ this.init();
53
+ }
1616
54
  }
1617
55
 
1618
- // 导出CacheBase类
1619
- exports.CacheBase = CacheBase;
56
+ /**
57
+ * 初始化服务
58
+ * @private
59
+ * @returns {Promise<void>}
60
+ */
61
+ CacheBase.prototype._initService = async function() {
62
+ try {
63
+ // 加载配置文件中的配置 - 使用字符串路径的原型函数
64
+ const config_path = './config/cachebase.config.json'.fullname(__dirname);
65
+ if (config_path.hasFile()) {
66
+ // 使用mm_expand的原型函数读取JSON文件
67
+ const config = config_path.loadJson();
68
+ this.setConfig(config);
69
+ }
70
+
71
+ // 初始化过期检查定时器
72
+ this._check_timer = setInterval(() => {
73
+ this.clearExpire();
74
+ }, this.config.expire_check_interval || 1000);
75
+
76
+ // 设置最大大小限制
77
+ if (this.config.max_size) {
78
+ this._max_size = this.config.max_size;
79
+ }
80
+
81
+ // 加载数据
82
+ this._loadData();
83
+
84
+ // 设置定时保存
85
+ this._connect();
86
+
87
+ // 设置初始化完成状态
88
+ this.is_inited = true;
89
+ } catch (error) {
90
+ throw new Error('Failed to initialize service: ' + error.message);
91
+ }
92
+ };
93
+
94
+ /**
95
+ * 设置定时保存(内部方法)
96
+ * @private
97
+ */
98
+ CacheBase.prototype._connect = function() {
99
+ // 如果启用了自动保存
100
+ if (this.config.auto_save) {
101
+ this._save_timer = setInterval(() => {
102
+ this.save();
103
+ }, this.config.save_interval || 30000);
104
+ }
105
+ };
106
+
107
+ /**
108
+ * 从文件加载数据
109
+ * @private
110
+ */
111
+ CacheBase.prototype._loadData = function() {
112
+ try {
113
+ if (this.config.persist && this.config.file_path) {
114
+ // 使用字符串的原型函数获取完整路径
115
+ const file_path = this.config.file_path.fullname();
116
+ $.log.info(`[CacheBase] [_loadData] 尝试加载文件: ${file_path}`);
117
+
118
+ if (file_path.hasFile()) {
119
+ $.log.info(`[CacheBase] [_loadData] 文件存在,开始读取`);
120
+ // 使用mm_expand的原型函数读取JSON文件
121
+ const parsed_data = file_path.loadJson();
122
+ $.log.info(`[CacheBase] [_loadData] 文件读取成功,解析后的数据:`, Object.keys(parsed_data || {}));
123
+
124
+ if (parsed_data && parsed_data.data) {
125
+ this._data = parsed_data.data || {};
126
+ $.log.info(`[CacheBase] [_loadData] 数据加载成功,键数量:`, Object.keys(this._data).length);
127
+ } else {
128
+ $.log.warn(`[CacheBase] [_loadData] 解析后的数据中没有data字段`);
129
+ }
130
+
131
+ if (parsed_data && parsed_data.expires) {
132
+ this._expires = parsed_data.expires || {};
133
+ }
134
+
135
+ if (parsed_data && parsed_data.types) {
136
+ this._types = parsed_data.types || {};
137
+ }
138
+ } else {
139
+ $.log.info(`[CacheBase] [_loadData] 文件不存在: ${file_path}`);
140
+ }
141
+ } else {
142
+ $.log.info(`[CacheBase] [_loadData] 持久化未启用或文件路径未设置`);
143
+ }
144
+ } catch (error) {
145
+ $.log.error(`[CacheBase] [_loadData] 加载数据失败:`, error);
146
+ this.emit('error', new Error('Failed to load data: ' + error.message));
147
+ }
148
+ };
149
+
150
+ /**
151
+ * 保存数据到文件
152
+ */
153
+ CacheBase.prototype.save = function() {
154
+ // 检查服务状态
155
+ if (!this.is_inited || this.is_destroyed) {
156
+ this.emit('error', new Error('Service is not initialized or has been destroyed'));
157
+ return;
158
+ }
159
+
160
+ try {
161
+ this.emit('save_before');
162
+ this._saveData();
163
+ this.emit('save_after');
164
+ } catch (error) {
165
+ this.emit('error', new Error('Failed to save data: ' + error.message));
166
+ }
167
+ };
168
+
169
+ /**
170
+ * 保存数据到文件
171
+ * @private
172
+ */
173
+ CacheBase.prototype._saveData = function() {
174
+ try {
175
+ if (this.config.persist && this.config.file_path) {
176
+ // 使用字符串的原型函数获取完整路径
177
+ const file_path = this.config.file_path.fullname();
178
+
179
+ // 确保目录存在 - 使用目录路径的原型函数
180
+ file_path.addDir();
181
+
182
+ const data_to_save = {
183
+ data: this._data,
184
+ expires: this._expires,
185
+ types: this._types,
186
+ timestamp: Date.now()
187
+ };
188
+
189
+ // 使用mm_expand的原型函数写入JSON文件
190
+ file_path.saveJson(data_to_save, null, 2);
191
+ }
192
+ } catch (error) {
193
+ throw new Error('Failed to save data: ' + error.message);
194
+ }
195
+ };
196
+
197
+ /**
198
+ * 销毁服务
199
+ * @returns {Promise<void>}
200
+ */
201
+ CacheBase.prototype.destroy = async function() {
202
+ if (this.is_destroyed) {
203
+ return;
204
+ }
205
+
206
+ // 调用父类destroy方法处理通用销毁逻辑
207
+ await BaseService.prototype.destroy.call(this);
208
+
209
+ try {
210
+ // 保存数据
211
+ this.save();
212
+
213
+ // 清除定时器
214
+ if (this._save_timer) {
215
+ clearInterval(this._save_timer);
216
+ this._save_timer = null;
217
+ }
218
+
219
+ if (this._check_timer) {
220
+ clearInterval(this._check_timer);
221
+ this._check_timer = null;
222
+ }
223
+
224
+ // 清空数据
225
+ this._data = {};
226
+ this._expires = {};
227
+ this._types = {};
228
+
229
+ // 设置销毁状态
230
+ this.is_inited = false;
231
+ this.is_destroyed = true;
232
+
233
+ // 触发自定义销毁事件
234
+ this.emit('destroy_after');
235
+ } catch (error) {
236
+ this.emit('error', new Error('Failed to destroy service: ' + error.message));
237
+ }
238
+ };
239
+
240
+ /**
241
+ * 设置键值对
242
+ * @param {string} key - 键名
243
+ * @param {*} value - 值
244
+ * @param {number} [ttl] - 过期时间(秒)
245
+ * @returns {boolean} 操作结果
246
+ */
247
+ CacheBase.prototype.set = function(key, value, ttl) {
248
+ // 检查服务状态
249
+ if (!this.is_inited || this.is_destroyed) {
250
+ this.emit('error', new Error('Service is not initialized or has been destroyed'));
251
+ return false;
252
+ }
253
+
254
+ try {
255
+ // 参数验证
256
+ if (!key || typeof key !== 'string') {
257
+ return false;
258
+ }
259
+
260
+ // 触发操作前事件
261
+ const event_data = {
262
+ key,
263
+ value,
264
+ ttl,
265
+ cancel: false
266
+ };
267
+ this.emit('set_before', event_data);
268
+
269
+ if (event_data.cancel) {
270
+ return false;
271
+ }
272
+
273
+ // 保存数据
274
+ this._data[key] = value;
275
+
276
+ // 设置类型
277
+ this._types[key] = this._getDataTypes(value);
278
+
279
+ // 设置过期时间
280
+ if (ttl && typeof ttl === 'number' && ttl > 0) {
281
+ this._expires[key] = Date.now() + ttl * 1000;
282
+ } else {
283
+ // 清除过期时间
284
+ delete this._expires[key];
285
+ }
286
+
287
+ // 触发操作后事件
288
+ this.emit('set_after', {
289
+ key,
290
+ value,
291
+ ttl,
292
+ result: 'OK'
293
+ });
294
+
295
+ return true;
296
+ } catch (error) {
297
+ this.emit('error', new Error(`Failed to set key ${key}: ${error.message}`));
298
+ return false;
299
+ }
300
+ };
301
+
302
+ /**
303
+ * 获取键值
304
+ * @param {string} key - 键
305
+ * @returns {*} 值
306
+ */
307
+ CacheBase.prototype.get = function(key) {
308
+ // 检查服务状态
309
+ if (!this.is_inited || this.is_destroyed) {
310
+ this.emit('error', new Error('Service is not initialized or has been destroyed'));
311
+ return null;
312
+ }
313
+
314
+ try {
315
+ // 检查是否过期
316
+ if (this._isExpired(key)) {
317
+ this.del(key);
318
+ return null;
319
+ }
320
+
321
+ return this._data[key];
322
+ } catch (error) {
323
+ this.emit('error', new Error(`Failed to get key ${key}: ${error.message}`));
324
+ return null;
325
+ }
326
+ };
327
+
328
+ /**
329
+ * 删除键
330
+ * @param {string} key - 键名
331
+ * @returns {boolean} 是否成功删除
332
+ */
333
+ CacheBase.prototype.del = function(key) {
334
+ try {
335
+ // 参数验证
336
+ if (!key || typeof key !== 'string') {
337
+ return false;
338
+ }
339
+
340
+ // 触发操作前事件
341
+ const event_data = {
342
+ key,
343
+ cancel: false
344
+ };
345
+ this.emit('del_before', event_data);
346
+
347
+ if (event_data.cancel) {
348
+ return false;
349
+ }
350
+
351
+ // 检查键是否存在
352
+ if (!(key in this._data)) {
353
+ return false;
354
+ }
355
+
356
+ // 删除键
357
+ delete this._data[key];
358
+ delete this._types[key];
359
+ delete this._expires[key];
360
+
361
+ // 触发操作后事件
362
+ this.emit('del_after', {
363
+ key
364
+ });
365
+
366
+ return true;
367
+ } catch (error) {
368
+ this.emit('error', new Error(`Failed to del: ${error.message}`));
369
+ return false;
370
+ }
371
+ };
372
+
373
+ /**
374
+ * 批量删除键
375
+ * @param {Array} keys - 键名数组
376
+ * @returns {number} 删除的键数量
377
+ */
378
+ CacheBase.prototype.delBatch = function(keys) {
379
+ try {
380
+ if (!Array.isArray(keys)) {
381
+ return 0;
382
+ }
383
+
384
+ let deletedCount = 0;
385
+ for (const key of keys) {
386
+ if (this.del(key)) {
387
+ deletedCount++;
388
+ }
389
+ }
390
+
391
+ return deletedCount;
392
+ } catch (error) {
393
+ this.emit('error', new Error(`Failed to delBatch: ${error.message}`));
394
+ return 0;
395
+ }
396
+ };
397
+
398
+ /**
399
+ * 检查键是否存在
400
+ * @param {string} key - 键
401
+ * @returns {boolean} 存在状态
402
+ */
403
+ CacheBase.prototype.exists = function(key) {
404
+ try {
405
+ if (this._isExpired(key)) {
406
+ this.del(key);
407
+ return false;
408
+ }
409
+
410
+ return key in this._data;
411
+ } catch (error) {
412
+ this.emit('error', new Error(`Failed to check key ${key}: ${error.message}`));
413
+ return false;
414
+ }
415
+ };
416
+
417
+ /**
418
+ * 检查键是否存在
419
+ * @param {string} key - 键名
420
+ * @returns {boolean} 是否存在
421
+ */
422
+ CacheBase.prototype.has = function(key) {
423
+ return this.exists(key);
424
+ };
425
+
426
+ /**
427
+ * 清空过期数据
428
+ */
429
+ CacheBase.prototype.clearExpire = function() {
430
+ try {
431
+ const now = Date.now();
432
+ for (const key in this._expires) {
433
+ if (this._expires[key] < now) {
434
+ this.del(key);
435
+ }
436
+ }
437
+ } catch (error) {
438
+ this.emit('error', new Error(`Failed to clear expired data: ${error.message}`));
439
+ }
440
+ };
441
+
442
+ /**
443
+ * 检查键是否过期
444
+ * @private
445
+ * @param {string} key - 键名
446
+ * @returns {boolean} 是否过期
447
+ */
448
+ CacheBase.prototype._isExpired = function(key) {
449
+ // 先检查键是否存在
450
+ if (!(key in this._data)) {
451
+ return true;
452
+ }
453
+
454
+ // 如果没有设置过期时间,则未过期
455
+ if (!(key in this._expires)) {
456
+ return false;
457
+ }
458
+
459
+ const now = Date.now();
460
+ const is_expired = now > this._expires[key];
461
+
462
+ // 惰性删除:如果过期了,删除键
463
+ if (is_expired) {
464
+ delete this._data[key];
465
+ delete this._types[key];
466
+ delete this._expires[key];
467
+ }
468
+
469
+ return is_expired;
470
+ };
471
+
472
+ /**
473
+ * 获取数据类型
474
+ * @private
475
+ * @param {*} value - 值
476
+ * @returns {string} 数据类型
477
+ */
478
+ CacheBase.prototype._getDataTypes = function(value) {
479
+ if (value === null) return 'null';
480
+ if (Array.isArray(value)) return 'array';
481
+ return typeof value;
482
+ };
483
+
484
+ /**
485
+ * 设置键的过期时间
486
+ * @param {string} key - 键名
487
+ * @param {number} seconds - 过期时间(秒)
488
+ * @returns {boolean} 是否成功
489
+ */
490
+ CacheBase.prototype.expire = function(key, seconds) {
491
+ try {
492
+ // 参数验证
493
+ if (!key || typeof key !== 'string') {
494
+ return false;
495
+ }
496
+
497
+ if (!Number.isInteger(seconds) || seconds <= 0) {
498
+ return false;
499
+ }
500
+
501
+ // 触发设置过期前事件
502
+ const event_data = {
503
+ key,
504
+ seconds,
505
+ cancel: false
506
+ };
507
+ this.emit('expire_before', event_data);
508
+
509
+ if (event_data.cancel) {
510
+ return false;
511
+ }
512
+
513
+ // 检查键是否存在
514
+ if (!(key in this._data)) {
515
+ return false;
516
+ }
517
+
518
+ // 设置过期时间
519
+ this._expires[key] = Date.now() + seconds * 1000;
520
+
521
+ // 触发设置过期后事件
522
+ this.emit('expire_after', {
523
+ key,
524
+ seconds,
525
+ success: true
526
+ });
527
+
528
+ return true;
529
+ } catch (error) {
530
+ this.emit('error', new Error(`Failed to set expire for key ${key}: ${error.message}`));
531
+ return false;
532
+ }
533
+ };
534
+
535
+ /**
536
+ * 设置哈希表字段
537
+ * @param {string} key - 哈希表键名
538
+ * @param {string} field - 字段名
539
+ * @param {*} value - 值
540
+ * @returns {number} 1表示新增,0表示更新或失败
541
+ */
542
+ CacheBase.prototype.hset = function(key, field, value) {
543
+ try {
544
+ // 参数验证
545
+ if (!key || typeof key !== 'string' || !field || typeof field !== 'string') {
546
+ return 0;
547
+ }
548
+
549
+ // 触发设置前事件
550
+ const event_data = {
551
+ key,
552
+ field,
553
+ value,
554
+ cancel: false
555
+ };
556
+ this.emit('hset_before', event_data);
557
+
558
+ if (event_data.cancel) {
559
+ return 0;
560
+ }
561
+
562
+ // 初始化哈希表
563
+ if (!(key in this._data)) {
564
+ this._data[key] = {};
565
+ this._types[key] = 'hash';
566
+ } else if (this._types[key] !== 'hash') {
567
+ this._data[key] = {};
568
+ this._types[key] = 'hash';
569
+ }
570
+
571
+ // 检查是否是新增字段
572
+ const is_new_field = !(field in this._data[key]);
573
+
574
+ // 设置字段值
575
+ this._data[key][field] = value;
576
+
577
+ // 触发设置后事件
578
+ this.emit('hset_after', {
579
+ key,
580
+ field,
581
+ value,
582
+ success: true
583
+ });
584
+
585
+ return is_new_field ? 1 : 0;
586
+ } catch (error) {
587
+ this.emit('error', new Error(`Failed to hset for key ${key}: ${error.message}`));
588
+ return 0;
589
+ }
590
+ };
591
+
592
+ /**
593
+ * 获取哈希表字段值
594
+ * @param {string} key - 哈希表键名
595
+ * @param {string} field - 字段名
596
+ * @returns {*} 字段值
597
+ */
598
+ CacheBase.prototype.hget = function(key, field) {
599
+ try {
600
+ // 参数验证
601
+ if (!key || typeof key !== 'string' || !field || typeof field !== 'string') {
602
+ return undefined;
603
+ }
604
+
605
+ // 检查是否过期
606
+ if (this._isExpired(key)) {
607
+ this.del(key);
608
+ return undefined;
609
+ }
610
+
611
+ // 检查键是否存在且类型正确
612
+ if (!(key in this._data) || this._types[key] !== 'hash') {
613
+ return undefined;
614
+ }
615
+
616
+ return this._data[key][field];
617
+ } catch (error) {
618
+ this.emit('error', new Error(`Failed to hget for key ${key}: ${error.message}`));
619
+ return undefined;
620
+ }
621
+ };
622
+
623
+ /**
624
+ * 清空所有数据
625
+ * @returns {boolean} 操作结果
626
+ */
627
+ CacheBase.prototype.clear = function() {
628
+ return this.flushdb();
629
+ };
630
+
631
+ /**
632
+ * 获取哈希表所有字段和值
633
+ * @param {string} key - 哈希表键名
634
+ * @returns {Object} 哈希表内容
635
+ */
636
+ CacheBase.prototype.hgetall = function(key) {
637
+ try {
638
+ // 参数验证
639
+ if (!key || typeof key !== 'string') {
640
+ return {};
641
+ }
642
+
643
+ // 检查是否过期
644
+ if (this._isExpired(key)) {
645
+ this.del(key);
646
+ return {};
647
+ }
648
+
649
+ // 检查键是否存在且类型正确
650
+ if (!(key in this._data) || this._types[key] !== 'hash') {
651
+ return {};
652
+ }
653
+
654
+ // 返回副本,避免直接修改
655
+ return {
656
+ ...this._data[key]
657
+ };
658
+ } catch (error) {
659
+ this.emit('error', new Error(`Failed to hgetall for key ${key}: ${error.message}`));
660
+ return {};
661
+ }
662
+ };
663
+
664
+ /**
665
+ * 删除哈希表字段
666
+ * @param {string} key - 哈希表键名
667
+ * @param {string|Array<string>} field - 字段名或字段名数组
668
+ * @returns {boolean} 是否成功删除
669
+ */
670
+ CacheBase.prototype.hdel = function(key, field) {
671
+ try {
672
+ // 参数验证
673
+ if (!key || typeof key !== 'string') {
674
+ return false;
675
+ }
676
+
677
+ // 触发操作前事件
678
+ const event_data = {
679
+ key,
680
+ field,
681
+ cancel: false
682
+ };
683
+ this.emit('hdel_before', event_data);
684
+
685
+ if (event_data.cancel) {
686
+ return false;
687
+ }
688
+
689
+ // 检查是否过期
690
+ if (this._isExpired(key)) {
691
+ this.del(key);
692
+ this.emit('hdel_after', {
693
+ key,
694
+ field,
695
+ success: false
696
+ });
697
+ return false;
698
+ }
699
+
700
+ // 检查键是否存在且类型正确
701
+ if (!(key in this._data) || this._types[key] !== 'hash') {
702
+ this.emit('hdel_after', {
703
+ key,
704
+ field,
705
+ success: false
706
+ });
707
+ return false;
708
+ }
709
+
710
+ // 处理字段参数,支持单个字段或字段数组
711
+ const fields = Array.isArray(field) ? field : [field];
712
+ let deleted = false;
713
+
714
+ // 遍历所有字段进行删除
715
+ for (const f of fields) {
716
+ if (typeof f === 'string' && f && f in this._data[key]) {
717
+ delete this._data[key][f];
718
+ deleted = true;
719
+ }
720
+ }
721
+
722
+ // 如果哈希表为空,删除整个键
723
+ if (deleted && Object.keys(this._data[key]).length === 0) {
724
+ delete this._data[key];
725
+ delete this._types[key];
726
+ delete this._expires[key];
727
+ }
728
+
729
+ // 触发操作后事件
730
+ this.emit('hdel_after', {
731
+ key,
732
+ field,
733
+ success: deleted
734
+ });
735
+
736
+ return deleted;
737
+ } catch (error) {
738
+ this.emit('hdel_error', {
739
+ key,
740
+ field,
741
+ error
742
+ });
743
+ this.emit('error', new Error(`Failed to hdel: ${error.message}`));
744
+ return false;
745
+ }
746
+ };
747
+
748
+ /**
749
+ * 增加键的数值
750
+ * @param {string} key - 键名
751
+ * @param {number} value - 增加的值(默认为1)
752
+ * @returns {number} 增加后的值
753
+ */
754
+ CacheBase.prototype.incrby = function(key, value) {
755
+ // 参数验证
756
+ if (!key || typeof key !== 'string' || typeof value !== 'number') {
757
+ return 0;
758
+ }
759
+
760
+ // 确保_data对象存在
761
+ if (!this._data) {
762
+ this._data = {};
763
+ }
764
+
765
+ // 获取当前值,如果存在则使用,不存在则为0
766
+ let current = 0;
767
+ if (key in this._data) {
768
+ // 尝试将现有值转换为数字
769
+ current = Number(this._data[key]) || 0;
770
+ }
771
+
772
+ // 增加指定值
773
+ const newValue = current + value;
774
+
775
+ // 保存新值
776
+ this._data[key] = newValue;
777
+
778
+ // 确保_types对象存在并设置类型
779
+ if (!this._types) {
780
+ this._types = {};
781
+ }
782
+ this._types[key] = 'number';
783
+
784
+ // 清理过期时间
785
+ if (this._expires && this._expires[key]) {
786
+ delete this._expires[key];
787
+ }
788
+
789
+ return newValue;
790
+ };
791
+
792
+ /**
793
+ * 增加键的数值(增加1)
794
+ * @param {string} key - 键名
795
+ * @param {number} seconds - 过期时间(秒),0表示永不过期
796
+ * @returns {number|null} 增加后的值,失败返回null
797
+ */
798
+ CacheBase.prototype.incr = function(key, seconds = 0) {
799
+ try {
800
+ // 参数验证
801
+ if (!key || typeof key !== 'string') {
802
+ $.log.error('[cachebase] incr参数错误: key必须是非空字符串');
803
+ return 0;
804
+ }
805
+
806
+ this.emit('incr_before', {
807
+ key,
808
+ ttl: seconds,
809
+ cancel: false
810
+ });
811
+
812
+ // 确保_data对象存在
813
+ if (!this._data) {
814
+ this._data = {};
815
+ }
816
+
817
+ // 获取当前值,如果存在则使用,不存在则为0
818
+ let current = 0;
819
+ if (key in this._data && !this._isExpired(key)) {
820
+ current = Number(this._data[key]) || 0;
821
+ }
822
+
823
+ // 增加1
824
+ const newValue = current + 1;
825
+
826
+ // 保存新值
827
+ this._data[key] = newValue;
828
+ this._types[key] = 'number';
829
+
830
+ if (seconds > 0) {
831
+ this._expires[key] = Date.now() + seconds * 1000;
832
+ } else {
833
+ delete this._expires[key];
834
+ }
835
+
836
+ this.emit('incr_after', {
837
+ key,
838
+ ttl: seconds,
839
+ success: true,
840
+ result: newValue
841
+ });
842
+ return newValue;
843
+ } catch (error) {
844
+ this.emit('incr_error', {
845
+ key,
846
+ ttl: seconds,
847
+ error
848
+ });
849
+ $.log.error('[cachebase] incr操作失败:', error);
850
+ return 0;
851
+ }
852
+ };
853
+
854
+ CacheBase.prototype.decrby = function(key, value) {
855
+ // 参数验证
856
+ if (!key || typeof key !== 'string' || typeof value !== 'number') {
857
+ return 0;
858
+ }
859
+
860
+ // 确保_data对象存在
861
+ if (!this._data) {
862
+ this._data = {};
863
+ }
864
+
865
+ // 获取当前值,如果存在则使用,不存在则为0
866
+ let current = 0;
867
+ if (key in this._data) {
868
+ // 尝试将现有值转换为数字
869
+ current = Number(this._data[key]) || 0;
870
+ }
871
+
872
+ // 减少指定值
873
+ const newValue = current - value;
874
+
875
+ // 保存新值
876
+ this._data[key] = newValue;
877
+
878
+ // 确保_types对象存在并设置类型
879
+ if (!this._types) {
880
+ this._types = {};
881
+ }
882
+ this._types[key] = 'number';
883
+
884
+ // 清理过期时间
885
+ if (this._expires && this._expires[key]) {
886
+ delete this._expires[key];
887
+ }
888
+
889
+ return newValue;
890
+ };
891
+
892
+ /**
893
+ * 减少键的数值(减少1)
894
+ * @param {string} key - 键名
895
+ * @param {number} seconds - 过期时间(秒),0表示永不过期
896
+ * @returns {number} 减少后的值
897
+ */
898
+ CacheBase.prototype.decr = function(key, seconds = 0) {
899
+ try {
900
+ // 参数验证
901
+ if (!key || typeof key !== 'string') {
902
+ $.log.error('[cachebase] decr参数错误: key必须是非空字符串');
903
+ return 0;
904
+ }
905
+
906
+ this.emit('decr_before', {
907
+ key,
908
+ ttl: seconds,
909
+ cancel: false
910
+ });
911
+
912
+ // 确保_data对象存在
913
+ if (!this._data) {
914
+ this._data = {};
915
+ }
916
+
917
+ // 获取当前值,如果存在则使用,不存在则为0
918
+ let current = 0;
919
+ if (key in this._data && !this._isExpired(key)) {
920
+ current = Number(this._data[key]) || 0;
921
+ }
922
+
923
+ // 减少1
924
+ const newValue = current - 1;
925
+
926
+ // 保存新值
927
+ this._data[key] = newValue;
928
+ this._types[key] = 'number';
929
+
930
+ if (seconds > 0) {
931
+ this._expires[key] = Date.now() + seconds * 1000;
932
+ } else {
933
+ delete this._expires[key];
934
+ }
935
+
936
+ this.emit('decr_after', {
937
+ key,
938
+ ttl: seconds,
939
+ success: true,
940
+ result: newValue
941
+ });
942
+ return newValue;
943
+ } catch (error) {
944
+ this.emit('decr_error', {
945
+ key,
946
+ ttl: seconds,
947
+ error
948
+ });
949
+ $.log.error('[cachebase] decr操作失败:', error);
950
+ return 0;
951
+ }
952
+ };
953
+
954
+ /**
955
+ * 增加整数值(兼容方法)
956
+ * @param {string} key - 键名
957
+ * @param {number} value - 要增加的整数值
958
+ * @param {number} seconds - 过期时间(秒),0表示永不过期
959
+ * @returns {Promise<number>} - 返回增加后的值
960
+ */
961
+ CacheBase.prototype.addInt = function(key, value, seconds = 0) {
962
+ try {
963
+ // 参数验证
964
+ if (!key || typeof key !== 'string' || typeof value !== 'number') {
965
+ $.log.error('[cachebase] addInt参数错误: key必须是非空字符串,value必须是数字');
966
+ return 0;
967
+ }
968
+
969
+ this.emit('addInt_before', {
970
+ key,
971
+ value,
972
+ ttl: seconds,
973
+ cancel: false
974
+ });
975
+
976
+ // 实现整数递增逻辑
977
+ let currentValue = 0;
978
+ if (key in this._data && !this._isExpired(key)) {
979
+ currentValue = parseInt(this._data[key], 10) || 0;
980
+ }
981
+ const result = Math.floor(currentValue + parseInt(value, 10));
982
+
983
+ // 设置值和过期时间
984
+ this._data[key] = result;
985
+ this._types[key] = 'number';
986
+
987
+ if (seconds > 0) {
988
+ this._expires[key] = Date.now() + seconds * 1000;
989
+ } else {
990
+ delete this._expires[key];
991
+ }
992
+
993
+ this.emit('addInt_after', {
994
+ key,
995
+ value,
996
+ ttl: seconds,
997
+ success: true,
998
+ result
999
+ });
1000
+ return result;
1001
+ } catch (error) {
1002
+ this.emit('addInt_error', {
1003
+ key,
1004
+ value,
1005
+ ttl: seconds,
1006
+ error
1007
+ });
1008
+ $.log.error('[cachebase] addInt操作失败:', error);
1009
+ return 0;
1010
+ }
1011
+ };
1012
+
1013
+ /**
1014
+ * 向字符串末尾追加内容
1015
+ * @param {string} key - 键名
1016
+ * @param {string} append - 要追加的内容
1017
+ * @returns {number|null} 追加后的字符串长度,失败返回null
1018
+ */
1019
+ CacheBase.prototype.append = function(key, append) {
1020
+ try {
1021
+ // 参数验证
1022
+ if (!key || typeof key !== 'string' || typeof append !== 'string') {
1023
+ return null;
1024
+ }
1025
+
1026
+ // 检查是否过期
1027
+ if (this._isExpired(key)) {
1028
+ this.del(key);
1029
+ return null;
1030
+ }
1031
+
1032
+ // 获取当前值
1033
+ let current = '';
1034
+
1035
+ // 如果键存在且是字符串,使用当前值
1036
+ if (key in this._data && typeof this._data[key] === 'string') {
1037
+ current = this._data[key];
1038
+ }
1039
+
1040
+ // 追加内容
1041
+ current += append;
1042
+ this._data[key] = current;
1043
+ this._types[key] = 'string';
1044
+
1045
+ return current.length;
1046
+ } catch (error) {
1047
+ this.emit('error', new Error(`Failed to append for key ${key}: ${error.message}`));
1048
+ return null;
1049
+ }
1050
+ };
1051
+
1052
+ /**
1053
+ * 获取字符串长度
1054
+ * @param {string} key - 键名
1055
+ * @returns {number} 字符串长度,不存在返回0
1056
+ */
1057
+ CacheBase.prototype.strlen = function(key) {
1058
+ try {
1059
+ // 参数验证
1060
+ if (!key || typeof key !== 'string') {
1061
+ return 0;
1062
+ }
1063
+
1064
+ // 检查是否过期
1065
+ if (this._isExpired(key)) {
1066
+ this.del(key);
1067
+ return 0;
1068
+ }
1069
+
1070
+ // 检查键是否存在
1071
+ if (!(key in this._data)) {
1072
+ return 0;
1073
+ }
1074
+
1075
+ // 检查值类型
1076
+ if (typeof this._data[key] !== 'string') {
1077
+ return 0;
1078
+ }
1079
+
1080
+ return this._data[key].length;
1081
+ } catch (error) {
1082
+ this.emit('error', new Error(`Failed to strlen for key ${key}: ${error.message}`));
1083
+ return 0;
1084
+ }
1085
+ };
1086
+
1087
+ /**
1088
+ * 清空所有数据库键
1089
+ * @returns {boolean} 操作结果
1090
+ */
1091
+ CacheBase.prototype.flushdb = function() {
1092
+ try {
1093
+ // 触发操作前事件
1094
+ const event_data = {
1095
+ cancel: false
1096
+ };
1097
+ this.emit('flushdb_before', event_data);
1098
+
1099
+ if (event_data.cancel) {
1100
+ return false;
1101
+ }
1102
+
1103
+ // 清空所有数据
1104
+ this._data = {};
1105
+ this._expires = {};
1106
+ this._types = {};
1107
+
1108
+ // 触发操作后事件
1109
+ this.emit('flushdb_after', {
1110
+ success: true
1111
+ });
1112
+
1113
+ return true;
1114
+ } catch (error) {
1115
+ this.emit('error', new Error(`Failed to flushdb: ${error.message}`));
1116
+ return false;
1117
+ }
1118
+ };
1119
+ /**
1120
+ * 获取所有键名
1121
+ * @param {string} [pattern] - 键名匹配模式,支持*通配符
1122
+ * @returns {Array} 所有键名数组
1123
+ */
1124
+ CacheBase.prototype.keys = function(pattern) {
1125
+ try {
1126
+ const result = [];
1127
+ for (const key in this._data) {
1128
+ // 调用_is_expired方法检查过期并自动删除,无需再调用del
1129
+ if (!this._isExpired(key)) {
1130
+ // 添加基于pattern的键过滤条件
1131
+ if (!pattern || this._matchPattern(key, pattern)) {
1132
+ result.push(key);
1133
+ }
1134
+ }
1135
+ }
1136
+ return result;
1137
+ } catch (error) {
1138
+ this.emit('error', new Error(`Failed to get keys: ${error.message}`));
1139
+ return [];
1140
+ }
1141
+ };
1142
+
1143
+ /**
1144
+ * 检查键是否匹配指定模式
1145
+ * @private
1146
+ * @param {string} key - 要检查的键名
1147
+ * @param {string} pattern - 匹配模式,支持*通配符
1148
+ * @returns {boolean} 是否匹配
1149
+ */
1150
+ CacheBase.prototype._matchPattern = function(key, pattern) {
1151
+ // 将模式转换为正则表达式
1152
+ try {
1153
+ var regexPattern = pattern
1154
+ .replace(/[.+?^${}()|[\\]/g, '\\$&')
1155
+ .replace(/\*/g, '.*');
1156
+ var regex = new RegExp('^' + regexPattern + '$');
1157
+ return regex.test(key);
1158
+ } catch (error) {
1159
+ // 如果正则表达式无效,返回false
1160
+ return false;
1161
+ }
1162
+ };
1163
+
1164
+ /**
1165
+ * 获取键的数量
1166
+ * @returns {number} 键的数量
1167
+ */
1168
+ CacheBase.prototype.dbsize = function() {
1169
+ try {
1170
+ // 计算未过期的键数量
1171
+ let count = 0;
1172
+ for (const key in this._data) {
1173
+ if (!this._isExpired(key)) {
1174
+ count++;
1175
+ }
1176
+ }
1177
+ return count;
1178
+ } catch (error) {
1179
+ this.emit('error', new Error(`Failed to get dbsize: ${error.message}`));
1180
+ return 0;
1181
+ }
1182
+ };
1183
+
1184
+ /**
1185
+ * 增加浮点数(兼容方法)
1186
+ * @param {string} key - 键名
1187
+ * @param {number} value - 增加的浮点数
1188
+ * @param {number} seconds - 过期时间(秒),0表示永不过期
1189
+ * @returns {number} 增加后的值,失败返回0
1190
+ */
1191
+ CacheBase.prototype.incrbyfloat = function(key, value = 1.0, seconds = 0) {
1192
+ try {
1193
+ // 参数验证
1194
+ if (!key || typeof key !== 'string' || typeof value !== 'number') {
1195
+ $.log.error('[cachebase] incrbyfloat参数错误: key必须是非空字符串,value必须是数字');
1196
+ return 0;
1197
+ }
1198
+
1199
+ this.emit('incrbyfloat_before', {
1200
+ key,
1201
+ value,
1202
+ ttl: seconds,
1203
+ cancel: false
1204
+ });
1205
+
1206
+ // 确保_data对象存在
1207
+ if (!this._data) {
1208
+ this._data = {};
1209
+ }
1210
+
1211
+ // 获取当前值,如果存在则使用,不存在则为0
1212
+ let current = 0;
1213
+ if (key in this._data && !this._isExpired(key)) {
1214
+ current = Number(this._data[key]) || 0;
1215
+ }
1216
+
1217
+ // 增加指定值
1218
+ const newValue = current + value;
1219
+
1220
+ // 保存新值
1221
+ this._data[key] = newValue;
1222
+ this._types[key] = 'number';
1223
+
1224
+ if (seconds > 0) {
1225
+ this._expires[key] = Date.now() + seconds * 1000;
1226
+ } else {
1227
+ delete this._expires[key];
1228
+ }
1229
+
1230
+ this.emit('incrbyfloat_after', {
1231
+ key,
1232
+ value,
1233
+ ttl: seconds,
1234
+ success: true,
1235
+ result: newValue
1236
+ });
1237
+ return newValue;
1238
+ } catch (error) {
1239
+ this.emit('incrbyfloat_error', {
1240
+ key,
1241
+ value,
1242
+ ttl: seconds,
1243
+ error
1244
+ });
1245
+ $.log.error('[cachebase] incrbyfloat操作失败:', error);
1246
+ return 0;
1247
+ }
1248
+ };
1249
+
1250
+ /**
1251
+ * 获取并设置值
1252
+ * @param {string} key - 键名
1253
+ * @param {*} value - 新值
1254
+ * @param {number} expire - 过期时间(毫秒)
1255
+ * @returns {*} 旧值
1256
+ */
1257
+ CacheBase.prototype.getset = function(key, value, expire) {
1258
+ try {
1259
+ // 参数验证
1260
+ if (!key || typeof key !== 'string') {
1261
+ return null;
1262
+ }
1263
+
1264
+ // 获取旧值
1265
+ const oldValue = this.get(key);
1266
+
1267
+ // 设置新值
1268
+ this.set(key, value, expire);
1269
+
1270
+ return oldValue;
1271
+ } catch (error) {
1272
+ this.emit('error', new Error(`Failed to getset: ${error.message}`));
1273
+ return null;
1274
+ }
1275
+ };
1276
+
1277
+ /**
1278
+ * 增加整数值 - 兼容方法
1279
+ * @param {string} key - 键名
1280
+ * @param {*} value - 要设置的值
1281
+ * @param {number} maxAge - 过期时间(秒),0表示永不过期
1282
+ * @returns {Promise<boolean>} - 操作结果,键不存在并设置成功返回true,键已存在返回false
1283
+ */
1284
+ CacheBase.prototype.add = function(key, value, maxAge = 0) {
1285
+ try {
1286
+ // 参数验证
1287
+ if (!key || typeof key !== 'string') {
1288
+ $.log.error('[cachebase] add参数错误: key必须是非空字符串');
1289
+ return false;
1290
+ }
1291
+
1292
+ // 触发前置事件
1293
+ const beforeEventData = {
1294
+ key,
1295
+ value,
1296
+ maxAge,
1297
+ cancel: false
1298
+ };
1299
+ this.emit('add_before', beforeEventData);
1300
+
1301
+ if (beforeEventData.cancel) {
1302
+ $.log.debug(`[cachebase] add操作被事件处理器取消 [${key}]`);
1303
+ return false;
1304
+ }
1305
+
1306
+ const {
1307
+ key: finalKey,
1308
+ value: finalValue,
1309
+ maxAge: finalMaxAge
1310
+ } = beforeEventData;
1311
+
1312
+ // 检查键是否存在且未过期
1313
+ if (finalKey in this._data && !this._isExpired(finalKey)) {
1314
+ // 键已存在,返回false
1315
+ this.emit('add_after', {
1316
+ key: finalKey,
1317
+ value: finalValue,
1318
+ maxAge: finalMaxAge,
1319
+ success: false
1320
+ });
1321
+ return false;
1322
+ }
1323
+
1324
+ // 键不存在,设置新值
1325
+ this._data[finalKey] = finalValue;
1326
+ this._types[finalKey] = this._getDataTypes(finalValue);
1327
+
1328
+ // 设置过期时间
1329
+ if (finalMaxAge > 0) {
1330
+ this._expires[finalKey] = Date.now() + finalMaxAge * 1000;
1331
+ } else {
1332
+ delete this._expires[finalKey];
1333
+ }
1334
+
1335
+ // 触发后置事件
1336
+ this.emit('add_after', {
1337
+ key: finalKey,
1338
+ value: finalValue,
1339
+ maxAge: finalMaxAge,
1340
+ success: true
1341
+ });
1342
+ return true;
1343
+ } catch (error) {
1344
+ $.log.error(`[cachebase] add操作失败 [${key}]:`, error);
1345
+ this.emit('add_error', {
1346
+ key,
1347
+ value,
1348
+ maxAge,
1349
+ error
1350
+ });
1351
+ return false;
1352
+ }
1353
+ };
1354
+
1355
+ /**
1356
+ * 增加浮点数(兼容方法)
1357
+ * @param {string} key - 键名
1358
+ * @param {number} num - 要增加的浮点数值
1359
+ * @param {number} seconds - 过期时间(秒),0表示永不过期
1360
+ * @returns {Promise<number>} - 返回增加后的值
1361
+ */
1362
+ CacheBase.prototype.addFloat = function(key, num, seconds = 0) {
1363
+ try {
1364
+ // 参数验证
1365
+ if (!key || typeof key !== 'string' || typeof num !== 'number') {
1366
+ $.log.error('[cachebase] addFloat参数错误: key必须是非空字符串,num必须是数字');
1367
+ return 0;
1368
+ }
1369
+
1370
+ this.emit('addFloat_before', {
1371
+ key,
1372
+ num,
1373
+ ttl: seconds,
1374
+ cancel: false
1375
+ });
1376
+
1377
+ // 实现浮点数递增逻辑
1378
+ let currentValue = 0;
1379
+ if (key in this._data && !this._isExpired(key)) {
1380
+ currentValue = Number(this._data[key]) || 0;
1381
+ }
1382
+ const result = currentValue + Number(num);
1383
+
1384
+ // 设置值和过期时间
1385
+ this._data[key] = result;
1386
+ this._types[key] = 'number';
1387
+
1388
+ if (seconds > 0) {
1389
+ this._expires[key] = Date.now() + seconds * 1000;
1390
+ } else {
1391
+ delete this._expires[key];
1392
+ }
1393
+
1394
+ this.emit('addFloat_after', {
1395
+ key,
1396
+ num,
1397
+ ttl: seconds,
1398
+ success: true,
1399
+ result
1400
+ });
1401
+ return result;
1402
+ } catch (error) {
1403
+ this.emit('addFloat_error', {
1404
+ key,
1405
+ num,
1406
+ ttl: seconds,
1407
+ error
1408
+ });
1409
+ $.log.error('[cachebase] addFloat操作失败:', error);
1410
+ return 0;
1411
+ }
1412
+ };
1413
+
1414
+ /**
1415
+ * 追加字符串(兼容方法)
1416
+ * @param {string} key - 键名
1417
+ * @param {string} value - 要追加的字符串
1418
+ * @param {number} seconds - 过期时间(秒),0表示永不过期
1419
+ * @returns {Promise<number>} - 返回追加后字符串的长度
1420
+ */
1421
+ CacheBase.prototype.addStr = function(key, value, seconds = 0) {
1422
+ try {
1423
+ // 参数验证
1424
+ if (!key || typeof key !== 'string' || typeof value !== 'string') {
1425
+ $.log.error('[cachebase] addStr参数错误: key和value必须是非空字符串');
1426
+ return 0;
1427
+ }
1428
+
1429
+ this.emit('addStr_before', {
1430
+ key,
1431
+ value,
1432
+ ttl: seconds,
1433
+ cancel: false
1434
+ });
1435
+
1436
+ // 实现字符串追加逻辑
1437
+ let currentValue = '';
1438
+ if (key in this._data && !this._isExpired(key)) {
1439
+ currentValue = String(this._data[key]) || '';
1440
+ }
1441
+ const result = currentValue + value;
1442
+ const length = result.length;
1443
+
1444
+ // 设置值和过期时间
1445
+ this._data[key] = result;
1446
+ this._types[key] = 'string';
1447
+
1448
+ if (seconds > 0) {
1449
+ this._expires[key] = Date.now() + seconds * 1000;
1450
+ } else {
1451
+ delete this._expires[key];
1452
+ }
1453
+
1454
+ this.emit('addStr_after', {
1455
+ key,
1456
+ value,
1457
+ ttl: seconds,
1458
+ result,
1459
+ length,
1460
+ success: true
1461
+ });
1462
+ return length;
1463
+ } catch (error) {
1464
+ this.emit('addStr_error', {
1465
+ key,
1466
+ value,
1467
+ ttl: seconds,
1468
+ error
1469
+ });
1470
+ $.log.error('[cachebase] addStr操作失败:', error);
1471
+ return 0;
1472
+ }
1473
+ };
1474
+
1475
+ /**
1476
+ * 一次性设置多个键值对 - 兼容Redis的MSET命令
1477
+ * @param {string} key1, value1, key2, value2... - 键值对参数列表
1478
+ * @returns {boolean} - 成功返回true,失败返回false
1479
+ */
1480
+ CacheBase.prototype.mset = function(...args) {
1481
+ try {
1482
+ // 参数验证 - 特别处理null参数情况
1483
+ if (args.length === 1 && args[0] === null) {
1484
+ const error = new Error('mset参数必须是对象');
1485
+ this.emit('mset_error', {
1486
+ args,
1487
+ error
1488
+ });
1489
+ throw error;
1490
+ }
1491
+
1492
+ // 常规参数验证
1493
+ if (args.length === 0 || args.length % 2 !== 0) {
1494
+ $.log.error('[CacheBase] [mset] 参数错误: 参数数量必须为偶数');
1495
+ const error = new Error('参数数量必须为偶数');
1496
+ this.emit('mset_error', {
1497
+ args,
1498
+ error
1499
+ });
1500
+ throw error;
1501
+ }
1502
+
1503
+ // 将参数列表转换为键值对对象
1504
+ const keyValuePairs = {};
1505
+ for (let i = 0; i < args.length; i += 2) {
1506
+ const key = args[i];
1507
+ const value = args[i + 1];
1508
+
1509
+ if (typeof key !== 'string' || !key) {
1510
+ $.log.error(`[CacheBase] [mset] 参数错误: 键 ${key} 必须是非空字符串`);
1511
+ const error = new Error('键必须是非空字符串');
1512
+ this.emit('mset_error', {
1513
+ args,
1514
+ error
1515
+ });
1516
+ throw error;
1517
+ }
1518
+
1519
+ keyValuePairs[key] = value;
1520
+ }
1521
+
1522
+ // 触发前置事件
1523
+ const beforeEventData = {
1524
+ keyValuePairs,
1525
+ cancel: false
1526
+ };
1527
+ this.emit('mset_before', beforeEventData);
1528
+
1529
+ if (beforeEventData.cancel) {
1530
+ $.log.debug('[CacheBase] [mset] 操作被事件处理器取消');
1531
+ return false;
1532
+ }
1533
+
1534
+ const {
1535
+ keyValuePairs: finalKeyValuePairs
1536
+ } = beforeEventData;
1537
+
1538
+ // 批量设置键值对
1539
+ for (const [key, value] of Object.entries(finalKeyValuePairs)) {
1540
+ // 检查键是否过期
1541
+ if (this._isExpired(key)) {
1542
+ this.del(key);
1543
+ }
1544
+
1545
+ // 保存数据
1546
+ this._data[key] = value;
1547
+ // 设置数据类型
1548
+ this._types[key] = this._getDataTypes(value);
1549
+ // 清除过期时间(与Redis MSET一致)
1550
+ delete this._expires[key];
1551
+ }
1552
+
1553
+ // 触发后置事件
1554
+ this.emit('mset_after', {
1555
+ keyValuePairs: finalKeyValuePairs,
1556
+ result: true,
1557
+ success: true
1558
+ });
1559
+ return true;
1560
+ } catch (error) {
1561
+ // 确保触发错误事件
1562
+ this.emit('mset_error', {
1563
+ args,
1564
+ error
1565
+ });
1566
+ this.emit('error', new Error(`Failed to mset: ${error.message}`));
1567
+ throw error;
1568
+ }
1569
+ };
1570
+
1571
+ /**
1572
+ * 获取或设置键的过期时间
1573
+ * @param {String} key 键名
1574
+ * @param {Number} [seconds] 过期时间(秒),不传此参数则获取剩余过期时间
1575
+ * @return {Promise<Number|Boolean>} 不传seconds时返回剩余过期时间(-1表示无过期设置,-2表示已过期),传seconds时返回是否设置成功
1576
+ */
1577
+ CacheBase.prototype.ttl = function(key, seconds) {
1578
+ try {
1579
+ // 参数验证
1580
+ if (!key || typeof key !== 'string') {
1581
+ return arguments.length === 1 ? -2 : false;
1582
+ }
1583
+
1584
+ // 判断是否为设置过期时间模式
1585
+ if (arguments.length > 1) {
1586
+ // 设置过期时间模式
1587
+ if (typeof seconds !== 'number' || seconds < 0) {
1588
+ return false;
1589
+ }
1590
+
1591
+ // 检查键是否存在
1592
+ if (!(key in this._data)) {
1593
+ return false;
1594
+ }
1595
+
1596
+ // 检查是否过期
1597
+ if (this._isExpired(key)) {
1598
+ this.del(key);
1599
+ return false;
1600
+ }
1601
+
1602
+ // 设置过期时间
1603
+ if (seconds > 0) {
1604
+ this._expires[key] = Date.now() + (seconds * 1000);
1605
+ } else if (seconds === 0) {
1606
+ // 移除过期时间
1607
+ delete this._expires[key];
1608
+ }
1609
+
1610
+ return true;
1611
+ } else {
1612
+ // 获取剩余过期时间模式
1613
+ // 检查键是否存在
1614
+ if (!(key in this._data)) {
1615
+ return -2;
1616
+ }
1617
+
1618
+ // 检查是否过期
1619
+ if (this._isExpired(key)) {
1620
+ this.del(key);
1621
+ return -2;
1622
+ }
1623
+
1624
+ // 检查是否设置了过期时间
1625
+ if (!(key in this._expires)) {
1626
+ return -1; // 无过期时间
1627
+ }
1628
+
1629
+ // 计算剩余时间
1630
+ const now = Date.now();
1631
+ const remaining = this._expires[key] - now;
1632
+
1633
+ return Math.max(0, Math.ceil(remaining / 1000));
1634
+ }
1635
+ } catch (error) {
1636
+ const errorMsg = arguments.length > 1
1637
+ ? `Failed to set ttl for key ${key}: ${error.message}`
1638
+ : `Failed to get ttl for key ${key}: ${error.message}`;
1639
+ this.emit('error', new Error(errorMsg));
1640
+ return arguments.length > 1 ? false : -2;
1641
+ }
1642
+ };
1643
+
1644
+ /**
1645
+ * 移除键的过期时间
1646
+ * @param {string} key - 键名
1647
+ * @returns {boolean} 是否成功移除
1648
+ */
1649
+ CacheBase.prototype.persist = function(key) {
1650
+ try {
1651
+ // 参数验证
1652
+ if (!key || typeof key !== 'string') {
1653
+ return false;
1654
+ }
1655
+
1656
+ // 触发操作前事件
1657
+ const event_data = {
1658
+ key,
1659
+ cancel: false
1660
+ };
1661
+ this.emit('persist_before', event_data);
1662
+
1663
+ if (event_data.cancel) {
1664
+ return false;
1665
+ }
1666
+
1667
+ // 检查键是否存在
1668
+ if (!(key in this._data)) {
1669
+ return false;
1670
+ }
1671
+
1672
+ // 检查是否过期
1673
+ if (this._isExpired(key)) {
1674
+ this.del(key);
1675
+ return false;
1676
+ }
1677
+
1678
+ // 移除过期时间
1679
+ if (key in this._expires) {
1680
+ delete this._expires[key];
1681
+
1682
+ // 触发操作后事件
1683
+ this.emit('persist_after', {
1684
+ key
1685
+ });
1686
+ return true;
1687
+ }
1688
+
1689
+ return false; // 键没有设置过期时间
1690
+ } catch (error) {
1691
+ this.emit('error', new Error(`Failed to persist key ${key}: ${error.message}`));
1692
+ return false;
1693
+ }
1694
+ };
1695
+ /**
1696
+ * 批量获取多个键的值
1697
+ * @param {Array} keys - 键名数组
1698
+ * @returns {Array} 值数组,按键的顺序返回
1699
+ */
1700
+ CacheBase.prototype.mget = function(keys) {
1701
+ try {
1702
+ // 参数验证
1703
+ if (!Array.isArray(keys)) {
1704
+ return [];
1705
+ }
1706
+
1707
+ const results = [];
1708
+
1709
+ // 批量获取值
1710
+ for (const key of keys) {
1711
+ if (typeof key === 'string') {
1712
+ results.push(this.get(key));
1713
+ } else {
1714
+ results.push(null);
1715
+ }
1716
+ }
1717
+
1718
+ return results;
1719
+ } catch (error) {
1720
+ this.emit('error', new Error(`Failed to mget: ${error.message}`));
1721
+ return [];
1722
+ }
1723
+ };
1620
1724
 
1725
+ /**
1726
+ * 清理资源并销毁实例
1727
+ * @returns {boolean} 操作结果
1728
+ */
1729
+ CacheBase.prototype.dispose = function() {
1730
+ try {
1731
+ // 清除定时任务
1732
+ if (this._auto_save_timer) {
1733
+ clearInterval(this._auto_save_timer);
1734
+ this._auto_save_timer = null;
1735
+ }
1736
+
1737
+ // 清除所有数据
1738
+ this._data = {};
1739
+ this._expires = {};
1740
+ this._types = {};
1741
+
1742
+ // 触发销毁事件
1743
+ this.emit('destroy', {
1744
+ success: true
1745
+ });
1746
+
1747
+ return true;
1748
+ } catch (error) {
1749
+ this.emit('error', new Error(`Failed to dispose: ${error.message}`));
1750
+ return false;
1751
+ }
1752
+ };
1621
1753
 
1622
1754
  /**
1623
1755
  * 确保连接池对象存在
1624
1756
  */
1625
- if (!$.pool) {
1626
- $.pool = {};
1757
+ global.$ = global.$ || {};
1758
+ if (!global.$.pool) {
1759
+ global.$.pool = {};
1627
1760
  }
1628
- if (!$.pool.cachebase) {
1629
- $.pool.cachebase = {};
1761
+ if (!global.$.pool.cachebase) {
1762
+ global.$.pool.cachebase = {};
1630
1763
  }
1631
1764
 
1632
1765
  /**
@@ -1636,18 +1769,560 @@ if (!$.pool.cachebase) {
1636
1769
  * @return {Object} 返回一个cachebase类实例
1637
1770
  */
1638
1771
  function cachebase_admin(scope, dir) {
1639
- if (!scope) {
1640
- scope = 'sys';
1641
- }
1642
- var obj = $.pool.cachebase[scope];
1643
- if (!obj) {
1644
- $.pool.cachebase[scope] = new CacheBase(scope, dir);
1645
- obj = $.pool.cachebase[scope];
1646
- }
1647
- return obj;
1772
+ if (!scope) {
1773
+ scope = 'sys';
1774
+ }
1775
+ var obj = global.$.pool.cachebase[scope];
1776
+ if (!obj) {
1777
+ const config = {
1778
+ file_path: dir ? `./cachebase_${scope}.json`.fullname(dir) : `/cache/cachebase_${scope}.json`
1779
+ };
1780
+ global.$.pool.cachebase[scope] = new CacheBase(config);
1781
+ obj = global.$.pool.cachebase[scope];
1782
+ }
1783
+ return obj;
1648
1784
  }
1649
1785
 
1650
1786
  /**
1651
- * @module 导出cachebase管理器
1787
+ * 设置列表
1788
+ * @param {string} key - 键名
1789
+ * @param {Array} value - 列表值
1790
+ * @param {number} [maxAge=0] - 过期时间(秒),0表示永不过期
1791
+ * @returns {Promise<number>} - 列表长度
1792
+ */
1793
+ CacheBase.prototype.setForList = async function(key, value, maxAge = 0) {
1794
+ try {
1795
+ // 参数验证
1796
+ if (!key || typeof key !== 'string') {
1797
+ throw new Error('key必须是非空字符串');
1798
+ }
1799
+
1800
+ // 确保value是数组
1801
+ let stringValues = [];
1802
+ if (Array.isArray(value)) {
1803
+ stringValues = value.map(item => String(item));
1804
+ } else {
1805
+ stringValues = [String(value)];
1806
+ }
1807
+
1808
+ // 触发前置事件
1809
+ const beforeEventData = {
1810
+ key,
1811
+ value,
1812
+ maxAge,
1813
+ cancel: false
1814
+ };
1815
+ this.emit('setForList_before', beforeEventData);
1816
+
1817
+ if (beforeEventData.cancel) {
1818
+ $.log.debug(`[CacheBase] setForList操作被事件处理器取消 [${key}]`);
1819
+ return 0;
1820
+ }
1821
+
1822
+ // 检查键是否存在且未过期
1823
+ if (key in this._data && !this._isExpired(key)) {
1824
+ // 如果已存在但不是数组,删除并重新设置
1825
+ if (!Array.isArray(this._data[key])) {
1826
+ delete this._data[key];
1827
+ delete this._types[key];
1828
+ delete this._expires[key];
1829
+ }
1830
+ }
1831
+
1832
+ // 设置列表数据
1833
+ this._data[key] = stringValues;
1834
+ this._types[key] = 'array';
1835
+
1836
+ // 设置过期时间
1837
+ if (maxAge > 0) {
1838
+ this._expires[key] = Date.now() + maxAge * 1000;
1839
+ } else {
1840
+ delete this._expires[key];
1841
+ }
1842
+
1843
+ const length = stringValues.length;
1844
+
1845
+ // 触发后置事件
1846
+ this.emit('setForList_after', {
1847
+ key,
1848
+ value: stringValues,
1849
+ maxAge,
1850
+ result: length
1851
+ });
1852
+ return length;
1853
+ } catch (error) {
1854
+ // 触发错误事件
1855
+ this.emit('setForList_error', {
1856
+ key,
1857
+ value,
1858
+ maxAge,
1859
+ error
1860
+ });
1861
+ $.log.error('[CacheBase] setForList操作失败:', error);
1862
+ throw error;
1863
+ }
1864
+ };
1865
+
1866
+ /**
1867
+ * 向列表添加元素
1868
+ * @param {string} key - 键名
1869
+ * @param {*} value - 要添加的元素
1870
+ * @param {number} [maxAge=0] - 过期时间(秒),0表示永不过期
1871
+ * @returns {Promise<number>} - 添加后列表长度
1872
+ */
1873
+ CacheBase.prototype.addForList = async function(key, value, maxAge = 0) {
1874
+ try {
1875
+ // 参数验证
1876
+ if (!key || typeof key !== 'string') {
1877
+ throw new Error('key必须是非空字符串');
1878
+ }
1879
+
1880
+ // 确保value是字符串
1881
+ const stringValue = String(value);
1882
+
1883
+ // 触发前置事件
1884
+ const beforeEventData = {
1885
+ key,
1886
+ value,
1887
+ maxAge,
1888
+ cancel: false
1889
+ };
1890
+ this.emit('addForList_before', beforeEventData);
1891
+
1892
+ if (beforeEventData.cancel) {
1893
+ $.log.debug(`[CacheBase] addForList操作被事件处理器取消 [${key}]`);
1894
+ return 0;
1895
+ }
1896
+
1897
+ // 检查键是否存在且未过期
1898
+ if (key in this._data && !this._isExpired(key)) {
1899
+ // 如果已存在但不是数组,删除并重新设置为数组
1900
+ if (!Array.isArray(this._data[key])) {
1901
+ this._data[key] = [stringValue];
1902
+ this._types[key] = 'array';
1903
+ } else {
1904
+ // 向现有数组添加元素
1905
+ this._data[key].push(stringValue);
1906
+ }
1907
+ } else {
1908
+ // 键不存在或已过期,创建新数组
1909
+ this._data[key] = [stringValue];
1910
+ this._types[key] = 'array';
1911
+ }
1912
+
1913
+ // 设置过期时间
1914
+ if (maxAge > 0) {
1915
+ this._expires[key] = Date.now() + maxAge * 1000;
1916
+ } else if (key in this._expires) {
1917
+ delete this._expires[key];
1918
+ }
1919
+
1920
+ const length = this._data[key].length;
1921
+
1922
+ // 触发后置事件
1923
+ this.emit('addForList_after', {
1924
+ key,
1925
+ value: stringValue,
1926
+ maxAge,
1927
+ result: length
1928
+ });
1929
+ return length;
1930
+ } catch (error) {
1931
+ // 触发错误事件
1932
+ this.emit('addForList_error', {
1933
+ key,
1934
+ value,
1935
+ maxAge,
1936
+ error
1937
+ });
1938
+ $.log.error('[CacheBase] addForList操作失败:', error);
1939
+ throw error;
1940
+ }
1941
+ };
1942
+
1943
+ /**
1944
+ * 检查列表中是否包含指定元素
1945
+ * @param {string} key - 键名
1946
+ * @param {*} value - 要检查的元素
1947
+ * @returns {Promise<boolean>} - 是否包含
1948
+ */
1949
+ CacheBase.prototype.hasForList = async function(key, value) {
1950
+ try {
1951
+ // 参数验证
1952
+ if (!key || typeof key !== 'string') {
1953
+ return false;
1954
+ }
1955
+
1956
+ // 确保value是字符串
1957
+ const stringValue = String(value);
1958
+
1959
+ // 触发前置事件
1960
+ const beforeEventData = {
1961
+ key,
1962
+ value,
1963
+ cancel: false
1964
+ };
1965
+ this.emit('hasForList_before', beforeEventData);
1966
+
1967
+ if (beforeEventData.cancel) {
1968
+ $.log.debug(`[CacheBase] hasForList操作被事件处理器取消 [${key}]`);
1969
+ return false;
1970
+ }
1971
+
1972
+ // 检查键是否存在且未过期
1973
+ if (key in this._data && !this._isExpired(key)) {
1974
+ // 检查是否为数组
1975
+ if (Array.isArray(this._data[key])) {
1976
+ // 检查数组中是否包含指定元素
1977
+ const result = this._data[key].includes(stringValue);
1978
+ // 触发后置事件
1979
+ this.emit('hasForList_after', {
1980
+ key,
1981
+ value,
1982
+ result
1983
+ });
1984
+ return result;
1985
+ }
1986
+ }
1987
+
1988
+ // 键不存在、已过期或不是数组
1989
+ this.emit('hasForList_after', {
1990
+ key,
1991
+ value,
1992
+ result: 0
1993
+ });
1994
+ return false;
1995
+ } catch (error) {
1996
+ // 触发错误事件
1997
+ this.emit('hasForList_error', {
1998
+ key,
1999
+ value,
2000
+ error
2001
+ });
2002
+ $.log.error('[CacheBase] hasForList操作失败:', error);
2003
+ throw error;
2004
+ }
2005
+ };
2006
+
2007
+ /**
2008
+ * 获取列表
2009
+ * @param {string} key - 键名
2010
+ * @param {number} [start=0] - 起始索引
2011
+ * @param {number} [stop=-1] - 结束索引,-1表示全部
2012
+ * @returns {Promise<Array>} - 列表数组
2013
+ */
2014
+ CacheBase.prototype.getForList = async function(key, start = 0, stop = -1) {
2015
+ try {
2016
+ // 参数验证
2017
+ if (!key || typeof key !== 'string') {
2018
+ return [];
2019
+ }
2020
+
2021
+ // 触发前置事件
2022
+ const beforeEventData = {
2023
+ key,
2024
+ start,
2025
+ stop,
2026
+ cancel: false
2027
+ };
2028
+ this.emit('getForList_before', beforeEventData);
2029
+
2030
+ if (beforeEventData.cancel) {
2031
+ $.log.debug(`[CacheBase] getForList操作被事件处理器取消 [${key}]`);
2032
+ return [];
2033
+ }
2034
+
2035
+ // 检查键是否存在且未过期
2036
+ if (key in this._data && !this._isExpired(key)) {
2037
+ // 检查是否为数组
2038
+ if (Array.isArray(this._data[key])) {
2039
+ const array = this._data[key];
2040
+
2041
+ // 处理负索引
2042
+ const startIndex = start < 0 ? Math.max(0, array.length + start) : start;
2043
+ const endIndex = stop < 0 ? array.length : stop + 1;
2044
+
2045
+ // 截取数组
2046
+ const result = array.slice(startIndex, endIndex);
2047
+
2048
+ // 触发后置事件
2049
+ this.emit('getForList_after', {
2050
+ key,
2051
+ start,
2052
+ stop,
2053
+ result
2054
+ });
2055
+ return result;
2056
+ }
2057
+ }
2058
+
2059
+ // 键不存在、已过期或不是数组
2060
+ this.emit('getForList_after', {
2061
+ key,
2062
+ start,
2063
+ stop,
2064
+ result: []
2065
+ });
2066
+ return [];
2067
+ } catch (error) {
2068
+ // 触发错误事件
2069
+ this.emit('getForList_error', {
2070
+ key,
2071
+ start,
2072
+ stop,
2073
+ error
2074
+ });
2075
+ $.log.error('[CacheBase] getForList操作失败:', error);
2076
+ throw error;
2077
+ }
2078
+ };
2079
+
2080
+ /**
2081
+ * 清空列表
2082
+ * @param {string} key - 键名
2083
+ * @returns {Promise<boolean>} - 操作结果
2084
+ */
2085
+ CacheBase.prototype.clearForList = async function(key) {
2086
+ try {
2087
+ // 参数验证
2088
+ if (!key || typeof key !== 'string') {
2089
+ return false;
2090
+ }
2091
+
2092
+ // 触发前置事件
2093
+ const beforeEventData = {
2094
+ key,
2095
+ cancel: false
2096
+ };
2097
+ this.emit('clearForList_before', beforeEventData);
2098
+
2099
+ if (beforeEventData.cancel) {
2100
+ $.log.debug(`[CacheBase] clearForList操作被事件处理器取消 [${key}]`);
2101
+ return false;
2102
+ }
2103
+
2104
+ // 检查键是否存在且未过期
2105
+ if (key in this._data && !this._isExpired(key)) {
2106
+ // 检查是否为数组
2107
+ if (Array.isArray(this._data[key])) {
2108
+ // 清空数组
2109
+ this._data[key] = [];
2110
+
2111
+ // 触发后置事件
2112
+ this.emit('clearForList_after', {
2113
+ key,
2114
+ result: true
2115
+ });
2116
+ return true;
2117
+ } else {
2118
+ // 如果不是数组,删除键
2119
+ delete this._data[key];
2120
+ delete this._types[key];
2121
+ delete this._expires[key];
2122
+
2123
+ // 触发后置事件
2124
+ this.emit('clearForList_after', {
2125
+ key,
2126
+ result: false
2127
+ });
2128
+ return false;
2129
+ }
2130
+ }
2131
+
2132
+ // 键不存在或已过期,创建空数组
2133
+ this._data[key] = [];
2134
+ this._types[key] = 'array';
2135
+
2136
+ // 触发后置事件
2137
+ this.emit('clearForList_after', {
2138
+ key,
2139
+ result: true
2140
+ });
2141
+ return true;
2142
+ } catch (error) {
2143
+ // 触发错误事件
2144
+ this.emit('clearForList_error', {
2145
+ key,
2146
+ error
2147
+ });
2148
+ $.log.error('[CacheBase] clearForList操作失败:', error);
2149
+ throw error;
2150
+ }
2151
+ };
2152
+
2153
+ /**
2154
+ * 批量设置哈希表字段
2155
+ * @param {string} key - 哈希表键名
2156
+ * @param {Object} obj - 包含多个字段的对象
2157
+ * @returns {Promise<boolean>} - 操作结果
2158
+ */
2159
+ CacheBase.prototype.hmset = function(key, obj) {
2160
+ return new Promise((resolve) => {
2161
+ try {
2162
+ // 参数验证
2163
+ if (!key || typeof key !== 'string' || !obj || typeof obj !== 'object' || Array.isArray(obj)) {
2164
+ resolve(false);
2165
+ return;
2166
+ }
2167
+
2168
+ // 触发前置事件
2169
+ const beforeEventData = {
2170
+ key,
2171
+ obj,
2172
+ cancel: false
2173
+ };
2174
+ this.emit('hmset_before', beforeEventData);
2175
+
2176
+ if (beforeEventData.cancel) {
2177
+ resolve(false);
2178
+ return;
2179
+ }
2180
+
2181
+ // 检查是否过期
2182
+ if (this._isExpired(key)) {
2183
+ this.del(key);
2184
+ }
2185
+
2186
+ // 初始化哈希表
2187
+ if (!(key in this._data) || this._types[key] !== 'hash') {
2188
+ this._data[key] = {};
2189
+ this._types[key] = 'hash';
2190
+ }
2191
+
2192
+ // 批量设置字段
2193
+ for (const field in obj) {
2194
+ if (Object.prototype.hasOwnProperty.call(obj, field)) {
2195
+ this._data[key][field] = obj[field];
2196
+ }
2197
+ }
2198
+
2199
+ // 触发后置事件
2200
+ this.emit('hmset_after', {
2201
+ key,
2202
+ obj,
2203
+ success: true
2204
+ });
2205
+ resolve(true);
2206
+ } catch (error) {
2207
+ this.emit('hmset_error', {
2208
+ key,
2209
+ obj,
2210
+ error
2211
+ });
2212
+ this.emit('error', new Error(`Failed to hmset: ${error.message}`));
2213
+ resolve(false);
2214
+ }
2215
+ });
2216
+ };
2217
+
2218
+ /**
2219
+ * 批量获取哈希表字段
2220
+ * @param {string} key - 哈希表键名
2221
+ * @param {Array} fields - 字段名数组
2222
+ * @returns {Promise<Array>} - 字段值数组,不存在的字段返回null
1652
2223
  */
1653
- exports.cachebase_admin = cachebase_admin;
2224
+ CacheBase.prototype.hmget = function(key, fields) {
2225
+ return new Promise((resolve) => {
2226
+ try {
2227
+ // 参数验证
2228
+ if (!key || typeof key !== 'string' || !Array.isArray(fields)) {
2229
+ resolve([]);
2230
+ return;
2231
+ }
2232
+
2233
+ // 检查是否过期
2234
+ if (this._isExpired(key)) {
2235
+ this.del(key);
2236
+ resolve([]);
2237
+ return;
2238
+ }
2239
+
2240
+ // 检查键是否存在且类型正确
2241
+ if (!(key in this._data) || this._types[key] !== 'hash') {
2242
+ resolve(fields.map(() => null));
2243
+ return;
2244
+ }
2245
+
2246
+ // 批量获取字段值
2247
+ const results = [];
2248
+ for (const field of fields) {
2249
+ results.push(this._data[key][field] !== undefined ? this._data[key][field] : null);
2250
+ }
2251
+
2252
+ resolve(results);
2253
+ } catch (error) {
2254
+ this.emit('hmget_error', {
2255
+ key,
2256
+ fields,
2257
+ error
2258
+ });
2259
+ this.emit('error', new Error(`Failed to hmget: ${error.message}`));
2260
+ resolve([]);
2261
+ }
2262
+ });
2263
+ };
2264
+
2265
+ /**
2266
+ * 获取哈希表字段数量
2267
+ * @param {string} key - 哈希表键名
2268
+ * @returns {Promise<number>} - 字段数量,失败返回0
2269
+ */
2270
+ CacheBase.prototype.hlen = function(key) {
2271
+ return new Promise((resolve) => {
2272
+ try {
2273
+ // 参数验证
2274
+ if (!key || typeof key !== 'string') {
2275
+ resolve(0);
2276
+ return;
2277
+ }
2278
+
2279
+ // 检查是否过期
2280
+ if (this._isExpired(key)) {
2281
+ this.del(key);
2282
+ resolve(0);
2283
+ return;
2284
+ }
2285
+
2286
+ // 检查键是否存在且类型正确
2287
+ if (!(key in this._data) || this._types[key] !== 'hash') {
2288
+ resolve(0);
2289
+ return;
2290
+ }
2291
+
2292
+ resolve(Object.keys(this._data[key]).length);
2293
+ } catch (error) {
2294
+ this.emit('hlen_error', {
2295
+ key,
2296
+ error
2297
+ });
2298
+ this.emit('error', new Error(`Failed to hlen: ${error.message}`));
2299
+ resolve(0);
2300
+ }
2301
+ });
2302
+ };
2303
+
2304
+ // 导出类
2305
+ exports.CacheBase = CacheBase;
2306
+ exports.cachebase_admin = function(scope, dir) {
2307
+ if (!global.$) {
2308
+ global.$ = {};
2309
+ }
2310
+ if (!global.$.pool) {
2311
+ global.$.pool = {};
2312
+ }
2313
+ if (!global.$.pool.cachebase) {
2314
+ global.$.pool.cachebase = {};
2315
+ }
2316
+ if (!scope) {
2317
+ scope = 'sys';
2318
+ }
2319
+ var obj = global.$.pool.cachebase[scope];
2320
+ if (!obj) {
2321
+ const config = {
2322
+ file_path: dir ? './cachebase_' + scope + '.json' : '/cache/cachebase_' + scope + '.json'
2323
+ };
2324
+ global.$.pool.cachebase[scope] = new CacheBase(config);
2325
+ obj = global.$.pool.cachebase[scope];
2326
+ }
2327
+ return obj;
2328
+ };