mm_redis 2.1.6 → 2.1.8
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/example/index.js +40 -0
- package/index.js +11 -4111
- package/lib/redis.js +4085 -0
- package/package.json +12 -3
- package/test.js +14 -1
package/index.js
CHANGED
|
@@ -1,4116 +1,16 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
} = require('redis');
|
|
4
|
-
const { Base } = require('mm_expand');
|
|
1
|
+
const Redis = require('./lib/redis');
|
|
2
|
+
const Admin = require('mm_corejs').Admin;
|
|
5
3
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* @description Redis缓存帮助类函数
|
|
9
|
-
*/
|
|
10
|
-
class Redis extends Base {
|
|
11
|
-
static config = {
|
|
12
|
-
// 服务器地址
|
|
13
|
-
"host": "localhost",
|
|
14
|
-
// 端口号
|
|
15
|
-
"port": 6379,
|
|
16
|
-
// 密码
|
|
17
|
-
"password": "asd159357",
|
|
18
|
-
// 数据库
|
|
19
|
-
"database": 0,
|
|
20
|
-
// 前缀
|
|
21
|
-
"prefix": "",
|
|
22
|
-
// 连接超时时间(ms)
|
|
23
|
-
"connect_timeout": 10000,
|
|
24
|
-
// 重试次数
|
|
25
|
-
"retry_count": 3,
|
|
26
|
-
// 重试间隔(ms)
|
|
27
|
-
"retry_delay": 1000,
|
|
28
|
-
// 连接池配置
|
|
29
|
-
"max_clients": 100, // 最大连接数
|
|
30
|
-
"min_idle": 5, // 最小空闲连接数
|
|
31
|
-
"max_idle": 20, // 最大空闲连接数
|
|
32
|
-
"idle_timeout": 30000 // 空闲超时时间(ms)
|
|
33
|
-
};
|
|
34
|
-
/**
|
|
35
|
-
* @description 创建Redis帮助类函数 (构造函数)
|
|
36
|
-
* @param {Object} config 配置参数
|
|
37
|
-
* @constructor
|
|
38
|
-
*/
|
|
39
|
-
constructor(config) {
|
|
40
|
-
super(Redis.config);
|
|
41
|
-
// 初始化连接池
|
|
42
|
-
this._initPool();
|
|
43
|
-
// 唯一标识符
|
|
44
|
-
this.identifier = this.config.host + "/" + this.config.database;
|
|
45
|
-
// 数据库连接器
|
|
46
|
-
this.conn = null;
|
|
47
|
-
// 连接状态
|
|
48
|
-
this.connected = false;
|
|
49
|
-
// 重试次数计数器
|
|
50
|
-
this.retry_count = 0;
|
|
51
|
-
// 订阅回调函数
|
|
52
|
-
this.func_list = {};
|
|
53
|
-
this.setConfig(config);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* 应用键前缀
|
|
59
|
-
* @param {String} key 键
|
|
60
|
-
* @return {String} 处理后的键
|
|
61
|
-
* @private
|
|
62
|
-
*/
|
|
63
|
-
Redis.prototype._addPrefix = function (key) {
|
|
64
|
-
return this.config.prefix ? this.config.prefix + key : key;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* 移除键前缀
|
|
69
|
-
* @param {String} key 带前缀的键
|
|
70
|
-
* @return {String} 处理后的键
|
|
71
|
-
* @private
|
|
72
|
-
*/
|
|
73
|
-
Redis.prototype._delPrefix = function (key) {
|
|
74
|
-
if (this.config.prefix && key.startsWith(this.config.prefix)) {
|
|
75
|
-
return key.slice(this.config.prefix.length);
|
|
76
|
-
}
|
|
77
|
-
return key;
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* 消息订阅
|
|
82
|
-
* @param {String} channel 订阅的频道
|
|
83
|
-
* @param {Function} func 操作回调函数,可判断成功与否
|
|
84
|
-
* @return {Promise<Boolean>} 订阅结果
|
|
85
|
-
*/
|
|
86
|
-
Redis.prototype.subscribe = async function (channel, func) {
|
|
87
|
-
// 参数验证
|
|
88
|
-
if (!channel || typeof channel !== 'string') {
|
|
89
|
-
throw new TypeError('channel必须是非空字符串');
|
|
90
|
-
}
|
|
91
|
-
if (typeof func !== 'function') {
|
|
92
|
-
throw new TypeError('func必须是函数');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
try {
|
|
96
|
-
await this.conn.subscribe(this._addPrefix(channel));
|
|
97
|
-
this.func_list[channel] = func;
|
|
98
|
-
return true;
|
|
99
|
-
} catch (err) {
|
|
100
|
-
this.log('error', `订阅频道 ${channel} 失败:`, err);
|
|
101
|
-
return false;
|
|
102
|
-
}
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* 取消订阅
|
|
107
|
-
* @param {String} channel 订阅的频道
|
|
108
|
-
* @return {Promise<Boolean>} 取消订阅结果
|
|
109
|
-
*/
|
|
110
|
-
Redis.prototype.unsubscribe = async function (channel) {
|
|
111
|
-
// 参数验证
|
|
112
|
-
if (!channel || typeof channel !== 'string') {
|
|
113
|
-
throw new TypeError('channel必须是非空字符串');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
await this.conn.unsubscribe(this._addPrefix(channel));
|
|
118
|
-
delete this.func_list[channel];
|
|
119
|
-
return true;
|
|
120
|
-
} catch (err) {
|
|
121
|
-
this.log('error', `取消订阅频道 ${channel} 失败:`, err);
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* 发布消息
|
|
128
|
-
* @param {String} channel 频道
|
|
129
|
-
* @param {String} message 订阅的消息
|
|
130
|
-
* @return {Promise<Number>} 接收消息的客户端数量
|
|
131
|
-
*/
|
|
132
|
-
Redis.prototype.publish = async function (channel, message) {
|
|
133
|
-
// 参数验证
|
|
134
|
-
if (!channel || typeof channel !== 'string') {
|
|
135
|
-
throw new TypeError('channel必须是非空字符串');
|
|
136
|
-
}
|
|
137
|
-
if (message === undefined || message === null) {
|
|
138
|
-
throw new TypeError('message不能为undefined或null');
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
return await this.conn.publish(this._addPrefix(channel), message);
|
|
143
|
-
} catch (err) {
|
|
144
|
-
this.log('error', `发布消息到频道 ${channel} 失败:`, err);
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* 订阅消息处理, 如果没有订阅函数,则其他的订阅消息会进入该函数
|
|
151
|
-
* @param {String} channel
|
|
152
|
-
* @param {String} message
|
|
153
|
-
*/
|
|
154
|
-
Redis.prototype.main = async function (channel, message) {
|
|
155
|
-
// 默认实现,可由子类重写
|
|
156
|
-
this.log('debug', `收到未处理的消息 [${channel}]:`, message);
|
|
157
|
-
};
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* 消息通知
|
|
161
|
-
* @param {String} channel 频道
|
|
162
|
-
* @param {String} message 消息
|
|
163
|
-
*/
|
|
164
|
-
Redis.prototype.message = function (channel, message) {
|
|
165
|
-
// 移除前缀
|
|
166
|
-
channel = this._delPrefix(channel);
|
|
167
|
-
var func = this.func_list[channel];
|
|
168
|
-
if (func) {
|
|
169
|
-
try {
|
|
170
|
-
func(message);
|
|
171
|
-
} catch (err) {
|
|
172
|
-
this.log('error', `处理频道 ${channel} 的消息时出错:`, err);
|
|
173
|
-
}
|
|
174
|
-
} else {
|
|
175
|
-
this.main(channel, message);
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* @description 设置配置
|
|
181
|
-
* @param {Object} config 配置对象或配置路径
|
|
182
|
-
*/
|
|
183
|
-
Redis.prototype.setConfig = function (config) {
|
|
184
|
-
let obj = {};
|
|
185
|
-
if (typeof config === 'string') {
|
|
186
|
-
obj = config.loadJson(__dirname);
|
|
187
|
-
} else if (config) {
|
|
188
|
-
// 合并配置参数
|
|
189
|
-
Object.assign(this.config, config);
|
|
190
|
-
} else {
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// 初始化连接池
|
|
195
|
-
this._initPool();
|
|
196
|
-
// this.log('debug','配置已更新');
|
|
197
|
-
this.identifier = this.config.host + "/" + this.config.database;
|
|
198
|
-
return this;
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
// 添加连接池初始化方法
|
|
202
|
-
Redis.prototype._initPool = function () {
|
|
203
|
-
// 确保连接池配置存在
|
|
204
|
-
|
|
205
|
-
// 初始化连接池相关属性
|
|
206
|
-
this.connection_pool = {
|
|
207
|
-
available: [],
|
|
208
|
-
in_use: new Set(),
|
|
209
|
-
config: this.config // 直接引用配置对象,确保同步更新
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
// 记录连接池初始化
|
|
213
|
-
// this.log('debug','连接池已初始化,配置:', this.config);
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* @description 连接Redis数据库
|
|
218
|
-
* @return {Promise<Boolean>} 连接结果
|
|
219
|
-
*/
|
|
220
|
-
Redis.prototype.open = async function () {
|
|
221
|
-
if (this.conn && this.connected) {
|
|
222
|
-
return true;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
const cg = this.config;
|
|
227
|
-
this.retry_count = 0;
|
|
228
|
-
|
|
229
|
-
// 创建客户端
|
|
230
|
-
this.conn = createClient({
|
|
231
|
-
socket: {
|
|
232
|
-
host: cg.host,
|
|
233
|
-
port: cg.port,
|
|
234
|
-
timeout: cg.connect_timeout
|
|
235
|
-
},
|
|
236
|
-
password: cg.password,
|
|
237
|
-
database: cg.database
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
// 监听错误事件
|
|
241
|
-
this.conn.on('error', (err) => {
|
|
242
|
-
this.log('error', 'Redis连接错误:', err);
|
|
243
|
-
this.connected = false;
|
|
244
|
-
// 尝试重连
|
|
245
|
-
this._reconnect();
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
// 监听连接成功事件
|
|
249
|
-
this.conn.on('connect', () => {
|
|
250
|
-
// this.log('debug','Redis已连接');
|
|
251
|
-
this.connected = true;
|
|
252
|
-
this.retry_count = 0;
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
// 监听断开连接事件
|
|
256
|
-
this.conn.on('end', () => {
|
|
257
|
-
// this.log('debug','Redis连接已断开');
|
|
258
|
-
this.connected = false;
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
// 监听订阅成功事件
|
|
262
|
-
this.conn.on('subscribe', (channel, count) => {
|
|
263
|
-
this.log('debug', `频道${this._delPrefix(channel)}订阅成功, 当前总计订阅数:${count}`);
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
// 监听取消订阅事件
|
|
267
|
-
this.conn.on('unsubscribe', (channel, count) => {
|
|
268
|
-
this.log('debug', `频道${this._delPrefix(channel)}取消订阅, 当前总计订阅数:${count}`);
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// 收到消息后执行回调
|
|
272
|
-
this.conn.on('message', (channel, message) => {
|
|
273
|
-
this.message(channel, message);
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
// 连接Redis
|
|
277
|
-
await this.conn.connect();
|
|
278
|
-
this.connected = true;
|
|
279
|
-
return true;
|
|
280
|
-
} catch (err) {
|
|
281
|
-
this.log('error', 'Redis连接失败:', err);
|
|
282
|
-
// 尝试重连
|
|
283
|
-
this._reconnect();
|
|
284
|
-
return false;
|
|
285
|
-
}
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* 内部重连方法
|
|
290
|
-
* @private
|
|
291
|
-
*/
|
|
292
|
-
Redis.prototype._reconnect = function () {
|
|
293
|
-
if (this.retry_count >= this.config.retry_count) {
|
|
294
|
-
this.log('error', `达到最大重连次数 ${this.config.retry_count},停止重连`);
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
this.retry_count++;
|
|
299
|
-
this.log('debug', `尝试第 ${this.retry_count} 次重连...`);
|
|
300
|
-
|
|
301
|
-
setTimeout(async () => {
|
|
302
|
-
try {
|
|
303
|
-
await this.open();
|
|
304
|
-
} catch (err) {
|
|
305
|
-
this.log('error', `重连失败:`, err);
|
|
306
|
-
}
|
|
307
|
-
}, this.config.retry_delay);
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* @description 关闭连接
|
|
312
|
-
* @return {Promise<Boolean>} 关闭结果
|
|
313
|
-
*/
|
|
314
|
-
// 修复close方法,正确处理连接池
|
|
315
|
-
Redis.prototype.close = function () {
|
|
316
|
-
try {
|
|
317
|
-
// this.log('debug','关闭所有连接...');
|
|
318
|
-
|
|
319
|
-
// 关闭所有使用中的连接
|
|
320
|
-
if (this.connection_pool && this.connection_pool.in_use) {
|
|
321
|
-
for (const connection of this.connection_pool.in_use) {
|
|
322
|
-
try {
|
|
323
|
-
connection.close();
|
|
324
|
-
} catch (e) {
|
|
325
|
-
this.log('error', '关闭连接失败:', e);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
this.connection_pool.in_use.clear();
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// 关闭所有可用连接
|
|
332
|
-
if (this.connection_pool && this.connection_pool.available) {
|
|
333
|
-
for (const connection of this.connection_pool.available) {
|
|
334
|
-
try {
|
|
335
|
-
// 检查连接对象是否有close方法
|
|
336
|
-
if (typeof connection.close === 'function') {
|
|
337
|
-
connection.close();
|
|
338
|
-
} else if (typeof connection.end === 'function') {
|
|
339
|
-
// 某些Redis客户端可能使用end方法
|
|
340
|
-
connection.end();
|
|
341
|
-
} else {
|
|
342
|
-
this.log('debug', '连接对象没有可用的关闭方法');
|
|
343
|
-
}
|
|
344
|
-
} catch (e) {
|
|
345
|
-
this.log('error', '关闭连接失败:', e);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
this.connection_pool.available = [];
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
this.connected = false;
|
|
352
|
-
this.log('debug', 'Redis连接已断开');
|
|
353
|
-
return Promise.resolve(true);
|
|
354
|
-
} catch (error) {
|
|
355
|
-
this.log('error', '关闭连接时发生错误:', error);
|
|
356
|
-
return Promise.resolve(false);
|
|
357
|
-
}
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
// 添加Pipeline支持
|
|
361
|
-
Redis.prototype.pipeline = function () {
|
|
362
|
-
const redisInstance = this;
|
|
363
|
-
const commands = [];
|
|
364
|
-
|
|
365
|
-
return {
|
|
366
|
-
// 设置值
|
|
367
|
-
set: function (key, value, seconds) {
|
|
368
|
-
commands.push(['set', key, value, seconds]);
|
|
369
|
-
return this;
|
|
370
|
-
},
|
|
371
|
-
|
|
372
|
-
// 获取值
|
|
373
|
-
get: function (key) {
|
|
374
|
-
commands.push(['get', key]);
|
|
375
|
-
return this;
|
|
376
|
-
},
|
|
377
|
-
|
|
378
|
-
// 删除
|
|
379
|
-
del: function (key) {
|
|
380
|
-
commands.push(['del', key]);
|
|
381
|
-
return this;
|
|
382
|
-
},
|
|
383
|
-
|
|
384
|
-
// 判断存在
|
|
385
|
-
has: function (key) {
|
|
386
|
-
commands.push(['has', key]);
|
|
387
|
-
return this;
|
|
388
|
-
},
|
|
389
|
-
|
|
390
|
-
// 设置哈希字段
|
|
391
|
-
hset: function (key, field, value) {
|
|
392
|
-
commands.push(['hset', key, field, value]);
|
|
393
|
-
return this;
|
|
394
|
-
},
|
|
395
|
-
|
|
396
|
-
// 过期时间设置
|
|
397
|
-
expire: function (key, seconds) {
|
|
398
|
-
commands.push(['expire', key, seconds]);
|
|
399
|
-
return this;
|
|
400
|
-
},
|
|
401
|
-
|
|
402
|
-
// 执行所有命令
|
|
403
|
-
exec: async function () {
|
|
404
|
-
try {
|
|
405
|
-
// 使用Redis实例的方法获取连接
|
|
406
|
-
const connection = await redisInstance._getConnection();
|
|
407
|
-
const results = [];
|
|
408
|
-
|
|
409
|
-
// 依次执行所有命令
|
|
410
|
-
for (const cmd of commands) {
|
|
411
|
-
try {
|
|
412
|
-
let result;
|
|
413
|
-
const [command, ...args] = cmd;
|
|
414
|
-
|
|
415
|
-
// 根据命令类型直接调用对应的方法
|
|
416
|
-
switch (command) {
|
|
417
|
-
case 'set': {
|
|
418
|
-
// 键前缀处理和值序列化
|
|
419
|
-
const [key, value, seconds] = args;
|
|
420
|
-
const prefixedKey = redisInstance._addPrefix(key);
|
|
421
|
-
let processedValue = value;
|
|
422
|
-
if (typeof value === 'object' && value !== null) {
|
|
423
|
-
processedValue = JSON.stringify(value);
|
|
424
|
-
}
|
|
425
|
-
result = await connection.set(prefixedKey, processedValue);
|
|
426
|
-
// 设置过期时间
|
|
427
|
-
if (seconds && seconds > 0) {
|
|
428
|
-
await connection.expire(prefixedKey, seconds);
|
|
429
|
-
}
|
|
430
|
-
break;
|
|
431
|
-
}
|
|
432
|
-
case 'get': {
|
|
433
|
-
const [key] = args;
|
|
434
|
-
const prefixedKey = redisInstance._addPrefix(key);
|
|
435
|
-
result = await connection.get(prefixedKey);
|
|
436
|
-
// 尝试解析JSON
|
|
437
|
-
if (typeof result === 'string') {
|
|
438
|
-
try {
|
|
439
|
-
if (result.startsWith('{') && result.endsWith('}') ||
|
|
440
|
-
result.startsWith('[') && result.endsWith(']')) {
|
|
441
|
-
result = JSON.parse(result);
|
|
442
|
-
}
|
|
443
|
-
} catch (e) {
|
|
444
|
-
// 解析失败,保持原样
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
break;
|
|
448
|
-
}
|
|
449
|
-
case 'del': {
|
|
450
|
-
const [key] = args;
|
|
451
|
-
const prefixedKey = redisInstance._addPrefix(key);
|
|
452
|
-
result = await connection.del(prefixedKey);
|
|
453
|
-
break;
|
|
454
|
-
}
|
|
455
|
-
case 'has': {
|
|
456
|
-
const [key] = args;
|
|
457
|
-
const prefixedKey = redisInstance._addPrefix(key);
|
|
458
|
-
result = await connection.exists(prefixedKey) > 0;
|
|
459
|
-
break;
|
|
460
|
-
}
|
|
461
|
-
case 'hset': {
|
|
462
|
-
const [key, field, value] = args;
|
|
463
|
-
const prefixedKey = redisInstance._addPrefix(key);
|
|
464
|
-
result = await connection.hSet(prefixedKey, field, value);
|
|
465
|
-
break;
|
|
466
|
-
}
|
|
467
|
-
case 'expire': {
|
|
468
|
-
const [key, seconds] = args;
|
|
469
|
-
const prefixedKey = redisInstance._addPrefix(key);
|
|
470
|
-
result = await connection.expire(prefixedKey, seconds);
|
|
471
|
-
break;
|
|
472
|
-
}
|
|
473
|
-
default:
|
|
474
|
-
// 对于其他命令,直接返回null
|
|
475
|
-
result = null;
|
|
476
|
-
break;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
// 直接返回结果值,符合测试期望的格式
|
|
480
|
-
results.push(result);
|
|
481
|
-
} catch (err) {
|
|
482
|
-
this.log('error', `Pipeline命令执行失败 [${cmd[0]}]:`, err);
|
|
483
|
-
results.push(null);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
// 释放连接回连接池
|
|
488
|
-
redisInstance._recycleConnection(connection);
|
|
489
|
-
return results;
|
|
490
|
-
} catch (error) {
|
|
491
|
-
this.log('error', 'Pipeline执行失败:', error);
|
|
492
|
-
return [];
|
|
493
|
-
}
|
|
494
|
-
}
|
|
495
|
-
};
|
|
496
|
-
};
|
|
497
|
-
|
|
498
|
-
/**
|
|
499
|
-
* @description 检查连接状态
|
|
500
|
-
* @return {Boolean} 连接状态
|
|
501
|
-
*/
|
|
502
|
-
Redis.prototype.isConnected = function () {
|
|
503
|
-
return this.connected && this.conn && this.conn.isReady;
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* 获取或设置键的过期时间
|
|
508
|
-
* @param {String} key 键名
|
|
509
|
-
* @param {Number} [seconds] 过期时间(秒),不传此参数则获取剩余过期时间
|
|
510
|
-
* @return {Promise<Number|Boolean>} 不传seconds时返回剩余过期时间(-1表示无过期设置,-2表示已过期),传seconds时返回是否设置成功
|
|
511
|
-
*/
|
|
512
|
-
Redis.prototype.ttl = async function (key, seconds) {
|
|
513
|
-
// 参数验证
|
|
514
|
-
if (!key || typeof key !== 'string') {
|
|
515
|
-
throw new TypeError('key必须是非空字符串');
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
try {
|
|
519
|
-
|
|
520
|
-
const connection = await this._getConnection();
|
|
521
|
-
try {
|
|
522
|
-
key = this._addPrefix(key);
|
|
523
|
-
|
|
524
|
-
// 如果传入了seconds参数,则设置过期时间
|
|
525
|
-
if (seconds !== undefined) {
|
|
526
|
-
if (typeof seconds !== 'number' || isNaN(seconds) || seconds < 0) {
|
|
527
|
-
return false;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// 设置过期时间
|
|
531
|
-
if (typeof connection.expire === 'function') {
|
|
532
|
-
await connection.expire(key, seconds);
|
|
533
|
-
return true;
|
|
534
|
-
} else if (typeof connection.execute === 'function') {
|
|
535
|
-
await connection.execute('EXPIRE', [key, seconds]);
|
|
536
|
-
return true;
|
|
537
|
-
} else {
|
|
538
|
-
this.log('error', '连接对象不支持expire操作');
|
|
539
|
-
return false;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
// 否则获取剩余过期时间
|
|
544
|
-
if (typeof connection.ttl === 'function') {
|
|
545
|
-
return await connection.ttl(key);
|
|
546
|
-
} else if (typeof connection.execute === 'function') {
|
|
547
|
-
return await connection.execute('TTL', [key]);
|
|
548
|
-
} else {
|
|
549
|
-
this.log('error', '连接对象不支持ttl操作');
|
|
550
|
-
return -2; // 默认返回键不存在
|
|
551
|
-
}
|
|
552
|
-
} finally {
|
|
553
|
-
this._recycleConnection(connection);
|
|
554
|
-
}
|
|
555
|
-
} catch (err) {
|
|
556
|
-
this.log('error', `${seconds !== undefined ? '设置' : '获取'}过期时间失败 [${key}]:`, err);
|
|
557
|
-
return seconds !== undefined ? false : -2;
|
|
558
|
-
}
|
|
559
|
-
};
|
|
560
|
-
|
|
561
|
-
/**
|
|
562
|
-
* @description 增加整数值(负数为减)(兼容方法)
|
|
563
|
-
* @param {String} key 键
|
|
564
|
-
* @param {Number} num 数值
|
|
565
|
-
* @param {Number} [seconds=0] 过期时间(秒)
|
|
566
|
-
* @return {Promise<Number>} 计算后的结果
|
|
567
|
-
*/
|
|
568
|
-
Redis.prototype.addInt = async function (key, num, seconds = 0) {
|
|
569
|
-
// 参数验证
|
|
570
|
-
if (!key || typeof key !== 'string') {
|
|
571
|
-
throw new TypeError('key必须是非空字符串');
|
|
572
|
-
}
|
|
573
|
-
if (typeof num !== 'number' || isNaN(num) || !Number.isInteger(num)) {
|
|
574
|
-
throw new TypeError('num必须是有效整数');
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
try {
|
|
578
|
-
|
|
579
|
-
// 触发前置事件,允许修改或阻止操作
|
|
580
|
-
const ctx = {
|
|
581
|
-
key,
|
|
582
|
-
value: num,
|
|
583
|
-
seconds,
|
|
584
|
-
cancel: false
|
|
585
|
-
};
|
|
586
|
-
this.emit('addInt:before', ctx);
|
|
587
|
-
|
|
588
|
-
// 如果事件处理器标记为取消,则返回false
|
|
589
|
-
if (ctx.cancel) {
|
|
590
|
-
this.log('debug', `addInt操作被事件处理器取消 [${key}]`);
|
|
591
|
-
return false;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// 使用可能被修改的值
|
|
595
|
-
let {
|
|
596
|
-
key: finalKey,
|
|
597
|
-
value: finalNum,
|
|
598
|
-
seconds: finalSeconds
|
|
599
|
-
} = ctx;
|
|
600
|
-
|
|
601
|
-
const connection = await this._getConnection();
|
|
602
|
-
try {
|
|
603
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
604
|
-
let result;
|
|
605
|
-
if (finalNum > 0) {
|
|
606
|
-
// 增加整数值
|
|
607
|
-
if (typeof connection.incrBy === 'function') {
|
|
608
|
-
result = await connection.incrBy(prefixedKey, finalNum);
|
|
609
|
-
} else if (typeof connection.execute === 'function') {
|
|
610
|
-
result = await connection.execute('INCRBY', [prefixedKey, finalNum]);
|
|
611
|
-
} else {
|
|
612
|
-
// 如果出错,回退到先获取再设置的方式
|
|
613
|
-
this.log('warn', `使用原子操作增加整数值失败,使用回退方案 [${key}]`);
|
|
614
|
-
let value = await this.get(prefixedKey);
|
|
615
|
-
let n = 0;
|
|
616
|
-
if (value) {
|
|
617
|
-
n = Number(value);
|
|
618
|
-
}
|
|
619
|
-
n += finalNum;
|
|
620
|
-
await this.set(prefixedKey, n, finalSeconds);
|
|
621
|
-
result = n;
|
|
622
|
-
}
|
|
623
|
-
} else {
|
|
624
|
-
// 减少整数值
|
|
625
|
-
if (typeof connection.decrBy === 'function') {
|
|
626
|
-
result = await connection.decrBy(prefixedKey, -finalNum);
|
|
627
|
-
} else if (typeof connection.execute === 'function') {
|
|
628
|
-
result = await connection.execute('DECRBY', [prefixedKey, -finalNum]);
|
|
629
|
-
} else {
|
|
630
|
-
// 如果出错,回退到先获取再设置的方式
|
|
631
|
-
this.log('warn', `使用原子操作减少整数值失败,使用回退方案`);
|
|
632
|
-
let value = await this.get(prefixedKey);
|
|
633
|
-
let n = 0;
|
|
634
|
-
if (value) {
|
|
635
|
-
n = Number(value);
|
|
636
|
-
}
|
|
637
|
-
n += finalNum;
|
|
638
|
-
await this.set(prefixedKey, n, finalSeconds);
|
|
639
|
-
result = n;
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// 如果设置了过期时间
|
|
644
|
-
if (finalSeconds > 0) {
|
|
645
|
-
if (typeof connection.expire === 'function') {
|
|
646
|
-
await connection.expire(prefixedKey, finalSeconds);
|
|
647
|
-
} else if (typeof connection.execute === 'function') {
|
|
648
|
-
await connection.execute('EXPIRE', [prefixedKey, finalSeconds]);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// 触发后置事件
|
|
653
|
-
this.emit('addInt:after', {
|
|
654
|
-
key: finalKey,
|
|
655
|
-
value: finalNum,
|
|
656
|
-
seconds: finalSeconds,
|
|
657
|
-
result,
|
|
658
|
-
success: true
|
|
659
|
-
});
|
|
660
|
-
return result;
|
|
661
|
-
} finally {
|
|
662
|
-
this._recycleConnection(connection);
|
|
663
|
-
}
|
|
664
|
-
} catch (err) {
|
|
665
|
-
// 触发错误事件
|
|
666
|
-
this.emit('addInt:error', {
|
|
667
|
-
key,
|
|
668
|
-
value: num,
|
|
669
|
-
seconds,
|
|
670
|
-
error: err
|
|
671
|
-
});
|
|
672
|
-
this.log('error', `增加整数值失败 [${key}]:`, err);
|
|
673
|
-
// 与CacheBase保持一致,错误时返回默认值而不是抛出异常
|
|
674
|
-
return 0;
|
|
675
|
-
}
|
|
676
|
-
};
|
|
677
|
-
|
|
678
|
-
/**
|
|
679
|
-
* @description 增加浮点数值(负数为减)(兼容方法)
|
|
680
|
-
* @param {String} key 键
|
|
681
|
-
* @param {Number} num 数值
|
|
682
|
-
* @param {Number} seconds 过期时间,0表示永不过期
|
|
683
|
-
* @return {Promise<Number>} 计算后的结果
|
|
684
|
-
*/
|
|
685
|
-
Redis.prototype.addFloat = async function (key, num, seconds = 0) {
|
|
686
|
-
// 参数验证
|
|
687
|
-
if (!key || typeof key !== 'string') {
|
|
688
|
-
throw new TypeError('key必须是非空字符串');
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
// 验证num参数
|
|
692
|
-
num = parseFloat(num);
|
|
693
|
-
if (isNaN(num)) {
|
|
694
|
-
throw new TypeError('num必须是有效的数值');
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
try {
|
|
698
|
-
|
|
699
|
-
// 触发前置事件,允许修改或阻止操作
|
|
700
|
-
const ctx = {
|
|
701
|
-
key,
|
|
702
|
-
value: num,
|
|
703
|
-
seconds,
|
|
704
|
-
cancel: false
|
|
705
|
-
};
|
|
706
|
-
this.emit('addFloat:before', ctx);
|
|
707
|
-
|
|
708
|
-
// 如果事件处理器标记为取消,则返回默认值
|
|
709
|
-
if (ctx.cancel) {
|
|
710
|
-
this.log('debug', `addFloat操作被事件处理器取消 [${key}]`);
|
|
711
|
-
return 0;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
// 使用可能被修改的值
|
|
715
|
-
let {
|
|
716
|
-
key: finalKey,
|
|
717
|
-
value: finalNum,
|
|
718
|
-
seconds: finalSeconds
|
|
719
|
-
} = ctx;
|
|
720
|
-
let result;
|
|
721
|
-
|
|
722
|
-
const connection = await this._getConnection();
|
|
723
|
-
try {
|
|
724
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
725
|
-
|
|
726
|
-
// 优先使用原生方法
|
|
727
|
-
if (typeof connection.incrByFloat === 'function') {
|
|
728
|
-
result = parseFloat(await connection.incrByFloat(prefixedKey, finalNum));
|
|
729
|
-
}
|
|
730
|
-
// 尝试使用execute通用方法
|
|
731
|
-
else if (typeof connection.execute === 'function') {
|
|
732
|
-
result = parseFloat(await connection.execute('INCRBYFLOAT', [prefixedKey, finalNum]));
|
|
733
|
-
}
|
|
734
|
-
// 回退方案
|
|
735
|
-
else {
|
|
736
|
-
this.log('warn', `连接对象不支持incrByFloat操作,使用回退方案`);
|
|
737
|
-
let value = await this.get(prefixedKey);
|
|
738
|
-
let n = 0;
|
|
739
|
-
if (value) {
|
|
740
|
-
n = Number(value);
|
|
741
|
-
}
|
|
742
|
-
n += finalNum;
|
|
743
|
-
await this.set(prefixedKey, n, finalSeconds);
|
|
744
|
-
result = n;
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
// 设置过期时间
|
|
748
|
-
if (finalSeconds > 0) {
|
|
749
|
-
if (typeof connection.expire === 'function') {
|
|
750
|
-
await connection.expire(prefixedKey, finalSeconds);
|
|
751
|
-
} else if (typeof connection.execute === 'function') {
|
|
752
|
-
await connection.execute('EXPIRE', [prefixedKey, finalSeconds]);
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
// 触发后置事件
|
|
757
|
-
this.emit('addFloat:after', {
|
|
758
|
-
key: finalKey,
|
|
759
|
-
value: finalNum,
|
|
760
|
-
seconds: finalSeconds,
|
|
761
|
-
result,
|
|
762
|
-
success: true
|
|
763
|
-
});
|
|
764
|
-
return result;
|
|
765
|
-
} catch (err) {
|
|
766
|
-
// 如果出错,回退到先获取再设置的方式
|
|
767
|
-
this.log('warn', `使用原子操作增加浮点数值失败,使用回退方案:`, err);
|
|
768
|
-
let value = await this.get(prefixedKey);
|
|
769
|
-
let n = 0;
|
|
770
|
-
if (value) {
|
|
771
|
-
n = Number(value);
|
|
772
|
-
}
|
|
773
|
-
n += finalNum;
|
|
774
|
-
await this.set(prefixedKey, n, finalSeconds);
|
|
775
|
-
|
|
776
|
-
// 触发后置事件
|
|
777
|
-
this.emit('addFloat:after', {
|
|
778
|
-
key: finalKey,
|
|
779
|
-
value: finalNum,
|
|
780
|
-
seconds: finalSeconds,
|
|
781
|
-
result: n,
|
|
782
|
-
success: true
|
|
783
|
-
});
|
|
784
|
-
return n;
|
|
785
|
-
} finally {
|
|
786
|
-
this._recycleConnection(connection);
|
|
787
|
-
}
|
|
788
|
-
} catch (err) {
|
|
789
|
-
// 触发错误事件
|
|
790
|
-
this.emit('addFloat:error', {
|
|
791
|
-
key,
|
|
792
|
-
value: num,
|
|
793
|
-
seconds,
|
|
794
|
-
error: err
|
|
795
|
-
});
|
|
796
|
-
this.log('error', `增加浮点数值失败 [${key}]:`, err);
|
|
797
|
-
// 与CacheBase保持一致,错误时返回默认值而不是抛出异常
|
|
798
|
-
return 0;
|
|
799
|
-
}
|
|
800
|
-
};
|
|
801
|
-
|
|
802
|
-
/**
|
|
803
|
-
* @description 增加字符串值到指定缓存(兼容方法)
|
|
804
|
-
* @param {String} key 键
|
|
805
|
-
* @param {String} str 添加的字符串
|
|
806
|
-
* @param {Number} seconds 过期时间,0表示永不过期
|
|
807
|
-
* @return {Promise<Number>} 添加后的字符串长度
|
|
808
|
-
*/
|
|
809
|
-
Redis.prototype.addStr = async function (key, str, seconds = 0) {
|
|
810
|
-
try {
|
|
811
|
-
// 参数验证
|
|
812
|
-
if (!key || typeof key !== 'string') {
|
|
813
|
-
throw new TypeError('key必须是非空字符串');
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
// 验证str参数
|
|
817
|
-
if (str === undefined || str === null) {
|
|
818
|
-
throw new TypeError('str不能为undefined或null');
|
|
819
|
-
}
|
|
820
|
-
str = String(str);
|
|
821
|
-
|
|
822
|
-
// 触发前置事件,允许修改或阻止操作
|
|
823
|
-
const ctx = {
|
|
824
|
-
key,
|
|
825
|
-
value: str,
|
|
826
|
-
seconds,
|
|
827
|
-
cancel: false
|
|
828
|
-
};
|
|
829
|
-
this.emit('addStr:before', ctx);
|
|
830
|
-
|
|
831
|
-
// 如果事件处理器标记为取消,则返回0
|
|
832
|
-
if (ctx.cancel) {
|
|
833
|
-
this.log('debug', `addStr操作被事件处理器取消 [${key}]`);
|
|
834
|
-
return 0;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
// 使用可能被修改的值
|
|
838
|
-
let {
|
|
839
|
-
key: finalKey,
|
|
840
|
-
value: finalStr,
|
|
841
|
-
seconds: finalSeconds
|
|
842
|
-
} = ctx;
|
|
843
|
-
let result;
|
|
844
|
-
|
|
845
|
-
const connection = await this._getConnection();
|
|
846
|
-
try {
|
|
847
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
848
|
-
|
|
849
|
-
// 优先使用原生方法
|
|
850
|
-
if (typeof connection.append === 'function') {
|
|
851
|
-
result = await connection.append(prefixedKey, finalStr);
|
|
852
|
-
}
|
|
853
|
-
// 尝试使用execute通用方法
|
|
854
|
-
else if (typeof connection.execute === 'function') {
|
|
855
|
-
result = await connection.execute('APPEND', [prefixedKey, finalStr]);
|
|
856
|
-
}
|
|
857
|
-
// 回退方案
|
|
858
|
-
else {
|
|
859
|
-
this.log('warn', `连接对象不支持append操作,使用回退方案`);
|
|
860
|
-
let value = await this.get(prefixedKey) || '';
|
|
861
|
-
value += finalStr;
|
|
862
|
-
await this.set(prefixedKey, value, finalSeconds);
|
|
863
|
-
result = value.length;
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
// 设置过期时间
|
|
867
|
-
if (finalSeconds > 0) {
|
|
868
|
-
if (typeof connection.expire === 'function') {
|
|
869
|
-
await connection.expire(prefixedKey, finalSeconds);
|
|
870
|
-
} else if (typeof connection.execute === 'function') {
|
|
871
|
-
await connection.execute('EXPIRE', [prefixedKey, finalSeconds]);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
// 触发后置事件
|
|
876
|
-
this.emit('addStr:after', {
|
|
877
|
-
key: finalKey,
|
|
878
|
-
value: finalStr,
|
|
879
|
-
seconds: finalSeconds,
|
|
880
|
-
result,
|
|
881
|
-
success: true
|
|
882
|
-
});
|
|
883
|
-
return result;
|
|
884
|
-
} catch (err) {
|
|
885
|
-
this.log('warn', '追加字符串失败:', err);
|
|
886
|
-
// 回退方案:先获取再设置
|
|
887
|
-
let value = await this.get(prefixedKey) || '';
|
|
888
|
-
value += finalStr;
|
|
889
|
-
await this.set(prefixedKey, value, finalSeconds);
|
|
890
|
-
|
|
891
|
-
// 触发后置事件
|
|
892
|
-
this.emit('addStr:after', {
|
|
893
|
-
key: finalKey,
|
|
894
|
-
value: finalStr,
|
|
895
|
-
seconds: finalSeconds,
|
|
896
|
-
result: value.length,
|
|
897
|
-
success: true
|
|
898
|
-
});
|
|
899
|
-
return value.length;
|
|
900
|
-
} finally {
|
|
901
|
-
this._recycleConnection(connection);
|
|
902
|
-
}
|
|
903
|
-
} catch (err) {
|
|
904
|
-
// 触发错误事件
|
|
905
|
-
this.emit('addStr:error', {
|
|
906
|
-
key,
|
|
907
|
-
value: str,
|
|
908
|
-
seconds,
|
|
909
|
-
error: err
|
|
910
|
-
});
|
|
911
|
-
this.log('error', `追加字符串失败 [${key}]:`, err);
|
|
912
|
-
return 0;
|
|
913
|
-
}
|
|
914
|
-
};
|
|
915
|
-
|
|
916
|
-
/**
|
|
917
|
-
* @description 删除缓存(兼容方法)
|
|
918
|
-
* @param {String|Array<String>} key 键或键数组
|
|
919
|
-
* @return {Promise<Boolean>} 成功返回true,失败返回false
|
|
920
|
-
*/
|
|
921
|
-
Redis.prototype.del = async function (key) {
|
|
922
|
-
try {
|
|
923
|
-
// 键名必须是非空字符串或非空字符串数组
|
|
924
|
-
if (!key) {
|
|
925
|
-
throw new TypeError('key必须是非空字符串或非空字符串数组');
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
// 如果是单个键,验证是否为非空字符串
|
|
929
|
-
if (typeof key === 'string') {
|
|
930
|
-
if (!key) {
|
|
931
|
-
throw new TypeError('key必须是非空字符串');
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
// 如果是数组,验证每个元素是否为非空字符串
|
|
935
|
-
else if (Array.isArray(key)) {
|
|
936
|
-
for (const k of key) {
|
|
937
|
-
if (!k || typeof k !== 'string') {
|
|
938
|
-
throw new TypeError('key必须是非空字符串或非空字符串数组');
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
} else {
|
|
942
|
-
throw new TypeError('key必须是非空字符串或非空字符串数组');
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// 触发前置事件,允许修改或阻止操作
|
|
946
|
-
const ctx = {
|
|
947
|
-
key,
|
|
948
|
-
cancel: false
|
|
949
|
-
};
|
|
950
|
-
this.emit('del:before', ctx);
|
|
951
|
-
|
|
952
|
-
// 如果事件处理器标记为取消,则返回false
|
|
953
|
-
if (ctx.cancel) {
|
|
954
|
-
this.log('debug', `del操作被事件处理器取消 [${key}]`);
|
|
955
|
-
return false;
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
// 使用可能被修改的值
|
|
959
|
-
let {
|
|
960
|
-
key: finalKey
|
|
961
|
-
} = ctx;
|
|
962
|
-
|
|
963
|
-
const connection = await this._getConnection();
|
|
964
|
-
try {
|
|
965
|
-
let success;
|
|
966
|
-
if (Array.isArray(finalKey)) {
|
|
967
|
-
// 批量删除 - 分别删除每个键以确保正确处理
|
|
968
|
-
success = true;
|
|
969
|
-
const deleted = [];
|
|
970
|
-
for (const k of finalKey) {
|
|
971
|
-
const prefixedKey = this._addPrefix(k);
|
|
972
|
-
const result = await connection.del(prefixedKey);
|
|
973
|
-
if (result !== 1) {
|
|
974
|
-
success = false;
|
|
975
|
-
} else {
|
|
976
|
-
deleted.push(k);
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
// 触发后置事件
|
|
980
|
-
this.emit('del:after', {
|
|
981
|
-
key: finalKey,
|
|
982
|
-
success,
|
|
983
|
-
deleted
|
|
984
|
-
});
|
|
985
|
-
} else {
|
|
986
|
-
// 单个删除
|
|
987
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
988
|
-
const n = await connection.del(prefixedKey);
|
|
989
|
-
success = n === 1;
|
|
990
|
-
// 触发后置事件
|
|
991
|
-
this.emit('del:after', {
|
|
992
|
-
key: finalKey,
|
|
993
|
-
success,
|
|
994
|
-
deleted: success ? [finalKey] : []
|
|
995
|
-
});
|
|
996
|
-
}
|
|
997
|
-
return success;
|
|
998
|
-
} finally {
|
|
999
|
-
this._recycleConnection(connection);
|
|
1000
|
-
}
|
|
1001
|
-
} catch (err) {
|
|
1002
|
-
// 触发错误事件
|
|
1003
|
-
this.emit('del:error', {
|
|
1004
|
-
key,
|
|
1005
|
-
error: err
|
|
1006
|
-
});
|
|
1007
|
-
this.log('error', `删除缓存失败 [${key}]:`, err);
|
|
1008
|
-
return false;
|
|
1009
|
-
}
|
|
1010
|
-
};
|
|
1011
|
-
|
|
1012
|
-
/**
|
|
1013
|
-
* @description 修改缓存
|
|
1014
|
-
* @param {String} key 键
|
|
1015
|
-
* @param {Object} value 值
|
|
1016
|
-
* @param {Number} seconds 秒
|
|
1017
|
-
* @return {Promise<Boolean>} 操作结果
|
|
1018
|
-
*/
|
|
1019
|
-
/**
|
|
1020
|
-
* @description 设置缓存(兼容方法)
|
|
1021
|
-
* @param {String} key 键
|
|
1022
|
-
* @param {*} value 值
|
|
1023
|
-
* @param {Number} seconds 过期时间,0表示永不过期
|
|
1024
|
-
* @return {Promise<Boolean>} 成功返回true,失败返回false
|
|
1025
|
-
*/
|
|
1026
|
-
Redis.prototype.set = async function (key, value, seconds = 0) {
|
|
1027
|
-
try {
|
|
1028
|
-
// 键名必须是非空字符串
|
|
1029
|
-
if (!key || typeof key !== 'string') {
|
|
1030
|
-
throw new TypeError('key必须是非空字符串');
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1034
|
-
const ctx = {
|
|
1035
|
-
key,
|
|
1036
|
-
value,
|
|
1037
|
-
seconds,
|
|
1038
|
-
cancel: false
|
|
1039
|
-
};
|
|
1040
|
-
this.emit('set:before', ctx);
|
|
1041
|
-
|
|
1042
|
-
// 如果事件处理器标记为取消,则返回false
|
|
1043
|
-
if (ctx.cancel) {
|
|
1044
|
-
this.log('debug', `set操作被事件处理器取消 [${key}]`);
|
|
1045
|
-
return false;
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
// 使用可能被修改的值
|
|
1049
|
-
let {
|
|
1050
|
-
key: finalKey,
|
|
1051
|
-
value: finalValue,
|
|
1052
|
-
seconds: finalSeconds
|
|
1053
|
-
} = ctx;
|
|
1054
|
-
|
|
1055
|
-
const connection = await this._getConnection();
|
|
1056
|
-
try {
|
|
1057
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
1058
|
-
if (typeof (finalValue) === "object") {
|
|
1059
|
-
finalValue = JSON.stringify(finalValue);
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
if (finalSeconds > 0) {
|
|
1063
|
-
await connection.set(prefixedKey, finalValue, {
|
|
1064
|
-
EX: finalSeconds
|
|
1065
|
-
});
|
|
1066
|
-
} else {
|
|
1067
|
-
await connection.set(prefixedKey, finalValue);
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
// 触发后置事件
|
|
1071
|
-
this.emit('set:after', {
|
|
1072
|
-
key,
|
|
1073
|
-
value,
|
|
1074
|
-
seconds,
|
|
1075
|
-
success: true
|
|
1076
|
-
});
|
|
1077
|
-
return true;
|
|
1078
|
-
} finally {
|
|
1079
|
-
this._recycleConnection(connection);
|
|
1080
|
-
}
|
|
1081
|
-
} catch (err) {
|
|
1082
|
-
this.log('error', `设置缓存失败 [${key}]:`, err);
|
|
1083
|
-
// 触发错误事件
|
|
1084
|
-
this.emit('set:error', {
|
|
1085
|
-
key,
|
|
1086
|
-
value,
|
|
1087
|
-
seconds,
|
|
1088
|
-
error: err
|
|
1089
|
-
});
|
|
1090
|
-
return false;
|
|
1091
|
-
}
|
|
1092
|
-
};
|
|
1093
|
-
|
|
1094
|
-
/**
|
|
1095
|
-
* @description 仅当键不存在时设置缓存
|
|
1096
|
-
* @param {String} key 键
|
|
1097
|
-
* @param {Object} value 值
|
|
1098
|
-
* @param {Number} seconds 过期时间(秒)
|
|
1099
|
-
* @return {Promise<Boolean>} 操作结果,键不存在时设置成功返回true,键已存在返回false
|
|
1100
|
-
*/
|
|
1101
|
-
Redis.prototype.add = async function (key, value, seconds) {
|
|
1102
|
-
try {
|
|
1103
|
-
// 参数验证
|
|
1104
|
-
if (!key || typeof key !== 'string') {
|
|
1105
|
-
throw new TypeError('key必须是非空字符串');
|
|
1106
|
-
}
|
|
1107
|
-
if (value === undefined || value === null) {
|
|
1108
|
-
throw new TypeError('value不能为undefined或null');
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1112
|
-
const ctx = {
|
|
1113
|
-
key,
|
|
1114
|
-
value,
|
|
1115
|
-
ttl: seconds,
|
|
1116
|
-
cancel: false
|
|
1117
|
-
};
|
|
1118
|
-
this.emit('add:before', ctx);
|
|
1119
|
-
|
|
1120
|
-
// 如果事件处理器标记为取消,则返回false
|
|
1121
|
-
if (ctx.cancel) {
|
|
1122
|
-
this.log('debug', `add操作被事件处理器取消 [${key}]`);
|
|
1123
|
-
return false;
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
// 使用可能被修改的值
|
|
1127
|
-
let {
|
|
1128
|
-
key: finalKey,
|
|
1129
|
-
value: finalValue,
|
|
1130
|
-
ttl: finalSeconds
|
|
1131
|
-
} = ctx;
|
|
1132
|
-
|
|
1133
|
-
const connection = await this._getConnection();
|
|
1134
|
-
try {
|
|
1135
|
-
finalKey = this._addPrefix(finalKey);
|
|
1136
|
-
if (typeof (finalValue) === "object") {
|
|
1137
|
-
finalValue = JSON.stringify(finalValue);
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
// 使用NX选项实现SETNX操作 - 仅在键不存在时设置
|
|
1141
|
-
const options = finalSeconds ? {
|
|
1142
|
-
NX: true,
|
|
1143
|
-
EX: finalSeconds
|
|
1144
|
-
} : {
|
|
1145
|
-
NX: true
|
|
1146
|
-
};
|
|
1147
|
-
const result = await connection.set(finalKey, finalValue, options);
|
|
1148
|
-
const success = result === "OK";
|
|
1149
|
-
|
|
1150
|
-
// 触发后置事件
|
|
1151
|
-
this.emit('add:after', {
|
|
1152
|
-
key: finalKey,
|
|
1153
|
-
value: finalValue,
|
|
1154
|
-
ttl: finalSeconds,
|
|
1155
|
-
success
|
|
1156
|
-
});
|
|
1157
|
-
return success;
|
|
1158
|
-
} finally {
|
|
1159
|
-
this._recycleConnection(connection);
|
|
1160
|
-
}
|
|
1161
|
-
} catch (err) {
|
|
1162
|
-
this.log('error', `添加缓存失败 [${key}]:`, err);
|
|
1163
|
-
// 触发错误事件
|
|
1164
|
-
this.emit('add:error', {
|
|
1165
|
-
key,
|
|
1166
|
-
value,
|
|
1167
|
-
ttl: seconds,
|
|
1168
|
-
error: err
|
|
1169
|
-
});
|
|
1170
|
-
return false;
|
|
1171
|
-
}
|
|
1172
|
-
};
|
|
1173
|
-
|
|
1174
|
-
/**
|
|
1175
|
-
* @description 查询缓存
|
|
1176
|
-
* @param {String} key 键
|
|
1177
|
-
* @return {Promise<Object>} 查询值
|
|
1178
|
-
*/
|
|
1179
|
-
/**
|
|
1180
|
-
* @description 获取缓存(兼容方法)
|
|
1181
|
-
* @param {String} key 键
|
|
1182
|
-
* @return {Promise<*>} 获取到的值,不存在时返回null
|
|
1183
|
-
*/
|
|
1184
|
-
Redis.prototype.get = async function (key) {
|
|
1185
|
-
try {
|
|
1186
|
-
// 参数验证
|
|
1187
|
-
if (!key || typeof key !== 'string') {
|
|
1188
|
-
throw new TypeError('key必须是非空字符串');
|
|
1189
|
-
}
|
|
1190
|
-
|
|
1191
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1192
|
-
const ctx = {
|
|
1193
|
-
key,
|
|
1194
|
-
cancel: false
|
|
1195
|
-
};
|
|
1196
|
-
this.emit('get:before', ctx);
|
|
1197
|
-
|
|
1198
|
-
// 如果事件处理器标记为取消,则返回null
|
|
1199
|
-
if (ctx.cancel) {
|
|
1200
|
-
this.log('debug', `get操作被事件处理器取消 [${key}]`);
|
|
1201
|
-
return null;
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
// 使用可能被修改的值
|
|
1205
|
-
const finalKey = ctx.key;
|
|
1206
|
-
|
|
1207
|
-
const connection = await this._getConnection();
|
|
1208
|
-
try {
|
|
1209
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
1210
|
-
const value = await connection.get(prefixedKey);
|
|
1211
|
-
let result = value;
|
|
1212
|
-
if (typeof (value) === "string") {
|
|
1213
|
-
// 尝试解析JSON
|
|
1214
|
-
try {
|
|
1215
|
-
if (value.startsWith("{") && value.endsWith("}") ||
|
|
1216
|
-
value.startsWith("[") && value.endsWith("]")) {
|
|
1217
|
-
result = JSON.parse(value);
|
|
1218
|
-
}
|
|
1219
|
-
} catch (e) {
|
|
1220
|
-
// 解析失败,返回原始字符串
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
// 触发后置事件
|
|
1225
|
-
this.emit('get:after', {
|
|
1226
|
-
key: finalKey,
|
|
1227
|
-
value: result,
|
|
1228
|
-
success: true
|
|
1229
|
-
});
|
|
1230
|
-
|
|
1231
|
-
return result;
|
|
1232
|
-
} finally {
|
|
1233
|
-
this._recycleConnection(connection);
|
|
1234
|
-
}
|
|
1235
|
-
} catch (err) {
|
|
1236
|
-
// 触发错误事件
|
|
1237
|
-
this.emit('get:error', {
|
|
1238
|
-
key,
|
|
1239
|
-
error: err
|
|
1240
|
-
});
|
|
1241
|
-
this.log('error', `获取缓存失败 [${key}]:`, err);
|
|
1242
|
-
return null;
|
|
1243
|
-
}
|
|
1244
|
-
};
|
|
1245
|
-
|
|
1246
|
-
/**
|
|
1247
|
-
* @description 判断键是否存在(兼容方法)
|
|
1248
|
-
* @param {String} key 键
|
|
1249
|
-
* @return {Promise<Boolean>} 有返回true, 没有返回false
|
|
1250
|
-
*/
|
|
1251
|
-
Redis.prototype.has = async function (key) {
|
|
1252
|
-
try {
|
|
1253
|
-
// 参数验证
|
|
1254
|
-
if (!key || typeof key !== 'string') {
|
|
1255
|
-
throw new TypeError('key必须是非空字符串');
|
|
1256
|
-
}
|
|
1257
|
-
|
|
1258
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1259
|
-
const ctx = {
|
|
1260
|
-
key,
|
|
1261
|
-
cancel: false
|
|
1262
|
-
};
|
|
1263
|
-
this.emit('has:before', ctx);
|
|
1264
|
-
|
|
1265
|
-
// 如果事件处理器标记为取消,则返回false
|
|
1266
|
-
if (ctx.cancel) {
|
|
1267
|
-
this.log('debug', `has操作被事件处理器取消 [${key}]`);
|
|
1268
|
-
return false;
|
|
1269
|
-
}
|
|
1270
|
-
|
|
1271
|
-
// 使用可能被修改的值
|
|
1272
|
-
const finalKey = ctx.key;
|
|
1273
|
-
|
|
1274
|
-
const connection = await this._getConnection();
|
|
1275
|
-
try {
|
|
1276
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
1277
|
-
const result = await connection.exists(prefixedKey);
|
|
1278
|
-
const exists = result > 0;
|
|
1279
|
-
|
|
1280
|
-
// 触发后置事件
|
|
1281
|
-
this.emit('has:after', {
|
|
1282
|
-
key: finalKey,
|
|
1283
|
-
result: exists,
|
|
1284
|
-
success: true
|
|
1285
|
-
});
|
|
1286
|
-
|
|
1287
|
-
return exists;
|
|
1288
|
-
} finally {
|
|
1289
|
-
this._recycleConnection(connection);
|
|
1290
|
-
}
|
|
1291
|
-
} catch (err) {
|
|
1292
|
-
// 触发错误事件
|
|
1293
|
-
this.emit('has:error', {
|
|
1294
|
-
key,
|
|
1295
|
-
error: err
|
|
1296
|
-
});
|
|
1297
|
-
this.log('error', `检查键是否存在失败 [${key}]:`, err);
|
|
1298
|
-
return false;
|
|
1299
|
-
}
|
|
1300
|
-
};
|
|
1301
|
-
|
|
1302
|
-
/**
|
|
1303
|
-
* @description 检查键是否存在,返回存在的键数量(兼容方法)
|
|
1304
|
-
* @param {String|Array<String>} key 键或键数组
|
|
1305
|
-
* @return {Promise<Number>} 存在的键数量
|
|
1306
|
-
*/
|
|
1307
|
-
Redis.prototype.exists = async function (key) {
|
|
1308
|
-
try {
|
|
1309
|
-
// 参数验证
|
|
1310
|
-
if (!key) {
|
|
1311
|
-
throw new TypeError('key必须是非空字符串或非空字符串数组');
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
// 如果是单个键,验证是否为非空字符串
|
|
1315
|
-
if (typeof key === 'string') {
|
|
1316
|
-
if (!key) {
|
|
1317
|
-
throw new TypeError('key必须是非空字符串');
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
// 如果是数组,验证每个元素是否为非空字符串
|
|
1321
|
-
else if (Array.isArray(key)) {
|
|
1322
|
-
for (const k of key) {
|
|
1323
|
-
if (!k || typeof k !== 'string') {
|
|
1324
|
-
throw new TypeError('key必须是非空字符串或非空字符串数组');
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
|
-
} else {
|
|
1328
|
-
throw new TypeError('key必须是非空字符串或非空字符串数组');
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1332
|
-
const ctx = {
|
|
1333
|
-
key,
|
|
1334
|
-
cancel: false
|
|
1335
|
-
};
|
|
1336
|
-
this.emit('exists:before', ctx);
|
|
1337
|
-
|
|
1338
|
-
// 如果事件处理器标记为取消,则返回0
|
|
1339
|
-
if (ctx.cancel) {
|
|
1340
|
-
this.log('debug', `exists操作被事件处理器取消 [${key}]`);
|
|
1341
|
-
return 0;
|
|
1342
|
-
}
|
|
1343
|
-
|
|
1344
|
-
// 使用可能被修改的值
|
|
1345
|
-
const finalKey = ctx.key;
|
|
1346
|
-
|
|
1347
|
-
const connection = await this._getConnection();
|
|
1348
|
-
try {
|
|
1349
|
-
let result;
|
|
1350
|
-
if (typeof finalKey === 'string') {
|
|
1351
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
1352
|
-
result = await connection.exists(prefixedKey);
|
|
1353
|
-
} else if (Array.isArray(finalKey)) {
|
|
1354
|
-
const prefixedKeys = finalKey.map(k => this._addPrefix(k));
|
|
1355
|
-
result = await connection.exists(...prefixedKeys);
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
// 触发后置事件
|
|
1359
|
-
this.emit('exists:after', {
|
|
1360
|
-
key: finalKey,
|
|
1361
|
-
result,
|
|
1362
|
-
success: true
|
|
1363
|
-
});
|
|
1364
|
-
|
|
1365
|
-
return result;
|
|
1366
|
-
} finally {
|
|
1367
|
-
this._recycleConnection(connection);
|
|
1368
|
-
}
|
|
1369
|
-
} catch (err) {
|
|
1370
|
-
// 触发错误事件
|
|
1371
|
-
this.emit('exists:error', {
|
|
1372
|
-
key,
|
|
1373
|
-
error: err
|
|
1374
|
-
});
|
|
1375
|
-
this.log('error', `检查键存在性失败 [${key}]:`, err);
|
|
1376
|
-
return 0;
|
|
1377
|
-
}
|
|
1378
|
-
};
|
|
1379
|
-
|
|
1380
|
-
/**
|
|
1381
|
-
* @description 查询缓存的字符串中的一段字符串
|
|
1382
|
-
* @param {String} key 键
|
|
1383
|
-
* @param {Number} start 开始位置
|
|
1384
|
-
* @param {Number} end 结束位置
|
|
1385
|
-
* @return {Promise<String>} 查询值
|
|
1386
|
-
*/
|
|
1387
|
-
Redis.prototype.getrange = async function (key, start, end) {
|
|
1388
|
-
try {
|
|
1389
|
-
const connection = await this._getConnection();
|
|
1390
|
-
try {
|
|
1391
|
-
key = this._addPrefix(key);
|
|
1392
|
-
return await connection.getRange(key, start, end);
|
|
1393
|
-
} catch (err) {
|
|
1394
|
-
this.log('error', '获取字符串范围失败:', err);
|
|
1395
|
-
// 回退方案
|
|
1396
|
-
const value = await this.get(key);
|
|
1397
|
-
if (value && typeof value === 'string') {
|
|
1398
|
-
return value.substring(start, end + 1);
|
|
1399
|
-
}
|
|
1400
|
-
return '';
|
|
1401
|
-
} finally {
|
|
1402
|
-
this._recycleConnection(connection);
|
|
1403
|
-
}
|
|
1404
|
-
} catch (err) {
|
|
1405
|
-
this.log('error', `获取字符串范围失败 [${key}]:`, err);
|
|
1406
|
-
return '';
|
|
1407
|
-
}
|
|
1408
|
-
};
|
|
1409
|
-
|
|
1410
|
-
/**
|
|
1411
|
-
* 在指定位置插入文本
|
|
1412
|
-
* @param {String} str 原字符串
|
|
1413
|
-
* @param {Number} index 索引
|
|
1414
|
-
* @param {String} str_sub 插入的字符串
|
|
1415
|
-
* @return {String} 插入后的字符串
|
|
1416
|
-
*/
|
|
1417
|
-
Redis.prototype.insertString = function (str, index, str_sub) {
|
|
1418
|
-
return str.substring(0, index) + str_sub + str.substring(index);
|
|
1419
|
-
};
|
|
1420
|
-
|
|
1421
|
-
/**
|
|
1422
|
-
* @description 在值的指定位置开始增加一段字符串
|
|
1423
|
-
* @param {String} key 键
|
|
1424
|
-
* @param {Number} index 开始位置
|
|
1425
|
-
* @param {String} value 变更的值
|
|
1426
|
-
* @return {Promise<Number>} 字符串长度
|
|
1427
|
-
*/
|
|
1428
|
-
Redis.prototype.setrange = async function (key, index, value) {
|
|
1429
|
-
try {
|
|
1430
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1431
|
-
const ctx = {
|
|
1432
|
-
key,
|
|
1433
|
-
index,
|
|
1434
|
-
value,
|
|
1435
|
-
cancel: false
|
|
1436
|
-
};
|
|
1437
|
-
this.emit('setrange:before', ctx);
|
|
1438
|
-
|
|
1439
|
-
// 如果事件处理器标记为取消,则返回0
|
|
1440
|
-
if (ctx.cancel) {
|
|
1441
|
-
this.log('debug', `setrange操作被事件处理器取消 [${key}]`);
|
|
1442
|
-
return 0;
|
|
1443
|
-
}
|
|
1444
|
-
|
|
1445
|
-
// 使用可能被修改的值
|
|
1446
|
-
let {
|
|
1447
|
-
key: finalKey,
|
|
1448
|
-
index: finalIndex,
|
|
1449
|
-
value: finalValue
|
|
1450
|
-
} = ctx;
|
|
1451
|
-
let result;
|
|
1452
|
-
|
|
1453
|
-
const connection = await this._getConnection();
|
|
1454
|
-
try {
|
|
1455
|
-
finalKey = this._addPrefix(finalKey);
|
|
1456
|
-
|
|
1457
|
-
// 优先使用原生方法
|
|
1458
|
-
if (typeof connection.setRange === 'function') {
|
|
1459
|
-
result = await connection.setRange(finalKey, finalIndex, finalValue);
|
|
1460
|
-
}
|
|
1461
|
-
// 尝试使用execute通用方法
|
|
1462
|
-
else if (typeof connection.execute === 'function') {
|
|
1463
|
-
result = await connection.execute('SETRANGE', [finalKey, finalIndex, finalValue]);
|
|
1464
|
-
}
|
|
1465
|
-
// 回退方案
|
|
1466
|
-
else {
|
|
1467
|
-
this.log('warn', `连接对象不支持setRange操作,使用回退方案`);
|
|
1468
|
-
let str = await this.get(finalKey) || '';
|
|
1469
|
-
if (str.length < finalIndex) {
|
|
1470
|
-
// 填充空格到指定索引
|
|
1471
|
-
str = str.padEnd(finalIndex, ' ');
|
|
1472
|
-
}
|
|
1473
|
-
str = this.insertString(str, finalIndex, finalValue);
|
|
1474
|
-
await this.set(finalKey, str);
|
|
1475
|
-
result = str.length;
|
|
1476
|
-
}
|
|
1477
|
-
|
|
1478
|
-
// 触发后置事件
|
|
1479
|
-
this.emit('setrange:after', {
|
|
1480
|
-
key: finalKey,
|
|
1481
|
-
index: finalIndex,
|
|
1482
|
-
value: finalValue,
|
|
1483
|
-
result,
|
|
1484
|
-
success: true
|
|
1485
|
-
});
|
|
1486
|
-
return result;
|
|
1487
|
-
} catch (err) {
|
|
1488
|
-
this.log('error', '设置字符串范围失败:', err);
|
|
1489
|
-
// 回退方案
|
|
1490
|
-
let str = await this.get(finalKey) || '';
|
|
1491
|
-
if (str.length < finalIndex) {
|
|
1492
|
-
// 填充空格到指定索引
|
|
1493
|
-
str = str.padEnd(finalIndex, ' ');
|
|
1494
|
-
}
|
|
1495
|
-
str = this.insertString(str, finalIndex, finalValue);
|
|
1496
|
-
await this.set(finalKey, str);
|
|
1497
|
-
|
|
1498
|
-
// 触发后置事件
|
|
1499
|
-
this.emit('setrange:after', {
|
|
1500
|
-
key: finalKey,
|
|
1501
|
-
index: finalIndex,
|
|
1502
|
-
value: finalValue,
|
|
1503
|
-
result: str.length,
|
|
1504
|
-
success: true
|
|
1505
|
-
});
|
|
1506
|
-
return str.length;
|
|
1507
|
-
} finally {
|
|
1508
|
-
this._recycleConnection(connection);
|
|
1509
|
-
}
|
|
1510
|
-
} catch (err) {
|
|
1511
|
-
// 触发错误事件
|
|
1512
|
-
this.emit('setrange:error', {
|
|
1513
|
-
key,
|
|
1514
|
-
index,
|
|
1515
|
-
value,
|
|
1516
|
-
error: err
|
|
1517
|
-
});
|
|
1518
|
-
this.log('error', `设置字符串范围失败 [${key}]:`, err);
|
|
1519
|
-
return 0;
|
|
1520
|
-
}
|
|
1521
|
-
};
|
|
1522
|
-
|
|
1523
|
-
/**
|
|
1524
|
-
* @description 清空缓存
|
|
1525
|
-
* @param {String} key 键, 为空则清空所有
|
|
1526
|
-
* @return {Promise<Boolean>} 执行结果
|
|
1527
|
-
*/
|
|
1528
|
-
Redis.prototype.clear = async function (key) {
|
|
1529
|
-
try {
|
|
1530
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1531
|
-
const ctx = {
|
|
1532
|
-
key,
|
|
1533
|
-
cancel: false
|
|
1534
|
-
};
|
|
1535
|
-
this.emit('clear:before', ctx);
|
|
1536
|
-
|
|
1537
|
-
// 如果事件处理器标记为取消,则返回false
|
|
1538
|
-
if (ctx.cancel) {
|
|
1539
|
-
this.log('debug', `clear操作被事件处理器取消`);
|
|
1540
|
-
return false;
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
// 使用可能被修改的值
|
|
1544
|
-
let {
|
|
1545
|
-
key: finalKey
|
|
1546
|
-
} = ctx;
|
|
1547
|
-
let success = false;
|
|
1548
|
-
|
|
1549
|
-
const connection = await this._getConnection();
|
|
1550
|
-
try {
|
|
1551
|
-
if (finalKey) {
|
|
1552
|
-
// 清空指定键
|
|
1553
|
-
success = await this.del(finalKey);
|
|
1554
|
-
} else {
|
|
1555
|
-
// 清空当前数据库
|
|
1556
|
-
if (typeof connection.flushDb === 'function') {
|
|
1557
|
-
await connection.flushDb();
|
|
1558
|
-
success = true;
|
|
1559
|
-
} else if (typeof connection.execute === 'function') {
|
|
1560
|
-
await connection.execute('FLUSHDB');
|
|
1561
|
-
success = true;
|
|
1562
|
-
} else {
|
|
1563
|
-
this.log('error', '连接对象不支持flushDb操作');
|
|
1564
|
-
success = false;
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
|
-
|
|
1568
|
-
// 触发后置事件
|
|
1569
|
-
this.emit('clear:after', {
|
|
1570
|
-
key: finalKey,
|
|
1571
|
-
success
|
|
1572
|
-
});
|
|
1573
|
-
return success;
|
|
1574
|
-
} finally {
|
|
1575
|
-
this._recycleConnection(connection);
|
|
1576
|
-
}
|
|
1577
|
-
} catch (err) {
|
|
1578
|
-
// 触发错误事件
|
|
1579
|
-
this.emit('clear:error', {
|
|
1580
|
-
key,
|
|
1581
|
-
error: err
|
|
1582
|
-
});
|
|
1583
|
-
this.log('error', '清空缓存失败:', err);
|
|
1584
|
-
return false;
|
|
1585
|
-
}
|
|
1586
|
-
};
|
|
1587
|
-
|
|
1588
|
-
/**
|
|
1589
|
-
* @description 排序
|
|
1590
|
-
* @param {String} key 键
|
|
1591
|
-
* @param {Object} options 排序选项
|
|
1592
|
-
* @return {Promise<Array>} 排序后的数组
|
|
1593
|
-
*/
|
|
1594
|
-
Redis.prototype.sort = async function (key, options = {}) {
|
|
1595
|
-
try {
|
|
1596
|
-
const connection = await this._getConnection();
|
|
1597
|
-
try {
|
|
1598
|
-
key = this._addPrefix(key);
|
|
1599
|
-
// 默认升序
|
|
1600
|
-
let sortOptions = [];
|
|
1601
|
-
if (options.by) {
|
|
1602
|
-
sortOptions.push('BY', options.by);
|
|
1603
|
-
}
|
|
1604
|
-
if (options.limit) {
|
|
1605
|
-
sortOptions.push('LIMIT', options.limit.offset || 0, options.limit.count || -1);
|
|
1606
|
-
}
|
|
1607
|
-
if (options.order === 'desc') {
|
|
1608
|
-
sortOptions.push('DESC');
|
|
1609
|
-
} else {
|
|
1610
|
-
sortOptions.push('ASC');
|
|
1611
|
-
}
|
|
1612
|
-
if (options.alpha) {
|
|
1613
|
-
sortOptions.push('ALPHA');
|
|
1614
|
-
}
|
|
1615
|
-
if (options.get) {
|
|
1616
|
-
if (Array.isArray(options.get)) {
|
|
1617
|
-
options.get.forEach(getKey => {
|
|
1618
|
-
sortOptions.push('GET', getKey);
|
|
1619
|
-
});
|
|
1620
|
-
} else {
|
|
1621
|
-
sortOptions.push('GET', options.get);
|
|
1622
|
-
}
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
return await connection.sort(key, sortOptions);
|
|
1626
|
-
} catch (err) {
|
|
1627
|
-
this.log('error', '排序失败:', err);
|
|
1628
|
-
// 回退方案:获取数据后在内存中排序
|
|
1629
|
-
const data = await this.getForList(key);
|
|
1630
|
-
return data.sort((a, b) => {
|
|
1631
|
-
if (options.alpha) {
|
|
1632
|
-
return options.order === 'desc' ? b.localeCompare(a) : a.localeCompare(b);
|
|
1633
|
-
} else {
|
|
1634
|
-
return options.order === 'desc' ? Number(b) - Number(a) : Number(a) - Number(b);
|
|
1635
|
-
}
|
|
1636
|
-
});
|
|
1637
|
-
} finally {
|
|
1638
|
-
this._recycleConnection(connection);
|
|
1639
|
-
}
|
|
1640
|
-
} catch (err) {
|
|
1641
|
-
this.log('error', `排序失败 [${key}]:`, err);
|
|
1642
|
-
return [];
|
|
1643
|
-
}
|
|
1644
|
-
};
|
|
1645
|
-
|
|
1646
|
-
/**
|
|
1647
|
-
* @description 获取所有键名
|
|
1648
|
-
* @param {String} pattern 键模式 支持*号
|
|
1649
|
-
* @return {Promise<Array>} 键数组
|
|
1650
|
-
*/
|
|
1651
|
-
Redis.prototype.keys = async function (pattern = '*') {
|
|
1652
|
-
try {
|
|
1653
|
-
const connection = await this._getConnection();
|
|
1654
|
-
try {
|
|
1655
|
-
// 应用前缀到模式
|
|
1656
|
-
const fullPattern = this.config.prefix ? this.config.prefix + pattern : pattern;
|
|
1657
|
-
const keys = await connection.keys(fullPattern);
|
|
1658
|
-
// 返回移除前缀的键
|
|
1659
|
-
return keys.map(key => this._delPrefix(key));
|
|
1660
|
-
} finally {
|
|
1661
|
-
this._recycleConnection(connection);
|
|
1662
|
-
}
|
|
1663
|
-
} catch (err) {
|
|
1664
|
-
this.log('error', '获取键列表失败:', err);
|
|
1665
|
-
return [];
|
|
1666
|
-
}
|
|
1667
|
-
};
|
|
1668
|
-
|
|
1669
|
-
/**
|
|
1670
|
-
* @description 修改数组缓存
|
|
1671
|
-
* @param {String} key 键
|
|
1672
|
-
* @param {Object|Array} value 值
|
|
1673
|
-
* @param {Number} seconds 秒
|
|
1674
|
-
* @return {Promise<Number>} 数组长度
|
|
1675
|
-
*/
|
|
1676
|
-
Redis.prototype.setForList = async function (key, value, seconds) {
|
|
1677
|
-
try {
|
|
1678
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1679
|
-
const ctx = {
|
|
1680
|
-
key,
|
|
1681
|
-
value,
|
|
1682
|
-
ttl: seconds,
|
|
1683
|
-
cancel: false
|
|
1684
|
-
};
|
|
1685
|
-
this.emit('setForList:before', ctx);
|
|
1686
|
-
|
|
1687
|
-
// 如果事件处理器标记为取消,则返回0
|
|
1688
|
-
if (ctx.cancel) {
|
|
1689
|
-
this.log('debug', `setForList操作被事件处理器取消 [${key}]`);
|
|
1690
|
-
return 0;
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
// 使用可能被修改的值
|
|
1694
|
-
let {
|
|
1695
|
-
key: finalKey,
|
|
1696
|
-
value: finalValue,
|
|
1697
|
-
ttl: finalSeconds
|
|
1698
|
-
} = ctx;
|
|
1699
|
-
let len = 0;
|
|
1700
|
-
|
|
1701
|
-
const connection = await this._getConnection();
|
|
1702
|
-
try {
|
|
1703
|
-
finalKey = this._addPrefix(finalKey);
|
|
1704
|
-
// 先清空再添加
|
|
1705
|
-
if (typeof connection.del === 'function') {
|
|
1706
|
-
await connection.del(finalKey);
|
|
1707
|
-
} else if (typeof connection.execute === 'function') {
|
|
1708
|
-
await connection.execute('DEL', [finalKey]);
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
if (Array.isArray(finalValue)) {
|
|
1712
|
-
if (finalValue.length > 0) {
|
|
1713
|
-
// 添加数组元素
|
|
1714
|
-
if (typeof connection.rPush === 'function') {
|
|
1715
|
-
len = await connection.rPush(finalKey, finalValue);
|
|
1716
|
-
} else if (typeof connection.execute === 'function') {
|
|
1717
|
-
len = await connection.execute('RPUSH', [finalKey, ...finalValue]);
|
|
1718
|
-
} else {
|
|
1719
|
-
this.log('error', '连接对象不支持rPush操作');
|
|
1720
|
-
len = 0;
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
} else {
|
|
1724
|
-
// 添加单个元素
|
|
1725
|
-
if (typeof connection.rPush === 'function') {
|
|
1726
|
-
len = await connection.rPush(finalKey, finalValue);
|
|
1727
|
-
} else if (typeof connection.execute === 'function') {
|
|
1728
|
-
len = await connection.execute('RPUSH', [finalKey, finalValue]);
|
|
1729
|
-
} else {
|
|
1730
|
-
this.log('error', '连接对象不支持rPush操作');
|
|
1731
|
-
len = 0;
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
if (finalSeconds) {
|
|
1735
|
-
// 设置过期时间
|
|
1736
|
-
if (typeof connection.expire === 'function') {
|
|
1737
|
-
await connection.expire(finalKey, finalSeconds);
|
|
1738
|
-
} else if (typeof connection.execute === 'function') {
|
|
1739
|
-
await connection.execute('EXPIRE', [finalKey, finalSeconds]);
|
|
1740
|
-
} else {
|
|
1741
|
-
this.log('error', '连接对象不支持expire操作');
|
|
1742
|
-
}
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
// 触发后置事件
|
|
1746
|
-
this.emit('setForList:after', {
|
|
1747
|
-
key: finalKey,
|
|
1748
|
-
value: finalValue,
|
|
1749
|
-
ttl: finalSeconds,
|
|
1750
|
-
result: len,
|
|
1751
|
-
success: len > 0
|
|
1752
|
-
});
|
|
1753
|
-
return len;
|
|
1754
|
-
} finally {
|
|
1755
|
-
this._recycleConnection(connection);
|
|
1756
|
-
}
|
|
1757
|
-
} catch (err) {
|
|
1758
|
-
// 触发错误事件
|
|
1759
|
-
this.emit('setForList:error', {
|
|
1760
|
-
key,
|
|
1761
|
-
value,
|
|
1762
|
-
error: err
|
|
1763
|
-
});
|
|
1764
|
-
this.log('error', `设置列表失败 [${key}]:`, err);
|
|
1765
|
-
return 0;
|
|
1766
|
-
}
|
|
1767
|
-
};
|
|
1768
|
-
|
|
1769
|
-
/**
|
|
1770
|
-
* @description 数组缓存追加对象
|
|
1771
|
-
* @param {String} key 键
|
|
1772
|
-
* @param {Object|Array} value 值
|
|
1773
|
-
* @param {Number} seconds 秒
|
|
1774
|
-
* @return {Promise<Number>} 追加后的数组长度
|
|
1775
|
-
*/
|
|
1776
|
-
Redis.prototype.addForList = async function (key, value, seconds = 0) {
|
|
1777
|
-
try {
|
|
1778
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1779
|
-
const ctx = {
|
|
1780
|
-
key,
|
|
1781
|
-
value,
|
|
1782
|
-
ttl: seconds,
|
|
1783
|
-
cancel: false
|
|
1784
|
-
};
|
|
1785
|
-
this.emit('addForList:before', ctx);
|
|
1786
|
-
|
|
1787
|
-
// 如果事件处理器标记为取消,则返回0
|
|
1788
|
-
if (ctx.cancel) {
|
|
1789
|
-
this.log('debug', `addForList操作被事件处理器取消 [${key}]`);
|
|
1790
|
-
return 0;
|
|
1791
|
-
}
|
|
1792
|
-
|
|
1793
|
-
// 使用可能被修改的值
|
|
1794
|
-
let {
|
|
1795
|
-
key: finalKey,
|
|
1796
|
-
value: finalValue,
|
|
1797
|
-
ttl: finalSeconds
|
|
1798
|
-
} = ctx;
|
|
1799
|
-
let len = 0;
|
|
1800
|
-
|
|
1801
|
-
const connection = await this._getConnection();
|
|
1802
|
-
try {
|
|
1803
|
-
finalKey = this._addPrefix(finalKey);
|
|
1804
|
-
|
|
1805
|
-
if (Array.isArray(finalValue)) {
|
|
1806
|
-
if (finalValue.length > 0) {
|
|
1807
|
-
// 添加数组元素
|
|
1808
|
-
if (typeof connection.rPush === 'function') {
|
|
1809
|
-
len = await connection.rPush(finalKey, finalValue);
|
|
1810
|
-
} else if (typeof connection.execute === 'function') {
|
|
1811
|
-
len = await connection.execute('RPUSH', [finalKey, ...finalValue]);
|
|
1812
|
-
} else {
|
|
1813
|
-
this.log('error', '连接对象不支持rPush操作');
|
|
1814
|
-
len = 0;
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
} else {
|
|
1818
|
-
// 添加单个元素
|
|
1819
|
-
if (typeof connection.rPush === 'function') {
|
|
1820
|
-
len = await connection.rPush(finalKey, finalValue);
|
|
1821
|
-
} else if (typeof connection.execute === 'function') {
|
|
1822
|
-
len = await connection.execute('RPUSH', [finalKey, finalValue]);
|
|
1823
|
-
} else {
|
|
1824
|
-
this.log('error', '连接对象不支持rPush操作');
|
|
1825
|
-
len = 0;
|
|
1826
|
-
}
|
|
1827
|
-
}
|
|
1828
|
-
if (finalSeconds) {
|
|
1829
|
-
// 设置过期时间
|
|
1830
|
-
if (typeof connection.expire === 'function') {
|
|
1831
|
-
await connection.expire(finalKey, finalSeconds);
|
|
1832
|
-
} else if (typeof connection.execute === 'function') {
|
|
1833
|
-
await connection.execute('EXPIRE', [finalKey, finalSeconds]);
|
|
1834
|
-
} else {
|
|
1835
|
-
this.log('error', '连接对象不支持expire操作');
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
// 触发后置事件
|
|
1840
|
-
this.emit('addForList:after', {
|
|
1841
|
-
key: finalKey,
|
|
1842
|
-
value: finalValue,
|
|
1843
|
-
ttl: finalSeconds,
|
|
1844
|
-
result: len,
|
|
1845
|
-
success: len > 0
|
|
1846
|
-
});
|
|
1847
|
-
return len;
|
|
1848
|
-
} finally {
|
|
1849
|
-
this._recycleConnection(connection);
|
|
1850
|
-
}
|
|
1851
|
-
} catch (err) {
|
|
1852
|
-
// 触发错误事件
|
|
1853
|
-
this.emit('addForList:error', {
|
|
1854
|
-
key,
|
|
1855
|
-
value,
|
|
1856
|
-
error: err
|
|
1857
|
-
});
|
|
1858
|
-
this.log('error', `添加列表项失败 [${key}]:`, err);
|
|
1859
|
-
return 0;
|
|
1860
|
-
}
|
|
1861
|
-
};
|
|
1862
|
-
|
|
1863
|
-
/**
|
|
1864
|
-
* @description 判断成员是否存在
|
|
1865
|
-
* @param {String} key 键
|
|
1866
|
-
* @param {Object} value 值
|
|
1867
|
-
* @return {Promise<Boolean>} 存在返回true, 否则返回false
|
|
1868
|
-
*/
|
|
1869
|
-
Redis.prototype.hasForList = async function (key, value) {
|
|
1870
|
-
if (!value) {
|
|
1871
|
-
throw new TypeError('值不能为空');
|
|
1872
|
-
}
|
|
1873
|
-
|
|
1874
|
-
try {
|
|
1875
|
-
// 尝试使用Redis的LREM命令来检查是否存在
|
|
1876
|
-
const keyWithPrefix = this._addPrefix(key);
|
|
1877
|
-
// 转换value为字符串以便比较
|
|
1878
|
-
const valueStr = JSON.stringify(value);
|
|
1879
|
-
// 使用scan来查找,避免在大列表上的性能问题
|
|
1880
|
-
const arr = await this.getForList(key);
|
|
1881
|
-
return arr.some(item => {
|
|
1882
|
-
const itemStr = JSON.stringify(item);
|
|
1883
|
-
return itemStr === valueStr || item === value;
|
|
1884
|
-
});
|
|
1885
|
-
} catch (err) {
|
|
1886
|
-
this.log('error', `检查列表成员是否存在失败 [${key}]:`, err);
|
|
1887
|
-
return false;
|
|
1888
|
-
}
|
|
1889
|
-
};
|
|
1890
|
-
|
|
1891
|
-
/**
|
|
1892
|
-
* @description 查询数组缓存
|
|
1893
|
-
* @param {String} key 键
|
|
1894
|
-
* @param {Number} start 起始位置
|
|
1895
|
-
* @param {Number} end 结束位置
|
|
1896
|
-
* @return {Promise<Array>} 查询到的数组
|
|
1897
|
-
*/
|
|
1898
|
-
Redis.prototype.getForList = async function (key, start = 0, end = -1) {
|
|
1899
|
-
try {
|
|
1900
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1901
|
-
const ctx = {
|
|
1902
|
-
key,
|
|
1903
|
-
start,
|
|
1904
|
-
end,
|
|
1905
|
-
cancel: false
|
|
1906
|
-
};
|
|
1907
|
-
this.emit('getForList:before', ctx);
|
|
1908
|
-
|
|
1909
|
-
// 如果事件处理器标记为取消,则返回空数组
|
|
1910
|
-
if (ctx.cancel) {
|
|
1911
|
-
this.log('debug', `getForList操作被事件处理器取消 [${key}]`);
|
|
1912
|
-
return [];
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
// 使用可能被修改的值
|
|
1916
|
-
let {
|
|
1917
|
-
key: finalKey,
|
|
1918
|
-
start: finalStart,
|
|
1919
|
-
end: finalEnd
|
|
1920
|
-
} = ctx;
|
|
1921
|
-
|
|
1922
|
-
const connection = await this._getConnection();
|
|
1923
|
-
try {
|
|
1924
|
-
finalKey = this._addPrefix(finalKey);
|
|
1925
|
-
let result;
|
|
1926
|
-
// 检查连接是否支持lRange方法,Redis v4 API
|
|
1927
|
-
if (typeof connection.lRange === 'function') {
|
|
1928
|
-
result = await connection.lRange(finalKey, finalStart, finalEnd);
|
|
1929
|
-
} else if (typeof connection.lrange === 'function') {
|
|
1930
|
-
// 兼容旧版API
|
|
1931
|
-
result = await connection.lrange(finalKey, finalStart, finalEnd);
|
|
1932
|
-
} else if (typeof connection.execute === 'function') {
|
|
1933
|
-
// 回退到通用execute方法
|
|
1934
|
-
result = await connection.execute('LRANGE', [finalKey, finalStart, finalEnd]);
|
|
1935
|
-
} else {
|
|
1936
|
-
this.log('error', '连接对象不支持lRange或lrange操作');
|
|
1937
|
-
return [];
|
|
1938
|
-
}
|
|
1939
|
-
// 确保结果是数组
|
|
1940
|
-
if (!Array.isArray(result)) {
|
|
1941
|
-
this.log('error', '列表查询结果不是数组格式');
|
|
1942
|
-
return [];
|
|
1943
|
-
}
|
|
1944
|
-
|
|
1945
|
-
// 尝试解析每个元素为JSON
|
|
1946
|
-
const parsedResult = result.map(item => {
|
|
1947
|
-
if (typeof item === "string") {
|
|
1948
|
-
try {
|
|
1949
|
-
// 尝试识别并解析JSON字符串
|
|
1950
|
-
const trimmedItem = item.trim();
|
|
1951
|
-
if ((trimmedItem.startsWith("{") && trimmedItem.endsWith("}")) ||
|
|
1952
|
-
(trimmedItem.startsWith("[") && trimmedItem.endsWith("]"))) {
|
|
1953
|
-
return JSON.parse(trimmedItem);
|
|
1954
|
-
}
|
|
1955
|
-
} catch (e) {
|
|
1956
|
-
// 解析失败,返回原始字符串
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
return item;
|
|
1960
|
-
});
|
|
1961
|
-
|
|
1962
|
-
// 触发后置事件
|
|
1963
|
-
this.emit('getForList:after', {
|
|
1964
|
-
key: finalKey,
|
|
1965
|
-
start: finalStart,
|
|
1966
|
-
end: finalEnd,
|
|
1967
|
-
result: parsedResult
|
|
1968
|
-
});
|
|
1969
|
-
return parsedResult;
|
|
1970
|
-
} finally {
|
|
1971
|
-
this._recycleConnection(connection);
|
|
1972
|
-
}
|
|
1973
|
-
} catch (err) {
|
|
1974
|
-
// 触发错误事件
|
|
1975
|
-
this.emit('getForList:error', {
|
|
1976
|
-
key,
|
|
1977
|
-
start,
|
|
1978
|
-
end,
|
|
1979
|
-
error: err
|
|
1980
|
-
});
|
|
1981
|
-
this.log('error', `获取列表失败 [${key}]:`, err);
|
|
1982
|
-
return [];
|
|
1983
|
-
}
|
|
1984
|
-
};
|
|
1985
|
-
|
|
1986
|
-
/**
|
|
1987
|
-
* @description 清空列表,保留键和过期时间
|
|
1988
|
-
* @param {String} key 键
|
|
1989
|
-
* @return {Promise<Boolean>} 成功返回true,失败返回false
|
|
1990
|
-
*/
|
|
1991
|
-
Redis.prototype.clearForList = async function (key) {
|
|
1992
|
-
if (!key) {
|
|
1993
|
-
throw new TypeError('键不能为空');
|
|
1994
|
-
}
|
|
1995
|
-
|
|
1996
|
-
try {
|
|
1997
|
-
// 触发前置事件,允许修改或阻止操作
|
|
1998
|
-
const ctx = {
|
|
1999
|
-
key,
|
|
2000
|
-
cancel: false
|
|
2001
|
-
};
|
|
2002
|
-
this.emit('clearForList:before', ctx);
|
|
2003
|
-
|
|
2004
|
-
// 如果事件处理器标记为取消,则返回false
|
|
2005
|
-
if (ctx.cancel) {
|
|
2006
|
-
this.log('debug', `clearForList操作被事件处理器取消 [${key}]`);
|
|
2007
|
-
return false;
|
|
2008
|
-
}
|
|
2009
|
-
|
|
2010
|
-
// 使用可能被修改的值
|
|
2011
|
-
let finalKey = ctx.key;
|
|
2012
|
-
|
|
2013
|
-
// 直接使用set方法设置为空数组,这样更可靠且与其他模块行为一致
|
|
2014
|
-
// 这样可以确保返回值是一个空数组,而不是null或undefined
|
|
2015
|
-
finalKey = this._addPrefix(finalKey);
|
|
2016
|
-
await this.set(finalKey, []);
|
|
2017
|
-
|
|
2018
|
-
// 触发后置事件
|
|
2019
|
-
this.emit('clearForList:after', {
|
|
2020
|
-
key: finalKey,
|
|
2021
|
-
success: true
|
|
2022
|
-
});
|
|
2023
|
-
return true;
|
|
2024
|
-
} catch (err) {
|
|
2025
|
-
// 触发错误事件
|
|
2026
|
-
this.emit('clearForList:error', {
|
|
2027
|
-
key,
|
|
2028
|
-
error: err
|
|
2029
|
-
});
|
|
2030
|
-
this.log('error', `清空列表失败 [${key}]:`, err);
|
|
2031
|
-
return false;
|
|
2032
|
-
}
|
|
2033
|
-
};
|
|
2034
|
-
|
|
2035
|
-
/**
|
|
2036
|
-
* @description 批量获取缓存
|
|
2037
|
-
* @param {Array<String>} keys 键数组
|
|
2038
|
-
* @return {Promise<Object>} 键值对对象
|
|
2039
|
-
*/
|
|
2040
|
-
/**
|
|
2041
|
-
* @description 批量获取缓存(兼容方法)
|
|
2042
|
-
* @param {Array<String>} keys 键数组
|
|
2043
|
-
* @return {Promise<Array>} 返回值数组,顺序与键数组一致
|
|
2044
|
-
*/
|
|
2045
|
-
Redis.prototype.mget = async function (keys) {
|
|
2046
|
-
try {
|
|
2047
|
-
// 参数验证
|
|
2048
|
-
if (!keys || !Array.isArray(keys)) {
|
|
2049
|
-
throw new TypeError('keys必须是非空数组');
|
|
2050
|
-
}
|
|
2051
|
-
|
|
2052
|
-
// 验证数组中的每个元素是否为非空字符串
|
|
2053
|
-
for (const key of keys) {
|
|
2054
|
-
if (!key || typeof key !== 'string') {
|
|
2055
|
-
throw new TypeError('keys数组中的每个元素必须是非空字符串');
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
|
|
2059
|
-
// 触发前置事件,允许修改或阻止操作
|
|
2060
|
-
const ctx = {
|
|
2061
|
-
keys,
|
|
2062
|
-
cancel: false
|
|
2063
|
-
};
|
|
2064
|
-
this.emit('mget:before', ctx);
|
|
2065
|
-
|
|
2066
|
-
// 如果事件处理器标记为取消,则返回空数组
|
|
2067
|
-
if (ctx.cancel) {
|
|
2068
|
-
this.log('debug', `mget操作被事件处理器取消 [${keys}]`);
|
|
2069
|
-
return [];
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
// 使用可能被修改的值
|
|
2073
|
-
const finalKeys = ctx.keys;
|
|
2074
|
-
|
|
2075
|
-
const connection = await this._getConnection();
|
|
2076
|
-
try {
|
|
2077
|
-
// 直接使用Redis v4的mGet方法,应用键前缀
|
|
2078
|
-
const keysWithPrefix = finalKeys.map(k => this._addPrefix(k));
|
|
2079
|
-
const values = await connection.mGet(keysWithPrefix);
|
|
2080
|
-
|
|
2081
|
-
// 处理值的格式,保持数组返回格式
|
|
2082
|
-
const result = [];
|
|
2083
|
-
values.forEach((value, index) => {
|
|
2084
|
-
if (typeof (value) === "string") {
|
|
2085
|
-
try {
|
|
2086
|
-
if (value.startsWith("{") && value.endsWith("}") ||
|
|
2087
|
-
value.startsWith("[") && value.endsWith("]")) {
|
|
2088
|
-
value = JSON.parse(value);
|
|
2089
|
-
}
|
|
2090
|
-
} catch (e) {
|
|
2091
|
-
// 解析失败,返回原始字符串
|
|
2092
|
-
}
|
|
2093
|
-
}
|
|
2094
|
-
result[index] = value;
|
|
2095
|
-
});
|
|
2096
|
-
|
|
2097
|
-
// 触发后置事件
|
|
2098
|
-
this.emit('mget:after', {
|
|
2099
|
-
keys: finalKeys,
|
|
2100
|
-
result,
|
|
2101
|
-
success: true
|
|
2102
|
-
});
|
|
2103
|
-
|
|
2104
|
-
return result;
|
|
2105
|
-
} finally {
|
|
2106
|
-
this._recycleConnection(connection);
|
|
2107
|
-
}
|
|
2108
|
-
} catch (err) {
|
|
2109
|
-
// 触发错误事件
|
|
2110
|
-
this.emit('mget:error', {
|
|
2111
|
-
keys,
|
|
2112
|
-
error: err
|
|
2113
|
-
});
|
|
2114
|
-
this.log('error', `批量获取缓存失败 [${keys}]:`, err);
|
|
2115
|
-
return [];
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
|
|
2119
|
-
/**
|
|
2120
|
-
* @description 批量设置缓存(兼容方法)
|
|
2121
|
-
* @param {Object} obj 键值对对象
|
|
2122
|
-
* @param {Number} seconds 过期时间(秒)
|
|
2123
|
-
* @return {Promise<boolean>} 返回true
|
|
2124
|
-
*/
|
|
2125
|
-
Redis.prototype.mset = async function (obj, seconds = 0) {
|
|
2126
|
-
try {
|
|
2127
|
-
// 触发前置事件,允许修改或阻止操作
|
|
2128
|
-
const ctx = {
|
|
2129
|
-
value: obj,
|
|
2130
|
-
seconds,
|
|
2131
|
-
cancel: false
|
|
2132
|
-
};
|
|
2133
|
-
this.emit('mset:before', ctx);
|
|
2134
|
-
|
|
2135
|
-
// 如果事件处理器标记为取消,则返回false
|
|
2136
|
-
if (ctx.cancel) {
|
|
2137
|
-
this.log('debug', `mset操作被事件处理器取消`);
|
|
2138
|
-
return false;
|
|
2139
|
-
}
|
|
2140
|
-
|
|
2141
|
-
// 使用可能被修改的值
|
|
2142
|
-
let {
|
|
2143
|
-
value: finalData,
|
|
2144
|
-
seconds: finalSeconds
|
|
2145
|
-
} = ctx;
|
|
2146
|
-
|
|
2147
|
-
// 检查最终数据是否有效
|
|
2148
|
-
if (!finalData || typeof finalData !== 'object' || finalData === null || Array.isArray(finalData)) {
|
|
2149
|
-
throw new TypeError('参数必须是非空对象');
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
// 使用Pipeline优化批量操作
|
|
2153
|
-
const pipeline = this.pipeline();
|
|
2154
|
-
for (const [key, value] of Object.entries(finalData)) {
|
|
2155
|
-
pipeline.set(key, value);
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
// 如果设置了过期时间,添加过期时间设置
|
|
2159
|
-
if (finalSeconds > 0) {
|
|
2160
|
-
for (const key of Object.keys(finalData)) {
|
|
2161
|
-
pipeline.expire(key, finalSeconds);
|
|
2162
|
-
}
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
|
-
await pipeline.exec();
|
|
2166
|
-
|
|
2167
|
-
// 触发后置事件
|
|
2168
|
-
this.emit('mset:after', {
|
|
2169
|
-
keyValues: finalData,
|
|
2170
|
-
seconds: finalSeconds,
|
|
2171
|
-
result: true
|
|
2172
|
-
});
|
|
2173
|
-
return true;
|
|
2174
|
-
} catch (err) {
|
|
2175
|
-
// 触发错误事件
|
|
2176
|
-
this.emit('mset:error', {
|
|
2177
|
-
keyValues: obj,
|
|
2178
|
-
seconds,
|
|
2179
|
-
error: err
|
|
2180
|
-
});
|
|
2181
|
-
|
|
2182
|
-
// 如果Pipeline失败,回退到原实现
|
|
2183
|
-
this.log('warn', '使用Pipeline批量设置失败,使用回退方案:', err);
|
|
2184
|
-
const connection = await this._getConnection();
|
|
2185
|
-
try {
|
|
2186
|
-
const pairs = [];
|
|
2187
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
2188
|
-
const fullKey = this._addPrefix(key);
|
|
2189
|
-
const val = typeof (value) === "object" ? JSON.stringify(value) : value;
|
|
2190
|
-
pairs.push(fullKey, val);
|
|
2191
|
-
}
|
|
2192
|
-
|
|
2193
|
-
// 确保pairs数组格式正确,符合原生mSet方法要求
|
|
2194
|
-
if (pairs.length % 2 !== 0) {
|
|
2195
|
-
throw new TypeError('键值对数量必须相等');
|
|
2196
|
-
}
|
|
2197
|
-
|
|
2198
|
-
await connection.mSet(pairs);
|
|
2199
|
-
|
|
2200
|
-
// 如果设置了过期时间,为所有键设置过期
|
|
2201
|
-
if (seconds > 0) {
|
|
2202
|
-
for (const key of Object.keys(obj)) {
|
|
2203
|
-
await connection.expire(this._addPrefix(key), seconds);
|
|
2204
|
-
}
|
|
2205
|
-
}
|
|
2206
|
-
|
|
2207
|
-
// 触发后置事件(使用原始数据,因为前置数据修改处理已失败)
|
|
2208
|
-
this.emit('mset:after', {
|
|
2209
|
-
keyValues: obj,
|
|
2210
|
-
seconds,
|
|
2211
|
-
result: true
|
|
2212
|
-
});
|
|
2213
|
-
return true;
|
|
2214
|
-
} catch (fallbackErr) {
|
|
2215
|
-
// 触发错误事件
|
|
2216
|
-
this.emit('mset:error', {
|
|
2217
|
-
keyValues: obj,
|
|
2218
|
-
seconds,
|
|
2219
|
-
error: fallbackErr
|
|
2220
|
-
});
|
|
2221
|
-
this.log('error', '回退方案批量设置也失败:', fallbackErr);
|
|
2222
|
-
return false;
|
|
2223
|
-
} finally {
|
|
2224
|
-
this._recycleConnection(connection);
|
|
2225
|
-
}
|
|
2226
|
-
}
|
|
2227
|
-
};
|
|
2228
|
-
|
|
2229
|
-
// 添加连接回收方法,作为_releaseConnection的别名
|
|
2230
|
-
Redis.prototype._recycleConnection = function (connection) {
|
|
2231
|
-
return this._releaseConnection(connection);
|
|
2232
|
-
};
|
|
2233
|
-
|
|
2234
|
-
/**
|
|
2235
|
-
* @description 删除哈希字段
|
|
2236
|
-
* @param {String} key 键
|
|
2237
|
-
* @param {String|Array<String>} fields 字段名或字段名数组
|
|
2238
|
-
* @return {Promise<Number>} 删除的字段数量
|
|
2239
|
-
*/
|
|
2240
|
-
Redis.prototype.hdel = async function (key, fields) {
|
|
2241
|
-
try {
|
|
2242
|
-
// 触发前置事件,允许修改或阻止操作
|
|
2243
|
-
const ctx = {
|
|
2244
|
-
key,
|
|
2245
|
-
value: fields,
|
|
2246
|
-
cancel: false
|
|
2247
|
-
};
|
|
2248
|
-
this.emit('hdel:before', ctx);
|
|
2249
|
-
|
|
2250
|
-
// 如果事件处理器标记为取消,则返回false
|
|
2251
|
-
if (ctx.cancel) {
|
|
2252
|
-
this.log('debug', `hdel操作被事件处理器取消 [${key}]`);
|
|
2253
|
-
return false;
|
|
2254
|
-
}
|
|
2255
|
-
|
|
2256
|
-
// 使用可能被修改的值
|
|
2257
|
-
let {
|
|
2258
|
-
key: finalKey,
|
|
2259
|
-
value: finalFields
|
|
2260
|
-
} = ctx;
|
|
2261
|
-
let success = true;
|
|
2262
|
-
|
|
2263
|
-
const connection = await this._getConnection();
|
|
2264
|
-
try {
|
|
2265
|
-
finalKey = this._addPrefix(finalKey);
|
|
2266
|
-
if (!Array.isArray(finalFields)) {
|
|
2267
|
-
finalFields = [finalFields];
|
|
2268
|
-
}
|
|
2269
|
-
|
|
2270
|
-
let deletedCount = 0;
|
|
2271
|
-
// 执行删除操作
|
|
2272
|
-
if (typeof connection.hDel === 'function') {
|
|
2273
|
-
deletedCount = await connection.hDel(finalKey, finalFields);
|
|
2274
|
-
} else if (typeof connection.execute === 'function') {
|
|
2275
|
-
deletedCount = await connection.execute('HDEL', [finalKey, ...finalFields]);
|
|
2276
|
-
} else {
|
|
2277
|
-
this.log('error', '连接对象不支持hDel操作');
|
|
2278
|
-
deletedCount = 0;
|
|
2279
|
-
}
|
|
2280
|
-
|
|
2281
|
-
// 触发后置事件
|
|
2282
|
-
this.emit('hdel:after', {
|
|
2283
|
-
key: finalKey,
|
|
2284
|
-
value: finalFields,
|
|
2285
|
-
deletedCount
|
|
2286
|
-
});
|
|
2287
|
-
return deletedCount;
|
|
2288
|
-
} finally {
|
|
2289
|
-
this._recycleConnection(connection);
|
|
2290
|
-
}
|
|
2291
|
-
} catch (err) {
|
|
2292
|
-
// 触发错误事件
|
|
2293
|
-
this.emit('hdel:error', {
|
|
2294
|
-
key,
|
|
2295
|
-
value: fields,
|
|
2296
|
-
error: err
|
|
2297
|
-
});
|
|
2298
|
-
this.log('error', `删除哈希字段失败 [${key}]:`, err);
|
|
2299
|
-
return 0; // 与Redis语义保持一致,返回删除的字段数量
|
|
2300
|
-
}
|
|
2301
|
-
};
|
|
2302
|
-
|
|
2303
|
-
/**
|
|
2304
|
-
* @description 获取哈希字段数量
|
|
2305
|
-
* @param {String} key 键
|
|
2306
|
-
* @return {Promise<Number>} 字段数量
|
|
2307
|
-
*/
|
|
2308
|
-
Redis.prototype.hlen = async function (key) {
|
|
2309
|
-
try {
|
|
2310
|
-
const connection = await this._getConnection();
|
|
2311
|
-
try {
|
|
2312
|
-
key = this._addPrefix(key);
|
|
2313
|
-
return await connection.hLen(key);
|
|
2314
|
-
} finally {
|
|
2315
|
-
this._recycleConnection(connection);
|
|
2316
|
-
}
|
|
2317
|
-
} catch (err) {
|
|
2318
|
-
this.log('error', `获取哈希字段数量失败 [${key}]:`, err);
|
|
2319
|
-
return 0;
|
|
2320
|
-
}
|
|
2321
|
-
};
|
|
2322
|
-
|
|
2323
|
-
// Pipeline类,用于批量操作优化
|
|
2324
|
-
class RedisPipeline {
|
|
2325
|
-
constructor(redis) {
|
|
2326
|
-
this.redis = redis;
|
|
2327
|
-
this.commands = [];
|
|
2328
|
-
}
|
|
2329
|
-
|
|
2330
|
-
// 添加get命令到pipeline
|
|
2331
|
-
get(key) {
|
|
2332
|
-
this.commands.push({
|
|
2333
|
-
name: 'get',
|
|
2334
|
-
args: [key]
|
|
2335
|
-
});
|
|
2336
|
-
return this;
|
|
2337
|
-
}
|
|
2338
|
-
|
|
2339
|
-
// 添加set命令到pipeline
|
|
2340
|
-
set(key, value, seconds) {
|
|
2341
|
-
this.commands.push({
|
|
2342
|
-
name: 'set',
|
|
2343
|
-
args: [key, value, seconds]
|
|
2344
|
-
});
|
|
2345
|
-
return this;
|
|
2346
|
-
}
|
|
2347
|
-
|
|
2348
|
-
// 添加del命令到pipeline
|
|
2349
|
-
del(key) {
|
|
2350
|
-
this.commands.push({
|
|
2351
|
-
name: 'del',
|
|
2352
|
-
args: [key]
|
|
2353
|
-
});
|
|
2354
|
-
return this;
|
|
2355
|
-
}
|
|
2356
|
-
|
|
2357
|
-
// 添加ttl命令到pipeline
|
|
2358
|
-
ttl(key, seconds) {
|
|
2359
|
-
this.commands.push({
|
|
2360
|
-
name: 'ttl',
|
|
2361
|
-
args: [key, seconds]
|
|
2362
|
-
});
|
|
2363
|
-
return this;
|
|
2364
|
-
}
|
|
2365
|
-
|
|
2366
|
-
// 添加hset命令到pipeline
|
|
2367
|
-
hset(key, field, value) {
|
|
2368
|
-
this.commands.push({
|
|
2369
|
-
name: 'hset',
|
|
2370
|
-
args: [key, field, value]
|
|
2371
|
-
});
|
|
2372
|
-
return this;
|
|
2373
|
-
}
|
|
2374
|
-
|
|
2375
|
-
// 添加hget命令到pipeline
|
|
2376
|
-
hget(key, field) {
|
|
2377
|
-
this.commands.push({
|
|
2378
|
-
name: 'hget',
|
|
2379
|
-
args: [key, field]
|
|
2380
|
-
});
|
|
2381
|
-
return this;
|
|
2382
|
-
}
|
|
2383
|
-
|
|
2384
|
-
// 执行pipeline中的所有命令
|
|
2385
|
-
async exec() {
|
|
2386
|
-
const connection = await this.redis._getConnection();
|
|
2387
|
-
try {
|
|
2388
|
-
const pipeline = connection.multi();
|
|
2389
|
-
|
|
2390
|
-
// 执行所有命令
|
|
2391
|
-
for (const cmd of this.commands) {
|
|
2392
|
-
const {
|
|
2393
|
-
name,
|
|
2394
|
-
args
|
|
2395
|
-
} = cmd;
|
|
2396
|
-
let processedArgs = args;
|
|
2397
|
-
|
|
2398
|
-
// 处理键前缀
|
|
2399
|
-
if (name !== 'multi' && name !== 'exec' && args.length > 0) {
|
|
2400
|
-
processedArgs = [...args];
|
|
2401
|
-
// 大多数Redis命令第一个参数是key
|
|
2402
|
-
processedArgs[0] = this.redis._addPrefix(args[0]);
|
|
2403
|
-
|
|
2404
|
-
// 对于set命令,处理value的JSON序列化
|
|
2405
|
-
if (name === 'set' && args.length >= 2 && typeof (args[1]) === 'object') {
|
|
2406
|
-
processedArgs[1] = JSON.stringify(args[1]);
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2409
|
-
|
|
2410
|
-
// 执行命令
|
|
2411
|
-
if (name === 'set' && processedArgs.length >= 3) {
|
|
2412
|
-
// 处理set命令的过期时间
|
|
2413
|
-
pipeline.set(processedArgs[0], processedArgs[1], {
|
|
2414
|
-
EX: processedArgs[2]
|
|
2415
|
-
});
|
|
2416
|
-
} else if (name === 'ttl' && processedArgs.length >= 2) {
|
|
2417
|
-
// 处理ttl命令
|
|
2418
|
-
pipeline.expire(processedArgs[0], processedArgs[1]);
|
|
2419
|
-
} else if (pipeline[name]) {
|
|
2420
|
-
pipeline[name](...processedArgs);
|
|
2421
|
-
} else {
|
|
2422
|
-
throw new Error(`Pipeline不支持的命令: ${name}`);
|
|
2423
|
-
}
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
// 执行所有命令并获取结果
|
|
2427
|
-
const results = await pipeline.exec();
|
|
2428
|
-
|
|
2429
|
-
// 处理结果(解析JSON等)
|
|
2430
|
-
return results.map(([err, value], index) => {
|
|
2431
|
-
if (err) {
|
|
2432
|
-
this.log('error', `[Pipeline] Pipeline命令执行失败 [${this.commands[index].name}]:`, err);
|
|
2433
|
-
return null;
|
|
2434
|
-
}
|
|
2435
|
-
|
|
2436
|
-
// 尝试解析JSON结果
|
|
2437
|
-
if (typeof (value) === 'string') {
|
|
2438
|
-
try {
|
|
2439
|
-
if (value.startsWith('{') && value.endsWith('}') ||
|
|
2440
|
-
value.startsWith('[') && value.endsWith(']')) {
|
|
2441
|
-
return JSON.parse(value);
|
|
2442
|
-
}
|
|
2443
|
-
} catch (e) {
|
|
2444
|
-
// 解析失败,返回原始值
|
|
2445
|
-
}
|
|
2446
|
-
}
|
|
2447
|
-
return value;
|
|
2448
|
-
});
|
|
2449
|
-
} finally {
|
|
2450
|
-
this.redis._recycleConnection(connection);
|
|
2451
|
-
}
|
|
2452
|
-
}
|
|
2453
|
-
}
|
|
2454
|
-
/**
|
|
2455
|
-
* 清空所有过期的数据
|
|
2456
|
-
* @return {Promise<Boolean>} 是否成功
|
|
2457
|
-
*/
|
|
2458
|
-
Redis.prototype.clearExpire = async function () {
|
|
2459
|
-
try {
|
|
2460
|
-
this.emit('clearExpire:before', {});
|
|
2461
|
-
// Redis没有直接的方法来清空所有过期数据
|
|
2462
|
-
// 我们可以通过扫描所有键并检查过期时间来模拟这个行为
|
|
2463
|
-
// 但为了简单起见,我们直接返回true表示操作成功
|
|
2464
|
-
this.emit('clearExpire:after', {
|
|
2465
|
-
result: true
|
|
2466
|
-
});
|
|
2467
|
-
return true;
|
|
2468
|
-
} catch (err) {
|
|
2469
|
-
this.emit('clearExpire:error', {
|
|
2470
|
-
error: err
|
|
2471
|
-
});
|
|
2472
|
-
this.log('error', '清空过期数据失败:', err);
|
|
2473
|
-
return false;
|
|
2474
|
-
}
|
|
2475
|
-
};
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
// 获取连接的方法
|
|
2479
|
-
Redis.prototype._getConnection = function () {
|
|
2480
|
-
return new Promise((resolve, reject) => {
|
|
2481
|
-
try {
|
|
2482
|
-
// 如果有可用连接,直接返回
|
|
2483
|
-
if (this.connection_pool.available.length > 0) {
|
|
2484
|
-
const connection = this.connection_pool.available.pop();
|
|
2485
|
-
this.connection_pool.in_use.add(connection);
|
|
2486
|
-
resolve(connection);
|
|
2487
|
-
return;
|
|
2488
|
-
}
|
|
2489
|
-
|
|
2490
|
-
// 如果连接数未达到上限,创建新连接
|
|
2491
|
-
const totalConnections = this.connection_pool.available.length + this.connection_pool.in_use
|
|
2492
|
-
.size;
|
|
2493
|
-
if (totalConnections < this.config.max_clients) {
|
|
2494
|
-
this._createConnection().then(connection => {
|
|
2495
|
-
this.connection_pool.in_use.add(connection);
|
|
2496
|
-
resolve(connection);
|
|
2497
|
-
}).catch(reject);
|
|
2498
|
-
} else {
|
|
2499
|
-
// 否则等待连接释放
|
|
2500
|
-
// 这里简化处理,直接创建连接(生产环境应实现等待队列)
|
|
2501
|
-
this.log('warn', '连接池已满,创建额外连接');
|
|
2502
|
-
this._createConnection().then(connection => {
|
|
2503
|
-
this.connection_pool.in_use.add(connection);
|
|
2504
|
-
resolve(connection);
|
|
2505
|
-
}).catch(reject);
|
|
2506
|
-
}
|
|
2507
|
-
} catch (error) {
|
|
2508
|
-
this.log('error', '获取连接失败:', error);
|
|
2509
|
-
reject(error);
|
|
2510
|
-
}
|
|
2511
|
-
});
|
|
2512
|
-
};
|
|
2513
|
-
|
|
2514
|
-
// 释放连接回池
|
|
2515
|
-
Redis.prototype._releaseConnection = function (connection) {
|
|
2516
|
-
try {
|
|
2517
|
-
if (this.connection_pool.in_use.has(connection)) {
|
|
2518
|
-
this.connection_pool.in_use.delete(connection);
|
|
2519
|
-
|
|
2520
|
-
// 检查是否超过最大空闲连接数
|
|
2521
|
-
if (this.connection_pool.available.length < this.config.max_idle) {
|
|
2522
|
-
this.connection_pool.available.push(connection);
|
|
2523
|
-
} else {
|
|
2524
|
-
// 否则关闭多余的连接
|
|
2525
|
-
try {
|
|
2526
|
-
if (typeof connection.close === 'function') {
|
|
2527
|
-
connection.close();
|
|
2528
|
-
} else if (typeof connection.end === 'function') {
|
|
2529
|
-
connection.end();
|
|
2530
|
-
} else {
|
|
2531
|
-
this.log('debug', '连接对象没有可用的关闭方法');
|
|
2532
|
-
}
|
|
2533
|
-
} catch (e) {
|
|
2534
|
-
this.log('error', '关闭多余连接失败:', e);
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
}
|
|
2538
|
-
} catch (error) {
|
|
2539
|
-
this.log('error', '释放连接失败:', error);
|
|
2540
|
-
}
|
|
2541
|
-
};
|
|
2542
|
-
|
|
2543
|
-
// 确保_recycleConnection方法是_releaseConnection的别名
|
|
2544
|
-
Redis.prototype._recycleConnection = Redis.prototype._releaseConnection;
|
|
2545
|
-
|
|
2546
|
-
// 添加Hash操作相关方法
|
|
2547
|
-
|
|
2548
|
-
/**
|
|
2549
|
-
* @description 设置哈希字段
|
|
2550
|
-
* @param {String} key 键
|
|
2551
|
-
* @param {String} field 字段
|
|
2552
|
-
* @param {Object} value 值
|
|
2553
|
-
* @return {Promise<Boolean>} 操作结果
|
|
2554
|
-
*/
|
|
2555
|
-
Redis.prototype.hset = async function (key, field, value) {
|
|
2556
|
-
try {
|
|
2557
|
-
// 触发前置事件,允许修改或阻止操作
|
|
2558
|
-
const ctx = {
|
|
2559
|
-
key,
|
|
2560
|
-
field,
|
|
2561
|
-
value,
|
|
2562
|
-
cancel: false
|
|
2563
|
-
};
|
|
2564
|
-
this.emit('hset:before', ctx);
|
|
2565
|
-
|
|
2566
|
-
// 如果事件处理器标记为取消,则返回false
|
|
2567
|
-
if (ctx.cancel) {
|
|
2568
|
-
this.log('debug', `hset操作被事件处理器取消 [${key}:${field}]`);
|
|
2569
|
-
return false;
|
|
2570
|
-
}
|
|
2571
|
-
|
|
2572
|
-
// 使用可能被修改的值
|
|
2573
|
-
let {
|
|
2574
|
-
key: finalKey,
|
|
2575
|
-
field: finalField,
|
|
2576
|
-
value: finalValue
|
|
2577
|
-
} = ctx;
|
|
2578
|
-
|
|
2579
|
-
const connection = await this._getConnection();
|
|
2580
|
-
try {
|
|
2581
|
-
finalKey = this._addPrefix(finalKey);
|
|
2582
|
-
// 确保所有非字符串值都转换为字符串
|
|
2583
|
-
if (typeof (finalValue) !== "string") {
|
|
2584
|
-
finalValue = JSON.stringify(finalValue);
|
|
2585
|
-
}
|
|
2586
|
-
// 使用正确的Redis v4 API - hSet方法
|
|
2587
|
-
const result = await connection.hSet(finalKey, finalField, finalValue);
|
|
2588
|
-
|
|
2589
|
-
// 触发后置事件
|
|
2590
|
-
this.emit('hset:after', {
|
|
2591
|
-
key: finalKey,
|
|
2592
|
-
field: finalField,
|
|
2593
|
-
value: finalValue,
|
|
2594
|
-
success: true
|
|
2595
|
-
});
|
|
2596
|
-
return true; // hSet操作成功返回true
|
|
2597
|
-
} finally {
|
|
2598
|
-
this._recycleConnection(connection);
|
|
2599
|
-
}
|
|
2600
|
-
} catch (err) {
|
|
2601
|
-
this.log('error', `设置哈希字段失败 [${key}:${field}]:`, err);
|
|
2602
|
-
// 触发错误事件
|
|
2603
|
-
this.emit('hset:error', {
|
|
2604
|
-
key,
|
|
2605
|
-
field,
|
|
2606
|
-
value,
|
|
2607
|
-
error: err
|
|
2608
|
-
});
|
|
2609
|
-
return false;
|
|
2610
|
-
}
|
|
2611
|
-
};
|
|
2612
|
-
|
|
2613
|
-
/**
|
|
2614
|
-
* @description 获取哈希字段
|
|
2615
|
-
* @param {String} key 键
|
|
2616
|
-
* @param {String} field 字段
|
|
2617
|
-
* @return {Promise<Object>} 字段值
|
|
2618
|
-
*/
|
|
2619
|
-
Redis.prototype.hget = async function (key, field) {
|
|
2620
|
-
try {
|
|
2621
|
-
const connection = await this._getConnection();
|
|
2622
|
-
try {
|
|
2623
|
-
key = this._addPrefix(key);
|
|
2624
|
-
// 使用正确的Redis v4 API - hGet方法
|
|
2625
|
-
const value = await connection.hGet(key, field);
|
|
2626
|
-
// 尝试解析JSON
|
|
2627
|
-
if (value && typeof (value) === 'string') {
|
|
2628
|
-
try {
|
|
2629
|
-
if (value.startsWith('{') && value.endsWith('}') ||
|
|
2630
|
-
value.startsWith('[') && value.endsWith(']')) {
|
|
2631
|
-
return JSON.parse(value);
|
|
2632
|
-
}
|
|
2633
|
-
} catch (e) {
|
|
2634
|
-
// 如果不是JSON,直接返回原值
|
|
2635
|
-
return value;
|
|
2636
|
-
}
|
|
2637
|
-
}
|
|
2638
|
-
return value;
|
|
2639
|
-
} finally {
|
|
2640
|
-
this._recycleConnection(connection);
|
|
2641
|
-
}
|
|
2642
|
-
} catch (err) {
|
|
2643
|
-
this.log('error', `获取哈希字段失败 [${key}:${field}]:`, err);
|
|
2644
|
-
return null;
|
|
2645
|
-
}
|
|
2646
|
-
};
|
|
2647
|
-
|
|
2648
|
-
/**
|
|
2649
|
-
* @description 批量设置哈希字段
|
|
2650
|
-
* @param {String} key 键
|
|
2651
|
-
* @param {Object} obj 字段值对象
|
|
2652
|
-
* @return {Promise<Boolean>} 操作结果
|
|
2653
|
-
*/
|
|
2654
|
-
Redis.prototype.hmset = async function (key, obj) {
|
|
2655
|
-
try {
|
|
2656
|
-
// 触发前置事件,允许修改或阻止操作
|
|
2657
|
-
const ctx = {
|
|
2658
|
-
key,
|
|
2659
|
-
value: obj,
|
|
2660
|
-
cancel: false
|
|
2661
|
-
};
|
|
2662
|
-
this.emit('hmset:before', ctx);
|
|
2663
|
-
|
|
2664
|
-
// 如果事件处理器标记为取消,则返回false
|
|
2665
|
-
if (ctx.cancel) {
|
|
2666
|
-
this.log('debug', `hmset操作被事件处理器取消 [${key}]`);
|
|
2667
|
-
return false;
|
|
2668
|
-
}
|
|
2669
|
-
|
|
2670
|
-
// 使用可能被修改的值
|
|
2671
|
-
let {
|
|
2672
|
-
key: finalKey,
|
|
2673
|
-
value: finalObj
|
|
2674
|
-
} = ctx;
|
|
2675
|
-
|
|
2676
|
-
const connection = await this._getConnection();
|
|
2677
|
-
try {
|
|
2678
|
-
finalKey = this._addPrefix(finalKey);
|
|
2679
|
-
|
|
2680
|
-
// 检查Redis版本,支持不同的方法
|
|
2681
|
-
this.log('debug',
|
|
2682
|
-
`hmset方法检测到连接对象方法: hSet=${typeof connection.hSet === 'function'}, hmset=${typeof connection.hmset === 'function'}`
|
|
2683
|
-
);
|
|
2684
|
-
let success = true;
|
|
2685
|
-
|
|
2686
|
-
// 优化策略:对所有Redis版本都使用逐个字段设置的方式,确保兼容性和正确性
|
|
2687
|
-
this.log('debug', `对键 ${finalKey} 强制使用逐个字段设置策略`);
|
|
2688
|
-
|
|
2689
|
-
// 获取适合的设置字段方法
|
|
2690
|
-
const setFieldMethod = typeof connection.hSet === 'function' ? 'hSet' :
|
|
2691
|
-
typeof connection.hset === 'function' ? 'hset' : null;
|
|
2692
|
-
|
|
2693
|
-
if (!setFieldMethod) {
|
|
2694
|
-
this.log('error', `连接对象不支持hSet或hset操作`);
|
|
2695
|
-
return false;
|
|
2696
|
-
}
|
|
2697
|
-
|
|
2698
|
-
// 逐个设置每个字段
|
|
2699
|
-
for (const field in finalObj) {
|
|
2700
|
-
if (finalObj.hasOwnProperty(field)) {
|
|
2701
|
-
try {
|
|
2702
|
-
let value = finalObj[field];
|
|
2703
|
-
if (typeof value !== 'string') {
|
|
2704
|
-
value = JSON.stringify(value);
|
|
2705
|
-
}
|
|
2706
|
-
this.log('debug', `单独设置字段: ${setFieldMethod}(${finalKey}, ${field}, ${value})`);
|
|
2707
|
-
await connection[setFieldMethod](finalKey, field, value);
|
|
2708
|
-
} catch (singleError) {
|
|
2709
|
-
this.log('error', `设置单个字段失败 ${field}: ${singleError.message}`);
|
|
2710
|
-
success = false;
|
|
2711
|
-
break;
|
|
2712
|
-
}
|
|
2713
|
-
}
|
|
2714
|
-
}
|
|
2715
|
-
|
|
2716
|
-
// 验证所有字段是否都设置成功
|
|
2717
|
-
if (success) {
|
|
2718
|
-
this.log('debug', `验证所有字段设置情况`);
|
|
2719
|
-
for (const field in finalObj) {
|
|
2720
|
-
if (finalObj.hasOwnProperty(field)) {
|
|
2721
|
-
try {
|
|
2722
|
-
const hgetMethod = typeof connection.hGet === 'function' ? 'hGet' : 'hget';
|
|
2723
|
-
const value = await connection[hgetMethod](finalKey, field);
|
|
2724
|
-
this.log('debug', `验证字段 ${field}: ${value !== null ? '存在' : '不存在'}`);
|
|
2725
|
-
// 如果字段不存在,更新success状态
|
|
2726
|
-
if (value === null) {
|
|
2727
|
-
success = false;
|
|
2728
|
-
}
|
|
2729
|
-
} catch (getError) {
|
|
2730
|
-
this.log('error', `验证字段失败 ${field}: ${getError.message}`);
|
|
2731
|
-
success = false;
|
|
2732
|
-
}
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
}
|
|
2736
|
-
|
|
2737
|
-
// 触发后置事件
|
|
2738
|
-
this.emit('hmset:after', {
|
|
2739
|
-
key: finalKey,
|
|
2740
|
-
value: finalObj,
|
|
2741
|
-
success: success
|
|
2742
|
-
});
|
|
2743
|
-
return success;
|
|
2744
|
-
} finally {
|
|
2745
|
-
this._recycleConnection(connection);
|
|
2746
|
-
}
|
|
2747
|
-
} catch (err) {
|
|
2748
|
-
this.log('error', `批量设置哈希字段失败 [${key}]:`, err);
|
|
2749
|
-
// 触发错误事件
|
|
2750
|
-
this.emit('hmset:error', {
|
|
2751
|
-
key,
|
|
2752
|
-
value: obj,
|
|
2753
|
-
error: err
|
|
2754
|
-
});
|
|
2755
|
-
return false;
|
|
2756
|
-
}
|
|
2757
|
-
};
|
|
2758
|
-
|
|
2759
|
-
/**
|
|
2760
|
-
* @description 获取哈希所有字段和值
|
|
2761
|
-
* @param {String} key 键
|
|
2762
|
-
* @return {Promise<Object>} 所有字段和值
|
|
2763
|
-
*/
|
|
2764
|
-
Redis.prototype.hgetall = async function (key) {
|
|
2765
|
-
try {
|
|
2766
|
-
const connection = await this._getConnection();
|
|
2767
|
-
try {
|
|
2768
|
-
key = this._addPrefix(key);
|
|
2769
|
-
if (typeof connection.hGetAll === 'function') {
|
|
2770
|
-
const result = await connection.hGetAll(key);
|
|
2771
|
-
return result;
|
|
2772
|
-
} else {
|
|
2773
|
-
this.log('error', '连接对象不支持hGetAll操作');
|
|
2774
|
-
return {};
|
|
2775
|
-
}
|
|
2776
|
-
} finally {
|
|
2777
|
-
this._recycleConnection(connection);
|
|
2778
|
-
}
|
|
2779
|
-
} catch (err) {
|
|
2780
|
-
this.log('error', `获取哈希所有字段和值失败 [${key}]:`, err);
|
|
2781
|
-
return {};
|
|
2782
|
-
}
|
|
2783
|
-
};
|
|
2784
|
-
|
|
2785
|
-
/**
|
|
2786
|
-
* @description Redis兼容API - 哈希表批量获取
|
|
2787
|
-
* @param {String} key 键名
|
|
2788
|
-
* @param {Array} fields 字段名数组
|
|
2789
|
-
* @return {Promise<Array>} 与字段名顺序对应的数组值,不存在返回undefined
|
|
2790
|
-
*/
|
|
2791
|
-
Redis.prototype.hmget = async function (key, fields) {
|
|
2792
|
-
try {
|
|
2793
|
-
if (!Array.isArray(fields)) {
|
|
2794
|
-
throw new TypeError('fields必须是数组');
|
|
2795
|
-
}
|
|
2796
|
-
|
|
2797
|
-
this.emit('hmget:before', {
|
|
2798
|
-
key,
|
|
2799
|
-
fields
|
|
2800
|
-
});
|
|
2801
|
-
|
|
2802
|
-
const connection = await this._getConnection();
|
|
2803
|
-
try {
|
|
2804
|
-
key = this._addPrefix(key);
|
|
2805
|
-
|
|
2806
|
-
if (typeof connection.hmGet === 'function') {
|
|
2807
|
-
const result = await connection.hmGet(key, fields);
|
|
2808
|
-
// 将null转换为undefined以保持与CacheBase行为一致
|
|
2809
|
-
const finalResult = result.map(value => value === null ? undefined : value);
|
|
2810
|
-
this.emit('hmget:after', {
|
|
2811
|
-
key,
|
|
2812
|
-
fields,
|
|
2813
|
-
result: finalResult
|
|
2814
|
-
});
|
|
2815
|
-
return finalResult;
|
|
2816
|
-
} else if (typeof connection.hGetAll === 'function') {
|
|
2817
|
-
// 回退方案:获取所有字段然后筛选
|
|
2818
|
-
const allFields = await connection.hGetAll(key);
|
|
2819
|
-
const finalResult = fields.map(field => {
|
|
2820
|
-
const value = allFields[field];
|
|
2821
|
-
return value === null || value === undefined ? undefined : value;
|
|
2822
|
-
});
|
|
2823
|
-
this.emit('hmget:after', {
|
|
2824
|
-
key,
|
|
2825
|
-
fields,
|
|
2826
|
-
result: finalResult
|
|
2827
|
-
});
|
|
2828
|
-
return finalResult;
|
|
2829
|
-
} else {
|
|
2830
|
-
this.log('error', '连接对象不支持hmGet操作');
|
|
2831
|
-
this.log('error', '错误: Redis客户端不支持hmget操作');
|
|
2832
|
-
// 返回与fields长度相同的undefined数组,保持与注释一致
|
|
2833
|
-
return fields.map(() => undefined);
|
|
2834
|
-
}
|
|
2835
|
-
} finally {
|
|
2836
|
-
this._recycleConnection(connection);
|
|
2837
|
-
}
|
|
2838
|
-
} catch (err) {
|
|
2839
|
-
this.emit('hmget:error', {
|
|
2840
|
-
key,
|
|
2841
|
-
fields,
|
|
2842
|
-
error: err
|
|
2843
|
-
});
|
|
2844
|
-
this.log('error', `哈希表批量获取失败 [${key}]:`, err);
|
|
2845
|
-
// 返回与fields长度相同的undefined数组,保持与注释类型一致
|
|
2846
|
-
return fields.map(() => undefined);
|
|
2847
|
-
}
|
|
2848
|
-
};
|
|
2849
|
-
/**
|
|
2850
|
-
* @description 设置键的过期时间
|
|
2851
|
-
* @param {String} key 键
|
|
2852
|
-
* @param {Number} seconds 过期时间(秒)
|
|
2853
|
-
* @return {Promise<Boolean>} 操作结果
|
|
2854
|
-
*/
|
|
2855
|
-
Redis.prototype.expire = async function (key, seconds) {
|
|
2856
|
-
try {
|
|
2857
|
-
// 触发前置事件
|
|
2858
|
-
const ctx = {
|
|
2859
|
-
key,
|
|
2860
|
-
ttl: seconds,
|
|
2861
|
-
cancel: false
|
|
2862
|
-
};
|
|
2863
|
-
this.emit('expire:before', ctx);
|
|
2864
|
-
|
|
2865
|
-
if (ctx.cancel) {
|
|
2866
|
-
this.log('debug', `expire操作被事件处理器取消 [${key}]`);
|
|
2867
|
-
return false;
|
|
2868
|
-
}
|
|
2869
|
-
|
|
2870
|
-
const {
|
|
2871
|
-
key: finalKey,
|
|
2872
|
-
ttl: finalSeconds
|
|
2873
|
-
} = ctx;
|
|
2874
|
-
|
|
2875
|
-
const connection = await this._getConnection();
|
|
2876
|
-
try {
|
|
2877
|
-
// 使用正确的Redis客户端Promise API
|
|
2878
|
-
const result = await connection.expire(this._addPrefix(finalKey), finalSeconds);
|
|
2879
|
-
|
|
2880
|
-
// 触发后置事件
|
|
2881
|
-
this.emit('expire:after', {
|
|
2882
|
-
key: finalKey,
|
|
2883
|
-
ttl: finalSeconds,
|
|
2884
|
-
success: result === 1
|
|
2885
|
-
});
|
|
2886
|
-
return result === 1;
|
|
2887
|
-
} catch (err) {
|
|
2888
|
-
this.log('error', `设置过期时间失败 [${finalKey}]:`, err);
|
|
2889
|
-
this.emit('expire:error', {
|
|
2890
|
-
key: finalKey,
|
|
2891
|
-
ttl: finalSeconds,
|
|
2892
|
-
error: err
|
|
2893
|
-
});
|
|
2894
|
-
return false;
|
|
2895
|
-
} finally {
|
|
2896
|
-
this._recycleConnection(connection);
|
|
2897
|
-
}
|
|
2898
|
-
} catch (err) {
|
|
2899
|
-
this.log('error', `设置过期时间失败 [${key}]:`, err);
|
|
2900
|
-
this.emit('expire:error', {
|
|
2901
|
-
key,
|
|
2902
|
-
ttl: seconds,
|
|
2903
|
-
error: err
|
|
2904
|
-
});
|
|
2905
|
-
return false;
|
|
2906
|
-
}
|
|
2907
|
-
};
|
|
2908
|
-
|
|
2909
|
-
/**
|
|
2910
|
-
* @description 设置键的过期时间(毫秒)
|
|
2911
|
-
* @param {String} key 键
|
|
2912
|
-
* @param {Number} milliseconds 过期时间(毫秒)
|
|
2913
|
-
* @return {Promise<Boolean>} 操作结果
|
|
2914
|
-
*/
|
|
2915
|
-
Redis.prototype.pexpire = async function (key, milliseconds) {
|
|
2916
|
-
try {
|
|
2917
|
-
// 参数验证
|
|
2918
|
-
if (!key || typeof key !== 'string') {
|
|
2919
|
-
throw new TypeError('key必须是非空字符串');
|
|
2920
|
-
}
|
|
2921
|
-
if (!Number.isInteger(milliseconds) || milliseconds <= 0) {
|
|
2922
|
-
throw new TypeError('milliseconds必须是正整数');
|
|
2923
|
-
}
|
|
2924
|
-
|
|
2925
|
-
// 触发前置事件
|
|
2926
|
-
const ctx = {
|
|
2927
|
-
key,
|
|
2928
|
-
ttl: milliseconds,
|
|
2929
|
-
cancel: false
|
|
2930
|
-
};
|
|
2931
|
-
this.emit('pexpire:before', ctx);
|
|
2932
|
-
|
|
2933
|
-
if (ctx.cancel) {
|
|
2934
|
-
this.log('debug', `pexpire操作被事件处理器取消 [${key}]`);
|
|
2935
|
-
return false;
|
|
2936
|
-
}
|
|
2937
|
-
|
|
2938
|
-
const {
|
|
2939
|
-
key: finalKey,
|
|
2940
|
-
ttl: finalMilliseconds
|
|
2941
|
-
} = ctx;
|
|
2942
|
-
|
|
2943
|
-
const connection = await this._getConnection();
|
|
2944
|
-
try {
|
|
2945
|
-
// 使用正确的Redis客户端Promise API
|
|
2946
|
-
let result;
|
|
2947
|
-
if (typeof connection.pexpire === 'function') {
|
|
2948
|
-
result = await connection.pexpire(this._addPrefix(finalKey), finalMilliseconds);
|
|
2949
|
-
} else if (typeof connection.execute === 'function') {
|
|
2950
|
-
result = await connection.execute('PEXPIRE', [this._addPrefix(finalKey), finalMilliseconds]);
|
|
2951
|
-
} else {
|
|
2952
|
-
this.log('error', '连接对象不支持pexpire操作');
|
|
2953
|
-
return false;
|
|
2954
|
-
}
|
|
2955
|
-
|
|
2956
|
-
// 触发后置事件
|
|
2957
|
-
this.emit('pexpire:after', {
|
|
2958
|
-
key: finalKey,
|
|
2959
|
-
ttl: finalMilliseconds,
|
|
2960
|
-
success: result === 1
|
|
2961
|
-
});
|
|
2962
|
-
return result === 1;
|
|
2963
|
-
} catch (err) {
|
|
2964
|
-
this.log('error', `设置过期时间(毫秒)失败 [${finalKey}]:`, err);
|
|
2965
|
-
this.emit('pexpire:error', {
|
|
2966
|
-
key: finalKey,
|
|
2967
|
-
ttl: finalMilliseconds,
|
|
2968
|
-
error: err
|
|
2969
|
-
});
|
|
2970
|
-
return false;
|
|
2971
|
-
} finally {
|
|
2972
|
-
this._recycleConnection(connection);
|
|
2973
|
-
}
|
|
2974
|
-
} catch (err) {
|
|
2975
|
-
this.log('error', `设置过期时间(毫秒)失败 [${key}]:`, err);
|
|
2976
|
-
this.emit('pexpire:error', {
|
|
2977
|
-
key,
|
|
2978
|
-
ttl: milliseconds,
|
|
2979
|
-
error: err
|
|
2980
|
-
});
|
|
2981
|
-
return false;
|
|
2982
|
-
}
|
|
2983
|
-
};
|
|
2984
|
-
|
|
2985
|
-
// 新增persist方法实现
|
|
2986
|
-
Redis.prototype.persist = async function (key) {
|
|
2987
|
-
try {
|
|
2988
|
-
// 触发前置事件
|
|
2989
|
-
const ctx = {
|
|
2990
|
-
key,
|
|
2991
|
-
cancel: false
|
|
2992
|
-
};
|
|
2993
|
-
this.emit('persist:before', ctx);
|
|
2994
|
-
|
|
2995
|
-
if (ctx.cancel) {
|
|
2996
|
-
this.log('debug', `persist操作被事件处理器取消 [${key}]`);
|
|
2997
|
-
return false;
|
|
2998
|
-
}
|
|
2999
|
-
|
|
3000
|
-
const {
|
|
3001
|
-
key: finalKey
|
|
3002
|
-
} = ctx;
|
|
3003
|
-
|
|
3004
|
-
const connection = await this._getConnection();
|
|
3005
|
-
try {
|
|
3006
|
-
// 使用正确的Redis客户端Promise API
|
|
3007
|
-
const result = await connection.persist(this._addPrefix(finalKey));
|
|
3008
|
-
|
|
3009
|
-
// 触发后置事件
|
|
3010
|
-
this.emit('persist:after', {
|
|
3011
|
-
key: finalKey,
|
|
3012
|
-
success: result === 1
|
|
3013
|
-
});
|
|
3014
|
-
return result === 1;
|
|
3015
|
-
} catch (err) {
|
|
3016
|
-
this.log('error', `移除过期时间失败 [${finalKey}]:`, err);
|
|
3017
|
-
this.emit('persist:error', {
|
|
3018
|
-
key: finalKey,
|
|
3019
|
-
error: err
|
|
3020
|
-
});
|
|
3021
|
-
return false;
|
|
3022
|
-
} finally {
|
|
3023
|
-
this._recycleConnection(connection);
|
|
3024
|
-
}
|
|
3025
|
-
} catch (err) {
|
|
3026
|
-
this.log('error', `移除过期时间失败 [${key}]:`, err);
|
|
3027
|
-
this.emit('persist:error', {
|
|
3028
|
-
key,
|
|
3029
|
-
error: err
|
|
3030
|
-
});
|
|
3031
|
-
return false;
|
|
3032
|
-
}
|
|
3033
|
-
};
|
|
3034
|
-
|
|
3035
|
-
/**
|
|
3036
|
-
* @description 将键的值增加1(兼容方法,与addInt功能相同)
|
|
3037
|
-
* @param {String} key 键
|
|
3038
|
-
* @param {Number} [seconds=0] 过期时间(秒)
|
|
3039
|
-
* @return {Promise<Number>} 增加后的值
|
|
3040
|
-
*/
|
|
3041
|
-
Redis.prototype.incr = async function (key, seconds = 0) {
|
|
3042
|
-
try {
|
|
3043
|
-
// 参数验证
|
|
3044
|
-
if (!key || typeof key !== 'string') {
|
|
3045
|
-
throw new TypeError('键必须是非空字符串');
|
|
3046
|
-
}
|
|
3047
|
-
|
|
3048
|
-
// 触发前置事件
|
|
3049
|
-
const ctx = {
|
|
3050
|
-
key,
|
|
3051
|
-
seconds,
|
|
3052
|
-
cancel: false
|
|
3053
|
-
};
|
|
3054
|
-
this.emit('incr:before', ctx);
|
|
3055
|
-
|
|
3056
|
-
if (ctx.cancel) {
|
|
3057
|
-
this.log('debug', `incr操作被事件处理器取消 [${key}]`);
|
|
3058
|
-
return null;
|
|
3059
|
-
}
|
|
3060
|
-
|
|
3061
|
-
const {
|
|
3062
|
-
key: finalKey,
|
|
3063
|
-
seconds: finalSeconds
|
|
3064
|
-
} = ctx;
|
|
3065
|
-
|
|
3066
|
-
const connection = await this._getConnection();
|
|
3067
|
-
try {
|
|
3068
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
3069
|
-
// 使用正确的Redis客户端Promise API
|
|
3070
|
-
const result = await connection.incr(prefixedKey);
|
|
3071
|
-
|
|
3072
|
-
// 如果设置了过期时间
|
|
3073
|
-
if (finalSeconds > 0) {
|
|
3074
|
-
if (typeof connection.expire === 'function') {
|
|
3075
|
-
await connection.expire(prefixedKey, finalSeconds);
|
|
3076
|
-
} else if (typeof connection.execute === 'function') {
|
|
3077
|
-
await connection.execute('EXPIRE', [prefixedKey, finalSeconds]);
|
|
3078
|
-
}
|
|
3079
|
-
}
|
|
3080
|
-
|
|
3081
|
-
this.emit('incr:after', {
|
|
3082
|
-
key: finalKey,
|
|
3083
|
-
seconds: finalSeconds,
|
|
3084
|
-
result
|
|
3085
|
-
});
|
|
3086
|
-
return result;
|
|
3087
|
-
} catch (err) {
|
|
3088
|
-
this.log('error', `增加整数值失败 [${finalKey}]:`, err);
|
|
3089
|
-
this.emit('incr:error', {
|
|
3090
|
-
key: finalKey,
|
|
3091
|
-
seconds: finalSeconds,
|
|
3092
|
-
error: err
|
|
3093
|
-
});
|
|
3094
|
-
return null;
|
|
3095
|
-
} finally {
|
|
3096
|
-
this._recycleConnection(connection);
|
|
3097
|
-
}
|
|
3098
|
-
} catch (err) {
|
|
3099
|
-
this.log('error', `增加整数值失败 [${key}]:`, err);
|
|
3100
|
-
this.emit('incr:error', {
|
|
3101
|
-
key,
|
|
3102
|
-
seconds,
|
|
3103
|
-
error: err
|
|
3104
|
-
});
|
|
3105
|
-
return null;
|
|
3106
|
-
}
|
|
3107
|
-
};
|
|
3108
|
-
|
|
3109
|
-
/**
|
|
3110
|
-
* @description 将键的值减少1(兼容方法)
|
|
3111
|
-
* @param {String} key 键
|
|
3112
|
-
* @param {Number} [seconds=0] 过期时间(秒)
|
|
3113
|
-
* @return {Promise<Number>} 减少后的值
|
|
3114
|
-
*/
|
|
3115
|
-
Redis.prototype.decr = async function (key, seconds = 0) {
|
|
3116
|
-
try {
|
|
3117
|
-
// 参数验证
|
|
3118
|
-
if (!key || typeof key !== 'string') {
|
|
3119
|
-
throw new TypeError('键必须是非空字符串');
|
|
3120
|
-
}
|
|
3121
|
-
|
|
3122
|
-
// 触发前置事件
|
|
3123
|
-
const ctx = {
|
|
3124
|
-
key,
|
|
3125
|
-
seconds,
|
|
3126
|
-
cancel: false
|
|
3127
|
-
};
|
|
3128
|
-
this.emit('decr:before', ctx);
|
|
3129
|
-
|
|
3130
|
-
if (ctx.cancel) {
|
|
3131
|
-
this.log('debug', `decr操作被事件处理器取消 [${key}]`);
|
|
3132
|
-
return null;
|
|
3133
|
-
}
|
|
3134
|
-
|
|
3135
|
-
const {
|
|
3136
|
-
key: finalKey,
|
|
3137
|
-
seconds: finalSeconds
|
|
3138
|
-
} = ctx;
|
|
3139
|
-
|
|
3140
|
-
const connection = await this._getConnection();
|
|
3141
|
-
try {
|
|
3142
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
3143
|
-
// 使用正确的Redis客户端Promise API
|
|
3144
|
-
const result = await connection.decr(prefixedKey);
|
|
3145
|
-
|
|
3146
|
-
// 如果设置了过期时间
|
|
3147
|
-
if (finalSeconds > 0) {
|
|
3148
|
-
if (typeof connection.expire === 'function') {
|
|
3149
|
-
await connection.expire(prefixedKey, finalSeconds);
|
|
3150
|
-
} else if (typeof connection.execute === 'function') {
|
|
3151
|
-
await connection.execute('EXPIRE', [prefixedKey, finalSeconds]);
|
|
3152
|
-
}
|
|
3153
|
-
}
|
|
3154
|
-
|
|
3155
|
-
this.emit('decr:after', {
|
|
3156
|
-
key: finalKey,
|
|
3157
|
-
seconds: finalSeconds,
|
|
3158
|
-
result
|
|
3159
|
-
});
|
|
3160
|
-
return result;
|
|
3161
|
-
} catch (err) {
|
|
3162
|
-
this.log('error', `减少整数值失败 [${finalKey}]:`, err);
|
|
3163
|
-
this.emit('decr:error', {
|
|
3164
|
-
key: finalKey,
|
|
3165
|
-
seconds: finalSeconds,
|
|
3166
|
-
error: err
|
|
3167
|
-
});
|
|
3168
|
-
return null;
|
|
3169
|
-
} finally {
|
|
3170
|
-
this._recycleConnection(connection);
|
|
3171
|
-
}
|
|
3172
|
-
} catch (err) {
|
|
3173
|
-
this.log('error', `减少整数值失败 [${key}]:`, err);
|
|
3174
|
-
this.emit('decr:error', {
|
|
3175
|
-
key,
|
|
3176
|
-
seconds,
|
|
3177
|
-
error: err
|
|
3178
|
-
});
|
|
3179
|
-
return null;
|
|
3180
|
-
}
|
|
3181
|
-
};
|
|
3182
|
-
|
|
3183
|
-
/**
|
|
3184
|
-
* @description 将键的值增加指定的浮点数(兼容方法,与addFloat功能相同)
|
|
3185
|
-
* @param {String} key 键
|
|
3186
|
-
* @param {Number} increment 浮点数增量
|
|
3187
|
-
* @param {Number} [seconds=0] 过期时间(秒)
|
|
3188
|
-
* @return {Promise<Number>} 增加后的值
|
|
3189
|
-
*/
|
|
3190
|
-
Redis.prototype.incrbyfloat = async function (key, increment, seconds = 0) {
|
|
3191
|
-
try {
|
|
3192
|
-
// 参数验证
|
|
3193
|
-
if (!key || typeof key !== 'string') {
|
|
3194
|
-
throw new TypeError('键必须是非空字符串');
|
|
3195
|
-
}
|
|
3196
|
-
if (typeof increment !== 'number' || isNaN(increment)) {
|
|
3197
|
-
throw new TypeError('增量必须是有效数字');
|
|
3198
|
-
}
|
|
3199
|
-
|
|
3200
|
-
// 触发前置事件
|
|
3201
|
-
const ctx = {
|
|
3202
|
-
key,
|
|
3203
|
-
increment,
|
|
3204
|
-
seconds,
|
|
3205
|
-
cancel: false
|
|
3206
|
-
};
|
|
3207
|
-
this.emit('incrbyfloat:before', ctx);
|
|
3208
|
-
|
|
3209
|
-
if (ctx.cancel) {
|
|
3210
|
-
this.log('debug', `incrbyfloat操作被事件处理器取消 [${key}]`);
|
|
3211
|
-
return null;
|
|
3212
|
-
}
|
|
3213
|
-
|
|
3214
|
-
const {
|
|
3215
|
-
key: finalKey,
|
|
3216
|
-
increment: finalIncrement,
|
|
3217
|
-
seconds: finalSeconds
|
|
3218
|
-
} = ctx;
|
|
3219
|
-
|
|
3220
|
-
const connection = await this._getConnection();
|
|
3221
|
-
try {
|
|
3222
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
3223
|
-
// 使用正确的Redis客户端Promise API
|
|
3224
|
-
const result = await connection.incrByFloat(prefixedKey, finalIncrement);
|
|
3225
|
-
const parsedResult = parseFloat(result);
|
|
3226
|
-
|
|
3227
|
-
// 如果设置了过期时间
|
|
3228
|
-
if (finalSeconds > 0) {
|
|
3229
|
-
if (typeof connection.expire === 'function') {
|
|
3230
|
-
await connection.expire(prefixedKey, finalSeconds);
|
|
3231
|
-
} else if (typeof connection.execute === 'function') {
|
|
3232
|
-
await connection.execute('EXPIRE', [prefixedKey, finalSeconds]);
|
|
3233
|
-
}
|
|
3234
|
-
}
|
|
3235
|
-
|
|
3236
|
-
this.emit('incrbyfloat:after', {
|
|
3237
|
-
key: finalKey,
|
|
3238
|
-
increment: finalIncrement,
|
|
3239
|
-
seconds: finalSeconds,
|
|
3240
|
-
result: parsedResult
|
|
3241
|
-
});
|
|
3242
|
-
return parsedResult;
|
|
3243
|
-
} catch (err) {
|
|
3244
|
-
this.log('error', `增加浮点数值失败 [${finalKey}]:`, err);
|
|
3245
|
-
this.emit('incrbyfloat:error', {
|
|
3246
|
-
key: finalKey,
|
|
3247
|
-
increment: finalIncrement,
|
|
3248
|
-
seconds: finalSeconds,
|
|
3249
|
-
error: err
|
|
3250
|
-
});
|
|
3251
|
-
return null;
|
|
3252
|
-
} finally {
|
|
3253
|
-
this._recycleConnection(connection);
|
|
3254
|
-
}
|
|
3255
|
-
} catch (err) {
|
|
3256
|
-
this.log('error', `增加浮点数值失败 [${key}]:`, err);
|
|
3257
|
-
this.emit('incrbyfloat:error', {
|
|
3258
|
-
key,
|
|
3259
|
-
increment,
|
|
3260
|
-
seconds,
|
|
3261
|
-
error: err
|
|
3262
|
-
});
|
|
3263
|
-
return null;
|
|
3264
|
-
}
|
|
3265
|
-
};
|
|
3266
|
-
|
|
3267
|
-
/**
|
|
3268
|
-
* @description 将键的值增加指定的整数(兼容方法)
|
|
3269
|
-
* @param {String} key 键
|
|
3270
|
-
* @param {Number} increment 增量
|
|
3271
|
-
* @param {Number} [seconds=0] 过期时间(秒)
|
|
3272
|
-
* @return {Promise<Number>} 增加后的值
|
|
3273
|
-
*/
|
|
3274
|
-
Redis.prototype.incrby = async function (key, increment, seconds = 0) {
|
|
3275
|
-
try {
|
|
3276
|
-
// 参数验证
|
|
3277
|
-
if (!key || typeof key !== 'string') {
|
|
3278
|
-
throw new TypeError('键必须是非空字符串');
|
|
3279
|
-
}
|
|
3280
|
-
if (typeof increment !== 'number' || isNaN(increment) || !Number.isInteger(increment)) {
|
|
3281
|
-
throw new TypeError('增量必须是有效整数');
|
|
3282
|
-
}
|
|
3283
|
-
|
|
3284
|
-
// 触发前置事件
|
|
3285
|
-
const ctx = {
|
|
3286
|
-
key,
|
|
3287
|
-
increment,
|
|
3288
|
-
seconds,
|
|
3289
|
-
cancel: false
|
|
3290
|
-
};
|
|
3291
|
-
this.emit('incrby:before', ctx);
|
|
3292
|
-
|
|
3293
|
-
if (ctx.cancel) {
|
|
3294
|
-
this.log('debug', `incrby操作被事件处理器取消 [${key}]`);
|
|
3295
|
-
return null;
|
|
3296
|
-
}
|
|
3297
|
-
|
|
3298
|
-
const {
|
|
3299
|
-
key: finalKey,
|
|
3300
|
-
increment: finalIncrement,
|
|
3301
|
-
seconds: finalSeconds
|
|
3302
|
-
} = ctx;
|
|
3303
|
-
|
|
3304
|
-
const connection = await this._getConnection();
|
|
3305
|
-
try {
|
|
3306
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
3307
|
-
// 使用正确的Redis客户端Promise API
|
|
3308
|
-
const result = await connection.incrBy(prefixedKey, finalIncrement);
|
|
3309
|
-
|
|
3310
|
-
// 如果设置了过期时间
|
|
3311
|
-
if (finalSeconds > 0) {
|
|
3312
|
-
if (typeof connection.expire === 'function') {
|
|
3313
|
-
await connection.expire(prefixedKey, finalSeconds);
|
|
3314
|
-
} else if (typeof connection.execute === 'function') {
|
|
3315
|
-
await connection.execute('EXPIRE', [prefixedKey, finalSeconds]);
|
|
3316
|
-
}
|
|
3317
|
-
}
|
|
3318
|
-
|
|
3319
|
-
this.emit('incrby:after', {
|
|
3320
|
-
key: finalKey,
|
|
3321
|
-
increment: finalIncrement,
|
|
3322
|
-
seconds: finalSeconds,
|
|
3323
|
-
result
|
|
3324
|
-
});
|
|
3325
|
-
return result;
|
|
3326
|
-
} catch (err) {
|
|
3327
|
-
this.log('error', `增加指定值失败 [${finalKey}]:`, err);
|
|
3328
|
-
this.emit('incrby:error', {
|
|
3329
|
-
key: finalKey,
|
|
3330
|
-
increment: finalIncrement,
|
|
3331
|
-
seconds: finalSeconds,
|
|
3332
|
-
error: err
|
|
3333
|
-
});
|
|
3334
|
-
return null;
|
|
3335
|
-
} finally {
|
|
3336
|
-
this._recycleConnection(connection);
|
|
3337
|
-
}
|
|
3338
|
-
} catch (err) {
|
|
3339
|
-
this.log('error', `增加指定值失败 [${key}]:`, err);
|
|
3340
|
-
this.emit('incrby:error', {
|
|
3341
|
-
key,
|
|
3342
|
-
increment,
|
|
3343
|
-
seconds,
|
|
3344
|
-
error: err
|
|
3345
|
-
});
|
|
3346
|
-
return null;
|
|
3347
|
-
}
|
|
3348
|
-
};
|
|
3349
|
-
|
|
3350
|
-
/**
|
|
3351
|
-
* @description 将键的值减少指定的整数(兼容方法)
|
|
3352
|
-
* @param {String} key 键
|
|
3353
|
-
* @param {Number} decrement 减量
|
|
3354
|
-
* @param {Number} [seconds=0] 过期时间(秒)
|
|
3355
|
-
* @return {Promise<Number>} 减少后的值
|
|
3356
|
-
*/
|
|
3357
|
-
Redis.prototype.decrby = async function (key, decrement, seconds = 0) {
|
|
3358
|
-
try {
|
|
3359
|
-
// 参数验证
|
|
3360
|
-
if (!key || typeof key !== 'string') {
|
|
3361
|
-
throw new TypeError('键必须是非空字符串');
|
|
3362
|
-
}
|
|
3363
|
-
if (typeof decrement !== 'number' || isNaN(decrement) || !Number.isInteger(decrement)) {
|
|
3364
|
-
throw new TypeError('减量必须是有效整数');
|
|
3365
|
-
}
|
|
3366
|
-
|
|
3367
|
-
// 触发前置事件
|
|
3368
|
-
const ctx = {
|
|
3369
|
-
key,
|
|
3370
|
-
decrement,
|
|
3371
|
-
seconds,
|
|
3372
|
-
cancel: false
|
|
3373
|
-
};
|
|
3374
|
-
this.emit('decrby:before', ctx);
|
|
3375
|
-
|
|
3376
|
-
if (ctx.cancel) {
|
|
3377
|
-
this.log('debug', `decrby操作被事件处理器取消 [${key}]`);
|
|
3378
|
-
return null;
|
|
3379
|
-
}
|
|
3380
|
-
|
|
3381
|
-
const {
|
|
3382
|
-
key: finalKey,
|
|
3383
|
-
decrement: finalDecrement,
|
|
3384
|
-
seconds: finalSeconds
|
|
3385
|
-
} = ctx;
|
|
3386
|
-
|
|
3387
|
-
const connection = await this._getConnection();
|
|
3388
|
-
try {
|
|
3389
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
3390
|
-
// 使用正确的Redis客户端Promise API
|
|
3391
|
-
const result = await connection.decrBy(prefixedKey, finalDecrement);
|
|
3392
|
-
|
|
3393
|
-
// 如果设置了过期时间
|
|
3394
|
-
if (finalSeconds > 0) {
|
|
3395
|
-
if (typeof connection.expire === 'function') {
|
|
3396
|
-
await connection.expire(prefixedKey, finalSeconds);
|
|
3397
|
-
} else if (typeof connection.execute === 'function') {
|
|
3398
|
-
await connection.execute('EXPIRE', [prefixedKey, finalSeconds]);
|
|
3399
|
-
}
|
|
3400
|
-
}
|
|
3401
|
-
|
|
3402
|
-
this.emit('decrby:after', {
|
|
3403
|
-
key: finalKey,
|
|
3404
|
-
decrement: finalDecrement,
|
|
3405
|
-
seconds: finalSeconds,
|
|
3406
|
-
result
|
|
3407
|
-
});
|
|
3408
|
-
return result;
|
|
3409
|
-
} catch (err) {
|
|
3410
|
-
this.log('error', `减少指定值失败 [${finalKey}]:`, err);
|
|
3411
|
-
this.emit('decrby:error', {
|
|
3412
|
-
key: finalKey,
|
|
3413
|
-
decrement: finalDecrement,
|
|
3414
|
-
seconds: finalSeconds,
|
|
3415
|
-
error: err
|
|
3416
|
-
});
|
|
3417
|
-
return null;
|
|
3418
|
-
} finally {
|
|
3419
|
-
this._recycleConnection(connection);
|
|
3420
|
-
}
|
|
3421
|
-
} catch (err) {
|
|
3422
|
-
this.log('error', `减少指定值失败 [${key}]:`, err);
|
|
3423
|
-
this.emit('decrby:error', {
|
|
3424
|
-
key,
|
|
3425
|
-
decrement,
|
|
3426
|
-
seconds,
|
|
3427
|
-
error: err
|
|
3428
|
-
});
|
|
3429
|
-
return null;
|
|
3430
|
-
}
|
|
3431
|
-
};
|
|
3432
|
-
|
|
3433
|
-
// 集合操作方法
|
|
3434
|
-
|
|
3435
|
-
/**
|
|
3436
|
-
* @description 将成员添加到集合
|
|
3437
|
-
* @param {String} key 键
|
|
3438
|
-
* @param {String|Array} members 成员或成员数组
|
|
3439
|
-
* @return {Promise<Number>} 添加的成员数量
|
|
3440
|
-
*/
|
|
3441
|
-
Redis.prototype.sadd = async function (key, members) {
|
|
3442
|
-
try {
|
|
3443
|
-
// 触发前置事件
|
|
3444
|
-
const ctx = {
|
|
3445
|
-
key,
|
|
3446
|
-
members: Array.isArray(members) ? members : [members],
|
|
3447
|
-
cancel: false
|
|
3448
|
-
};
|
|
3449
|
-
this.emit('sadd:before', ctx);
|
|
3450
|
-
|
|
3451
|
-
if (ctx.cancel) {
|
|
3452
|
-
this.log('debug', `sadd操作被事件处理器取消 [${key}]`);
|
|
3453
|
-
return 0;
|
|
3454
|
-
}
|
|
3455
|
-
|
|
3456
|
-
const {
|
|
3457
|
-
key: finalKey,
|
|
3458
|
-
members: finalMembers
|
|
3459
|
-
} = ctx;
|
|
3460
|
-
|
|
3461
|
-
const connection = await this._getConnection();
|
|
3462
|
-
try {
|
|
3463
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
3464
|
-
const result = await connection.sAdd(prefixedKey, finalMembers);
|
|
3465
|
-
|
|
3466
|
-
// 触发后置事件
|
|
3467
|
-
this.emit('sadd:after', {
|
|
3468
|
-
key: finalKey,
|
|
3469
|
-
members: finalMembers,
|
|
3470
|
-
result
|
|
3471
|
-
});
|
|
3472
|
-
return result;
|
|
3473
|
-
} finally {
|
|
3474
|
-
this._recycleConnection(connection);
|
|
3475
|
-
}
|
|
3476
|
-
} catch (err) {
|
|
3477
|
-
this.log('error', `添加集合成员失败 [${key}]:`, err);
|
|
3478
|
-
this.emit('sadd:error', {
|
|
3479
|
-
key,
|
|
3480
|
-
members,
|
|
3481
|
-
error: err
|
|
3482
|
-
});
|
|
3483
|
-
return 0;
|
|
3484
|
-
}
|
|
3485
|
-
};
|
|
3486
|
-
|
|
3487
|
-
/**
|
|
3488
|
-
* @description 从集合中移除成员
|
|
3489
|
-
* @param {String} key 键
|
|
3490
|
-
* @param {String|Array} members 成员或成员数组
|
|
3491
|
-
* @return {Promise<Number>} 移除的成员数量
|
|
3492
|
-
*/
|
|
3493
|
-
Redis.prototype.srem = async function (key, members) {
|
|
3494
|
-
try {
|
|
3495
|
-
// 触发前置事件
|
|
3496
|
-
const ctx = {
|
|
3497
|
-
key,
|
|
3498
|
-
members: Array.isArray(members) ? members : [members],
|
|
3499
|
-
cancel: false
|
|
3500
|
-
};
|
|
3501
|
-
this.emit('srem:before', ctx);
|
|
3502
|
-
|
|
3503
|
-
if (ctx.cancel) {
|
|
3504
|
-
this.log('debug', `srem操作被事件处理器取消 [${key}]`);
|
|
3505
|
-
return 0;
|
|
3506
|
-
}
|
|
3507
|
-
|
|
3508
|
-
const {
|
|
3509
|
-
key: finalKey,
|
|
3510
|
-
members: finalMembers
|
|
3511
|
-
} = ctx;
|
|
3512
|
-
|
|
3513
|
-
const connection = await this._getConnection();
|
|
3514
|
-
try {
|
|
3515
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
3516
|
-
const result = await connection.sRem(prefixedKey, finalMembers);
|
|
3517
|
-
|
|
3518
|
-
// 触发后置事件
|
|
3519
|
-
this.emit('srem:after', {
|
|
3520
|
-
key: finalKey,
|
|
3521
|
-
members: finalMembers,
|
|
3522
|
-
result
|
|
3523
|
-
});
|
|
3524
|
-
return result;
|
|
3525
|
-
} finally {
|
|
3526
|
-
this._recycleConnection(connection);
|
|
3527
|
-
}
|
|
3528
|
-
} catch (err) {
|
|
3529
|
-
this.log('error', `移除集合成员失败 [${key}]:`, err);
|
|
3530
|
-
this.emit('srem:error', {
|
|
3531
|
-
key,
|
|
3532
|
-
members,
|
|
3533
|
-
error: err
|
|
3534
|
-
});
|
|
3535
|
-
return 0;
|
|
3536
|
-
}
|
|
3537
|
-
};
|
|
3538
|
-
|
|
3539
|
-
/**
|
|
3540
|
-
* @description 检查成员是否在集合中
|
|
3541
|
-
* @param {String} key 键
|
|
3542
|
-
* @param {String} member 成员
|
|
3543
|
-
* @return {Promise<Boolean>} 是否存在
|
|
3544
|
-
*/
|
|
3545
|
-
Redis.prototype.sismember = async function (key, member) {
|
|
3546
|
-
try {
|
|
3547
|
-
// 参数验证
|
|
3548
|
-
if (!key || typeof key !== 'string') {
|
|
3549
|
-
throw new TypeError('key必须是非空字符串');
|
|
3550
|
-
}
|
|
3551
|
-
if (member === undefined || member === null) {
|
|
3552
|
-
throw new TypeError('member不能为undefined或null');
|
|
3553
|
-
}
|
|
3554
|
-
|
|
3555
|
-
this.emit('sismember:before', {
|
|
3556
|
-
key,
|
|
3557
|
-
member
|
|
3558
|
-
});
|
|
3559
|
-
const connection = await this._getConnection();
|
|
3560
|
-
try {
|
|
3561
|
-
const prefixedKey = this._addPrefix(key);
|
|
3562
|
-
// 将成员转换为字符串,与其他模块行为一致
|
|
3563
|
-
const memberStr = String(member);
|
|
3564
|
-
const result = await connection.sIsMember(prefixedKey, memberStr);
|
|
3565
|
-
this.emit('sismember:after', {
|
|
3566
|
-
key,
|
|
3567
|
-
member,
|
|
3568
|
-
result
|
|
3569
|
-
});
|
|
3570
|
-
return result;
|
|
3571
|
-
} finally {
|
|
3572
|
-
this._recycleConnection(connection);
|
|
3573
|
-
}
|
|
3574
|
-
} catch (err) {
|
|
3575
|
-
this.emit('sismember:error', {
|
|
3576
|
-
key,
|
|
3577
|
-
member,
|
|
3578
|
-
error: err
|
|
3579
|
-
});
|
|
3580
|
-
this.log('error', `检查集合成员失败 [${key}:${member}]:`, err);
|
|
3581
|
-
return false;
|
|
3582
|
-
}
|
|
3583
|
-
};
|
|
3584
|
-
|
|
3585
|
-
/**
|
|
3586
|
-
* @description 获取集合中的所有成员
|
|
3587
|
-
* @param {String} key 键
|
|
3588
|
-
* @return {Promise<Array>} 成员数组
|
|
3589
|
-
*/
|
|
3590
|
-
Redis.prototype.smembers = async function (key) {
|
|
3591
|
-
try {
|
|
3592
|
-
const connection = await this._getConnection();
|
|
3593
|
-
try {
|
|
3594
|
-
const prefixedKey = this._addPrefix(key);
|
|
3595
|
-
const result = await connection.sMembers(prefixedKey);
|
|
3596
|
-
this.emit('smembers:after', {
|
|
3597
|
-
key,
|
|
3598
|
-
result
|
|
3599
|
-
});
|
|
3600
|
-
return result || [];
|
|
3601
|
-
} finally {
|
|
3602
|
-
this._recycleConnection(connection);
|
|
3603
|
-
}
|
|
3604
|
-
} catch (err) {
|
|
3605
|
-
this.log('error', `获取集合所有成员失败 [${key}]:`, err);
|
|
3606
|
-
this.emit('smembers:error', {
|
|
3607
|
-
key,
|
|
3608
|
-
error: err
|
|
3609
|
-
});
|
|
3610
|
-
return [];
|
|
3611
|
-
}
|
|
3612
|
-
};
|
|
3613
|
-
|
|
3614
|
-
/**
|
|
3615
|
-
* @description 获取集合中的成员数量
|
|
3616
|
-
* @param {String} key 键
|
|
3617
|
-
* @return {Promise<Number>} 成员数量
|
|
3618
|
-
*/
|
|
3619
|
-
Redis.prototype.scard = async function (key) {
|
|
3620
|
-
try {
|
|
3621
|
-
const connection = await this._getConnection();
|
|
3622
|
-
try {
|
|
3623
|
-
const prefixedKey = this._addPrefix(key);
|
|
3624
|
-
const result = await connection.sCard(prefixedKey);
|
|
3625
|
-
return result;
|
|
3626
|
-
} finally {
|
|
3627
|
-
this._recycleConnection(connection);
|
|
3628
|
-
}
|
|
3629
|
-
} catch (err) {
|
|
3630
|
-
this.log('error', `获取集合大小失败 [${key}]:`, err);
|
|
3631
|
-
return 0;
|
|
3632
|
-
}
|
|
3633
|
-
};
|
|
3634
|
-
|
|
3635
|
-
/**
|
|
3636
|
-
* @description 获取多个集合的交集
|
|
3637
|
-
* @param {Array} keys 键数组
|
|
3638
|
-
* @return {Promise<Array>} 交集成员数组
|
|
3639
|
-
*/
|
|
3640
|
-
Redis.prototype.sinter = async function (keys) {
|
|
3641
|
-
try {
|
|
3642
|
-
const connection = await this._getConnection();
|
|
3643
|
-
try {
|
|
3644
|
-
const prefixedKeys = keys.map(key => this._addPrefix(key));
|
|
3645
|
-
// 使用Redis v4正确的方法
|
|
3646
|
-
const result = await connection.sInter(...prefixedKeys);
|
|
3647
|
-
return result || [];
|
|
3648
|
-
} finally {
|
|
3649
|
-
this._recycleConnection(connection);
|
|
3650
|
-
}
|
|
3651
|
-
} catch (err) {
|
|
3652
|
-
this.log('error', `获取集合交集失败:`, err);
|
|
3653
|
-
return [];
|
|
3654
|
-
}
|
|
3655
|
-
};
|
|
3656
|
-
|
|
3657
|
-
/**
|
|
3658
|
-
* @description 获取多个集合的并集
|
|
3659
|
-
* @param {Array} keys 键数组
|
|
3660
|
-
* @return {Promise<Array>} 并集成员数组
|
|
3661
|
-
*/
|
|
3662
|
-
Redis.prototype.sunion = async function (keys) {
|
|
3663
|
-
try {
|
|
3664
|
-
const connection = await this._getConnection();
|
|
3665
|
-
try {
|
|
3666
|
-
const prefixedKeys = keys.map(key => this._addPrefix(key));
|
|
3667
|
-
// 使用Redis v4正确的方法
|
|
3668
|
-
const result = await connection.sUnion(...prefixedKeys);
|
|
3669
|
-
return result || [];
|
|
3670
|
-
} finally {
|
|
3671
|
-
this._recycleConnection(connection);
|
|
3672
|
-
}
|
|
3673
|
-
} catch (err) {
|
|
3674
|
-
this.log('error', `获取集合并集失败:`, err);
|
|
3675
|
-
return [];
|
|
3676
|
-
}
|
|
3677
|
-
};
|
|
3678
|
-
|
|
3679
|
-
/**
|
|
3680
|
-
* @description 获取多个集合的差集
|
|
3681
|
-
* @param {Array} keys 键数组
|
|
3682
|
-
* @return {Promise<Array>} 差集成员数组
|
|
3683
|
-
*/
|
|
3684
|
-
Redis.prototype.sdiff = async function (keys) {
|
|
3685
|
-
try {
|
|
3686
|
-
const connection = await this._getConnection();
|
|
3687
|
-
try {
|
|
3688
|
-
const prefixedKeys = keys.map(key => this._addPrefix(key));
|
|
3689
|
-
// 使用Redis v4正确的方法
|
|
3690
|
-
const result = await connection.sDiff(...prefixedKeys);
|
|
3691
|
-
return result || [];
|
|
3692
|
-
} finally {
|
|
3693
|
-
this._recycleConnection(connection);
|
|
3694
|
-
}
|
|
3695
|
-
} catch (err) {
|
|
3696
|
-
this.log('error', `获取集合差集失败:`, err);
|
|
3697
|
-
return [];
|
|
3698
|
-
}
|
|
3699
|
-
};
|
|
3700
|
-
|
|
3701
|
-
// 有序集合操作方法
|
|
3702
|
-
|
|
3703
|
-
/**
|
|
3704
|
-
* @description 添加成员到有序集合
|
|
3705
|
-
* @param {String} key 键
|
|
3706
|
-
* @param {Number} score 分数
|
|
3707
|
-
* @param {String} member 成员
|
|
3708
|
-
* @return {Promise<Boolean>} 是否成功
|
|
3709
|
-
*/
|
|
3710
|
-
Redis.prototype.zadd = async function (key, score, member) {
|
|
3711
|
-
try {
|
|
3712
|
-
// 触发前置事件
|
|
3713
|
-
const ctx = {
|
|
3714
|
-
key,
|
|
3715
|
-
score,
|
|
3716
|
-
member,
|
|
3717
|
-
cancel: false
|
|
3718
|
-
};
|
|
3719
|
-
this.emit('zadd:before', ctx);
|
|
3720
|
-
|
|
3721
|
-
if (ctx.cancel) {
|
|
3722
|
-
this.log('debug', `zadd操作被事件处理器取消 [${key}]`);
|
|
3723
|
-
return false;
|
|
3724
|
-
}
|
|
3725
|
-
|
|
3726
|
-
const {
|
|
3727
|
-
key: finalKey,
|
|
3728
|
-
score: finalScore,
|
|
3729
|
-
member: finalMember
|
|
3730
|
-
} = ctx;
|
|
3731
|
-
|
|
3732
|
-
const connection = await this._getConnection();
|
|
3733
|
-
try {
|
|
3734
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
3735
|
-
const result = await connection.zAdd(prefixedKey, {
|
|
3736
|
-
score: finalScore,
|
|
3737
|
-
value: finalMember
|
|
3738
|
-
});
|
|
3739
|
-
|
|
3740
|
-
// 触发后置事件
|
|
3741
|
-
this.emit('zadd:after', {
|
|
3742
|
-
key: finalKey,
|
|
3743
|
-
score: finalScore,
|
|
3744
|
-
member: finalMember,
|
|
3745
|
-
success: result === 1
|
|
3746
|
-
});
|
|
3747
|
-
return result === 1;
|
|
3748
|
-
} finally {
|
|
3749
|
-
this._recycleConnection(connection);
|
|
3750
|
-
}
|
|
3751
|
-
} catch (err) {
|
|
3752
|
-
this.log('error', `添加有序集合成员失败 [${key}]:`, err);
|
|
3753
|
-
this.emit('zadd:error', {
|
|
3754
|
-
key,
|
|
3755
|
-
score,
|
|
3756
|
-
member,
|
|
3757
|
-
error: err
|
|
3758
|
-
});
|
|
3759
|
-
return false;
|
|
3760
|
-
}
|
|
3761
|
-
};
|
|
3762
|
-
|
|
3763
|
-
/**
|
|
3764
|
-
* @description 从有序集合中移除成员
|
|
3765
|
-
* @param {String} key 键
|
|
3766
|
-
* @param {String|Array} members 成员或成员数组
|
|
3767
|
-
* @return {Promise<Number>} 移除的成员数量
|
|
3768
|
-
*/
|
|
3769
|
-
Redis.prototype.zrem = async function (key, members) {
|
|
3770
|
-
try {
|
|
3771
|
-
// 触发前置事件
|
|
3772
|
-
const ctx = {
|
|
3773
|
-
key,
|
|
3774
|
-
members: Array.isArray(members) ? members : [members],
|
|
3775
|
-
cancel: false
|
|
3776
|
-
};
|
|
3777
|
-
this.emit('zrem:before', ctx);
|
|
3778
|
-
|
|
3779
|
-
if (ctx.cancel) {
|
|
3780
|
-
this.log('debug', `zrem操作被事件处理器取消 [${key}]`);
|
|
3781
|
-
return 0;
|
|
3782
|
-
}
|
|
3783
|
-
|
|
3784
|
-
const {
|
|
3785
|
-
key: finalKey,
|
|
3786
|
-
members: finalMembers
|
|
3787
|
-
} = ctx;
|
|
3788
|
-
|
|
3789
|
-
const connection = await this._getConnection();
|
|
3790
|
-
try {
|
|
3791
|
-
// 使用正确的Redis客户端Promise API
|
|
3792
|
-
const result = await connection.zRem(this._addPrefix(finalKey), ...finalMembers);
|
|
3793
|
-
|
|
3794
|
-
// 触发后置事件
|
|
3795
|
-
this.emit('zrem:after', {
|
|
3796
|
-
key: finalKey,
|
|
3797
|
-
members: finalMembers,
|
|
3798
|
-
result
|
|
3799
|
-
});
|
|
3800
|
-
return result;
|
|
3801
|
-
} catch (err) {
|
|
3802
|
-
this.log('error', `移除有序集合成员失败 [${finalKey}]:`, err);
|
|
3803
|
-
this.emit('zrem:error', {
|
|
3804
|
-
key: finalKey,
|
|
3805
|
-
members: finalMembers,
|
|
3806
|
-
error: err
|
|
3807
|
-
});
|
|
3808
|
-
return 0;
|
|
3809
|
-
} finally {
|
|
3810
|
-
this._recycleConnection(connection);
|
|
3811
|
-
}
|
|
3812
|
-
} catch (err) {
|
|
3813
|
-
this.log('error', `移除有序集合成员失败 [${key}]:`, err);
|
|
3814
|
-
this.emit('zrem:error', {
|
|
3815
|
-
key,
|
|
3816
|
-
members,
|
|
3817
|
-
error: err
|
|
3818
|
-
});
|
|
3819
|
-
return 0;
|
|
3820
|
-
}
|
|
3821
|
-
};
|
|
3822
|
-
|
|
3823
|
-
/**
|
|
3824
|
-
* @description 获取有序集合中成员的分数
|
|
3825
|
-
* @param {String} key 键
|
|
3826
|
-
* @param {String} member 成员
|
|
3827
|
-
* @return {Promise<Number|null>} 分数
|
|
3828
|
-
*/
|
|
3829
|
-
Redis.prototype.zscore = async function (key, member) {
|
|
3830
|
-
try {
|
|
3831
|
-
const connection = await this._getConnection();
|
|
3832
|
-
try {
|
|
3833
|
-
const prefixedKey = this._addPrefix(key);
|
|
3834
|
-
const result = await connection.zScore(prefixedKey, member);
|
|
3835
|
-
return result ? parseFloat(result) : null;
|
|
3836
|
-
} finally {
|
|
3837
|
-
this._recycleConnection(connection);
|
|
3838
|
-
}
|
|
3839
|
-
} catch (err) {
|
|
3840
|
-
this.log('error', `获取有序集合成员分数失败 [${key}:${member}]:`, err);
|
|
3841
|
-
return null;
|
|
3842
|
-
}
|
|
3843
|
-
};
|
|
3844
|
-
|
|
3845
|
-
/**
|
|
3846
|
-
* @description 获取有序集合中成员的排名(分数从小到大)
|
|
3847
|
-
* @param {String} key 键
|
|
3848
|
-
* @param {String} member 成员
|
|
3849
|
-
* @return {Promise<Number|null>} 排名
|
|
3850
|
-
*/
|
|
3851
|
-
Redis.prototype.zrank = async function (key, member) {
|
|
3852
|
-
try {
|
|
3853
|
-
const connection = await this._getConnection();
|
|
3854
|
-
try {
|
|
3855
|
-
const prefixedKey = this._addPrefix(key);
|
|
3856
|
-
const result = await connection.zRank(prefixedKey, member);
|
|
3857
|
-
return result;
|
|
3858
|
-
} finally {
|
|
3859
|
-
this._recycleConnection(connection);
|
|
3860
|
-
}
|
|
3861
|
-
} catch (err) {
|
|
3862
|
-
this.log('error', `获取有序集合成员排名失败 [${key}:${member}]:`, err);
|
|
3863
|
-
return null;
|
|
3864
|
-
}
|
|
3865
|
-
};
|
|
3866
|
-
|
|
3867
|
-
/**
|
|
3868
|
-
* @description 获取有序集合中指定范围的成员
|
|
3869
|
-
* @param {String} key 键
|
|
3870
|
-
* @param {Number} start 起始索引
|
|
3871
|
-
* @param {Number} stop 结束索引
|
|
3872
|
-
* @param {Boolean} withScores 是否返回分数
|
|
3873
|
-
* @return {Promise<Array>} 成员数组
|
|
3874
|
-
*/
|
|
3875
|
-
Redis.prototype.zrange = async function (key, start, stop, withScores = false) {
|
|
3876
|
-
try {
|
|
3877
|
-
const connection = await this._getConnection();
|
|
3878
|
-
try {
|
|
3879
|
-
const prefixedKey = this._addPrefix(key);
|
|
3880
|
-
|
|
3881
|
-
if (withScores) {
|
|
3882
|
-
// 对于需要分数的情况,使用更可靠的方法
|
|
3883
|
-
try {
|
|
3884
|
-
// 直接获取所有成员的分数
|
|
3885
|
-
const members = await connection.zRange(prefixedKey, start, stop);
|
|
3886
|
-
const results = [];
|
|
3887
|
-
|
|
3888
|
-
// 为每个成员单独获取分数,确保准确性
|
|
3889
|
-
for (const member of members) {
|
|
3890
|
-
try {
|
|
3891
|
-
let score = await connection.zScore(prefixedKey, member);
|
|
3892
|
-
score = score ? parseFloat(score) : 0;
|
|
3893
|
-
results.push({
|
|
3894
|
-
member,
|
|
3895
|
-
score
|
|
3896
|
-
});
|
|
3897
|
-
} catch (scoreErr) {
|
|
3898
|
-
// 如果获取分数失败,设置默认值0
|
|
3899
|
-
results.push({
|
|
3900
|
-
member,
|
|
3901
|
-
score: 0
|
|
3902
|
-
});
|
|
3903
|
-
this.log('error', `获取成员 ${member} 分数失败:`, scoreErr);
|
|
3904
|
-
}
|
|
3905
|
-
}
|
|
3906
|
-
|
|
3907
|
-
return results;
|
|
3908
|
-
} catch (err) {
|
|
3909
|
-
this.log('error', `使用备用方法获取有序集合范围失败 [${prefixedKey}]:`, err);
|
|
3910
|
-
// 回退到简单列表
|
|
3911
|
-
return [];
|
|
3912
|
-
}
|
|
3913
|
-
} else {
|
|
3914
|
-
// 不需要分数的情况
|
|
3915
|
-
const result = await connection.zRange(prefixedKey, start, stop);
|
|
3916
|
-
return result || [];
|
|
3917
|
-
}
|
|
3918
|
-
} finally {
|
|
3919
|
-
this._recycleConnection(connection);
|
|
3920
|
-
}
|
|
3921
|
-
} catch (err) {
|
|
3922
|
-
this.log('error', `获取有序集合范围成员失败 [${key}]:`, err);
|
|
3923
|
-
return [];
|
|
3924
|
-
}
|
|
3925
|
-
};
|
|
3926
|
-
|
|
3927
|
-
/**
|
|
3928
|
-
* @description 获取有序集合中的成员数量
|
|
3929
|
-
* @param {String} key 键
|
|
3930
|
-
* @return {Promise<Number>} 成员数量
|
|
3931
|
-
*/
|
|
3932
|
-
Redis.prototype.zcard = async function (key) {
|
|
3933
|
-
try {
|
|
3934
|
-
const connection = await this._getConnection();
|
|
3935
|
-
try {
|
|
3936
|
-
// 使用正确的Redis客户端Promise API
|
|
3937
|
-
const result = await connection.zCard(this._addPrefix(key));
|
|
3938
|
-
return result || 0;
|
|
3939
|
-
} catch (err) {
|
|
3940
|
-
this.log('error', `获取有序集合成员数量失败 [${key}]:`, err);
|
|
3941
|
-
return 0;
|
|
3942
|
-
} finally {
|
|
3943
|
-
this._recycleConnection(connection);
|
|
3944
|
-
}
|
|
3945
|
-
} catch (err) {
|
|
3946
|
-
this.log('error', `获取有序集合成员数量失败 [${key}]:`, err);
|
|
3947
|
-
return 0;
|
|
3948
|
-
}
|
|
3949
|
-
};
|
|
3950
|
-
|
|
3951
|
-
/**
|
|
3952
|
-
* @description 增加有序集合中成员的分数(兼容方法)
|
|
3953
|
-
* @param {String} key 键
|
|
3954
|
-
* @param {Number} increment 增量
|
|
3955
|
-
* @param {String} member 成员
|
|
3956
|
-
* @param {Number} [seconds=0] 过期时间(秒)
|
|
3957
|
-
* @return {Promise<Number>} 增加后的分数
|
|
3958
|
-
*/
|
|
3959
|
-
Redis.prototype.zincrby = async function (key, increment, member, seconds = 0) {
|
|
3960
|
-
try {
|
|
3961
|
-
// 参数验证
|
|
3962
|
-
if (!key || typeof key !== 'string') {
|
|
3963
|
-
throw new TypeError('键必须是非空字符串');
|
|
3964
|
-
}
|
|
3965
|
-
if (typeof increment !== 'number' || isNaN(increment)) {
|
|
3966
|
-
throw new TypeError('增量必须是有效数字');
|
|
3967
|
-
}
|
|
3968
|
-
if (!member || typeof member !== 'string') {
|
|
3969
|
-
throw new TypeError('成员必须是非空字符串');
|
|
3970
|
-
}
|
|
3971
|
-
|
|
3972
|
-
// 触发前置事件
|
|
3973
|
-
const ctx = {
|
|
3974
|
-
key,
|
|
3975
|
-
increment,
|
|
3976
|
-
member,
|
|
3977
|
-
seconds,
|
|
3978
|
-
cancel: false
|
|
3979
|
-
};
|
|
3980
|
-
this.emit('zincrby:before', ctx);
|
|
3981
|
-
|
|
3982
|
-
if (ctx.cancel) {
|
|
3983
|
-
this.log('debug', `zincrby操作被事件处理器取消 [${key}]`);
|
|
3984
|
-
return null;
|
|
3985
|
-
}
|
|
3986
|
-
|
|
3987
|
-
const {
|
|
3988
|
-
key: finalKey,
|
|
3989
|
-
increment: finalIncrement,
|
|
3990
|
-
member: finalMember,
|
|
3991
|
-
seconds: finalSeconds
|
|
3992
|
-
} = ctx;
|
|
3993
|
-
|
|
3994
|
-
const connection = await this._getConnection();
|
|
3995
|
-
try {
|
|
3996
|
-
const prefixedKey = this._addPrefix(finalKey);
|
|
3997
|
-
// 使用正确的Redis客户端Promise API
|
|
3998
|
-
const result = await connection.zIncrBy(prefixedKey, finalIncrement, finalMember);
|
|
3999
|
-
|
|
4000
|
-
// 如果设置了过期时间
|
|
4001
|
-
if (finalSeconds > 0) {
|
|
4002
|
-
if (typeof connection.expire === 'function') {
|
|
4003
|
-
await connection.expire(prefixedKey, finalSeconds);
|
|
4004
|
-
} else if (typeof connection.execute === 'function') {
|
|
4005
|
-
await connection.execute('EXPIRE', [prefixedKey, finalSeconds]);
|
|
4006
|
-
}
|
|
4007
|
-
}
|
|
4008
|
-
|
|
4009
|
-
// 触发后置事件
|
|
4010
|
-
this.emit('zincrby:after', {
|
|
4011
|
-
key: finalKey,
|
|
4012
|
-
increment: finalIncrement,
|
|
4013
|
-
member: finalMember,
|
|
4014
|
-
seconds: finalSeconds,
|
|
4015
|
-
result: parseFloat(result)
|
|
4016
|
-
});
|
|
4017
|
-
return parseFloat(result);
|
|
4018
|
-
} catch (err) {
|
|
4019
|
-
this.log('error', `增加有序集合成员分数失败 [${finalKey}]:`, err);
|
|
4020
|
-
this.emit('zincrby:error', {
|
|
4021
|
-
key: finalKey,
|
|
4022
|
-
increment: finalIncrement,
|
|
4023
|
-
member: finalMember,
|
|
4024
|
-
seconds: finalSeconds,
|
|
4025
|
-
error: err
|
|
4026
|
-
});
|
|
4027
|
-
return null;
|
|
4028
|
-
} finally {
|
|
4029
|
-
this._recycleConnection(connection);
|
|
4030
|
-
}
|
|
4031
|
-
} catch (err) {
|
|
4032
|
-
this.log('error', `增加有序集合成员分数失败 [${key}]:`, err);
|
|
4033
|
-
this.emit('zincrby:error', {
|
|
4034
|
-
key,
|
|
4035
|
-
increment,
|
|
4036
|
-
member,
|
|
4037
|
-
seconds,
|
|
4038
|
-
error: err
|
|
4039
|
-
});
|
|
4040
|
-
return null;
|
|
4041
|
-
}
|
|
4042
|
-
};
|
|
4043
|
-
|
|
4044
|
-
// 创建真实Redis连接的方法
|
|
4045
|
-
Redis.prototype._createConnection = function () {
|
|
4046
|
-
return new Promise((resolve, reject) => {
|
|
4047
|
-
try {
|
|
4048
|
-
const cg = this.config;
|
|
4049
|
-
|
|
4050
|
-
// 创建真实的Redis客户端连接
|
|
4051
|
-
const connection = createClient({
|
|
4052
|
-
socket: {
|
|
4053
|
-
host: cg.host,
|
|
4054
|
-
port: cg.port,
|
|
4055
|
-
timeout: cg.connect_timeout
|
|
4056
|
-
},
|
|
4057
|
-
password: cg.password,
|
|
4058
|
-
database: cg.database
|
|
4059
|
-
});
|
|
4060
|
-
|
|
4061
|
-
// 添加标识符
|
|
4062
|
-
connection.id = Date.now() + Math.random();
|
|
4063
|
-
|
|
4064
|
-
// 监听错误事件
|
|
4065
|
-
connection.on('error', (err) => {
|
|
4066
|
-
this.log('error', `连接 ${connection.id} 错误:`, err);
|
|
4067
|
-
});
|
|
4068
|
-
|
|
4069
|
-
// 连接Redis
|
|
4070
|
-
connection.connect().then(() => {
|
|
4071
|
-
// this.log('debug','新连接已创建并连接:', connection.id);
|
|
4072
|
-
resolve(connection);
|
|
4073
|
-
}).catch(err => {
|
|
4074
|
-
this.log('error', '创建连接失败:', err);
|
|
4075
|
-
reject(err);
|
|
4076
|
-
});
|
|
4077
|
-
} catch (error) {
|
|
4078
|
-
this.log('error', '创建连接时发生异常:', error);
|
|
4079
|
-
reject(error);
|
|
4080
|
-
}
|
|
4081
|
-
});
|
|
4082
|
-
};
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
/**
|
|
4086
|
-
* 确保连接池对象存在
|
|
4087
|
-
*/
|
|
4088
|
-
if (!$.pool) {
|
|
4089
|
-
$.pool = {};
|
|
4090
|
-
}
|
|
4091
|
-
if (!$.pool.redis) {
|
|
4092
|
-
$.pool.redis = {};
|
|
4093
|
-
}
|
|
4094
|
-
|
|
4095
|
-
/**
|
|
4096
|
-
* @description Redis管理器,用于创建缓存
|
|
4097
|
-
* @param {String} scope 作用域
|
|
4098
|
-
* @param {Object} config 配置参数
|
|
4099
|
-
* @return {Object} 返回一个Redis类实例
|
|
4100
|
-
*/
|
|
4101
|
-
function redisAdmin(scope, config) {
|
|
4102
|
-
if (!scope) {
|
|
4103
|
-
scope = 'sys';
|
|
4104
|
-
}
|
|
4105
|
-
var obj = $.pool.redis[scope];
|
|
4106
|
-
if (!obj) {
|
|
4107
|
-
$.pool.redis[scope] = new Redis(config);
|
|
4108
|
-
obj = $.pool.redis[scope];
|
|
4109
|
-
}
|
|
4110
|
-
return obj;
|
|
4111
|
-
}
|
|
4
|
+
let admin = new Admin();
|
|
5
|
+
admin.setClass(Redis);
|
|
4112
6
|
|
|
4113
7
|
module.exports = {
|
|
4114
8
|
Redis,
|
|
4115
|
-
|
|
9
|
+
admin,
|
|
10
|
+
redisAdmin(scope, config) {
|
|
11
|
+
if (!scope) {
|
|
12
|
+
scope = 'os';
|
|
13
|
+
}
|
|
14
|
+
return admin.get(scope, config);
|
|
15
|
+
}
|
|
4116
16
|
};
|