mm_cachebase 1.8.8 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +152 -89
- package/package.json +2 -2
- package/index_old.js +0 -2521
- package/simple_test.js +0 -21
- package/simple_test_direct.js +0 -27
- package/test_cache/test_file_operations.json +0 -1
- package/test_cache/undefined.cache.json +0 -12
- package/test_new_methods.js +0 -170
package/index_old.js
DELETED
|
@@ -1,2521 +0,0 @@
|
|
|
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 0;
|
|
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 0;
|
|
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
|
-
return null;
|
|
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
|
-
return null;
|
|
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 0;
|
|
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<boolean>} - 操作成功返回true,失败返回false
|
|
271
|
-
* @note 确保行为与Redis兼容,支持所有Redis的参数模式
|
|
272
|
-
*/
|
|
273
|
-
async set(key, value, seconds) {
|
|
274
|
-
try {
|
|
275
|
-
// 键名必须是非空字符串
|
|
276
|
-
if (!key || typeof key !== 'string') {
|
|
277
|
-
$.log.error('[cachebase] 参数错误: key必须是非空字符串');
|
|
278
|
-
return 0;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// 触发前置事件,允许修改或阻止操作
|
|
282
|
-
const beforeEventData = { key, value, ttl: seconds, cancel: false };
|
|
283
|
-
this.emit('set_before', beforeEventData);
|
|
284
|
-
|
|
285
|
-
// 如果事件处理器标记为取消,则返回false
|
|
286
|
-
if (beforeEventData.cancel) {
|
|
287
|
-
$.log.debug(`[cachebase] set操作被事件处理器取消 [${key}]`);
|
|
288
|
-
return 0;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// 使用可能被修改的值
|
|
292
|
-
let { key: finalKey, value: finalValue, ttl: finalSeconds } = beforeEventData;
|
|
293
|
-
|
|
294
|
-
// 处理参数模式兼容性(Redis支持多种参数模式)
|
|
295
|
-
// 检查是否是Redis风格的参数模式:set(key, value, 'EX', seconds) 或 set(key, value, 'PX', milliseconds)
|
|
296
|
-
if (typeof finalSeconds === 'string') {
|
|
297
|
-
// 暂时不处理复杂参数模式,保持简单的过期时间设置
|
|
298
|
-
finalSeconds = 0;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// 设置值和过期时间,确保结构与Redis完全兼容
|
|
302
|
-
this.data[finalKey] = {
|
|
303
|
-
value: finalValue,
|
|
304
|
-
expire: finalSeconds > 0 ? Date.now() + finalSeconds * 1000 : 0
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
// 如果键不存在于列表中,添加到列表
|
|
308
|
-
if (!this.list.includes(finalKey)) {
|
|
309
|
-
this.list.push(finalKey);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// 确保返回值始终为布尔值true,与Redis一致
|
|
313
|
-
this.emit('set_after', { key: finalKey, value: finalValue, ttl: finalSeconds, success: true });
|
|
314
|
-
return true;
|
|
315
|
-
} catch (error) {
|
|
316
|
-
// 触发错误事件
|
|
317
|
-
this.emit('set_error', { key, value, ttl: seconds, error });
|
|
318
|
-
$.log.error('[cachebase] set操作失败:', error);
|
|
319
|
-
return 0;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
/**
|
|
324
|
-
* 获取键值
|
|
325
|
-
* @param {string} key - 键名
|
|
326
|
-
* @returns {Promise<*>} - 返回键值,不存在返回null
|
|
327
|
-
*/
|
|
328
|
-
async get(key) {
|
|
329
|
-
try {
|
|
330
|
-
// 参数验证
|
|
331
|
-
if (!key || typeof key !== 'string') {
|
|
332
|
-
return null;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// 检查键是否存在
|
|
336
|
-
if (!this.data.hasOwnProperty(key)) {
|
|
337
|
-
return null;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const item = this.data[key];
|
|
341
|
-
|
|
342
|
-
// 检查是否过期
|
|
343
|
-
if (item.expire > 0 && Date.now() > item.expire) {
|
|
344
|
-
// 删除过期键
|
|
345
|
-
delete this.data[key];
|
|
346
|
-
const index = this.list.indexOf(key);
|
|
347
|
-
if (index !== -1) {
|
|
348
|
-
this.list.splice(index, 1);
|
|
349
|
-
}
|
|
350
|
-
return null;
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
return item.value;
|
|
354
|
-
} catch (error) {
|
|
355
|
-
$.log.error('[cachebase] get操作失败:', error);
|
|
356
|
-
return null;
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* 删除键 - 兼容方法
|
|
362
|
-
* @param {string} key - 键名
|
|
363
|
-
* @returns {Promise<boolean>} - 操作成功返回true,失败返回false
|
|
364
|
-
* @note 确保与其他模块返回值风格一致
|
|
365
|
-
*/
|
|
366
|
-
async del(key) {
|
|
367
|
-
try {
|
|
368
|
-
// 键名必须是非空字符串或非空字符串数组
|
|
369
|
-
if (!key) {
|
|
370
|
-
$.log.error('[cachebase] 参数错误: key必须是非空字符串或非空字符串数组');
|
|
371
|
-
return false;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
// 如果是单个键,验证是否为非空字符串
|
|
375
|
-
if (typeof key === 'string') {
|
|
376
|
-
if (!key) {
|
|
377
|
-
$.log.error('[cachebase] 参数错误: key必须是非空字符串');
|
|
378
|
-
return false;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
// 如果是数组,验证每个元素是否为非空字符串
|
|
382
|
-
else if (Array.isArray(key)) {
|
|
383
|
-
for (const k of key) {
|
|
384
|
-
if (!k || typeof k !== 'string') {
|
|
385
|
-
$.log.error('[cachebase] 参数错误: key必须是非空字符串或非空字符串数组');
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
$.log.error('[cachebase] 参数错误: key必须是非空字符串或非空字符串数组');
|
|
392
|
-
return false;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
// 触发前置事件,允许修改或阻止操作
|
|
396
|
-
const beforeEventData = { key, cancel: false };
|
|
397
|
-
this.emit('del_before', beforeEventData);
|
|
398
|
-
|
|
399
|
-
// 如果事件处理器标记为取消,则返回false
|
|
400
|
-
if (beforeEventData.cancel) {
|
|
401
|
-
$.log.debug(`[cachebase] del操作被事件处理器取消 [${key}]`);
|
|
402
|
-
return false;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// 使用可能被修改的值
|
|
406
|
-
let { key: finalKey } = beforeEventData;
|
|
407
|
-
|
|
408
|
-
let deletedCount = 0;
|
|
409
|
-
|
|
410
|
-
if (Array.isArray(finalKey)) {
|
|
411
|
-
// 批量删除
|
|
412
|
-
for (const k of finalKey) {
|
|
413
|
-
if (this.data.hasOwnProperty(k)) {
|
|
414
|
-
const item = this.data[k];
|
|
415
|
-
// 检查是否过期
|
|
416
|
-
if (!(item.expire > 0 && Date.now() > item.expire)) {
|
|
417
|
-
// 执行删除操作
|
|
418
|
-
delete this.data[k];
|
|
419
|
-
const index = this.list.indexOf(k);
|
|
420
|
-
if (index !== -1) {
|
|
421
|
-
this.list.splice(index, 1);
|
|
422
|
-
}
|
|
423
|
-
deletedCount++;
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
427
|
-
} else {
|
|
428
|
-
// 单个删除
|
|
429
|
-
if (this.data.hasOwnProperty(finalKey)) {
|
|
430
|
-
const item = this.data[finalKey];
|
|
431
|
-
// 检查是否过期
|
|
432
|
-
if (!(item.expire > 0 && Date.now() > item.expire)) {
|
|
433
|
-
// 执行删除操作
|
|
434
|
-
delete this.data[finalKey];
|
|
435
|
-
const index = this.list.indexOf(finalKey);
|
|
436
|
-
if (index !== -1) {
|
|
437
|
-
this.list.splice(index, 1);
|
|
438
|
-
}
|
|
439
|
-
deletedCount++;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// 触发后置事件
|
|
445
|
-
this.emit('del_after', { key: finalKey, deletedCount, result: deletedCount > 0 });
|
|
446
|
-
return deletedCount > 0;
|
|
447
|
-
} catch (error) {
|
|
448
|
-
// 触发错误事件
|
|
449
|
-
this.emit('del_error', { key, error });
|
|
450
|
-
$.log.error('[cachebase] del操作失败:', error);
|
|
451
|
-
return false;
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
/**
|
|
456
|
-
* 检查键是否存在 - 兼容方法
|
|
457
|
-
* @param {string|Array<string>} key - 键名或键名数组
|
|
458
|
-
* @returns {Promise<boolean>} - 键存在返回true,不存在返回false
|
|
459
|
-
* @note 确保与其他模块返回值风格一致
|
|
460
|
-
*/
|
|
461
|
-
async exists(key) {
|
|
462
|
-
try {
|
|
463
|
-
// 验证数组中的每个元素都是非空字符串
|
|
464
|
-
if (Array.isArray(key)) {
|
|
465
|
-
for (const k of key) {
|
|
466
|
-
if (typeof k !== 'string' || !k) {
|
|
467
|
-
throw new Error('all keys must be non-empty strings');
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
// 处理多键检查,只要有一个键存在且未过期就返回true
|
|
471
|
-
for (const k of key) {
|
|
472
|
-
if (this.data.hasOwnProperty(k)) {
|
|
473
|
-
// 检查是否过期,与Redis一致:已过期的键被视为不存在
|
|
474
|
-
const item = this.data[k];
|
|
475
|
-
if (!(item.expire > 0 && Date.now() > item.expire)) {
|
|
476
|
-
return true;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
return false;
|
|
481
|
-
} else {
|
|
482
|
-
// 处理单键检查
|
|
483
|
-
if (this.data.hasOwnProperty(key)) {
|
|
484
|
-
// 检查是否过期,与Redis一致:已过期的键被视为不存在
|
|
485
|
-
const item = this.data[key];
|
|
486
|
-
if (!(item.expire > 0 && Date.now() > item.expire)) {
|
|
487
|
-
return true;
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
return false;
|
|
491
|
-
}
|
|
492
|
-
} catch (error) {
|
|
493
|
-
$.log.error('[cachebase] exists操作失败:', error);
|
|
494
|
-
return false;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* 设置键的过期时间 - 兼容Redis的EXPIRE命令
|
|
500
|
-
* @param {string} key - 键名
|
|
501
|
-
* @param {number} seconds - 过期时间(秒)
|
|
502
|
-
* @returns {Promise<boolean>} - 操作成功返回true,失败返回false
|
|
503
|
-
* @note 确保与其他模块返回值风格一致
|
|
504
|
-
*/
|
|
505
|
-
async expire(key, seconds) {
|
|
506
|
-
try {
|
|
507
|
-
// 参数验证
|
|
508
|
-
if (!key || typeof key !== 'string' || typeof seconds !== 'number' || seconds <= 0) {
|
|
509
|
-
$.log.error('[cachebase] expire参数错误: key必须是非空字符串,seconds必须是正数');
|
|
510
|
-
return false;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
// 触发前置事件
|
|
514
|
-
const beforeEventData = { key, ttl: seconds, cancel: false };
|
|
515
|
-
this.emit('expire_before', beforeEventData);
|
|
516
|
-
|
|
517
|
-
if (beforeEventData.cancel) {
|
|
518
|
-
$.log.debug(`[cachebase] expire操作被事件处理器取消 [${key}]`);
|
|
519
|
-
return false;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
const { key: finalKey, ttl: finalSeconds } = beforeEventData;
|
|
523
|
-
|
|
524
|
-
// 检查键是否存在
|
|
525
|
-
if (!this.data.hasOwnProperty(finalKey)) {
|
|
526
|
-
this.emit('expire_after', { key: finalKey, ttl: finalSeconds, result: false });
|
|
527
|
-
return false; // 键不存在,返回false
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// 检查是否已过期
|
|
531
|
-
const item = this.data[finalKey];
|
|
532
|
-
if (item.expire > 0 && Date.now() > item.expire) {
|
|
533
|
-
// 键已过期,删除并返回false
|
|
534
|
-
delete this.data[finalKey];
|
|
535
|
-
const index = this.list.indexOf(finalKey);
|
|
536
|
-
if (index !== -1) {
|
|
537
|
-
this.list.splice(index, 1);
|
|
538
|
-
}
|
|
539
|
-
this.emit('expire_after', { key: finalKey, ttl: finalSeconds, result: false });
|
|
540
|
-
return false; // 键已过期,返回false
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// 设置过期时间
|
|
544
|
-
this.data[finalKey].expire = Date.now() + finalSeconds * 1000;
|
|
545
|
-
|
|
546
|
-
// 触发后置事件
|
|
547
|
-
this.emit('expire_after', { key: finalKey, ttl: finalSeconds, result: true });
|
|
548
|
-
return true; // 成功设置,返回true
|
|
549
|
-
} catch (error) {
|
|
550
|
-
$.log.error(`[cachebase] 设置过期时间失败 [${key}]:`, error);
|
|
551
|
-
this.emit('expire_error', { key, ttl: seconds, error });
|
|
552
|
-
return false; // 发生错误,返回false
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
/**
|
|
557
|
-
* 设置哈希表字段 - 兼容方法
|
|
558
|
-
* @param {string} key - 键名
|
|
559
|
-
* @param {string} field - 字段名
|
|
560
|
-
* @param {*} value - 字段值
|
|
561
|
-
* @returns {Promise<number>} - 返回1表示新建字段,0表示更新字段
|
|
562
|
-
* @note 完全兼容Redis HSET命令,确保返回值和行为一致性
|
|
563
|
-
*/
|
|
564
|
-
async hset(key, field, value) {
|
|
565
|
-
try {
|
|
566
|
-
this.emit('hset_before', { key, field, value });
|
|
567
|
-
|
|
568
|
-
// 初始化哈希表(如果键不存在或已过期)
|
|
569
|
-
if (!this.data.hasOwnProperty(key)) {
|
|
570
|
-
// 键不存在,创建新的哈希表
|
|
571
|
-
this.data[key] = { value: {}, expire: 0 };
|
|
572
|
-
this.list.push(key);
|
|
573
|
-
} else if (this.data[key].expire > 0 && Date.now() > this.data[key].expire) {
|
|
574
|
-
// 键已过期,重新初始化哈希表
|
|
575
|
-
this.data[key] = { value: {}, expire: 0 };
|
|
576
|
-
} else if (typeof this.data[key].value !== 'object' || this.data[key].value === null) {
|
|
577
|
-
// 类型错误,与Redis一致:如果键不是哈希表类型,应该返回错误
|
|
578
|
-
throw new Error(`[cachebase] 键 ${key} 不是哈希表类型`);
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// 检查字段是否存在,以确定是新建还是更新
|
|
582
|
-
const isNewField = !this.data[key].value.hasOwnProperty(field);
|
|
583
|
-
// 设置字段值
|
|
584
|
-
this.data[key].value[field] = value;
|
|
585
|
-
|
|
586
|
-
// 确保返回值符合Redis规范:1表示新建字段,0表示更新字段
|
|
587
|
-
const result = isNewField ? 1 : 0;
|
|
588
|
-
this.emit('hset_after', { key, field, value, result });
|
|
589
|
-
return result;
|
|
590
|
-
} catch (error) {
|
|
591
|
-
this.emit('hset_error', { key, field, value, error });
|
|
592
|
-
$.log.error('[cachebase] hset操作失败:', error);
|
|
593
|
-
throw error;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
/**
|
|
598
|
-
* 获取哈希表字段
|
|
599
|
-
* @param {string} key - 键名
|
|
600
|
-
* @param {string} field - 字段名
|
|
601
|
-
* @returns {Promise<*>} - 返回字段值,不存在返回undefined
|
|
602
|
-
*/
|
|
603
|
-
async hget(key, field) {
|
|
604
|
-
try {
|
|
605
|
-
// 检查键是否存在且未过期
|
|
606
|
-
if (!this.data.hasOwnProperty(key) ||
|
|
607
|
-
(this.data[key].expire > 0 && Date.now() > this.data[key].expire) ||
|
|
608
|
-
typeof this.data[key].value !== 'object') {
|
|
609
|
-
return undefined;
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
return this.data[key].value[field];
|
|
613
|
-
} catch (error) {
|
|
614
|
-
$.log.error('[cachebase] hget操作失败:', error);
|
|
615
|
-
throw error;
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
/**
|
|
620
|
-
* 获取哈希表所有字段
|
|
621
|
-
* @param {string} key - 键名
|
|
622
|
-
* @returns {Promise<Object>} - 返回哈希表对象,不存在或不是哈希表返回{}
|
|
623
|
-
*/
|
|
624
|
-
async hgetall(key) {
|
|
625
|
-
try {
|
|
626
|
-
// 检查键是否存在且未过期
|
|
627
|
-
if (!this.data.hasOwnProperty(key) ||
|
|
628
|
-
(this.data[key].expire > 0 && Date.now() > this.data[key].expire) ||
|
|
629
|
-
typeof this.data[key].value !== 'object') {
|
|
630
|
-
return {};
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// 返回哈希表的副本
|
|
634
|
-
return { ...this.data[key].value };
|
|
635
|
-
} catch (error) {
|
|
636
|
-
$.log.error('[cachebase] hgetall操作失败:', error);
|
|
637
|
-
throw error;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
/**
|
|
642
|
-
* 删除哈希表字段
|
|
643
|
-
* @param {string} key - 键名
|
|
644
|
-
* @param {string|Array<string>} field - 字段名或字段名数组
|
|
645
|
-
* @returns {Promise<boolean>} - 操作成功返回true,失败返回false
|
|
646
|
-
*/
|
|
647
|
-
async hdel(key, field) {
|
|
648
|
-
try {
|
|
649
|
-
// 参数验证
|
|
650
|
-
if (typeof key !== 'string' || key === '') {
|
|
651
|
-
$.log.error('[cachebase] hdel操作失败: 键必须是非空字符串');
|
|
652
|
-
return false;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// 触发前置事件,允许修改或阻止操作
|
|
656
|
-
const beforeEventData = { key, field, cancel: false };
|
|
657
|
-
this.emit('hdel_before', beforeEventData);
|
|
658
|
-
|
|
659
|
-
if (beforeEventData.cancel) {
|
|
660
|
-
$.log.debug('[cachebase] hdel操作被事件处理器取消');
|
|
661
|
-
return false;
|
|
662
|
-
}
|
|
663
|
-
|
|
664
|
-
const { key: finalKey, field: finalField } = beforeEventData;
|
|
665
|
-
|
|
666
|
-
// 处理字段参数,支持单个字段或字段数组
|
|
667
|
-
const fields = Array.isArray(finalField) ? finalField : [finalField];
|
|
668
|
-
let deleted = false;
|
|
669
|
-
|
|
670
|
-
// 检查键是否存在且未过期
|
|
671
|
-
if (this.data.hasOwnProperty(finalKey) &&
|
|
672
|
-
!(this.data[finalKey].expire > 0 && Date.now() > this.data[finalKey].expire) &&
|
|
673
|
-
typeof this.data[finalKey].value === 'object') {
|
|
674
|
-
|
|
675
|
-
// 遍历所有字段进行删除
|
|
676
|
-
for (const f of fields) {
|
|
677
|
-
if (typeof f === 'string' && f !== '' && this.data[finalKey].value.hasOwnProperty(f)) {
|
|
678
|
-
delete this.data[finalKey].value[f];
|
|
679
|
-
deleted = true;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// 检查哈希表是否为空
|
|
684
|
-
const hashKeys = Object.keys(this.data[finalKey].value);
|
|
685
|
-
if (hashKeys.length === 0) {
|
|
686
|
-
// 删除空哈希表
|
|
687
|
-
delete this.data[finalKey];
|
|
688
|
-
const index = this.list.indexOf(finalKey);
|
|
689
|
-
if (index !== -1) {
|
|
690
|
-
this.list.splice(index, 1);
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
// 触发后置事件
|
|
695
|
-
this.emit('hdel_after', { key: finalKey, field: finalField, success: deleted });
|
|
696
|
-
return deleted;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
// 未删除返回false
|
|
700
|
-
this.emit('hdel_after', { key: finalKey, field: finalField, success: false });
|
|
701
|
-
return false;
|
|
702
|
-
} catch (error) {
|
|
703
|
-
this.emit('hdel_error', { key, field, error });
|
|
704
|
-
$.log.error('[cachebase] hdel操作失败:', error);
|
|
705
|
-
return false;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
/**
|
|
710
|
-
* 批量设置键值 - 兼容方法
|
|
711
|
-
* @param {Object} keyValues - 键值对对象
|
|
712
|
-
* @returns {Promise<boolean>} - 操作成功返回true,失败返回false
|
|
713
|
-
* @note 完全兼容Redis MSET命令,确保参数验证和行为一致性
|
|
714
|
-
*/
|
|
715
|
-
async mset(keyValues) {
|
|
716
|
-
try {
|
|
717
|
-
// 触发前置事件,允许修改或阻止操作
|
|
718
|
-
const beforeEventData = { keyValues, cancel: false };
|
|
719
|
-
this.emit('mset_before', beforeEventData);
|
|
720
|
-
|
|
721
|
-
if (beforeEventData.cancel) {
|
|
722
|
-
$.log.debug('[cachebase] mset操作被事件处理器取消');
|
|
723
|
-
return false;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
const { keyValues: finalKeyValues } = beforeEventData;
|
|
727
|
-
|
|
728
|
-
// 参数验证,确保keyValues是对象且不为null
|
|
729
|
-
if (typeof keyValues !== 'object' || keyValues === null || Array.isArray(keyValues)) {
|
|
730
|
-
$.log.error('[cachebase] mset参数错误: keyValues必须是非空对象');
|
|
731
|
-
return false;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
// 检查对象是否为空
|
|
735
|
-
if (Object.keys(keyValues).length === 0) {
|
|
736
|
-
$.log.error('[cachebase] mset参数错误: keyValues不能是空对象');
|
|
737
|
-
return false;
|
|
738
|
-
}
|
|
739
|
-
for (const key in finalKeyValues) {
|
|
740
|
-
if (finalKeyValues.hasOwnProperty(key)) {
|
|
741
|
-
// 为每个键设置值,不设置过期时间(与Redis MSET一致)
|
|
742
|
-
this.data[key] = { value: finalKeyValues[key], expire: 0 };
|
|
743
|
-
// 如果键不存在于列表中,添加到列表
|
|
744
|
-
if (!this.list.includes(key)) {
|
|
745
|
-
this.list.push(key);
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
// 确保返回值为布尔值true
|
|
751
|
-
this.emit('mset_after', { keyValues: finalKeyValues, result: true });
|
|
752
|
-
return true;
|
|
753
|
-
} catch (error) {
|
|
754
|
-
this.emit('mset_error', { keyValues, error });
|
|
755
|
-
$.log.error('[cachebase] mset操作失败:', error);
|
|
756
|
-
return false;
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
/**
|
|
761
|
-
* 批量获取键值 - 兼容方法
|
|
762
|
-
* @param {Array<string>} keys - 键名数组
|
|
763
|
-
* @returns {Promise<Array>} - 返回值数组,与输入键数组顺序一致,不存在的键返回null
|
|
764
|
-
* @note 完全兼容Redis MGET命令,确保返回值顺序和行为一致性
|
|
765
|
-
*/
|
|
766
|
-
async mget(keys) {
|
|
767
|
-
try {
|
|
768
|
-
// 参数验证,确保keys是数组
|
|
769
|
-
if (!Array.isArray(keys)) {
|
|
770
|
-
throw new Error('[cachebase] mget参数必须是数组');
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
// 批量获取键值,确保返回数组与输入键数组顺序一致(与Redis MGET行为一致)
|
|
774
|
-
const results = [];
|
|
775
|
-
for (const key of keys) {
|
|
776
|
-
if (this.data.hasOwnProperty(key)) {
|
|
777
|
-
// 检查是否过期
|
|
778
|
-
const item = this.data[key];
|
|
779
|
-
if (item.expire === 0 || Date.now() <= item.expire) {
|
|
780
|
-
// 键有效,添加其值到结果数组
|
|
781
|
-
results.push(item.value);
|
|
782
|
-
} else {
|
|
783
|
-
// 键已过期,删除并添加null到结果数组
|
|
784
|
-
delete this.data[key];
|
|
785
|
-
const index = this.list.indexOf(key);
|
|
786
|
-
if (index !== -1) {
|
|
787
|
-
this.list.splice(index, 1);
|
|
788
|
-
}
|
|
789
|
-
results.push(null);
|
|
790
|
-
}
|
|
791
|
-
} else {
|
|
792
|
-
// 键不存在,添加null到结果数组
|
|
793
|
-
results.push(null);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
// 确保返回值是数组,与输入键数组的顺序完全一致(与Redis MGET一致)
|
|
798
|
-
return results;
|
|
799
|
-
} catch (error) {
|
|
800
|
-
$.log.error('[cachebase] mget操作失败:', error);
|
|
801
|
-
throw error;
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
/**
|
|
806
|
-
* 移除键的过期时间,使其永不过期 - 兼容Redis的PERSIST命令
|
|
807
|
-
* @param {string} key - 键名
|
|
808
|
-
* @returns {Promise<boolean>} - 操作成功返回true,失败返回false
|
|
809
|
-
*/
|
|
810
|
-
async persist(key) {
|
|
811
|
-
try {
|
|
812
|
-
// 参数验证
|
|
813
|
-
if (!key || typeof key !== 'string') {
|
|
814
|
-
$.log.error('[cachebase] persist参数错误: key必须是非空字符串');
|
|
815
|
-
return 0;
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
// 触发前置事件
|
|
819
|
-
const beforeEventData = { key, cancel: false };
|
|
820
|
-
this.emit('persist_before', beforeEventData);
|
|
821
|
-
|
|
822
|
-
if (beforeEventData.cancel) {
|
|
823
|
-
$.log.debug(`[cachebase] persist操作被事件处理器取消 [${key}]`);
|
|
824
|
-
return 0;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
const { key: finalKey } = beforeEventData;
|
|
828
|
-
|
|
829
|
-
// 检查键是否存在
|
|
830
|
-
if (!this.data.hasOwnProperty(finalKey)) {
|
|
831
|
-
this.emit('persist_after', { key: finalKey, result: 0 });
|
|
832
|
-
return 0; // 键不存在,返回0
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
const item = this.data[finalKey];
|
|
836
|
-
|
|
837
|
-
// 检查是否已过期
|
|
838
|
-
if (item.expire > 0 && Date.now() > item.expire) {
|
|
839
|
-
// 键已过期,删除并返回0
|
|
840
|
-
delete this.data[finalKey];
|
|
841
|
-
const index = this.list.indexOf(finalKey);
|
|
842
|
-
if (index !== -1) {
|
|
843
|
-
this.list.splice(index, 1);
|
|
844
|
-
}
|
|
845
|
-
this.emit('persist_after', { key: finalKey, result: 0 });
|
|
846
|
-
return 0; // 键已过期,返回0
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
// 检查是否设置了过期时间
|
|
850
|
-
if (item.expire === 0) {
|
|
851
|
-
// 键没有设置过期时间,返回0
|
|
852
|
-
this.emit('persist_after', { key: finalKey, result: 0 });
|
|
853
|
-
return 0;
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
// 设置为永不过期
|
|
857
|
-
item.expire = 0;
|
|
858
|
-
|
|
859
|
-
// 触发后置事件
|
|
860
|
-
this.emit('persist_after', { key: finalKey, result: 1 });
|
|
861
|
-
return 1; // 成功移除过期时间,返回1
|
|
862
|
-
} catch (error) {
|
|
863
|
-
$.log.error(`[cachebase] persist操作失败 [${key}]:`, error);
|
|
864
|
-
this.emit('persist_error', { key, error });
|
|
865
|
-
return 0; // 发生错误,返回0
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
/**
|
|
870
|
-
* 获取或设置键的过期时间
|
|
871
|
-
* @param {string} key - 键名
|
|
872
|
-
* @param {number} [seconds] - 过期时间(秒),不传此参数则获取剩余过期时间
|
|
873
|
-
* @returns {Promise<number|boolean>} - 不传seconds时返回剩余过期时间(-1表示无过期设置,-2表示已过期),传seconds时返回是否设置成功
|
|
874
|
-
*/
|
|
875
|
-
async ttl(key, seconds) {
|
|
876
|
-
try {
|
|
877
|
-
// 参数验证
|
|
878
|
-
if (typeof key !== 'string' || key === '') {
|
|
879
|
-
$.log.error('[cachebase] ttl操作失败: 键必须是非空字符串');
|
|
880
|
-
return seconds === undefined ? -2 : false;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
// 如果传入了seconds参数,则设置过期时间
|
|
884
|
-
if (seconds !== undefined) {
|
|
885
|
-
// 参数验证
|
|
886
|
-
if (typeof seconds !== 'number' || isNaN(seconds) || seconds < 0) {
|
|
887
|
-
$.log.error('[cachebase] ttl设置操作失败: 过期时间必须是非负数字');
|
|
888
|
-
return false;
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
// 触发前置事件,允许修改或阻止操作
|
|
892
|
-
const beforeEventData = { key, seconds, cancel: false };
|
|
893
|
-
this.emit('ttl_before', beforeEventData);
|
|
894
|
-
|
|
895
|
-
if (beforeEventData.cancel) {
|
|
896
|
-
$.log.debug('[cachebase] ttl设置操作被事件处理器取消');
|
|
897
|
-
return false;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
const { key: finalKey, seconds: finalSeconds } = beforeEventData;
|
|
901
|
-
|
|
902
|
-
// 检查键是否存在
|
|
903
|
-
if (!this.data.hasOwnProperty(finalKey)) {
|
|
904
|
-
this.emit('ttl_after', { key: finalKey, seconds: finalSeconds, exists: false });
|
|
905
|
-
return false;
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
// 设置过期时间
|
|
909
|
-
this.data[finalKey].expire = finalSeconds > 0 ? Date.now() + (finalSeconds * 1000) : 0;
|
|
910
|
-
this.emit('ttl_after', { key: finalKey, seconds: finalSeconds, exists: true });
|
|
911
|
-
return true;
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
// 否则获取剩余过期时间
|
|
915
|
-
// 触发前置事件,允许修改或阻止操作
|
|
916
|
-
const beforeEventData = { key, cancel: false };
|
|
917
|
-
this.emit('ttl_before', beforeEventData);
|
|
918
|
-
|
|
919
|
-
if (beforeEventData.cancel) {
|
|
920
|
-
$.log.debug('[cachebase] ttl获取操作被事件处理器取消');
|
|
921
|
-
return -2;
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
const { key: finalKey } = beforeEventData;
|
|
925
|
-
|
|
926
|
-
// 检查键是否存在
|
|
927
|
-
if (!this.data.hasOwnProperty(finalKey)) {
|
|
928
|
-
this.emit('ttl_after', { key: finalKey, exists: false });
|
|
929
|
-
return -2; // 键不存在返回-2
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
const item = this.data[finalKey];
|
|
933
|
-
|
|
934
|
-
// 检查是否过期
|
|
935
|
-
if (item.expire > 0) {
|
|
936
|
-
if (Date.now() > item.expire) {
|
|
937
|
-
// 键已过期,删除键
|
|
938
|
-
delete this.data[finalKey];
|
|
939
|
-
const index = this.list.indexOf(finalKey);
|
|
940
|
-
if (index !== -1) {
|
|
941
|
-
this.list.splice(index, 1);
|
|
942
|
-
}
|
|
943
|
-
this.emit('ttl_after', { key: finalKey, exists: false });
|
|
944
|
-
return -2; // 已过期返回-2
|
|
945
|
-
} else {
|
|
946
|
-
// 计算剩余过期时间(秒)
|
|
947
|
-
const remaining = Math.ceil((item.expire - Date.now()) / 1000);
|
|
948
|
-
this.emit('ttl_after', { key: finalKey, exists: true, hasExpire: true });
|
|
949
|
-
return remaining;
|
|
950
|
-
}
|
|
951
|
-
} else {
|
|
952
|
-
// 键存在且永不过期
|
|
953
|
-
this.emit('ttl_after', { key: finalKey, exists: true, hasExpire: false });
|
|
954
|
-
return -1; // 无过期设置返回-1
|
|
955
|
-
}
|
|
956
|
-
} catch (error) {
|
|
957
|
-
this.emit('ttl_error', { key, seconds, error });
|
|
958
|
-
$.log.error('[cachebase] ttl操作失败:', error);
|
|
959
|
-
return seconds === undefined ? -2 : false;
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
|
|
963
|
-
/**
|
|
964
|
-
* 设置键的过期时间
|
|
965
|
-
* @param {string} key - 键名
|
|
966
|
-
* @param {number} seconds - 过期时间(秒)
|
|
967
|
-
* @returns {Promise<boolean>} - 操作成功返回true,失败返回false
|
|
968
|
-
* @note 完全兼容Redis的EXPIRE命令行为,确保返回值和事件数据一致性
|
|
969
|
-
*/
|
|
970
|
-
async expire(key, seconds) {
|
|
971
|
-
try {
|
|
972
|
-
// 参数验证
|
|
973
|
-
if (!key || typeof key !== 'string' || typeof seconds !== 'number' || seconds <= 0) {
|
|
974
|
-
$.log.error('[cachebase] expire参数错误: key必须是非空字符串,seconds必须是正数');
|
|
975
|
-
return false;
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
// 触发前置事件,允许修改或阻止操作
|
|
979
|
-
const beforeEventData = { key, ttl: seconds, cancel: false };
|
|
980
|
-
this.emit('expire_before', beforeEventData);
|
|
981
|
-
|
|
982
|
-
if (beforeEventData.cancel) {
|
|
983
|
-
$.log.debug(`[cachebase] expire操作被事件处理器取消 [${key}]`);
|
|
984
|
-
return false;
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
const { key: finalKey, ttl: finalSeconds } = beforeEventData;
|
|
988
|
-
// 检查键是否存在
|
|
989
|
-
if (!this.data.hasOwnProperty(finalKey)) {
|
|
990
|
-
this.emit('expire_after', { key: finalKey, ttl: finalSeconds, result: false });
|
|
991
|
-
return false;
|
|
992
|
-
}
|
|
993
|
-
|
|
994
|
-
// 检查是否已过期
|
|
995
|
-
const item = this.data[finalKey];
|
|
996
|
-
if (item.expire > 0 && Date.now() > item.expire) {
|
|
997
|
-
// 键已过期,删除并返回false
|
|
998
|
-
delete this.data[finalKey];
|
|
999
|
-
const index = this.list.indexOf(finalKey);
|
|
1000
|
-
if (index !== -1) {
|
|
1001
|
-
this.list.splice(index, 1);
|
|
1002
|
-
}
|
|
1003
|
-
this.emit('expire_after', { key: finalKey, ttl: finalSeconds, result: false });
|
|
1004
|
-
return false;
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
// 设置过期时间
|
|
1008
|
-
this.data[finalKey].expire = Date.now() + finalSeconds * 1000;
|
|
1009
|
-
|
|
1010
|
-
this.emit('expire_after', { key: finalKey, ttl: finalSeconds, result: true });
|
|
1011
|
-
return true;
|
|
1012
|
-
} catch (error) {
|
|
1013
|
-
this.emit('expire_error', { key, ttl: seconds, error });
|
|
1014
|
-
$.log.error('[cachebase] expire操作失败:', error);
|
|
1015
|
-
return false;
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
|
|
1019
|
-
/**
|
|
1020
|
-
* @description 移除键的过期时间
|
|
1021
|
-
* @param {string} key - 键名
|
|
1022
|
-
* @returns {Promise<boolean>} - 操作成功返回true,失败返回false
|
|
1023
|
-
*/
|
|
1024
|
-
async persist(key) {
|
|
1025
|
-
try {
|
|
1026
|
-
// 参数验证
|
|
1027
|
-
if (!key || typeof key !== 'string') {
|
|
1028
|
-
$.log.error('[cachebase] persist参数错误: key必须是非空字符串');
|
|
1029
|
-
return false;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
// 触发前置事件
|
|
1033
|
-
const beforeEventData = { key, cancel: false };
|
|
1034
|
-
this.emit('persist_before', beforeEventData);
|
|
1035
|
-
|
|
1036
|
-
if (beforeEventData.cancel) {
|
|
1037
|
-
$.log.debug(`[cachebase] persist操作被事件处理器取消 [${key}]`);
|
|
1038
|
-
return false;
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
const { key: finalKey } = beforeEventData;
|
|
1042
|
-
|
|
1043
|
-
// 检查键是否存在
|
|
1044
|
-
if (!this.data.hasOwnProperty(finalKey)) {
|
|
1045
|
-
this.emit('persist_after', { key: finalKey, success: false });
|
|
1046
|
-
return false;
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
// 检查是否已过期
|
|
1050
|
-
const item = this.data[finalKey];
|
|
1051
|
-
if (item.expire > 0 && Date.now() > item.expire) {
|
|
1052
|
-
// 键已过期,删除并返回false
|
|
1053
|
-
delete this.data[finalKey];
|
|
1054
|
-
const index = this.list.indexOf(finalKey);
|
|
1055
|
-
if (index !== -1) {
|
|
1056
|
-
this.list.splice(index, 1);
|
|
1057
|
-
}
|
|
1058
|
-
this.emit('persist_after', { key: finalKey, success: false });
|
|
1059
|
-
return false;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
// 检查是否有过期时间
|
|
1063
|
-
if (item.expire === 0) {
|
|
1064
|
-
// 没有过期时间,返回false
|
|
1065
|
-
this.emit('persist_after', { key: finalKey, success: false });
|
|
1066
|
-
return false;
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
// 移除过期时间
|
|
1070
|
-
this.data[finalKey].expire = 0;
|
|
1071
|
-
|
|
1072
|
-
// 触发后置事件
|
|
1073
|
-
this.emit('persist_after', { key: finalKey, success: true });
|
|
1074
|
-
return true;
|
|
1075
|
-
} catch (error) {
|
|
1076
|
-
$.log.error(`[cachebase] 移除过期时间失败 [${key}]:`, error);
|
|
1077
|
-
this.emit('persist_error', { key, error });
|
|
1078
|
-
return false;
|
|
1079
|
-
}
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
/**
|
|
1083
|
-
* 获取所有键名
|
|
1084
|
-
* @param {string} pattern - 模式匹配
|
|
1085
|
-
* @returns {Promise<Array>} - 返回键名数组
|
|
1086
|
-
*/
|
|
1087
|
-
async keys(pattern = '*') {
|
|
1088
|
-
try {
|
|
1089
|
-
// 支持通配符匹配
|
|
1090
|
-
let filteredKeys = [];
|
|
1091
|
-
if (pattern === '*') {
|
|
1092
|
-
// 通配符匹配所有
|
|
1093
|
-
filteredKeys = [...this.list];
|
|
1094
|
-
} else {
|
|
1095
|
-
// 简单的通配符匹配
|
|
1096
|
-
const regex = new RegExp(pattern.replace(/\*/g, '.*'));
|
|
1097
|
-
filteredKeys = this.list.filter(key => regex.test(key));
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
return filteredKeys;
|
|
1101
|
-
} catch (error) {
|
|
1102
|
-
$.log.error('[cachebase] keys操作失败:', error);
|
|
1103
|
-
throw error;
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
/**
|
|
1108
|
-
* 获取键数量
|
|
1109
|
-
* @returns {Promise<number>} - 返回键数量
|
|
1110
|
-
*/
|
|
1111
|
-
async dbsize() {
|
|
1112
|
-
try {
|
|
1113
|
-
// 过滤掉已过期的键
|
|
1114
|
-
let count = 0;
|
|
1115
|
-
for (const key of this.list) {
|
|
1116
|
-
if (this.data.hasOwnProperty(key)) {
|
|
1117
|
-
const item = this.data[key];
|
|
1118
|
-
if (item.expire === 0 || Date.now() <= item.expire) {
|
|
1119
|
-
count++;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
return count;
|
|
1125
|
-
} catch (error) {
|
|
1126
|
-
$.log.error('[cachebase] dbsize操作失败:', error);
|
|
1127
|
-
throw error;
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
|
-
/**
|
|
1132
|
-
* 设置键值并返回旧值
|
|
1133
|
-
* @param {string} key - 键名
|
|
1134
|
-
* @param {*} value - 键值
|
|
1135
|
-
* @returns {Promise<*>} - 返回旧值
|
|
1136
|
-
*/
|
|
1137
|
-
async getset(key, value) {
|
|
1138
|
-
try {
|
|
1139
|
-
this.emit('getset_before', { key, value });
|
|
1140
|
-
|
|
1141
|
-
// 获取旧值
|
|
1142
|
-
let oldValue = undefined;
|
|
1143
|
-
if (this.data.hasOwnProperty(key)) {
|
|
1144
|
-
const item = this.data[key];
|
|
1145
|
-
if (!(item.expire > 0 && Date.now() > item.expire)) {
|
|
1146
|
-
oldValue = item.value;
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
// 设置新值
|
|
1151
|
-
this.data[key] = { value, expire: 0 };
|
|
1152
|
-
|
|
1153
|
-
// 添加到键列表
|
|
1154
|
-
if (!this.list.includes(key)) {
|
|
1155
|
-
this.list.push(key);
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
|
-
this.emit('getset_after', { key, value, result: oldValue });
|
|
1159
|
-
return oldValue;
|
|
1160
|
-
} catch (error) {
|
|
1161
|
-
this.emit('getset_error', { key, value, error });
|
|
1162
|
-
$.log.error('[cachebase] getset操作失败:', error);
|
|
1163
|
-
throw error;
|
|
1164
|
-
}
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
/**
|
|
1168
|
-
* 数值递增
|
|
1169
|
-
* @param {string} key - 键名
|
|
1170
|
-
* @returns {Promise<number>} - 返回递增后的值
|
|
1171
|
-
*/
|
|
1172
|
-
async incr(key) {
|
|
1173
|
-
try {
|
|
1174
|
-
// 参数验证
|
|
1175
|
-
if (typeof key !== 'string' || key === '') {
|
|
1176
|
-
$.log.error('[cachebase] incr操作失败: 键必须是非空字符串');
|
|
1177
|
-
return 0;
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1181
|
-
const beforeEventData = { key, cancel: false };
|
|
1182
|
-
this.emit('incr_before', beforeEventData);
|
|
1183
|
-
|
|
1184
|
-
if (beforeEventData.cancel) {
|
|
1185
|
-
$.log.debug('[cachebase] incr操作被事件处理器取消');
|
|
1186
|
-
return 0;
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
const { key: finalKey } = beforeEventData;
|
|
1190
|
-
// 获取当前值或默认为0
|
|
1191
|
-
let currentValue = 0;
|
|
1192
|
-
if (this.data.hasOwnProperty(finalKey) && !this._checkExpire(finalKey)) {
|
|
1193
|
-
currentValue = parseFloat(this.data[finalKey].value) || 0;
|
|
1194
|
-
}
|
|
1195
|
-
|
|
1196
|
-
// 递增
|
|
1197
|
-
const result = currentValue + 1;
|
|
1198
|
-
|
|
1199
|
-
// 保存结果
|
|
1200
|
-
this.data[finalKey] = { value: result, expire: 0 };
|
|
1201
|
-
|
|
1202
|
-
// 如果键不存在于列表中,添加到列表
|
|
1203
|
-
if (!this.list.includes(finalKey)) {
|
|
1204
|
-
this.list.push(finalKey);
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
this.emit('incr_after', { key: finalKey, result, success: true });
|
|
1208
|
-
return result;
|
|
1209
|
-
} catch (error) {
|
|
1210
|
-
this.emit('incr_error', { key, error });
|
|
1211
|
-
$.log.error('[cachebase] incr操作失败:', error);
|
|
1212
|
-
return 0;
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
/**
|
|
1217
|
-
* 数值递减
|
|
1218
|
-
* @param {string} key - 键名
|
|
1219
|
-
* @returns {Promise<boolean>} - 操作成功返回true,失败返回false
|
|
1220
|
-
*/
|
|
1221
|
-
async decr(key) {
|
|
1222
|
-
try {
|
|
1223
|
-
// 参数验证
|
|
1224
|
-
if (typeof key !== 'string' || key === '') {
|
|
1225
|
-
$.log.error('[cachebase] decr操作失败: 键必须是非空字符串');
|
|
1226
|
-
return false;
|
|
1227
|
-
}
|
|
1228
|
-
|
|
1229
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1230
|
-
const beforeEventData = { key, cancel: false };
|
|
1231
|
-
this.emit('decr_before', beforeEventData);
|
|
1232
|
-
|
|
1233
|
-
if (beforeEventData.cancel) {
|
|
1234
|
-
$.log.debug('[cachebase] decr操作被事件处理器取消');
|
|
1235
|
-
return false;
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
const { key: finalKey } = beforeEventData;
|
|
1239
|
-
// 获取当前值或默认为0
|
|
1240
|
-
let currentValue = 0;
|
|
1241
|
-
if (this.data.hasOwnProperty(finalKey) && !this._checkExpire(finalKey)) {
|
|
1242
|
-
currentValue = parseFloat(this.data[finalKey].value) || 0;
|
|
1243
|
-
}
|
|
1244
|
-
|
|
1245
|
-
// 递减
|
|
1246
|
-
const result = currentValue - 1;
|
|
1247
|
-
|
|
1248
|
-
// 保存结果
|
|
1249
|
-
this.data[finalKey] = { value: result, expire: 0 };
|
|
1250
|
-
|
|
1251
|
-
// 如果键不存在于列表中,添加到列表
|
|
1252
|
-
if (!this.list.includes(finalKey)) {
|
|
1253
|
-
this.list.push(finalKey);
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
this.emit('decr_after', { key: finalKey, result, success: true });
|
|
1257
|
-
return true;
|
|
1258
|
-
} catch (error) {
|
|
1259
|
-
this.emit('decr_error', { key, error });
|
|
1260
|
-
$.log.error('[cachebase] decr操作失败:', error);
|
|
1261
|
-
return false;
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
/**
|
|
1266
|
-
* 数值增加指定值
|
|
1267
|
-
* @param {string} key - 键名
|
|
1268
|
-
* @param {number} increment - 增加的值
|
|
1269
|
-
* @returns {Promise<number>} - 返回增加后的值
|
|
1270
|
-
*/
|
|
1271
|
-
async incrby(key, increment) {
|
|
1272
|
-
try {
|
|
1273
|
-
// 参数验证
|
|
1274
|
-
if (typeof key !== 'string' || key === '' || typeof increment !== 'number') {
|
|
1275
|
-
$.log.error('[cachebase] incrby操作失败: 键必须是非空字符串,增量必须是数字');
|
|
1276
|
-
return 0;
|
|
1277
|
-
}
|
|
1278
|
-
|
|
1279
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1280
|
-
const beforeEventData = { key, increment, cancel: false };
|
|
1281
|
-
this.emit('incrby_before', beforeEventData);
|
|
1282
|
-
|
|
1283
|
-
if (beforeEventData.cancel) {
|
|
1284
|
-
$.log.debug('[cachebase] incrby操作被事件处理器取消');
|
|
1285
|
-
return 0;
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
const { key: finalKey, increment: finalIncrement } = beforeEventData;
|
|
1289
|
-
// 获取当前值或默认为0
|
|
1290
|
-
let currentValue = 0;
|
|
1291
|
-
if (this.data.hasOwnProperty(finalKey) && !this._checkExpire(finalKey)) {
|
|
1292
|
-
currentValue = parseFloat(this.data[finalKey].value) || 0;
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
// 增加指定值
|
|
1296
|
-
const result = currentValue + parseFloat(finalIncrement);
|
|
1297
|
-
|
|
1298
|
-
// 保存结果
|
|
1299
|
-
this.data[finalKey] = { value: result, expire: 0 };
|
|
1300
|
-
|
|
1301
|
-
// 如果键不存在于列表中,添加到列表
|
|
1302
|
-
if (!this.list.includes(finalKey)) {
|
|
1303
|
-
this.list.push(finalKey);
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
this.emit('incrby_after', { key: finalKey, increment: finalIncrement, result, success: true });
|
|
1307
|
-
return result;
|
|
1308
|
-
} catch (error) {
|
|
1309
|
-
this.emit('incrby_error', { key, increment, error });
|
|
1310
|
-
$.log.error('[cachebase] incrby操作失败:', error);
|
|
1311
|
-
return 0;
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
|
|
1315
|
-
/**
|
|
1316
|
-
* 数值减少指定值
|
|
1317
|
-
* @param {string} key - 键名
|
|
1318
|
-
* @param {number} decrement - 减少的值
|
|
1319
|
-
* @returns {Promise<number>} - 返回减少后的值
|
|
1320
|
-
*/
|
|
1321
|
-
async decrby(key, decrement) {
|
|
1322
|
-
try {
|
|
1323
|
-
// 参数验证
|
|
1324
|
-
if (typeof key !== 'string' || key === '' || typeof decrement !== 'number') {
|
|
1325
|
-
$.log.error('[cachebase] decrby操作失败: 键必须是非空字符串,减量必须是数字');
|
|
1326
|
-
return 0;
|
|
1327
|
-
}
|
|
1328
|
-
|
|
1329
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1330
|
-
const beforeEventData = { key, decrement, cancel: false };
|
|
1331
|
-
this.emit('decrby_before', beforeEventData);
|
|
1332
|
-
|
|
1333
|
-
if (beforeEventData.cancel) {
|
|
1334
|
-
$.log.debug('[cachebase] decrby操作被事件处理器取消');
|
|
1335
|
-
return 0;
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
const { key: finalKey, decrement: finalDecrement } = beforeEventData;
|
|
1339
|
-
// 获取当前值或默认为0
|
|
1340
|
-
let currentValue = 0;
|
|
1341
|
-
if (this.data.hasOwnProperty(finalKey) && !this._checkExpire(finalKey)) {
|
|
1342
|
-
currentValue = parseFloat(this.data[finalKey].value) || 0;
|
|
1343
|
-
}
|
|
1344
|
-
|
|
1345
|
-
// 减少指定值
|
|
1346
|
-
const result = currentValue - parseFloat(finalDecrement);
|
|
1347
|
-
|
|
1348
|
-
// 保存结果
|
|
1349
|
-
this.data[finalKey] = { value: result, expire: 0 };
|
|
1350
|
-
|
|
1351
|
-
// 如果键不存在于列表中,添加到列表
|
|
1352
|
-
if (!this.list.includes(finalKey)) {
|
|
1353
|
-
this.list.push(finalKey);
|
|
1354
|
-
}
|
|
1355
|
-
|
|
1356
|
-
this.emit('decrby_after', { key: finalKey, decrement: finalDecrement, result, success: true });
|
|
1357
|
-
return result;
|
|
1358
|
-
} catch (error) {
|
|
1359
|
-
this.emit('decrby_error', { key, decrement, error });
|
|
1360
|
-
$.log.error('[cachebase] decrby操作失败:', error);
|
|
1361
|
-
return 0;
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
/**
|
|
1366
|
-
* 浮点数增加指定值
|
|
1367
|
-
* @param {string} key - 键名
|
|
1368
|
-
* @param {number} increment - 增加的值
|
|
1369
|
-
* @returns {Promise<number>} - 返回增加后的值
|
|
1370
|
-
*/
|
|
1371
|
-
async incrbyfloat(key, increment) {
|
|
1372
|
-
try {
|
|
1373
|
-
// 参数验证
|
|
1374
|
-
if (typeof key !== 'string' || key === '' || typeof increment !== 'number') {
|
|
1375
|
-
$.log.error('[cachebase] incrbyfloat操作失败: 键必须是非空字符串,增量必须是数字');
|
|
1376
|
-
return 0;
|
|
1377
|
-
}
|
|
1378
|
-
|
|
1379
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1380
|
-
const beforeEventData = { key, increment, cancel: false };
|
|
1381
|
-
this.emit('incrbyfloat_before', beforeEventData);
|
|
1382
|
-
|
|
1383
|
-
if (beforeEventData.cancel) {
|
|
1384
|
-
$.log.debug('[cachebase] incrbyfloat操作被事件处理器取消');
|
|
1385
|
-
return 0;
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
const { key: finalKey, increment: finalIncrement } = beforeEventData;
|
|
1389
|
-
// 获取当前值或默认为0
|
|
1390
|
-
let currentValue = 0;
|
|
1391
|
-
if (this.data.hasOwnProperty(finalKey) && !this._checkExpire(finalKey)) {
|
|
1392
|
-
currentValue = parseFloat(this.data[finalKey].value) || 0;
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
// 增加指定值
|
|
1396
|
-
const result = currentValue + parseFloat(finalIncrement);
|
|
1397
|
-
|
|
1398
|
-
// 保存结果
|
|
1399
|
-
this.data[finalKey] = { value: result, expire: 0 };
|
|
1400
|
-
|
|
1401
|
-
// 如果键不存在于列表中,添加到列表
|
|
1402
|
-
if (!this.list.includes(finalKey)) {
|
|
1403
|
-
this.list.push(finalKey);
|
|
1404
|
-
}
|
|
1405
|
-
|
|
1406
|
-
this.emit('incrbyfloat_after', { key: finalKey, increment: finalIncrement, result, success: true });
|
|
1407
|
-
return result;
|
|
1408
|
-
} catch (error) {
|
|
1409
|
-
this.emit('incrbyfloat_error', { key, increment, error });
|
|
1410
|
-
$.log.error('[cachebase] incrbyfloat操作失败:', error);
|
|
1411
|
-
return 0;
|
|
1412
|
-
}
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
/**
|
|
1416
|
-
* 字符串追加
|
|
1417
|
-
* @param {string} key - 键名
|
|
1418
|
-
* @param {string} value - 追加的字符串
|
|
1419
|
-
* @returns {Promise<number>} - 返回追加后的字符串长度
|
|
1420
|
-
*/
|
|
1421
|
-
async append(key, value) {
|
|
1422
|
-
try {
|
|
1423
|
-
this.emit('append_before', { key, value });
|
|
1424
|
-
|
|
1425
|
-
// 获取当前值或默认为空字符串
|
|
1426
|
-
let currentValue = '';
|
|
1427
|
-
if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
|
|
1428
|
-
currentValue = String(this.data[key].value);
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
// 执行字符串拼接
|
|
1432
|
-
const newValue = currentValue + String(value);
|
|
1433
|
-
const result = newValue.length;
|
|
1434
|
-
|
|
1435
|
-
// 保存结果
|
|
1436
|
-
this.data[key] = { value: newValue, expire: 0 };
|
|
1437
|
-
|
|
1438
|
-
// 如果键不存在于列表中,添加到列表
|
|
1439
|
-
if (!this.list.includes(key)) {
|
|
1440
|
-
this.list.push(key);
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
this.emit('append_after', { key, value, result });
|
|
1444
|
-
return result;
|
|
1445
|
-
} catch (error) {
|
|
1446
|
-
this.emit('append_error', { key, value, error });
|
|
1447
|
-
$.log.error('[cachebase] append操作失败:', error);
|
|
1448
|
-
throw error;
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
/**
|
|
1453
|
-
* 获取字符串值的长度 - 兼容Redis的STRLEN命令
|
|
1454
|
-
* @param {string} key - 键名
|
|
1455
|
-
* @returns {Promise<number>} - 返回字符串长度,键不存在、不是字符串或发生错误时返回0
|
|
1456
|
-
* @note 完全兼容Redis的STRLEN命令行为,确保返回值和错误处理一致性
|
|
1457
|
-
*/
|
|
1458
|
-
async strlen(key) {
|
|
1459
|
-
try {
|
|
1460
|
-
// 参数验证
|
|
1461
|
-
if (!key || typeof key !== 'string') {
|
|
1462
|
-
$.log.error('[cachebase] strlen参数错误: key必须是非空字符串');
|
|
1463
|
-
this.emit('strlen_after', { key, result: 0 });
|
|
1464
|
-
return 0;
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1468
|
-
const beforeEventData = { key, cancel: false };
|
|
1469
|
-
this.emit('strlen_before', beforeEventData);
|
|
1470
|
-
|
|
1471
|
-
if (beforeEventData.cancel) {
|
|
1472
|
-
$.log.debug(`[cachebase] strlen操作被事件处理器取消 [${key}]`);
|
|
1473
|
-
this.emit('strlen_after', { key, result: 0 });
|
|
1474
|
-
return 0;
|
|
1475
|
-
}
|
|
1476
|
-
|
|
1477
|
-
const { key: finalKey } = beforeEventData;
|
|
1478
|
-
|
|
1479
|
-
// 检查键是否存在且未过期
|
|
1480
|
-
if (this.data.hasOwnProperty(finalKey) && !this._checkExpire(finalKey)) {
|
|
1481
|
-
const result = String(this.data[finalKey].value).length;
|
|
1482
|
-
this.emit('strlen_after', { key: finalKey, result });
|
|
1483
|
-
return result;
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
this.emit('strlen_after', { key: finalKey, result: 0 });
|
|
1487
|
-
return 0;
|
|
1488
|
-
} catch (error) {
|
|
1489
|
-
this.emit('strlen_error', { key, error });
|
|
1490
|
-
$.log.error('[cachebase] strlen操作失败:', error);
|
|
1491
|
-
return 0; // 发生错误时返回0,符合Redis API规范
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
// 保持兼容性的方法
|
|
1496
|
-
async has(key) {
|
|
1497
|
-
try {
|
|
1498
|
-
|
|
1499
|
-
const exists = await this.exists(key);
|
|
1500
|
-
return exists > 0;
|
|
1501
|
-
} catch (error) {
|
|
1502
|
-
$.log.error('[cachebase] has操作失败:', error);
|
|
1503
|
-
throw error;
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
/**
|
|
1508
|
-
* 删除一个或多个键 - 兼容Redis的DEL命令
|
|
1509
|
-
* @param {...string} keys - 要删除的键名列表
|
|
1510
|
-
* @returns {Promise<boolean>} - 操作成功返回true,失败返回false
|
|
1511
|
-
* @note 如果没有提供参数,行为类似FLUSHDB清空所有缓存
|
|
1512
|
-
*/
|
|
1513
|
-
async clear(...keys) {
|
|
1514
|
-
try {
|
|
1515
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1516
|
-
const beforeEventData = { keys, cancel: false };
|
|
1517
|
-
this.emit('clear_before', beforeEventData);
|
|
1518
|
-
|
|
1519
|
-
if (beforeEventData.cancel) {
|
|
1520
|
-
$.log.debug('[cachebase] clear操作被事件处理器取消');
|
|
1521
|
-
return false;
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
const { keys: finalKeys } = beforeEventData;
|
|
1525
|
-
|
|
1526
|
-
let deletedCount = 0;
|
|
1527
|
-
let success = false;
|
|
1528
|
-
|
|
1529
|
-
if (finalKeys.length === 0) {
|
|
1530
|
-
// 没有提供键,清空所有数据,类似Redis的FLUSHDB行为
|
|
1531
|
-
deletedCount = this.list.length;
|
|
1532
|
-
this.data = {};
|
|
1533
|
-
this.list = [];
|
|
1534
|
-
success = true;
|
|
1535
|
-
} else {
|
|
1536
|
-
// 删除指定的键,类似Redis的DEL命令行为
|
|
1537
|
-
success = true;
|
|
1538
|
-
for (const key of finalKeys) {
|
|
1539
|
-
// 参数验证,只处理字符串类型的键
|
|
1540
|
-
if (typeof key === 'string') {
|
|
1541
|
-
// 检查键是否存在且未过期
|
|
1542
|
-
if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
|
|
1543
|
-
delete this.data[key];
|
|
1544
|
-
const index = this.list.indexOf(key);
|
|
1545
|
-
if (index !== -1) {
|
|
1546
|
-
this.list.splice(index, 1);
|
|
1547
|
-
}
|
|
1548
|
-
deletedCount++;
|
|
1549
|
-
}
|
|
1550
|
-
} else {
|
|
1551
|
-
success = false;
|
|
1552
|
-
}
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
// 确保返回值为布尔值
|
|
1557
|
-
this.emit('clear_after', { keys: finalKeys, result: success, deletedCount });
|
|
1558
|
-
return success;
|
|
1559
|
-
} catch (error) {
|
|
1560
|
-
this.emit('clear_error', { keys, error });
|
|
1561
|
-
$.log.error('[cachebase] clear操作失败:', error);
|
|
1562
|
-
return false;
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
|
|
1566
|
-
/**
|
|
1567
|
-
* 清空所有过期的数据
|
|
1568
|
-
* @returns {Promise<boolean>} - 返回操作是否成功
|
|
1569
|
-
*/
|
|
1570
|
-
async clearExpire() {
|
|
1571
|
-
try {
|
|
1572
|
-
this.emit('clearExpire_before', {});
|
|
1573
|
-
|
|
1574
|
-
let expiredCount = 0;
|
|
1575
|
-
// 检查并删除所有过期的键
|
|
1576
|
-
for (const key of [...this.list]) {
|
|
1577
|
-
if (this._checkExpire(key)) {
|
|
1578
|
-
delete this.data[key];
|
|
1579
|
-
this.list = this.list.filter(k => k !== key);
|
|
1580
|
-
expiredCount++;
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
|
|
1584
|
-
this.emit('clearExpire_after', { expiredCount, result: true });
|
|
1585
|
-
return true;
|
|
1586
|
-
} catch (error) {
|
|
1587
|
-
this.emit('clearExpire_error', { error });
|
|
1588
|
-
$.log.error('[cachebase] clearExpire操作失败:', error);
|
|
1589
|
-
return false;
|
|
1590
|
-
}
|
|
1591
|
-
}
|
|
1592
|
-
/**
|
|
1593
|
-
* 增加整数值 - 兼容方法
|
|
1594
|
-
* @param {string} key - 键名
|
|
1595
|
-
* @param {*} value - 要设置的值
|
|
1596
|
-
* @param {number} maxAge - 过期时间(秒),0表示永不过期
|
|
1597
|
-
* @returns {Promise<boolean>} - 操作结果,键不存在并设置成功返回true,键已存在返回false
|
|
1598
|
-
*/
|
|
1599
|
-
async add(key, value, maxAge = 0) {
|
|
1600
|
-
try {
|
|
1601
|
-
// 参数验证
|
|
1602
|
-
if (!key || typeof key !== 'string') {
|
|
1603
|
-
$.log.error('[cachebase] add参数错误: key必须是非空字符串');
|
|
1604
|
-
return false;
|
|
1605
|
-
}
|
|
1606
|
-
|
|
1607
|
-
// 触发前置事件
|
|
1608
|
-
const beforeEventData = { key, value, maxAge, cancel: false };
|
|
1609
|
-
this.emit('add_before', beforeEventData);
|
|
1610
|
-
|
|
1611
|
-
if (beforeEventData.cancel) {
|
|
1612
|
-
$.log.debug(`[cachebase] add操作被事件处理器取消 [${key}]`);
|
|
1613
|
-
return false;
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
const { key: finalKey, value: finalValue, maxAge: finalMaxAge } = beforeEventData;
|
|
1617
|
-
|
|
1618
|
-
// 检查键是否存在且未过期
|
|
1619
|
-
if (this.data.hasOwnProperty(finalKey) && !this._checkExpire(finalKey)) {
|
|
1620
|
-
// 键已存在,返回false
|
|
1621
|
-
this.emit('add_after', { key: finalKey, value: finalValue, maxAge: finalMaxAge, success: false });
|
|
1622
|
-
return false;
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
// 键不存在,设置新值
|
|
1626
|
-
const now = Date.now();
|
|
1627
|
-
this.data[finalKey] = {
|
|
1628
|
-
value: finalValue,
|
|
1629
|
-
createTime: now,
|
|
1630
|
-
updateTime: now,
|
|
1631
|
-
expire: finalMaxAge > 0 ? now + finalMaxAge * 1000 : 0
|
|
1632
|
-
};
|
|
1633
|
-
|
|
1634
|
-
// 更新列表
|
|
1635
|
-
if (!this.list.includes(finalKey)) {
|
|
1636
|
-
this.list.push(finalKey);
|
|
1637
|
-
}
|
|
1638
|
-
|
|
1639
|
-
// 如果启用了持久化,同步保存到文件
|
|
1640
|
-
if (this._persistence && this._persistence.enabled) {
|
|
1641
|
-
await this._saveData();
|
|
1642
|
-
}
|
|
1643
|
-
|
|
1644
|
-
// 触发后置事件
|
|
1645
|
-
this.emit('add_after', { key: finalKey, value: finalValue, maxAge: finalMaxAge, success: true });
|
|
1646
|
-
return true;
|
|
1647
|
-
} catch (error) {
|
|
1648
|
-
$.log.error(`[cachebase] add操作失败 [${key}]:`, error);
|
|
1649
|
-
this.emit('add_error', { key, value, maxAge, error });
|
|
1650
|
-
return false;
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
/**
|
|
1655
|
-
* 设置列表项 - 兼容Redis LPUSH/RPUSH行为
|
|
1656
|
-
* @param {string} key - 键名
|
|
1657
|
-
* @param {any} value - 值(通常应为数组)
|
|
1658
|
-
* @param {number} maxAge - 过期时间(秒),0表示永不过期
|
|
1659
|
-
* @returns {Promise<number>} - 设置后列表的长度
|
|
1660
|
-
* @note 兼容Redis的列表设置行为,返回列表长度
|
|
1661
|
-
*/
|
|
1662
|
-
async setForList(key, value, maxAge = 0) {
|
|
1663
|
-
try {
|
|
1664
|
-
this.emit('setForList_before', { key, value, maxAge });
|
|
1665
|
-
|
|
1666
|
-
// 确保值是数组类型
|
|
1667
|
-
const arrayValue = Array.isArray(value) ? value : [value];
|
|
1668
|
-
|
|
1669
|
-
// 将数组中的所有值转换为字符串,与Redis行为一致
|
|
1670
|
-
const stringValues = arrayValue.map(item => String(item));
|
|
1671
|
-
|
|
1672
|
-
// 设置值和过期时间
|
|
1673
|
-
this.data[key] = {
|
|
1674
|
-
value: stringValues,
|
|
1675
|
-
expire: maxAge > 0 ? Date.now() + maxAge * 1000 : 0
|
|
1676
|
-
};
|
|
1677
|
-
|
|
1678
|
-
// 更新列表
|
|
1679
|
-
if (!this.list.includes(key)) {
|
|
1680
|
-
this.list.push(key);
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
const length = stringValues.length;
|
|
1684
|
-
this.emit('setForList_after', { key, value: stringValues, maxAge, result: length });
|
|
1685
|
-
return length;
|
|
1686
|
-
} catch (error) {
|
|
1687
|
-
this.emit('setForList_error', { key, value, maxAge, error });
|
|
1688
|
-
$.log.error('[cachebase] setForList操作失败:', error);
|
|
1689
|
-
throw error;
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
/**
|
|
1694
|
-
* 添加列表项 - 兼容Redis LPUSH/RPUSH行为
|
|
1695
|
-
* @param {string} key - 键名
|
|
1696
|
-
* @param {any} value - 值
|
|
1697
|
-
* @param {number} maxAge - 过期时间(秒),0表示永不过期
|
|
1698
|
-
* @returns {Promise<number>} - 添加后列表的长度
|
|
1699
|
-
* @note 兼容Redis的列表追加行为,返回列表长度
|
|
1700
|
-
*/
|
|
1701
|
-
async addForList(key, value, maxAge = 0) {
|
|
1702
|
-
try {
|
|
1703
|
-
this.emit('addForList_before', { key, value, maxAge });
|
|
1704
|
-
|
|
1705
|
-
// 获取现有列表或创建新列表
|
|
1706
|
-
let list = [];
|
|
1707
|
-
let existingExpire = 0;
|
|
1708
|
-
|
|
1709
|
-
if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
|
|
1710
|
-
// 确保现有值是数组类型
|
|
1711
|
-
list = Array.isArray(this.data[key].value) ? this.data[key].value : [];
|
|
1712
|
-
existingExpire = this.data[key].expire;
|
|
1713
|
-
}
|
|
1714
|
-
|
|
1715
|
-
// 将值转换为字符串,与Redis行为一致
|
|
1716
|
-
const stringValue = String(value);
|
|
1717
|
-
|
|
1718
|
-
// 添加值到列表
|
|
1719
|
-
list.push(stringValue);
|
|
1720
|
-
|
|
1721
|
-
// 设置值和过期时间
|
|
1722
|
-
// 如果提供了新的过期时间,则使用新的,否则保持原来的
|
|
1723
|
-
const expireTime = maxAge > 0 ? Date.now() + maxAge * 1000 : existingExpire;
|
|
1724
|
-
|
|
1725
|
-
this.data[key] = {
|
|
1726
|
-
value: list,
|
|
1727
|
-
expire: expireTime
|
|
1728
|
-
};
|
|
1729
|
-
|
|
1730
|
-
// 更新列表
|
|
1731
|
-
if (!this.list.includes(key)) {
|
|
1732
|
-
this.list.push(key);
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
const length = list.length;
|
|
1736
|
-
this.emit('addForList_after', { key, value: stringValue, maxAge, result: length });
|
|
1737
|
-
return length;
|
|
1738
|
-
} catch (error) {
|
|
1739
|
-
this.emit('addForList_error', { key, value, maxAge, error });
|
|
1740
|
-
$.log.error('[cachebase] addForList操作失败:', error);
|
|
1741
|
-
throw error;
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
/**
|
|
1746
|
-
* 检查集合是否包含指定成员 - 兼容Redis SISMEMBER行为
|
|
1747
|
-
* @param {string} key - 键名
|
|
1748
|
-
* @param {*} member - 要检查的成员
|
|
1749
|
-
* @returns {Promise<number>} - 1表示成员存在,0表示成员不存在或键不存在/类型错误
|
|
1750
|
-
* @note 完全兼容Redis的SISMEMBER命令行为,返回数字而非布尔值
|
|
1751
|
-
*/
|
|
1752
|
-
async sismember(key, member) {
|
|
1753
|
-
try {
|
|
1754
|
-
// 参数验证
|
|
1755
|
-
if (!key || typeof key !== 'string') {
|
|
1756
|
-
$.log.error('[cachebase] 参数错误: key必须是非空字符串');
|
|
1757
|
-
return 0;
|
|
1758
|
-
}
|
|
1759
|
-
|
|
1760
|
-
if (member === undefined || member === null) {
|
|
1761
|
-
$.log.error('[cachebase] 参数错误: member不能为undefined或null');
|
|
1762
|
-
return 0;
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1766
|
-
const beforeEventData = { key, member, cancel: false };
|
|
1767
|
-
this.emit('sismember_before', beforeEventData);
|
|
1768
|
-
|
|
1769
|
-
if (beforeEventData.cancel) {
|
|
1770
|
-
$.log.debug(`[cachebase] sismember操作被事件处理器取消 [${key}]`);
|
|
1771
|
-
return 0;
|
|
1772
|
-
}
|
|
1773
|
-
|
|
1774
|
-
const { key: finalKey, member: finalMember } = beforeEventData;
|
|
1775
|
-
|
|
1776
|
-
// 检查键是否存在且未过期
|
|
1777
|
-
if (!this.data.hasOwnProperty(finalKey) || this._checkExpire(finalKey)) {
|
|
1778
|
-
this.emit('sismember_after', { key: finalKey, member: finalMember, result: 0 });
|
|
1779
|
-
return 0; // 键不存在或已过期,返回0
|
|
1780
|
-
}
|
|
1781
|
-
|
|
1782
|
-
// 确保值是数组类型
|
|
1783
|
-
const value = this.data[finalKey].value;
|
|
1784
|
-
if (!Array.isArray(value)) {
|
|
1785
|
-
this.emit('sismember_after', { key: finalKey, member: finalMember, result: 0 });
|
|
1786
|
-
return 0;
|
|
1787
|
-
}
|
|
1788
|
-
|
|
1789
|
-
// 检查成员是否存在
|
|
1790
|
-
const result = value.includes(finalMember) ? 1 : 0;
|
|
1791
|
-
|
|
1792
|
-
// 触发后置事件
|
|
1793
|
-
this.emit('sismember_after', { key: finalKey, member: finalMember, result });
|
|
1794
|
-
return result;
|
|
1795
|
-
} catch (error) {
|
|
1796
|
-
$.log.error(`[cachebase] sismember操作失败 [${key}]:`, error);
|
|
1797
|
-
this.emit('sismember_error', { key, member, error });
|
|
1798
|
-
return 0;
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
/**
|
|
1803
|
-
* 检查列表是否包含指定项 - 兼容Redis SISMEMBER行为
|
|
1804
|
-
* @param {string} key - 键名
|
|
1805
|
-
* @param {any} value - 值
|
|
1806
|
-
* @returns {Promise<number>} - 包含返回1,不包含返回0
|
|
1807
|
-
* @note 兼容Redis的SISMEMBER命令,返回数字而非布尔值
|
|
1808
|
-
*/
|
|
1809
|
-
async hasForList(key, value) {
|
|
1810
|
-
try {
|
|
1811
|
-
this.emit('hasForList_before', { key, value });
|
|
1812
|
-
|
|
1813
|
-
// 检查键是否存在且未过期
|
|
1814
|
-
if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
|
|
1815
|
-
// 确保将值视为数组
|
|
1816
|
-
const list = Array.isArray(this.data[key].value) ? this.data[key].value : [];
|
|
1817
|
-
|
|
1818
|
-
// 对于对象类型的值,使用深度比较
|
|
1819
|
-
let result = 0;
|
|
1820
|
-
if (typeof value === 'object' && value !== null) {
|
|
1821
|
-
const valueStr = JSON.stringify(value);
|
|
1822
|
-
result = list.some(item => {
|
|
1823
|
-
try {
|
|
1824
|
-
return typeof item === 'object' && item !== null && JSON.stringify(item) === valueStr;
|
|
1825
|
-
} catch (e) {
|
|
1826
|
-
// 如果JSON序列化失败,回退到严格相等
|
|
1827
|
-
return item === value;
|
|
1828
|
-
}
|
|
1829
|
-
}) ? 1 : 0;
|
|
1830
|
-
} else {
|
|
1831
|
-
// 对于基本类型,使用严格相等
|
|
1832
|
-
result = list.includes(value) ? 1 : 0;
|
|
1833
|
-
}
|
|
1834
|
-
|
|
1835
|
-
this.emit('hasForList_after', { key, value, result });
|
|
1836
|
-
return result;
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
this.emit('hasForList_after', { key, value, result: 0 });
|
|
1840
|
-
return 0;
|
|
1841
|
-
} catch (error) {
|
|
1842
|
-
this.emit('hasForList_error', { key, value, error });
|
|
1843
|
-
$.log.error('[cachebase] hasForList操作失败:', error);
|
|
1844
|
-
throw error;
|
|
1845
|
-
}
|
|
1846
|
-
}
|
|
1847
|
-
|
|
1848
|
-
/**
|
|
1849
|
-
* 获取列表 - 兼容Redis LRANGE行为
|
|
1850
|
-
* @param {string} key - 键名
|
|
1851
|
-
* @param {number} start - 可选的起始索引,默认为0
|
|
1852
|
-
* @param {number} stop - 可选的结束索引,默认为-1(全部)
|
|
1853
|
-
* @returns {Promise<Array>} - 列表数据,确保始终返回数组类型
|
|
1854
|
-
* @note 兼容Redis的LRANGE命令,支持范围获取
|
|
1855
|
-
*/
|
|
1856
|
-
async getForList(key, start = 0, stop = -1) {
|
|
1857
|
-
try {
|
|
1858
|
-
this.emit('getForList_before', { key, start, stop });
|
|
1859
|
-
|
|
1860
|
-
// 检查键是否存在且未过期
|
|
1861
|
-
if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
|
|
1862
|
-
// 确保返回数组类型,如果不是数组则转换为空数组
|
|
1863
|
-
const list = Array.isArray(this.data[key].value) ? [...this.data[key].value] : [];
|
|
1864
|
-
|
|
1865
|
-
// 处理Redis风格的索引范围
|
|
1866
|
-
let result = [];
|
|
1867
|
-
if (start >= 0 && stop >= -1) {
|
|
1868
|
-
if (stop === -1) {
|
|
1869
|
-
result = list.slice(start);
|
|
1870
|
-
} else {
|
|
1871
|
-
result = list.slice(start, stop + 1);
|
|
1872
|
-
}
|
|
1873
|
-
}
|
|
1874
|
-
|
|
1875
|
-
this.emit('getForList_after', { key, start, stop, result });
|
|
1876
|
-
return result;
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
// 键不存在或已过期时返回空数组
|
|
1880
|
-
this.emit('getForList_after', { key, start, stop, result: [] });
|
|
1881
|
-
return [];
|
|
1882
|
-
} catch (error) {
|
|
1883
|
-
this.emit('getForList_error', { key, start, stop, error });
|
|
1884
|
-
$.log.error('[cachebase] getForList操作失败:', error);
|
|
1885
|
-
throw error;
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
/**
|
|
1890
|
-
* 清空列表 - 兼容方法
|
|
1891
|
-
* @param {string} key - 键名
|
|
1892
|
-
* @returns {Promise<boolean>} - 是否成功
|
|
1893
|
-
* @note 确保操作对象始终被视为数组以保持兼容性
|
|
1894
|
-
*/
|
|
1895
|
-
async clearForList(key) {
|
|
1896
|
-
try {
|
|
1897
|
-
this.emit('clearForList_before', { key });
|
|
1898
|
-
|
|
1899
|
-
// 检查键是否存在
|
|
1900
|
-
if (this.data.hasOwnProperty(key)) {
|
|
1901
|
-
// 检查是否过期
|
|
1902
|
-
if (this.data[key].expire > 0 && Date.now() > this.data[key].expire) {
|
|
1903
|
-
// 键已过期,删除键
|
|
1904
|
-
delete this.data[key];
|
|
1905
|
-
const index = this.list.indexOf(key);
|
|
1906
|
-
if (index !== -1) {
|
|
1907
|
-
this.list.splice(index, 1);
|
|
1908
|
-
}
|
|
1909
|
-
this.emit('clearForList_after', { key, result: false });
|
|
1910
|
-
return false;
|
|
1911
|
-
}
|
|
1912
|
-
|
|
1913
|
-
// 保持过期时间不变,将列表清空为[]
|
|
1914
|
-
const expire = this.data[key].expire;
|
|
1915
|
-
this.data[key] = {
|
|
1916
|
-
value: [],
|
|
1917
|
-
expire: expire
|
|
1918
|
-
};
|
|
1919
|
-
|
|
1920
|
-
this.emit('clearForList_after', { key, result: true });
|
|
1921
|
-
return true;
|
|
1922
|
-
}
|
|
1923
|
-
|
|
1924
|
-
// 键不存在,创建一个新的空列表键
|
|
1925
|
-
this.data[key] = {
|
|
1926
|
-
value: [],
|
|
1927
|
-
expire: 0
|
|
1928
|
-
};
|
|
1929
|
-
|
|
1930
|
-
// 更新列表
|
|
1931
|
-
if (!this.list.includes(key)) {
|
|
1932
|
-
this.list.push(key);
|
|
1933
|
-
}
|
|
1934
|
-
|
|
1935
|
-
this.emit('clearForList_after', { key, result: true });
|
|
1936
|
-
return true;
|
|
1937
|
-
} catch (error) {
|
|
1938
|
-
this.emit('clearForList_error', { key, error });
|
|
1939
|
-
$.log.error('[cachebase] clearForList操作失败:', error);
|
|
1940
|
-
throw error;
|
|
1941
|
-
}
|
|
1942
|
-
}
|
|
1943
|
-
|
|
1944
|
-
/**
|
|
1945
|
-
* 减少整数值(兼容Redis decr命令)
|
|
1946
|
-
* @param {string} key - 键名
|
|
1947
|
-
* @param {number} maxAge - 过期时间(秒),0表示永不过期
|
|
1948
|
-
* @returns {Promise<number>} - 返回减少后的值
|
|
1949
|
-
* @note 完全兼容Redis decr命令,键不存在时初始化为0后减1
|
|
1950
|
-
*/
|
|
1951
|
-
async decr(key, maxAge = 0) {
|
|
1952
|
-
try {
|
|
1953
|
-
this.emit('decr_before', { key, maxAge });
|
|
1954
|
-
|
|
1955
|
-
// 获取当前值或初始化为0(与Redis一致:键不存在时视为0)
|
|
1956
|
-
let currentValue = 0;
|
|
1957
|
-
let existingExpire = 0;
|
|
1958
|
-
|
|
1959
|
-
if (this.data.hasOwnProperty(key) && !this._checkExpire(key)) {
|
|
1960
|
-
const existingValue = this.data[key].value;
|
|
1961
|
-
// 确保值被解析为数字,非数字值视为0(与Redis行为一致)
|
|
1962
|
-
currentValue = Number(existingValue) || 0;
|
|
1963
|
-
existingExpire = this.data[key].expire;
|
|
1964
|
-
}
|
|
1965
|
-
|
|
1966
|
-
// 减少1
|
|
1967
|
-
const newValue = currentValue - 1;
|
|
1968
|
-
|
|
1969
|
-
// 设置新值和过期时间(如果提供了新的过期时间,则使用新的,否则保持原来的或使用默认值)
|
|
1970
|
-
const expireTime = maxAge > 0 ? Date.now() + maxAge * 1000 : existingExpire;
|
|
1971
|
-
|
|
1972
|
-
this.data[key] = {
|
|
1973
|
-
value: newValue,
|
|
1974
|
-
expire: expireTime
|
|
1975
|
-
};
|
|
1976
|
-
|
|
1977
|
-
// 更新列表
|
|
1978
|
-
if (!this.list.includes(key)) {
|
|
1979
|
-
this.list.push(key);
|
|
1980
|
-
}
|
|
1981
|
-
|
|
1982
|
-
this.emit('decr_after', { key, maxAge, result: newValue });
|
|
1983
|
-
return newValue;
|
|
1984
|
-
} catch (error) {
|
|
1985
|
-
this.emit('decr_error', { key, maxAge, error });
|
|
1986
|
-
$.log.error('[cachebase] decr操作失败:', error);
|
|
1987
|
-
throw error;
|
|
1988
|
-
}
|
|
1989
|
-
}
|
|
1990
|
-
|
|
1991
|
-
/**
|
|
1992
|
-
* 增加浮点数(兼容方法)
|
|
1993
|
-
* @param {string} key - 键名
|
|
1994
|
-
* @param {number} num - 要增加的浮点数值
|
|
1995
|
-
* @param {number} seconds - 过期时间(秒),0表示永不过期
|
|
1996
|
-
* @returns {Promise<number>} - 返回增加后的值
|
|
1997
|
-
*/
|
|
1998
|
-
async addFloat(key, num, seconds = 0) {
|
|
1999
|
-
try {
|
|
2000
|
-
// 参数验证
|
|
2001
|
-
if (!key || typeof key !== 'string' || typeof num !== 'number') {
|
|
2002
|
-
$.log.error('[cachebase] addFloat参数错误: key必须是非空字符串,num必须是数字');
|
|
2003
|
-
return 0;
|
|
2004
|
-
}
|
|
2005
|
-
|
|
2006
|
-
this.emit('addFloat_before', { key, num, ttl: seconds, cancel: false });
|
|
2007
|
-
|
|
2008
|
-
// 原生实现浮点数递增逻辑
|
|
2009
|
-
let currentValue = 0;
|
|
2010
|
-
if (this.data.hasOwnProperty(key)) {
|
|
2011
|
-
const item = this.data[key];
|
|
2012
|
-
if (item.expire === 0 || Date.now() < item.expire) {
|
|
2013
|
-
currentValue = Number(item.value) || 0;
|
|
2014
|
-
}
|
|
2015
|
-
}
|
|
2016
|
-
const result = currentValue + Number(num);
|
|
2017
|
-
|
|
2018
|
-
// 设置值和过期时间
|
|
2019
|
-
this.data[key] = {
|
|
2020
|
-
value: result,
|
|
2021
|
-
expire: seconds > 0 ? Date.now() + seconds * 1000 : 0
|
|
2022
|
-
};
|
|
2023
|
-
|
|
2024
|
-
// 更新列表
|
|
2025
|
-
if (!this.list.includes(key)) {
|
|
2026
|
-
this.list.push(key);
|
|
2027
|
-
}
|
|
2028
|
-
|
|
2029
|
-
this.emit('addFloat_after', { key, num, ttl: seconds, success: true, result });
|
|
2030
|
-
return result;
|
|
2031
|
-
} catch (error) {
|
|
2032
|
-
this.emit('addFloat_error', { key, num, ttl: seconds, error });
|
|
2033
|
-
$.log.error('[cachebase] addFloat操作失败:', error);
|
|
2034
|
-
return 0;
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
|
|
2038
|
-
/**
|
|
2039
|
-
* 增加整数(兼容方法)
|
|
2040
|
-
* @param {string} key - 键名
|
|
2041
|
-
* @param {number} value - 要增加的整数值
|
|
2042
|
-
* @param {number} seconds - 过期时间(秒),0表示永不过期
|
|
2043
|
-
* @returns {Promise<number>} - 返回增加后的值
|
|
2044
|
-
*/
|
|
2045
|
-
async addInt(key, value, seconds = 0) {
|
|
2046
|
-
try {
|
|
2047
|
-
// 参数验证
|
|
2048
|
-
if (!key || typeof key !== 'string' || typeof value !== 'number') {
|
|
2049
|
-
$.log.error('[cachebase] addInt参数错误: key必须是非空字符串,value必须是数字');
|
|
2050
|
-
return 0;
|
|
2051
|
-
}
|
|
2052
|
-
|
|
2053
|
-
this.emit('addInt_before', { key, value, ttl: seconds, cancel: false });
|
|
2054
|
-
|
|
2055
|
-
// 原生实现整数递增逻辑
|
|
2056
|
-
let currentValue = 0;
|
|
2057
|
-
if (this.data.hasOwnProperty(key)) {
|
|
2058
|
-
const item = this.data[key];
|
|
2059
|
-
if (item.expire === 0 || Date.now() < item.expire) {
|
|
2060
|
-
currentValue = parseInt(item.value, 10) || 0;
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
const result = currentValue + parseInt(value, 10);
|
|
2064
|
-
|
|
2065
|
-
// 设置值和过期时间
|
|
2066
|
-
this.data[key] = {
|
|
2067
|
-
value: result,
|
|
2068
|
-
expire: seconds > 0 ? Date.now() + seconds * 1000 : 0
|
|
2069
|
-
};
|
|
2070
|
-
|
|
2071
|
-
// 更新列表
|
|
2072
|
-
if (!this.list.includes(key)) {
|
|
2073
|
-
this.list.push(key);
|
|
2074
|
-
}
|
|
2075
|
-
|
|
2076
|
-
this.emit('addInt_after', { key, value, ttl: seconds, success: true, result });
|
|
2077
|
-
return result;
|
|
2078
|
-
} catch (error) {
|
|
2079
|
-
this.emit('addInt_error', { key, value, ttl: seconds, error });
|
|
2080
|
-
$.log.error('[cachebase] addInt操作失败:', error);
|
|
2081
|
-
return 0;
|
|
2082
|
-
}
|
|
2083
|
-
}
|
|
2084
|
-
/**
|
|
2085
|
-
* 追加字符串(兼容方法)
|
|
2086
|
-
* @param {string} key - 键名
|
|
2087
|
-
* @param {string} value - 要追加的字符串
|
|
2088
|
-
* @param {number} seconds - 过期时间(秒),0表示永不过期
|
|
2089
|
-
* @returns {Promise<number>} - 返回追加后字符串的长度
|
|
2090
|
-
*/
|
|
2091
|
-
async addStr(key, value, seconds = 0) {
|
|
2092
|
-
try {
|
|
2093
|
-
// 参数验证
|
|
2094
|
-
if (!key || typeof key !== 'string' || typeof value !== 'string') {
|
|
2095
|
-
$.log.error('[cachebase] addStr参数错误: key和value必须是非空字符串');
|
|
2096
|
-
return 0;
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
this.emit('addStr_before', { key, value, ttl: seconds, cancel: false });
|
|
2100
|
-
|
|
2101
|
-
// 原生实现字符串追加逻辑
|
|
2102
|
-
let currentValue = '';
|
|
2103
|
-
if (this.data.hasOwnProperty(key)) {
|
|
2104
|
-
const item = this.data[key];
|
|
2105
|
-
if (item.expire === 0 || Date.now() < item.expire) {
|
|
2106
|
-
currentValue = String(item.value) || '';
|
|
2107
|
-
}
|
|
2108
|
-
}
|
|
2109
|
-
const result = currentValue + value;
|
|
2110
|
-
const length = result.length;
|
|
2111
|
-
|
|
2112
|
-
// 设置值和过期时间
|
|
2113
|
-
this.data[key] = {
|
|
2114
|
-
value: result,
|
|
2115
|
-
expire: seconds > 0 ? Date.now() + seconds * 1000 : 0
|
|
2116
|
-
};
|
|
2117
|
-
|
|
2118
|
-
// 更新列表
|
|
2119
|
-
if (!this.list.includes(key)) {
|
|
2120
|
-
this.list.push(key);
|
|
2121
|
-
}
|
|
2122
|
-
|
|
2123
|
-
this.emit('addStr_after', { key, value, ttl: seconds, result, length, success: true });
|
|
2124
|
-
return length;
|
|
2125
|
-
} catch (error) {
|
|
2126
|
-
this.emit('addStr_error', { key, value, ttl: seconds, error });
|
|
2127
|
-
$.log.error('[cachebase] addStr操作失败:', error);
|
|
2128
|
-
return 0;
|
|
2129
|
-
}
|
|
2130
|
-
}
|
|
2131
|
-
|
|
2132
|
-
/**
|
|
2133
|
-
* 销毁缓存实例
|
|
2134
|
-
* @returns {Promise<void>}
|
|
2135
|
-
*/
|
|
2136
|
-
async dispose() {
|
|
2137
|
-
try {
|
|
2138
|
-
this.emit('dispose_before');
|
|
2139
|
-
|
|
2140
|
-
// 清除定时器
|
|
2141
|
-
if (this.saveTimer) {
|
|
2142
|
-
clearInterval(this.saveTimer);
|
|
2143
|
-
this.saveTimer = null;
|
|
2144
|
-
$.log.info('[cachebase] 定时保存已停止');
|
|
2145
|
-
}
|
|
2146
|
-
|
|
2147
|
-
// 如果启用了持久化,在销毁前保存一次
|
|
2148
|
-
if (this.config.persist) {
|
|
2149
|
-
await this._saveData().catch(error => {
|
|
2150
|
-
$.log.error('[cachebase] 销毁前保存数据失败:', error);
|
|
2151
|
-
});
|
|
2152
|
-
}
|
|
2153
|
-
|
|
2154
|
-
// 清空数据
|
|
2155
|
-
this.data = {};
|
|
2156
|
-
this.list = [];
|
|
2157
|
-
this.dict = this.data; // 确保dict也被清空
|
|
2158
|
-
|
|
2159
|
-
this.emit('dispose_after');
|
|
2160
|
-
|
|
2161
|
-
// 移除所有监听器
|
|
2162
|
-
this.removeAllListeners();
|
|
2163
|
-
} catch (error) {
|
|
2164
|
-
this.emit('dispose_error', { error });
|
|
2165
|
-
$.log.error('[cachebase] dispose操作失败:', error);
|
|
2166
|
-
}
|
|
2167
|
-
}
|
|
2168
|
-
/**
|
|
2169
|
-
* 关闭缓存实例
|
|
2170
|
-
*/
|
|
2171
|
-
async close() {
|
|
2172
|
-
await this.dispose()
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
/**
|
|
2176
|
-
* 设置键的过期时间(毫秒)- 兼容Redis的PEXPIRE命令
|
|
2177
|
-
* @param {string} key - 键名
|
|
2178
|
-
* @param {number} milliseconds - 过期时间(毫秒)
|
|
2179
|
-
* @returns {Promise<boolean>} - 操作成功返回true,失败返回false
|
|
2180
|
-
*/
|
|
2181
|
-
async pexpire(key, milliseconds) {
|
|
2182
|
-
try {
|
|
2183
|
-
// 参数验证
|
|
2184
|
-
if (!key || typeof key !== 'string') {
|
|
2185
|
-
$.log.error('[cachebase] pexpire参数错误: key必须是非空字符串');
|
|
2186
|
-
return false;
|
|
2187
|
-
}
|
|
2188
|
-
if (!Number.isInteger(milliseconds) || milliseconds <= 0) {
|
|
2189
|
-
$.log.error('[cachebase] pexpire参数错误: milliseconds必须是正整数');
|
|
2190
|
-
return false;
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2193
|
-
// 触发前置事件
|
|
2194
|
-
const beforeEventData = { key, ttl: milliseconds, cancel: false };
|
|
2195
|
-
this.emit('pexpire_before', beforeEventData);
|
|
2196
|
-
|
|
2197
|
-
if (beforeEventData.cancel) {
|
|
2198
|
-
$.log.debug(`[cachebase] pexpire操作被事件处理器取消 [${key}]`);
|
|
2199
|
-
return false;
|
|
2200
|
-
}
|
|
2201
|
-
|
|
2202
|
-
const { key: finalKey, ttl: finalMilliseconds } = beforeEventData;
|
|
2203
|
-
|
|
2204
|
-
// 检查键是否存在且未过期
|
|
2205
|
-
if (!this.data.hasOwnProperty(finalKey) || this._checkExpire(finalKey)) {
|
|
2206
|
-
this.emit('pexpire_after', { key: finalKey, ttl: finalMilliseconds, success: false });
|
|
2207
|
-
return false;
|
|
2208
|
-
}
|
|
2209
|
-
|
|
2210
|
-
// 设置过期时间(转为Date对象)
|
|
2211
|
-
const now = Date.now();
|
|
2212
|
-
this.data[finalKey].expire = now + finalMilliseconds;
|
|
2213
|
-
|
|
2214
|
-
// 如果启用了持久化,同步保存到文件
|
|
2215
|
-
if (this._persistence && this._persistence.enabled) {
|
|
2216
|
-
await this._saveData();
|
|
2217
|
-
}
|
|
2218
|
-
|
|
2219
|
-
// 触发后置事件
|
|
2220
|
-
this.emit('pexpire_after', { key: finalKey, ttl: finalMilliseconds, success: true });
|
|
2221
|
-
return true;
|
|
2222
|
-
} catch (error) {
|
|
2223
|
-
$.log.error(`[cachebase] pexpire操作失败 [${key}]:`, error);
|
|
2224
|
-
this.emit('pexpire_error', { key, milliseconds, error });
|
|
2225
|
-
return false;
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
/**
|
|
2229
|
-
* 清空所有数据 - 兼容Redis的FLUSHDB命令
|
|
2230
|
-
* @returns {Promise<boolean>} - 操作成功返回true,失败返回false
|
|
2231
|
-
*/
|
|
2232
|
-
async flushdb() {
|
|
2233
|
-
try {
|
|
2234
|
-
// 触发前置事件,允许修改或阻止操作
|
|
2235
|
-
const beforeEventData = { cancel: false };
|
|
2236
|
-
this.emit('flushdb_before', beforeEventData);
|
|
2237
|
-
|
|
2238
|
-
if (beforeEventData.cancel) {
|
|
2239
|
-
$.log.debug('[cachebase] flushdb操作被事件处理器取消');
|
|
2240
|
-
this.emit('flushdb_after', { result: false });
|
|
2241
|
-
return false;
|
|
2242
|
-
}
|
|
2243
|
-
|
|
2244
|
-
// 清空所有数据
|
|
2245
|
-
this.data = {};
|
|
2246
|
-
this.list = [];
|
|
2247
|
-
|
|
2248
|
-
// 如果启用了持久化,同步保存到文件
|
|
2249
|
-
if (this._persistence && this._persistence.enabled) {
|
|
2250
|
-
await this._saveData();
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
// 触发后置事件
|
|
2254
|
-
this.emit('flushdb_after', { result: true });
|
|
2255
|
-
return true;
|
|
2256
|
-
} catch (error) {
|
|
2257
|
-
this.emit('flushdb_error', { error });
|
|
2258
|
-
$.log.error('[cachebase] flushdb操作失败:', error);
|
|
2259
|
-
return false;
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
/**
|
|
2263
|
-
* 重命名键 - 兼容Redis的RENAME命令
|
|
2264
|
-
* @param {string} oldKey - 原键名
|
|
2265
|
-
* @param {string} newKey - 新键名
|
|
2266
|
-
* @returns {Promise<string|boolean>} - 成功返回'OK',失败返回false
|
|
2267
|
-
*/
|
|
2268
|
-
async rename(oldKey, newKey) {
|
|
2269
|
-
try {
|
|
2270
|
-
// 参数验证
|
|
2271
|
-
if (!oldKey || typeof oldKey !== 'string') {
|
|
2272
|
-
$.log.error('[cachebase] rename参数错误: oldKey必须是非空字符串');
|
|
2273
|
-
return false;
|
|
2274
|
-
}
|
|
2275
|
-
if (!newKey || typeof newKey !== 'string') {
|
|
2276
|
-
$.log.error('[cachebase] rename参数错误: newKey必须是非空字符串');
|
|
2277
|
-
return false;
|
|
2278
|
-
}
|
|
2279
|
-
|
|
2280
|
-
// 触发前置事件
|
|
2281
|
-
const beforeEventData = { oldKey, newKey, cancel: false };
|
|
2282
|
-
this.emit('rename_before', beforeEventData);
|
|
2283
|
-
|
|
2284
|
-
if (beforeEventData.cancel) {
|
|
2285
|
-
$.log.debug(`[cachebase] rename操作被事件处理器取消 [${oldKey} -> ${newKey}]`);
|
|
2286
|
-
return false;
|
|
2287
|
-
}
|
|
2288
|
-
|
|
2289
|
-
const { oldKey: finalOldKey, newKey: finalNewKey } = beforeEventData;
|
|
2290
|
-
|
|
2291
|
-
// 检查原键是否存在且未过期
|
|
2292
|
-
if (!this.data.hasOwnProperty(finalOldKey) || this._checkExpire(finalOldKey)) {
|
|
2293
|
-
this.emit('rename_after', { oldKey: finalOldKey, newKey: finalNewKey, success: false });
|
|
2294
|
-
return false;
|
|
2295
|
-
}
|
|
2296
|
-
|
|
2297
|
-
// 获取原键数据
|
|
2298
|
-
const oldValue = this.data[finalOldKey];
|
|
2299
|
-
|
|
2300
|
-
// 如果新键存在,先删除它
|
|
2301
|
-
if (this.data.hasOwnProperty(finalNewKey)) {
|
|
2302
|
-
delete this.data[finalNewKey];
|
|
2303
|
-
}
|
|
2304
|
-
|
|
2305
|
-
// 重命名键
|
|
2306
|
-
this.data[finalNewKey] = oldValue;
|
|
2307
|
-
delete this.data[finalOldKey];
|
|
2308
|
-
|
|
2309
|
-
// 如果启用了持久化,同步保存到文件
|
|
2310
|
-
if (this._persistence && this._persistence.enabled) {
|
|
2311
|
-
await this._saveData();
|
|
2312
|
-
}
|
|
2313
|
-
|
|
2314
|
-
// 触发后置事件
|
|
2315
|
-
this.emit('rename_after', { oldKey: finalOldKey, newKey: finalNewKey, success: true });
|
|
2316
|
-
return 'OK'; // 兼容Redis,成功时返回'OK'
|
|
2317
|
-
} catch (error) {
|
|
2318
|
-
$.log.error(`[cachebase] rename操作失败 [${oldKey} -> ${newKey}]:`, error);
|
|
2319
|
-
this.emit('rename_error', { oldKey, newKey, error });
|
|
2320
|
-
return false;
|
|
2321
|
-
}
|
|
2322
|
-
}
|
|
2323
|
-
|
|
2324
|
-
/**
|
|
2325
|
-
* 一次性设置多个键值对 - 兼容Redis的MSET命令
|
|
2326
|
-
* @param {string} key1, value1, key2, value2... - 键值对参数列表
|
|
2327
|
-
* @returns {Promise<boolean>} - 成功返回true,失败返回false
|
|
2328
|
-
*/
|
|
2329
|
-
async mset(...args) {
|
|
2330
|
-
try {
|
|
2331
|
-
// 参数验证
|
|
2332
|
-
if (args.length === 0 || args.length % 2 !== 0) {
|
|
2333
|
-
$.log.error('[cachebase] mset参数错误: 参数数量必须为偶数');
|
|
2334
|
-
return false;
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
// 将参数列表转换为键值对对象
|
|
2338
|
-
const keyValuePairs = {};
|
|
2339
|
-
for (let i = 0; i < args.length; i += 2) {
|
|
2340
|
-
const key = args[i];
|
|
2341
|
-
const value = args[i + 1];
|
|
2342
|
-
|
|
2343
|
-
if (typeof key !== 'string' || !key) {
|
|
2344
|
-
$.log.error(`[cachebase] mset参数错误: 键 ${key} 必须是非空字符串`);
|
|
2345
|
-
return false;
|
|
2346
|
-
}
|
|
2347
|
-
|
|
2348
|
-
keyValuePairs[key] = value;
|
|
2349
|
-
}
|
|
2350
|
-
|
|
2351
|
-
// 触发前置事件
|
|
2352
|
-
const beforeEventData = { keyValuePairs, cancel: false };
|
|
2353
|
-
this.emit('mset_before', beforeEventData);
|
|
2354
|
-
|
|
2355
|
-
if (beforeEventData.cancel) {
|
|
2356
|
-
$.log.debug('[cachebase] mset操作被事件处理器取消');
|
|
2357
|
-
return false;
|
|
2358
|
-
}
|
|
2359
|
-
|
|
2360
|
-
const { keyValuePairs: finalKeyValuePairs } = beforeEventData;
|
|
2361
|
-
|
|
2362
|
-
// 批量设置键值对
|
|
2363
|
-
for (const [key, value] of Object.entries(finalKeyValuePairs)) {
|
|
2364
|
-
// 创建新的数据项
|
|
2365
|
-
const now = Date.now();
|
|
2366
|
-
this.data[key] = {
|
|
2367
|
-
value: value,
|
|
2368
|
-
createTime: now,
|
|
2369
|
-
updateTime: now,
|
|
2370
|
-
expire: 0
|
|
2371
|
-
};
|
|
2372
|
-
}
|
|
2373
|
-
|
|
2374
|
-
// 如果启用了持久化,同步保存到文件
|
|
2375
|
-
if (this._persistence && this._persistence.enabled) {
|
|
2376
|
-
await this._saveData();
|
|
2377
|
-
}
|
|
2378
|
-
|
|
2379
|
-
// 触发后置事件
|
|
2380
|
-
this.emit('mset_after', { keyValuePairs: finalKeyValuePairs, success: true });
|
|
2381
|
-
return true;
|
|
2382
|
-
} catch (error) {
|
|
2383
|
-
$.log.error('[cachebase] mset操作失败:', error);
|
|
2384
|
-
this.emit('mset_error', { args, error });
|
|
2385
|
-
return false;
|
|
2386
|
-
}
|
|
2387
|
-
}
|
|
2388
|
-
|
|
2389
|
-
async strlen(key) {
|
|
2390
|
-
try {
|
|
2391
|
-
// 触发前置事件
|
|
2392
|
-
const beforeEventData = { key, cancel: false };
|
|
2393
|
-
this.emit('strlen_before', beforeEventData);
|
|
2394
|
-
|
|
2395
|
-
if (beforeEventData.cancel) {
|
|
2396
|
-
$.log.debug(`[cachebase] strlen操作被事件处理器取消 [${key}]`);
|
|
2397
|
-
return 0;
|
|
2398
|
-
}
|
|
2399
|
-
|
|
2400
|
-
const { key: finalKey } = beforeEventData;
|
|
2401
|
-
|
|
2402
|
-
// 检查键是否存在且未过期
|
|
2403
|
-
if (!this.data.hasOwnProperty(finalKey) || this._checkExpire(finalKey)) {
|
|
2404
|
-
this.emit('strlen_after', { key: finalKey, length: 0 });
|
|
2405
|
-
return 0;
|
|
2406
|
-
}
|
|
2407
|
-
|
|
2408
|
-
// 获取值
|
|
2409
|
-
const value = this.data[finalKey].value;
|
|
2410
|
-
let length = 0;
|
|
2411
|
-
|
|
2412
|
-
// 兼容Redis行为:只有字符串类型计算长度,其他类型返回0
|
|
2413
|
-
if (typeof value === 'string') {
|
|
2414
|
-
length = value.length;
|
|
2415
|
-
}
|
|
2416
|
-
// 注意:与Redis保持一致,非字符串类型不计算长度,返回0
|
|
2417
|
-
|
|
2418
|
-
// 触发后置事件
|
|
2419
|
-
this.emit('strlen_after', { key: finalKey, length });
|
|
2420
|
-
return length;
|
|
2421
|
-
} catch (error) {
|
|
2422
|
-
$.log.error(`[cachebase] strlen操作失败 [${key}]:`, error);
|
|
2423
|
-
this.emit('strlen_error', { key, error });
|
|
2424
|
-
return 0;
|
|
2425
|
-
}
|
|
2426
|
-
}
|
|
2427
|
-
|
|
2428
|
-
/**
|
|
2429
|
-
* 获取键的类型 - 兼容Redis的TYPE命令
|
|
2430
|
-
* @param {string} key - 键名
|
|
2431
|
-
* @returns {Promise<string>} - 返回类型字符串,不存在返回'none'
|
|
2432
|
-
*/
|
|
2433
|
-
async type(key) {
|
|
2434
|
-
try {
|
|
2435
|
-
// 参数验证
|
|
2436
|
-
if (!key || typeof key !== 'string') {
|
|
2437
|
-
$.log.error('[cachebase] type参数错误: key必须是非空字符串');
|
|
2438
|
-
return 'none';
|
|
2439
|
-
}
|
|
2440
|
-
|
|
2441
|
-
// 触发前置事件
|
|
2442
|
-
const beforeEventData = { key, cancel: false };
|
|
2443
|
-
this.emit('type_before', beforeEventData);
|
|
2444
|
-
|
|
2445
|
-
if (beforeEventData.cancel) {
|
|
2446
|
-
$.log.debug(`[cachebase] type操作被事件处理器取消 [${key}]`);
|
|
2447
|
-
return 'none';
|
|
2448
|
-
}
|
|
2449
|
-
|
|
2450
|
-
const { key: finalKey } = beforeEventData;
|
|
2451
|
-
|
|
2452
|
-
// 检查键是否存在且未过期
|
|
2453
|
-
if (!this.data.hasOwnProperty(finalKey) || this._checkExpire(finalKey)) {
|
|
2454
|
-
this.emit('type_after', { key: finalKey, type: 'none' });
|
|
2455
|
-
return 'none';
|
|
2456
|
-
}
|
|
2457
|
-
|
|
2458
|
-
// 获取值
|
|
2459
|
-
const value = this.data[finalKey].value;
|
|
2460
|
-
let type = 'none';
|
|
2461
|
-
|
|
2462
|
-
// 确定类型,与Redis保持完全一致
|
|
2463
|
-
if (value === null) {
|
|
2464
|
-
type = 'none';
|
|
2465
|
-
} else if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
2466
|
-
type = 'string'; // Redis将数字和布尔值也视为字符串类型
|
|
2467
|
-
} else if (Array.isArray(value)) {
|
|
2468
|
-
// 检查是否为集合类型(不包含重复元素)
|
|
2469
|
-
if (new Set(value).size === value.length) {
|
|
2470
|
-
type = 'set';
|
|
2471
|
-
} else {
|
|
2472
|
-
type = 'list';
|
|
2473
|
-
}
|
|
2474
|
-
} else if (typeof value === 'object') {
|
|
2475
|
-
type = 'hash'; // 对象类型对应Redis的hash
|
|
2476
|
-
}
|
|
2477
|
-
|
|
2478
|
-
// 触发后置事件
|
|
2479
|
-
this.emit('type_after', { key: finalKey, type });
|
|
2480
|
-
return type;
|
|
2481
|
-
} catch (error) {
|
|
2482
|
-
$.log.error(`[cachebase] type操作失败 [${key}]:`, error);
|
|
2483
|
-
this.emit('type_error', { key, error });
|
|
2484
|
-
return 'none';
|
|
2485
|
-
}
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
exports.CacheBase = CacheBase;
|
|
2489
|
-
|
|
2490
|
-
/**
|
|
2491
|
-
* 确保连接池对象存在
|
|
2492
|
-
*/
|
|
2493
|
-
if (!$.pool) {
|
|
2494
|
-
$.pool = {};
|
|
2495
|
-
}
|
|
2496
|
-
if (!$.pool.cachebase) {
|
|
2497
|
-
$.pool.cachebase = {};
|
|
2498
|
-
}
|
|
2499
|
-
|
|
2500
|
-
/**
|
|
2501
|
-
* @description cachebase管理器,用于创建缓存
|
|
2502
|
-
* @param {String} scope 作用域
|
|
2503
|
-
* @param {String} dir 当前路径
|
|
2504
|
-
* @return {Object} 返回一个cachebase类实例
|
|
2505
|
-
*/
|
|
2506
|
-
function cachebase_admin(scope, dir) {
|
|
2507
|
-
if (!scope) {
|
|
2508
|
-
scope = 'sys';
|
|
2509
|
-
}
|
|
2510
|
-
var obj = $.pool.cachebase[scope];
|
|
2511
|
-
if (!obj) {
|
|
2512
|
-
$.pool.cachebase[scope] = new CacheBase(scope, dir);
|
|
2513
|
-
obj = $.pool.cachebase[scope];
|
|
2514
|
-
}
|
|
2515
|
-
return obj;
|
|
2516
|
-
}
|
|
2517
|
-
|
|
2518
|
-
/**
|
|
2519
|
-
* @module 导出cachebase管理器
|
|
2520
|
-
*/
|
|
2521
|
-
exports.cachebase_admin = cachebase_admin;
|