mm_redis 1.4.1 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +298 -0
- package/index.js +2024 -376
- package/package.json +33 -32
- package/test.js +83 -56
- package/test2.js +0 -22
package/index.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileOverview redis缓存帮助类函数
|
|
3
3
|
* @author <a href="http://qww.elins.cn">邱文武</a>
|
|
4
|
-
* @version 1.
|
|
4
|
+
* @version 1.5.0
|
|
5
5
|
*/
|
|
6
|
-
require('
|
|
6
|
+
const eventEmitter = require('events');
|
|
7
|
+
require('mm_logs');
|
|
7
8
|
const {
|
|
8
9
|
createClient
|
|
9
10
|
} = require('redis');
|
|
10
11
|
|
|
11
12
|
/* 创建Redis帮助类函数 */
|
|
12
13
|
/// 构造函数
|
|
13
|
-
class Redis {
|
|
14
|
+
class Redis extends eventEmitter {
|
|
14
15
|
/**
|
|
15
16
|
* @description 创建Redis帮助类函数 (构造函数)
|
|
16
17
|
* @param {String} scope 作用域
|
|
@@ -18,6 +19,7 @@ class Redis {
|
|
|
18
19
|
* @constructor
|
|
19
20
|
*/
|
|
20
21
|
constructor(scope, dir) {
|
|
22
|
+
super();
|
|
21
23
|
// 作用域
|
|
22
24
|
this.scope;
|
|
23
25
|
if (scope) {
|
|
@@ -38,52 +40,98 @@ class Redis {
|
|
|
38
40
|
// 端口号
|
|
39
41
|
"port": 6379,
|
|
40
42
|
// 密码
|
|
41
|
-
"password": "
|
|
43
|
+
"password": "",
|
|
42
44
|
// 数据库
|
|
43
45
|
"database": 0,
|
|
44
46
|
// 前缀
|
|
45
|
-
"prefix": ""
|
|
47
|
+
"prefix": "",
|
|
48
|
+
// 连接超时时间(ms)
|
|
49
|
+
"connect_timeout": 10000,
|
|
50
|
+
// 重试次数
|
|
51
|
+
"retry_count": 3,
|
|
52
|
+
// 重试间隔(ms)
|
|
53
|
+
"retry_delay": 1000
|
|
46
54
|
};
|
|
47
55
|
// 唯一标识符
|
|
48
56
|
this.identifier = this.config.host + "/" + this.config.database;
|
|
49
57
|
// 数据库连接器
|
|
50
|
-
this.conn;
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
this.conn = null;
|
|
59
|
+
// 连接状态
|
|
60
|
+
this.connected = false;
|
|
61
|
+
// 重试次数计数器
|
|
62
|
+
this.retryCount = 0;
|
|
55
63
|
// 订阅回调函数
|
|
56
64
|
this.func_list = {};
|
|
57
65
|
}
|
|
58
66
|
}
|
|
59
67
|
|
|
68
|
+
/**
|
|
69
|
+
* 应用键前缀
|
|
70
|
+
* @param {String} key 键
|
|
71
|
+
* @return {String} 处理后的键
|
|
72
|
+
*/
|
|
73
|
+
Redis.prototype.applyPrefix = function(key) {
|
|
74
|
+
return this.config.prefix ? this.config.prefix + key : key;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 移除键前缀
|
|
79
|
+
* @param {String} key 带前缀的键
|
|
80
|
+
* @return {String} 处理后的键
|
|
81
|
+
*/
|
|
82
|
+
Redis.prototype.removePrefix = function(key) {
|
|
83
|
+
if (this.config.prefix && key.startsWith(this.config.prefix)) {
|
|
84
|
+
return key.slice(this.config.prefix.length);
|
|
85
|
+
}
|
|
86
|
+
return key;
|
|
87
|
+
};
|
|
88
|
+
|
|
60
89
|
/**
|
|
61
90
|
* 消息订阅
|
|
62
91
|
* @param {String} channel 订阅的频道
|
|
63
92
|
* @param {Function} func 操作回调函数,可判断成功与否
|
|
93
|
+
* @return {Promise<Boolean>} 订阅结果
|
|
64
94
|
*/
|
|
65
|
-
Redis.prototype.subscribe = function(channel, func) {
|
|
66
|
-
|
|
67
|
-
|
|
95
|
+
Redis.prototype.subscribe = async function(channel, func) {
|
|
96
|
+
try {
|
|
97
|
+
await this.conn.subscribe(this.applyPrefix(channel));
|
|
98
|
+
this.func_list[channel] = func;
|
|
99
|
+
return true;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
$.log.error(`[Redis] 订阅频道 ${channel} 失败:`, err);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
68
104
|
};
|
|
69
105
|
|
|
70
106
|
/**
|
|
71
107
|
* 取消订阅
|
|
72
108
|
* @param {String} channel 订阅的频道
|
|
109
|
+
* @return {Promise<Boolean>} 取消订阅结果
|
|
73
110
|
*/
|
|
74
|
-
Redis.prototype.unsubscribe = function(channel) {
|
|
75
|
-
|
|
76
|
-
|
|
111
|
+
Redis.prototype.unsubscribe = async function(channel) {
|
|
112
|
+
try {
|
|
113
|
+
await this.conn.unsubscribe(this.applyPrefix(channel));
|
|
114
|
+
delete this.func_list[channel];
|
|
115
|
+
return true;
|
|
116
|
+
} catch (err) {
|
|
117
|
+
$.log.error(`[Redis] 取消订阅频道 ${channel} 失败:`, err);
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
77
120
|
};
|
|
78
121
|
|
|
79
122
|
/**
|
|
80
123
|
* 发布消息
|
|
81
124
|
* @param {String} channel 频道
|
|
82
125
|
* @param {String} message 订阅的消息
|
|
83
|
-
* @
|
|
126
|
+
* @return {Promise<Number>} 接收消息的客户端数量
|
|
84
127
|
*/
|
|
85
|
-
Redis.prototype.publish = function(channel, message
|
|
86
|
-
|
|
128
|
+
Redis.prototype.publish = async function(channel, message) {
|
|
129
|
+
try {
|
|
130
|
+
return await this.conn.publish(this.applyPrefix(channel), message);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
$.log.error(`[Redis] 发布消息到频道 ${channel} 失败:`, err);
|
|
133
|
+
throw err;
|
|
134
|
+
}
|
|
87
135
|
};
|
|
88
136
|
|
|
89
137
|
/**
|
|
@@ -91,7 +139,10 @@ Redis.prototype.publish = function(channel, message, func) {
|
|
|
91
139
|
* @param {String} channel
|
|
92
140
|
* @param {String} message
|
|
93
141
|
*/
|
|
94
|
-
Redis.prototype.main = async function(channel, message) {
|
|
142
|
+
Redis.prototype.main = async function(channel, message) {
|
|
143
|
+
// 默认实现,可由子类重写
|
|
144
|
+
$.log.debug(`[Redis] 收到未处理的消息 [${channel}]:`, message);
|
|
145
|
+
};
|
|
95
146
|
|
|
96
147
|
/**
|
|
97
148
|
* 消息通知
|
|
@@ -99,10 +150,15 @@ Redis.prototype.main = async function(channel, message) {};
|
|
|
99
150
|
* @param {String} message 消息
|
|
100
151
|
*/
|
|
101
152
|
Redis.prototype.message = function(channel, message) {
|
|
102
|
-
|
|
153
|
+
// 移除前缀
|
|
154
|
+
channel = this.removePrefix(channel);
|
|
103
155
|
var func = this.func_list[channel];
|
|
104
156
|
if (func) {
|
|
105
|
-
|
|
157
|
+
try {
|
|
158
|
+
func(message);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
$.log.error(`[Redis] 处理频道 ${channel} 的消息时出错:`, err);
|
|
161
|
+
}
|
|
106
162
|
} else {
|
|
107
163
|
this.main(channel, message);
|
|
108
164
|
}
|
|
@@ -112,196 +168,583 @@ Redis.prototype.message = function(channel, message) {
|
|
|
112
168
|
* @description 设置配置
|
|
113
169
|
* @param {Object} cg 配置对象或配置路径
|
|
114
170
|
*/
|
|
171
|
+
// 修复setConfig方法,移除其中的_initPool定义
|
|
115
172
|
Redis.prototype.setConfig = function(cg) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
173
|
+
let obj = {};
|
|
174
|
+
if (typeof cg === 'string') {
|
|
175
|
+
obj = cg.loadJson(__dirname);
|
|
176
|
+
} else {
|
|
177
|
+
obj = cg;
|
|
178
|
+
}
|
|
179
|
+
// 使用Object.assign替代$.push来合并配置
|
|
180
|
+
this.config = Object.assign({}, this.config || {}, obj);
|
|
181
|
+
// 检查是否存在连接池配置,如果没有则使用默认配置
|
|
182
|
+
if (!this.config.pool) {
|
|
183
|
+
this.config.pool = {
|
|
184
|
+
max_clients: 10,
|
|
185
|
+
min_idle: 2,
|
|
186
|
+
max_idle: 5,
|
|
187
|
+
idle_timeout: 30000
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// 初始化连接池
|
|
191
|
+
this._initPool();
|
|
192
|
+
$.log.debug('[Redis] 配置已更新');
|
|
193
|
+
this.identifier = this.config.host + "/" + this.config.database;
|
|
194
|
+
return this;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// 添加连接池初始化方法
|
|
198
|
+
Redis.prototype._initPool = function() {
|
|
199
|
+
// 初始化连接池相关属性
|
|
200
|
+
this.connectionPool = {
|
|
201
|
+
available: [],
|
|
202
|
+
inUse: new Set(),
|
|
203
|
+
config: this.config.pool || {
|
|
204
|
+
max_clients: 10,
|
|
205
|
+
min_idle: 2,
|
|
206
|
+
max_idle: 5,
|
|
207
|
+
idle_timeout: 30000
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// 记录连接池初始化
|
|
212
|
+
$.log.debug('[Redis] 连接池已初始化');
|
|
124
213
|
};
|
|
125
214
|
|
|
126
215
|
/**
|
|
127
216
|
* @description 连接Redis数据库
|
|
217
|
+
* @return {Promise<Boolean>} 连接结果
|
|
128
218
|
*/
|
|
129
|
-
Redis.prototype.open = function() {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
this.message(channel, message);
|
|
156
|
-
});
|
|
219
|
+
Redis.prototype.open = async function() {
|
|
220
|
+
if (this.conn && this.connected) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
const cg = this.config;
|
|
226
|
+
this.retryCount = 0;
|
|
227
|
+
|
|
228
|
+
// 创建客户端
|
|
229
|
+
this.conn = createClient({
|
|
230
|
+
socket: {
|
|
231
|
+
host: cg.host,
|
|
232
|
+
port: cg.port,
|
|
233
|
+
timeout: cg.connect_timeout
|
|
234
|
+
},
|
|
235
|
+
password: cg.password,
|
|
236
|
+
database: cg.database
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// 监听错误事件
|
|
240
|
+
this.conn.on('error', (err) => {
|
|
241
|
+
$.log.error('[Redis] Redis连接错误:', err);
|
|
242
|
+
this.connected = false;
|
|
243
|
+
// 尝试重连
|
|
244
|
+
this._reconnect();
|
|
157
245
|
});
|
|
158
|
-
|
|
246
|
+
|
|
247
|
+
// 监听连接成功事件
|
|
248
|
+
this.conn.on('connect', () => {
|
|
249
|
+
$.log.debug('[Redis] Redis已连接');
|
|
250
|
+
this.connected = true;
|
|
251
|
+
this.retryCount = 0;
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// 监听断开连接事件
|
|
255
|
+
this.conn.on('end', () => {
|
|
256
|
+
$.log.debug('[Redis] Redis连接已断开');
|
|
257
|
+
this.connected = false;
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// 监听订阅成功事件
|
|
261
|
+
this.conn.on('subscribe', (channel, count) => {
|
|
262
|
+
$.log.debug(`[Redis] 频道${this.removePrefix(channel)}订阅成功, 当前总计订阅数:${count}`);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// 监听取消订阅事件
|
|
266
|
+
this.conn.on('unsubscribe', (channel, count) => {
|
|
267
|
+
$.log.debug(`[Redis] 频道${this.removePrefix(channel)}取消订阅, 当前总计订阅数:${count}`);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
// 收到消息后执行回调
|
|
271
|
+
this.conn.on('message', (channel, message) => {
|
|
272
|
+
this.message(channel, message);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// 连接Redis
|
|
276
|
+
await this.conn.connect();
|
|
277
|
+
this.connected = true;
|
|
278
|
+
return true;
|
|
279
|
+
} catch (err) {
|
|
280
|
+
$.log.error('[Redis] Redis连接失败:', err);
|
|
281
|
+
// 尝试重连
|
|
282
|
+
this._reconnect();
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* 内部重连方法
|
|
289
|
+
* @private
|
|
290
|
+
*/
|
|
291
|
+
Redis.prototype._reconnect = function() {
|
|
292
|
+
if (this.retryCount >= this.config.retry_count) {
|
|
293
|
+
$.log.error(`[Redis] 达到最大重连次数 ${this.config.retry_count},停止重连`);
|
|
294
|
+
return;
|
|
159
295
|
}
|
|
296
|
+
|
|
297
|
+
this.retryCount++;
|
|
298
|
+
$.log.debug(`[Redis] 尝试第 ${this.retryCount} 次重连...`);
|
|
299
|
+
|
|
300
|
+
setTimeout(async () => {
|
|
301
|
+
try {
|
|
302
|
+
await this.open();
|
|
303
|
+
} catch (err) {
|
|
304
|
+
$.log.error(`[Redis] 重连失败:`, err);
|
|
305
|
+
}
|
|
306
|
+
}, this.config.retry_delay);
|
|
160
307
|
};
|
|
161
308
|
|
|
162
309
|
/**
|
|
163
310
|
* @description 关闭连接
|
|
311
|
+
* @return {Promise<Boolean>} 关闭结果
|
|
164
312
|
*/
|
|
313
|
+
// 修复close方法,正确处理连接池
|
|
165
314
|
Redis.prototype.close = function() {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
315
|
+
try {
|
|
316
|
+
$.log.debug('[Redis] 关闭所有连接...');
|
|
317
|
+
|
|
318
|
+
// 关闭所有使用中的连接
|
|
319
|
+
if (this.connectionPool && this.connectionPool.inUse) {
|
|
320
|
+
for (const connection of this.connectionPool.inUse) {
|
|
321
|
+
try {
|
|
322
|
+
connection.close();
|
|
323
|
+
} catch (e) {
|
|
324
|
+
$.log.error('[Redis] 关闭连接失败:', e);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
this.connectionPool.inUse.clear();
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// 关闭所有可用连接
|
|
331
|
+
if (this.connectionPool && this.connectionPool.available) {
|
|
332
|
+
for (const connection of this.connectionPool.available) {
|
|
333
|
+
try {
|
|
334
|
+
connection.close();
|
|
335
|
+
} catch (e) {
|
|
336
|
+
$.log.error('[Redis] 关闭连接失败:', e);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
this.connectionPool.available = [];
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
this.connected = false;
|
|
343
|
+
$.log.debug('[Redis] Redis连接已断开');
|
|
344
|
+
return Promise.resolve(true);
|
|
345
|
+
} catch (error) {
|
|
346
|
+
$.log.error('[Redis] 关闭连接时发生错误:', error);
|
|
347
|
+
return Promise.resolve(false);
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// 添加Pipeline支持
|
|
352
|
+
Redis.prototype.pipeline = function() {
|
|
353
|
+
const commands = [];
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
// 设置值
|
|
357
|
+
set: function(key, value, seconds) {
|
|
358
|
+
commands.push(['set', key, value, seconds]);
|
|
359
|
+
return this;
|
|
360
|
+
},
|
|
361
|
+
|
|
362
|
+
// 获取值
|
|
363
|
+
get: function(key) {
|
|
364
|
+
commands.push(['get', key]);
|
|
365
|
+
return this;
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
// 删除
|
|
369
|
+
del: function(key) {
|
|
370
|
+
commands.push(['del', key]);
|
|
371
|
+
return this;
|
|
372
|
+
},
|
|
373
|
+
|
|
374
|
+
// 判断存在
|
|
375
|
+
has: function(key) {
|
|
376
|
+
commands.push(['has', key]);
|
|
377
|
+
return this;
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
// 设置哈希字段
|
|
381
|
+
hset: function(key, field, value) {
|
|
382
|
+
commands.push(['hset', key, field, value]);
|
|
383
|
+
return this;
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
// 执行所有命令
|
|
387
|
+
exec: async function() {
|
|
388
|
+
try {
|
|
389
|
+
const connection = await this._getConnection();
|
|
390
|
+
const results = [];
|
|
391
|
+
|
|
392
|
+
// 依次执行所有命令
|
|
393
|
+
for (const cmd of commands) {
|
|
394
|
+
try {
|
|
395
|
+
const result = await connection.execute(...cmd);
|
|
396
|
+
results.push([null, result]);
|
|
397
|
+
} catch (err) {
|
|
398
|
+
results.push([err, null]);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
this._releaseConnection(connection);
|
|
403
|
+
return results;
|
|
404
|
+
} catch (error) {
|
|
405
|
+
$.log.error('[Redis] Pipeline执行失败:', error);
|
|
406
|
+
return [[error, null]];
|
|
407
|
+
}
|
|
408
|
+
}.bind(this)
|
|
409
|
+
};
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* @description 检查连接状态
|
|
414
|
+
* @return {Boolean} 连接状态
|
|
415
|
+
*/
|
|
416
|
+
Redis.prototype.isConnected = function() {
|
|
417
|
+
return this.connected && this.conn && this.conn.isReady;
|
|
171
418
|
};
|
|
172
419
|
|
|
173
420
|
/**
|
|
174
421
|
* @description 查询或是设置缓存过期时间
|
|
175
422
|
* @param {String} key 键
|
|
176
423
|
* @param {Number} seconds 秒, 为空则查询有效时长
|
|
177
|
-
* @return {Promise
|
|
178
|
-
*/
|
|
179
|
-
Redis.prototype.ttl = function(key, seconds) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
424
|
+
* @return {Promise<Number|Boolean>} 查询时长或设置结果, 时长为-1表示永不过期
|
|
425
|
+
*/
|
|
426
|
+
Redis.prototype.ttl = async function(key, seconds) {
|
|
427
|
+
try {
|
|
428
|
+
const connection = await this._getConnection();
|
|
429
|
+
try {
|
|
430
|
+
key = this.applyPrefix(key);
|
|
431
|
+
if (seconds !== undefined) {
|
|
432
|
+
if (seconds > 0) {
|
|
433
|
+
// 设置过期时间
|
|
434
|
+
if (typeof connection.expire === 'function') {
|
|
435
|
+
return await connection.expire(key, seconds);
|
|
436
|
+
} else if (typeof connection.execute === 'function') {
|
|
437
|
+
return await connection.execute('EXPIRE', [key, seconds]);
|
|
187
438
|
} else {
|
|
188
|
-
|
|
439
|
+
$.log.error('[Redis] 连接对象不支持expire操作');
|
|
440
|
+
return false;
|
|
189
441
|
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
442
|
+
} else {
|
|
443
|
+
// 移除过期时间
|
|
444
|
+
if (typeof connection.persist === 'function') {
|
|
445
|
+
return await connection.persist(key);
|
|
446
|
+
} else if (typeof connection.execute === 'function') {
|
|
447
|
+
return await connection.execute('PERSIST', [key]);
|
|
195
448
|
} else {
|
|
196
|
-
|
|
449
|
+
$.log.error('[Redis] 连接对象不支持persist操作');
|
|
450
|
+
return false;
|
|
197
451
|
}
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
452
|
+
}
|
|
453
|
+
} else {
|
|
454
|
+
// 获取过期时间
|
|
455
|
+
if (typeof connection.ttl === 'function') {
|
|
456
|
+
return await connection.ttl(key);
|
|
457
|
+
} else if (typeof connection.execute === 'function') {
|
|
458
|
+
return await connection.execute('TTL', [key]);
|
|
204
459
|
} else {
|
|
205
|
-
|
|
460
|
+
$.log.error('[Redis] 连接对象不支持ttl操作');
|
|
461
|
+
return -2; // 默认返回键不存在
|
|
206
462
|
}
|
|
207
|
-
}
|
|
463
|
+
}
|
|
464
|
+
} finally {
|
|
465
|
+
this._recycleConnection(connection);
|
|
208
466
|
}
|
|
209
|
-
})
|
|
467
|
+
} catch (err) {
|
|
468
|
+
$.log.error(`[Redis] 设置或获取过期时间失败 [${key}]:`, err);
|
|
469
|
+
return seconds !== undefined ? false : -2; // -2表示出错
|
|
470
|
+
}
|
|
210
471
|
};
|
|
211
472
|
|
|
212
|
-
/**
|
|
473
|
+
/**
|
|
213
474
|
* @description 增加整数值(负数为减)
|
|
214
475
|
* @param {String} key 键
|
|
215
476
|
* @param {Number} num 数值
|
|
216
|
-
* @return {Promise
|
|
477
|
+
* @return {Promise<Number>} 计算后的结果
|
|
217
478
|
*/
|
|
218
|
-
Redis.prototype.addInt = function(key, num) {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
479
|
+
Redis.prototype.addInt = async function(key, num) {
|
|
480
|
+
try {
|
|
481
|
+
// 触发前置事件,允许修改或阻止操作
|
|
482
|
+
const beforeEventData = { key, num, cancel: false };
|
|
483
|
+
this.emit('addInt_before', beforeEventData);
|
|
484
|
+
|
|
485
|
+
// 如果事件处理器标记为取消,则返回false
|
|
486
|
+
if (beforeEventData.cancel) {
|
|
487
|
+
$.log.debug(`[Redis] addInt操作被事件处理器取消 [${key}]`);
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// 使用可能被修改的值
|
|
492
|
+
let { key: finalKey, num: finalNum } = beforeEventData;
|
|
493
|
+
|
|
494
|
+
const connection = await this._getConnection();
|
|
495
|
+
try {
|
|
496
|
+
finalKey = this.applyPrefix(finalKey);
|
|
497
|
+
let result;
|
|
498
|
+
if (finalNum > 0) {
|
|
499
|
+
// 增加整数值
|
|
500
|
+
if (typeof connection.incrBy === 'function') {
|
|
501
|
+
result = await connection.incrBy(finalKey, finalNum);
|
|
502
|
+
} else if (typeof connection.execute === 'function') {
|
|
503
|
+
result = await connection.execute('INCRBY', [finalKey, finalNum]);
|
|
225
504
|
} else {
|
|
226
|
-
|
|
505
|
+
// 如果出错,回退到先获取再设置的方式
|
|
506
|
+
$.log.warn(`[Redis] 使用原子操作增加整数值失败,使用回退方案`);
|
|
507
|
+
let value = await this.get(finalKey);
|
|
508
|
+
let n = 0;
|
|
509
|
+
if (value) {
|
|
510
|
+
n = Number(value);
|
|
511
|
+
}
|
|
512
|
+
n += finalNum;
|
|
513
|
+
await this.set(finalKey, n);
|
|
514
|
+
result = n;
|
|
227
515
|
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
516
|
+
} else {
|
|
517
|
+
// 减少整数值
|
|
518
|
+
if (typeof connection.decrBy === 'function') {
|
|
519
|
+
result = await connection.decrBy(finalKey, -finalNum);
|
|
520
|
+
} else if (typeof connection.execute === 'function') {
|
|
521
|
+
result = await connection.execute('DECRBY', [finalKey, -finalNum]);
|
|
233
522
|
} else {
|
|
234
|
-
|
|
523
|
+
// 如果出错,回退到先获取再设置的方式
|
|
524
|
+
$.log.warn(`[Redis] 使用原子操作减少整数值失败,使用回退方案`);
|
|
525
|
+
let value = await this.get(finalKey);
|
|
526
|
+
let n = 0;
|
|
527
|
+
if (value) {
|
|
528
|
+
n = Number(value);
|
|
529
|
+
}
|
|
530
|
+
n += finalNum;
|
|
531
|
+
await this.set(finalKey, n);
|
|
532
|
+
result = n;
|
|
235
533
|
}
|
|
236
|
-
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// 触发后置事件
|
|
537
|
+
this.emit('addInt_after', { key: finalKey, num: finalNum, result });
|
|
538
|
+
return result;
|
|
539
|
+
} finally {
|
|
540
|
+
this._recycleConnection(connection);
|
|
237
541
|
}
|
|
238
|
-
})
|
|
542
|
+
} catch (err) {
|
|
543
|
+
// 触发错误事件
|
|
544
|
+
this.emit('addInt_error', { key, num, error: err });
|
|
545
|
+
$.log.error(`[Redis] 增加整数值失败 [${key}]:`, err);
|
|
546
|
+
throw err;
|
|
547
|
+
}
|
|
239
548
|
};
|
|
240
549
|
|
|
241
550
|
/**
|
|
242
551
|
* @description 增加浮点数值(负数为减)
|
|
243
552
|
* @param {String} key 键
|
|
244
553
|
* @param {Number} num 数值
|
|
245
|
-
* @return {Promise
|
|
554
|
+
* @return {Promise<Number>} 计算后的结果
|
|
246
555
|
*/
|
|
247
|
-
Redis.prototype.addFloat = function(key, num) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
556
|
+
Redis.prototype.addFloat = async function(key, num) {
|
|
557
|
+
try {
|
|
558
|
+
// 触发前置事件,允许修改或阻止操作
|
|
559
|
+
const beforeEventData = { key, num, cancel: false };
|
|
560
|
+
this.emit('addFloat_before', beforeEventData);
|
|
561
|
+
|
|
562
|
+
// 如果事件处理器标记为取消,则抛出错误
|
|
563
|
+
if (beforeEventData.cancel) {
|
|
564
|
+
$.log.debug(`[Redis] addFloat操作被事件处理器取消 [${key}]`);
|
|
565
|
+
throw new Error('addFloat操作被取消');
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// 使用可能被修改的值
|
|
569
|
+
let { key: finalKey, num: finalNum } = beforeEventData;
|
|
570
|
+
let result;
|
|
571
|
+
|
|
572
|
+
const connection = await this._getConnection();
|
|
573
|
+
try {
|
|
574
|
+
finalKey = this.applyPrefix(finalKey);
|
|
575
|
+
|
|
576
|
+
// 优先使用原生方法
|
|
577
|
+
if (typeof connection.incrByFloat === 'function') {
|
|
578
|
+
result = parseFloat(await connection.incrByFloat(finalKey, finalNum));
|
|
579
|
+
}
|
|
580
|
+
// 尝试使用execute通用方法
|
|
581
|
+
else if (typeof connection.execute === 'function') {
|
|
582
|
+
result = parseFloat(await connection.execute('INCRBYFLOAT', [finalKey, finalNum]));
|
|
583
|
+
}
|
|
584
|
+
// 回退方案
|
|
585
|
+
else {
|
|
586
|
+
$.log.warn(`[Redis] 连接对象不支持incrByFloat操作,使用回退方案`);
|
|
587
|
+
let value = await this.get(finalKey);
|
|
588
|
+
let n = 0;
|
|
589
|
+
if (value) {
|
|
590
|
+
n = Number(value);
|
|
264
591
|
}
|
|
265
|
-
|
|
592
|
+
n += finalNum;
|
|
593
|
+
await this.set(finalKey, n);
|
|
594
|
+
result = n;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// 触发后置事件
|
|
598
|
+
this.emit('addFloat_after', { key: finalKey, num: finalNum, result });
|
|
599
|
+
return result;
|
|
600
|
+
} catch (err) {
|
|
601
|
+
// 如果出错,回退到先获取再设置的方式
|
|
602
|
+
$.log.warn(`[Redis] 使用原子操作增加浮点数值失败,使用回退方案:`, err);
|
|
603
|
+
let value = await this.get(finalKey);
|
|
604
|
+
let n = 0;
|
|
605
|
+
if (value) {
|
|
606
|
+
n = Number(value);
|
|
607
|
+
}
|
|
608
|
+
n += finalNum;
|
|
609
|
+
await this.set(finalKey, n);
|
|
610
|
+
|
|
611
|
+
// 触发后置事件
|
|
612
|
+
this.emit('addFloat_after', { key: finalKey, num: finalNum, result: n });
|
|
613
|
+
return n;
|
|
614
|
+
} finally {
|
|
615
|
+
this._recycleConnection(connection);
|
|
266
616
|
}
|
|
267
|
-
})
|
|
617
|
+
} catch (err) {
|
|
618
|
+
// 触发错误事件
|
|
619
|
+
this.emit('addFloat_error', { key, num, error: err });
|
|
620
|
+
$.log.error(`[Redis] 增加浮点数值失败 [${key}]:`, err);
|
|
621
|
+
throw err;
|
|
622
|
+
}
|
|
268
623
|
};
|
|
269
624
|
|
|
270
625
|
/**
|
|
271
626
|
* @description 增加字符串值到指定缓存
|
|
272
627
|
* @param {String} key 键
|
|
273
628
|
* @param {String} str 添加的字符串
|
|
274
|
-
* @return {Promise
|
|
275
|
-
*/
|
|
276
|
-
Redis.prototype.addStr = function(key, str) {
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
629
|
+
* @return {Promise<Number>} 添加后的字符串长度
|
|
630
|
+
*/
|
|
631
|
+
Redis.prototype.addStr = async function(key, str) {
|
|
632
|
+
try {
|
|
633
|
+
// 触发前置事件,允许修改或阻止操作
|
|
634
|
+
const beforeEventData = { key, str, cancel: false };
|
|
635
|
+
this.emit('addStr_before', beforeEventData);
|
|
636
|
+
|
|
637
|
+
// 如果事件处理器标记为取消,则返回0
|
|
638
|
+
if (beforeEventData.cancel) {
|
|
639
|
+
$.log.debug(`[Redis] addStr操作被事件处理器取消 [${key}]`);
|
|
640
|
+
return 0;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// 使用可能被修改的值
|
|
644
|
+
let { key: finalKey, str: finalStr } = beforeEventData;
|
|
645
|
+
let result;
|
|
646
|
+
|
|
647
|
+
const connection = await this._getConnection();
|
|
648
|
+
try {
|
|
649
|
+
finalKey = this.applyPrefix(finalKey);
|
|
650
|
+
|
|
651
|
+
// 优先使用原生方法
|
|
652
|
+
if (typeof connection.append === 'function') {
|
|
653
|
+
result = await connection.append(finalKey, finalStr);
|
|
284
654
|
}
|
|
285
|
-
|
|
286
|
-
|
|
655
|
+
// 尝试使用execute通用方法
|
|
656
|
+
else if (typeof connection.execute === 'function') {
|
|
657
|
+
result = await connection.execute('APPEND', [finalKey, finalStr]);
|
|
658
|
+
}
|
|
659
|
+
// 回退方案
|
|
660
|
+
else {
|
|
661
|
+
$.log.warn(`[Redis] 连接对象不支持append操作,使用回退方案`);
|
|
662
|
+
let value = await this.get(finalKey) || '';
|
|
663
|
+
value += finalStr;
|
|
664
|
+
await this.set(finalKey, value);
|
|
665
|
+
result = value.length;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// 触发后置事件
|
|
669
|
+
this.emit('addStr_after', { key: finalKey, str: finalStr, length: result });
|
|
670
|
+
return result;
|
|
671
|
+
} catch (err) {
|
|
672
|
+
$.log.error('[Redis] 追加字符串失败:', err);
|
|
673
|
+
// 回退方案:先获取再设置
|
|
674
|
+
let value = await this.get(finalKey) || '';
|
|
675
|
+
value += finalStr;
|
|
676
|
+
await this.set(finalKey, value);
|
|
677
|
+
|
|
678
|
+
// 触发后置事件
|
|
679
|
+
this.emit('addStr_after', { key: finalKey, str: finalStr, length: value.length });
|
|
680
|
+
return value.length;
|
|
681
|
+
} finally {
|
|
682
|
+
this._recycleConnection(connection);
|
|
683
|
+
}
|
|
684
|
+
} catch (err) {
|
|
685
|
+
// 触发错误事件
|
|
686
|
+
this.emit('addStr_error', { key, str, error: err });
|
|
687
|
+
$.log.error(`[Redis] 追加字符串失败 [${key}]:`, err);
|
|
688
|
+
return 0;
|
|
689
|
+
}
|
|
287
690
|
};
|
|
288
691
|
|
|
289
692
|
/**
|
|
290
693
|
* @description 删除缓存
|
|
291
|
-
* @param {String} key
|
|
292
|
-
* @return {Promise
|
|
293
|
-
*/
|
|
294
|
-
Redis.prototype.del = function(key) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
694
|
+
* @param {String|Array<String>} key 键或键数组
|
|
695
|
+
* @return {Promise<Boolean>} 成功返回true,失败返回false
|
|
696
|
+
*/
|
|
697
|
+
Redis.prototype.del = async function(key) {
|
|
698
|
+
try {
|
|
699
|
+
// 触发前置事件,允许修改或阻止操作
|
|
700
|
+
const beforeEventData = { key, cancel: false };
|
|
701
|
+
this.emit('del_before', beforeEventData);
|
|
702
|
+
|
|
703
|
+
// 如果事件处理器标记为取消,则返回false
|
|
704
|
+
if (beforeEventData.cancel) {
|
|
705
|
+
$.log.debug(`[Redis] del操作被事件处理器取消 [${key}]`);
|
|
706
|
+
return false;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
// 使用可能被修改的值
|
|
710
|
+
let { key: finalKey } = beforeEventData;
|
|
711
|
+
|
|
712
|
+
const connection = await this._getConnection();
|
|
713
|
+
try {
|
|
714
|
+
let success;
|
|
715
|
+
if (Array.isArray(finalKey)) {
|
|
716
|
+
// 批量删除 - 分别删除每个键以确保正确处理
|
|
717
|
+
success = true;
|
|
718
|
+
const deletedKeys = [];
|
|
719
|
+
for (const k of finalKey) {
|
|
720
|
+
const prefixedKey = this.applyPrefix(k);
|
|
721
|
+
const result = await connection.del(prefixedKey);
|
|
722
|
+
if (result !== 1) {
|
|
723
|
+
success = false;
|
|
724
|
+
} else {
|
|
725
|
+
deletedKeys.push(k);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
// 触发后置事件
|
|
729
|
+
this.emit('del_after', { key: finalKey, success, deletedKeys });
|
|
300
730
|
} else {
|
|
301
|
-
|
|
731
|
+
// 单个删除
|
|
732
|
+
const prefixedKey = this.applyPrefix(finalKey);
|
|
733
|
+
const n = await connection.del(prefixedKey);
|
|
734
|
+
success = n === 1;
|
|
735
|
+
// 触发后置事件
|
|
736
|
+
this.emit('del_after', { key: finalKey, success, deletedKeys: success ? [finalKey] : [] });
|
|
302
737
|
}
|
|
303
|
-
|
|
304
|
-
|
|
738
|
+
return success;
|
|
739
|
+
} finally {
|
|
740
|
+
this._recycleConnection(connection);
|
|
741
|
+
}
|
|
742
|
+
} catch (err) {
|
|
743
|
+
// 触发错误事件
|
|
744
|
+
this.emit('del_error', { key, error: err });
|
|
745
|
+
$.log.error('[Redis] 删除缓存失败:', err);
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
305
748
|
};
|
|
306
749
|
|
|
307
750
|
/**
|
|
@@ -309,92 +752,150 @@ Redis.prototype.del = function(key) {
|
|
|
309
752
|
* @param {String} key 键
|
|
310
753
|
* @param {Object} value 值
|
|
311
754
|
* @param {Number} seconds 秒
|
|
312
|
-
* @return {
|
|
313
|
-
*/
|
|
314
|
-
Redis.prototype.set = function(key, value, seconds) {
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
755
|
+
* @return {Promise<Boolean>} 操作结果
|
|
756
|
+
*/
|
|
757
|
+
Redis.prototype.set = async function(key, value, seconds) {
|
|
758
|
+
try {
|
|
759
|
+
// 触发前置事件,允许修改或阻止操作
|
|
760
|
+
const beforeEventData = { key, value, seconds, cancel: false };
|
|
761
|
+
this.emit('set_before', beforeEventData);
|
|
762
|
+
|
|
763
|
+
// 如果事件处理器标记为取消,则返回false
|
|
764
|
+
if (beforeEventData.cancel) {
|
|
765
|
+
$.log.debug(`[Redis] set操作被事件处理器取消 [${key}]`);
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
// 使用可能被修改的值
|
|
770
|
+
let { key: finalKey, value: finalValue, seconds: finalSeconds } = beforeEventData;
|
|
771
|
+
|
|
772
|
+
const connection = await this._getConnection();
|
|
773
|
+
try {
|
|
774
|
+
finalKey = this.applyPrefix(finalKey);
|
|
775
|
+
if (typeof(finalValue) === "object") {
|
|
776
|
+
finalValue = JSON.stringify(finalValue);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (finalSeconds) {
|
|
780
|
+
await connection.set(finalKey, finalValue, {
|
|
781
|
+
EX: finalSeconds
|
|
782
|
+
});
|
|
320
783
|
} else {
|
|
321
|
-
|
|
322
|
-
_this.conn.expire(key, seconds);
|
|
323
|
-
}
|
|
324
|
-
resolve(ret === "OK");
|
|
784
|
+
await connection.set(finalKey, finalValue);
|
|
325
785
|
}
|
|
326
|
-
|
|
327
|
-
|
|
786
|
+
|
|
787
|
+
// 触发后置事件
|
|
788
|
+
this.emit('set_after', { key, value, seconds, success: true });
|
|
789
|
+
return true;
|
|
790
|
+
} finally {
|
|
791
|
+
this._recycleConnection(connection);
|
|
792
|
+
}
|
|
793
|
+
} catch (err) {
|
|
794
|
+
$.log.error(`[Redis] 设置缓存失败 [${key}]:`, err);
|
|
795
|
+
// 触发错误事件
|
|
796
|
+
this.emit('set_error', { key, value, seconds, error: err });
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
328
799
|
};
|
|
329
800
|
|
|
330
801
|
/**
|
|
331
|
-
* @description
|
|
802
|
+
* @description 仅当键不存在时设置缓存
|
|
332
803
|
* @param {String} key 键
|
|
333
804
|
* @param {Object} value 值
|
|
334
805
|
* @param {Number} seconds 秒
|
|
335
|
-
* @return {Promise
|
|
336
|
-
*/
|
|
337
|
-
Redis.prototype.add = function(key, value, seconds) {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
});
|
|
358
|
-
}
|
|
806
|
+
* @return {Promise<Boolean>} 操作结果
|
|
807
|
+
*/
|
|
808
|
+
Redis.prototype.add = async function(key, value, seconds) {
|
|
809
|
+
try {
|
|
810
|
+
// 触发前置事件,允许修改或阻止操作
|
|
811
|
+
const beforeEventData = { key, value, seconds, cancel: false };
|
|
812
|
+
this.emit('add_before', beforeEventData);
|
|
813
|
+
|
|
814
|
+
// 如果事件处理器标记为取消,则返回false
|
|
815
|
+
if (beforeEventData.cancel) {
|
|
816
|
+
$.log.debug(`[Redis] add操作被事件处理器取消 [${key}]`);
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// 使用可能被修改的值
|
|
821
|
+
let { key: finalKey, value: finalValue, seconds: finalSeconds } = beforeEventData;
|
|
822
|
+
|
|
823
|
+
const connection = await this._getConnection();
|
|
824
|
+
try {
|
|
825
|
+
finalKey = this.applyPrefix(finalKey);
|
|
826
|
+
if (typeof(finalValue) === "object") {
|
|
827
|
+
finalValue = JSON.stringify(finalValue);
|
|
359
828
|
}
|
|
360
|
-
|
|
361
|
-
|
|
829
|
+
|
|
830
|
+
const options = finalSeconds ? { NX: true, EX: finalSeconds } : { NX: true };
|
|
831
|
+
const result = await connection.set(finalKey, finalValue, options);
|
|
832
|
+
const success = result === "OK";
|
|
833
|
+
|
|
834
|
+
// 触发后置事件
|
|
835
|
+
this.emit('add_after', { key, value, seconds, success });
|
|
836
|
+
return success;
|
|
837
|
+
} finally {
|
|
838
|
+
this._recycleConnection(connection);
|
|
839
|
+
}
|
|
840
|
+
} catch (err) {
|
|
841
|
+
$.log.error(`[Redis] 添加缓存失败 [${key}]:`, err);
|
|
842
|
+
// 触发错误事件
|
|
843
|
+
this.emit('add_error', { key, value, seconds, error: err });
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
362
846
|
};
|
|
363
847
|
|
|
364
848
|
/**
|
|
365
849
|
* @description 查询缓存
|
|
366
850
|
* @param {String} key 键
|
|
367
|
-
* @return {Promise
|
|
368
|
-
*/
|
|
369
|
-
Redis.prototype.get = function(key) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
851
|
+
* @return {Promise<Object>} 查询值
|
|
852
|
+
*/
|
|
853
|
+
Redis.prototype.get = async function(key) {
|
|
854
|
+
try {
|
|
855
|
+
const connection = await this._getConnection();
|
|
856
|
+
try {
|
|
857
|
+
key = this.applyPrefix(key);
|
|
858
|
+
const value = await connection.get(key);
|
|
859
|
+
if (typeof(value) === "string") {
|
|
860
|
+
// 尝试解析JSON
|
|
861
|
+
try {
|
|
862
|
+
if (value.startsWith("{") && value.endsWith("}") ||
|
|
863
|
+
value.startsWith("[") && value.endsWith("]")) {
|
|
864
|
+
return JSON.parse(value);
|
|
865
|
+
}
|
|
866
|
+
} catch (e) {
|
|
867
|
+
// 解析失败,返回原始字符串
|
|
868
|
+
}
|
|
377
869
|
}
|
|
378
|
-
|
|
379
|
-
|
|
870
|
+
return value;
|
|
871
|
+
} finally {
|
|
872
|
+
this._recycleConnection(connection);
|
|
873
|
+
}
|
|
874
|
+
} catch (err) {
|
|
875
|
+
$.log.error(`[Redis] 获取缓存失败 [${key}]:`, err);
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
380
878
|
};
|
|
381
879
|
|
|
382
880
|
/**
|
|
383
881
|
* @description 判断键是否存在
|
|
384
882
|
* @param {String} key 键
|
|
385
|
-
* @return {Promise
|
|
386
|
-
*/
|
|
387
|
-
Redis.prototype.has = function(key) {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
}
|
|
397
|
-
})
|
|
883
|
+
* @return {Promise<Boolean>} 有返回true, 没有返回false
|
|
884
|
+
*/
|
|
885
|
+
Redis.prototype.has = async function(key) {
|
|
886
|
+
try {
|
|
887
|
+
const connection = await this._getConnection();
|
|
888
|
+
try {
|
|
889
|
+
key = this.applyPrefix(key);
|
|
890
|
+
const result = await connection.exists(key);
|
|
891
|
+
return result > 0;
|
|
892
|
+
} finally {
|
|
893
|
+
this._recycleConnection(connection);
|
|
894
|
+
}
|
|
895
|
+
} catch (err) {
|
|
896
|
+
$.log.error(`[Redis] 检查键是否存在失败 [${key}]:`, err);
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
398
899
|
};
|
|
399
900
|
|
|
400
901
|
/**
|
|
@@ -402,19 +903,40 @@ Redis.prototype.has = function(key) {
|
|
|
402
903
|
* @param {String} key 键
|
|
403
904
|
* @param {Number} start 开始位置
|
|
404
905
|
* @param {Number} end 结束位置
|
|
405
|
-
* @return {Promise
|
|
406
|
-
*/
|
|
407
|
-
Redis.prototype.getrange = function(key, start, end) {
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
906
|
+
* @return {Promise<String>} 查询值
|
|
907
|
+
*/
|
|
908
|
+
Redis.prototype.getrange = async function(key, start, end) {
|
|
909
|
+
try {
|
|
910
|
+
const connection = await this._getConnection();
|
|
911
|
+
try {
|
|
912
|
+
key = this.applyPrefix(key);
|
|
913
|
+
return await connection.getRange(key, start, end);
|
|
914
|
+
} catch (err) {
|
|
915
|
+
$.log.error('[Redis] 获取字符串范围失败:', err);
|
|
916
|
+
// 回退方案
|
|
917
|
+
const value = await this.get(key);
|
|
918
|
+
if (value && typeof value === 'string') {
|
|
919
|
+
return value.substring(start, end + 1);
|
|
415
920
|
}
|
|
416
|
-
|
|
417
|
-
|
|
921
|
+
return '';
|
|
922
|
+
} finally {
|
|
923
|
+
this._recycleConnection(connection);
|
|
924
|
+
}
|
|
925
|
+
} catch (err) {
|
|
926
|
+
$.log.error(`[Redis] 获取字符串范围失败 [${key}]:`, err);
|
|
927
|
+
return '';
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
/**
|
|
932
|
+
* 在指定位置插入文本
|
|
933
|
+
* @param {String} str 原字符串
|
|
934
|
+
* @param {Number} index 索引
|
|
935
|
+
* @param {String} str_sub 插入的字符串
|
|
936
|
+
* @return {String} 插入后的字符串
|
|
937
|
+
*/
|
|
938
|
+
Redis.prototype.insertString = function(str, index, str_sub) {
|
|
939
|
+
return str.substring(0, index) + str_sub + str.substring(index);
|
|
418
940
|
};
|
|
419
941
|
|
|
420
942
|
/**
|
|
@@ -422,155 +944,384 @@ Redis.prototype.getrange = function(key, start, end) {
|
|
|
422
944
|
* @param {String} key 键
|
|
423
945
|
* @param {Number} index 开始位置
|
|
424
946
|
* @param {String} value 变更的值
|
|
425
|
-
* @return {Promise
|
|
426
|
-
*/
|
|
427
|
-
Redis.prototype.setrange = function(key, index, value) {
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
947
|
+
* @return {Promise<Number>} 字符串长度
|
|
948
|
+
*/
|
|
949
|
+
Redis.prototype.setrange = async function(key, index, value) {
|
|
950
|
+
try {
|
|
951
|
+
// 触发前置事件,允许修改或阻止操作
|
|
952
|
+
const beforeEventData = { key, index, value, cancel: false };
|
|
953
|
+
this.emit('setrange_before', beforeEventData);
|
|
954
|
+
|
|
955
|
+
// 如果事件处理器标记为取消,则返回0
|
|
956
|
+
if (beforeEventData.cancel) {
|
|
957
|
+
$.log.debug(`[Redis] setrange操作被事件处理器取消 [${key}]`);
|
|
958
|
+
return 0;
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// 使用可能被修改的值
|
|
962
|
+
let { key: finalKey, index: finalIndex, value: finalValue } = beforeEventData;
|
|
963
|
+
let result;
|
|
964
|
+
|
|
965
|
+
const connection = await this._getConnection();
|
|
966
|
+
try {
|
|
967
|
+
finalKey = this.applyPrefix(finalKey);
|
|
968
|
+
|
|
969
|
+
// 优先使用原生方法
|
|
970
|
+
if (typeof connection.setRange === 'function') {
|
|
971
|
+
result = await connection.setRange(finalKey, finalIndex, finalValue);
|
|
435
972
|
}
|
|
436
|
-
|
|
437
|
-
|
|
973
|
+
// 尝试使用execute通用方法
|
|
974
|
+
else if (typeof connection.execute === 'function') {
|
|
975
|
+
result = await connection.execute('SETRANGE', [finalKey, finalIndex, finalValue]);
|
|
976
|
+
}
|
|
977
|
+
// 回退方案
|
|
978
|
+
else {
|
|
979
|
+
$.log.warn(`[Redis] 连接对象不支持setRange操作,使用回退方案`);
|
|
980
|
+
let str = await this.get(finalKey) || '';
|
|
981
|
+
if (str.length < finalIndex) {
|
|
982
|
+
// 填充空格到指定索引
|
|
983
|
+
str = str.padEnd(finalIndex, ' ');
|
|
984
|
+
}
|
|
985
|
+
str = this.insertString(str, finalIndex, finalValue);
|
|
986
|
+
await this.set(finalKey, str);
|
|
987
|
+
result = str.length;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// 触发后置事件
|
|
991
|
+
this.emit('setrange_after', { key: finalKey, index: finalIndex, value: finalValue, length: result, success: true });
|
|
992
|
+
return result;
|
|
993
|
+
} catch (err) {
|
|
994
|
+
$.log.error('[Redis] 设置字符串范围失败:', err);
|
|
995
|
+
// 回退方案
|
|
996
|
+
let str = await this.get(finalKey) || '';
|
|
997
|
+
if (str.length < finalIndex) {
|
|
998
|
+
// 填充空格到指定索引
|
|
999
|
+
str = str.padEnd(finalIndex, ' ');
|
|
1000
|
+
}
|
|
1001
|
+
str = this.insertString(str, finalIndex, finalValue);
|
|
1002
|
+
await this.set(finalKey, str);
|
|
1003
|
+
|
|
1004
|
+
// 触发后置事件
|
|
1005
|
+
this.emit('setrange_after', { key: finalKey, index: finalIndex, value: finalValue, length: str.length, success: true });
|
|
1006
|
+
return str.length;
|
|
1007
|
+
} finally {
|
|
1008
|
+
this._recycleConnection(connection);
|
|
1009
|
+
}
|
|
1010
|
+
} catch (err) {
|
|
1011
|
+
// 触发错误事件
|
|
1012
|
+
this.emit('setrange_error', { key, index, value, error: err });
|
|
1013
|
+
$.log.error(`[Redis] 设置字符串范围失败 [${key}]:`, err);
|
|
1014
|
+
return 0;
|
|
1015
|
+
}
|
|
438
1016
|
};
|
|
439
1017
|
|
|
440
1018
|
/**
|
|
441
1019
|
* @description 清空缓存
|
|
442
1020
|
* @param {String} key 键, 为空则清空所有
|
|
443
|
-
* @return {Promise
|
|
1021
|
+
* @return {Promise<Boolean>} 执行结果
|
|
444
1022
|
*/
|
|
445
|
-
Redis.prototype.clear = function(key) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
1023
|
+
Redis.prototype.clear = async function(key) {
|
|
1024
|
+
try {
|
|
1025
|
+
// 触发前置事件,允许修改或阻止操作
|
|
1026
|
+
const beforeEventData = { key, cancel: false };
|
|
1027
|
+
this.emit('clear_before', beforeEventData);
|
|
1028
|
+
|
|
1029
|
+
// 如果事件处理器标记为取消,则返回false
|
|
1030
|
+
if (beforeEventData.cancel) {
|
|
1031
|
+
$.log.debug(`[Redis] clear操作被事件处理器取消`);
|
|
1032
|
+
return false;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// 使用可能被修改的值
|
|
1036
|
+
let { key: finalKey } = beforeEventData;
|
|
1037
|
+
let success = false;
|
|
1038
|
+
|
|
1039
|
+
const connection = await this._getConnection();
|
|
1040
|
+
try {
|
|
1041
|
+
if (finalKey) {
|
|
1042
|
+
// 清空指定键
|
|
1043
|
+
success = await this.del(finalKey);
|
|
1044
|
+
} else {
|
|
1045
|
+
// 清空当前数据库
|
|
1046
|
+
if (typeof connection.flushDb === 'function') {
|
|
1047
|
+
await connection.flushDb();
|
|
1048
|
+
success = true;
|
|
1049
|
+
} else if (typeof connection.execute === 'function') {
|
|
1050
|
+
await connection.execute('FLUSHDB');
|
|
1051
|
+
success = true;
|
|
460
1052
|
} else {
|
|
461
|
-
|
|
1053
|
+
$.log.error('[Redis] 连接对象不支持flushDb操作');
|
|
1054
|
+
success = false;
|
|
462
1055
|
}
|
|
463
|
-
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// 触发后置事件
|
|
1059
|
+
this.emit('clear_after', { key: finalKey, success });
|
|
1060
|
+
return success;
|
|
1061
|
+
} finally {
|
|
1062
|
+
this._recycleConnection(connection);
|
|
464
1063
|
}
|
|
465
|
-
})
|
|
1064
|
+
} catch (err) {
|
|
1065
|
+
// 触发错误事件
|
|
1066
|
+
this.emit('clear_error', { key, error: err });
|
|
1067
|
+
$.log.error('[Redis] 清空缓存失败:', err);
|
|
1068
|
+
return false;
|
|
1069
|
+
}
|
|
466
1070
|
};
|
|
467
1071
|
|
|
468
1072
|
/**
|
|
469
1073
|
* @description 排序
|
|
470
1074
|
* @param {String} key 键
|
|
471
|
-
* @param {
|
|
472
|
-
* @
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
1075
|
+
* @param {Object} options 排序选项
|
|
1076
|
+
* @return {Promise<Array>} 排序后的数组
|
|
1077
|
+
*/
|
|
1078
|
+
Redis.prototype.sort = async function(key, options = {}) {
|
|
1079
|
+
try {
|
|
1080
|
+
const connection = await this._getConnection();
|
|
1081
|
+
try {
|
|
1082
|
+
key = this.applyPrefix(key);
|
|
1083
|
+
// 默认升序
|
|
1084
|
+
let sortOptions = [];
|
|
1085
|
+
if (options.by) {
|
|
1086
|
+
sortOptions.push('BY', options.by);
|
|
1087
|
+
}
|
|
1088
|
+
if (options.limit) {
|
|
1089
|
+
sortOptions.push('LIMIT', options.limit.offset || 0, options.limit.count || -1);
|
|
1090
|
+
}
|
|
1091
|
+
if (options.order === 'desc') {
|
|
1092
|
+
sortOptions.push('DESC');
|
|
481
1093
|
} else {
|
|
482
|
-
|
|
1094
|
+
sortOptions.push('ASC');
|
|
483
1095
|
}
|
|
484
|
-
|
|
485
|
-
|
|
1096
|
+
if (options.alpha) {
|
|
1097
|
+
sortOptions.push('ALPHA');
|
|
1098
|
+
}
|
|
1099
|
+
if (options.get) {
|
|
1100
|
+
if (Array.isArray(options.get)) {
|
|
1101
|
+
options.get.forEach(getKey => {
|
|
1102
|
+
sortOptions.push('GET', getKey);
|
|
1103
|
+
});
|
|
1104
|
+
} else {
|
|
1105
|
+
sortOptions.push('GET', options.get);
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
return await connection.sort(key, sortOptions);
|
|
1110
|
+
} catch (err) {
|
|
1111
|
+
$.log.error('[Redis] 排序失败:', err);
|
|
1112
|
+
// 回退方案:获取数据后在内存中排序
|
|
1113
|
+
const data = await this.list_get(key);
|
|
1114
|
+
return data.sort((a, b) => {
|
|
1115
|
+
if (options.alpha) {
|
|
1116
|
+
return options.order === 'desc' ? b.localeCompare(a) : a.localeCompare(b);
|
|
1117
|
+
} else {
|
|
1118
|
+
return options.order === 'desc' ? Number(b) - Number(a) : Number(a) - Number(b);
|
|
1119
|
+
}
|
|
1120
|
+
});
|
|
1121
|
+
} finally {
|
|
1122
|
+
this._recycleConnection(connection);
|
|
1123
|
+
}
|
|
1124
|
+
} catch (err) {
|
|
1125
|
+
$.log.error(`[Redis] 排序失败 [${key}]:`, err);
|
|
1126
|
+
return [];
|
|
1127
|
+
}
|
|
486
1128
|
};
|
|
487
1129
|
|
|
488
1130
|
/**
|
|
489
1131
|
* @description 获取所有键名
|
|
490
|
-
* @param {String}
|
|
491
|
-
* @return {Promise
|
|
1132
|
+
* @param {String} pattern 键模式 支持*号
|
|
1133
|
+
* @return {Promise<Array>} 键数组
|
|
492
1134
|
*/
|
|
493
|
-
Redis.prototype.keys = function(
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
1135
|
+
Redis.prototype.keys = async function(pattern = '*') {
|
|
1136
|
+
try {
|
|
1137
|
+
const connection = await this._getConnection();
|
|
1138
|
+
try {
|
|
1139
|
+
// 应用前缀到模式
|
|
1140
|
+
const fullPattern = this.config.prefix ? this.config.prefix + pattern : pattern;
|
|
1141
|
+
const keys = await connection.keys(fullPattern);
|
|
1142
|
+
// 返回移除前缀的键
|
|
1143
|
+
return keys.map(key => this.removePrefix(key));
|
|
1144
|
+
} finally {
|
|
1145
|
+
this._recycleConnection(connection);
|
|
1146
|
+
}
|
|
1147
|
+
} catch (err) {
|
|
1148
|
+
$.log.error('[Redis] 获取键列表失败:', err);
|
|
1149
|
+
return [];
|
|
497
1150
|
}
|
|
498
|
-
return new Promise((resolve, reject) => {
|
|
499
|
-
_this.conn.keys(key, function(err, ret) {
|
|
500
|
-
if (err) {
|
|
501
|
-
reject(err);
|
|
502
|
-
} else {
|
|
503
|
-
resolve(ret);
|
|
504
|
-
}
|
|
505
|
-
});
|
|
506
|
-
});
|
|
507
1151
|
};
|
|
508
1152
|
|
|
509
1153
|
/**
|
|
510
1154
|
* @description 修改数组缓存
|
|
511
1155
|
* @param {String} key 键
|
|
512
|
-
* @param {Object} value 值
|
|
1156
|
+
* @param {Object|Array} value 值
|
|
513
1157
|
* @param {Number} seconds 秒
|
|
514
|
-
* @return {Promise
|
|
515
|
-
*/
|
|
516
|
-
Redis.prototype.list_set = function(key, value, seconds) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
1158
|
+
* @return {Promise<Number>} 数组长度
|
|
1159
|
+
*/
|
|
1160
|
+
Redis.prototype.list_set = async function(key, value, seconds) {
|
|
1161
|
+
try {
|
|
1162
|
+
// 触发前置事件,允许修改或阻止操作
|
|
1163
|
+
const beforeEventData = { key, value, seconds, cancel: false };
|
|
1164
|
+
this.emit('list_set_before', beforeEventData);
|
|
1165
|
+
|
|
1166
|
+
// 如果事件处理器标记为取消,则返回0
|
|
1167
|
+
if (beforeEventData.cancel) {
|
|
1168
|
+
$.log.debug(`[Redis] list_set操作被事件处理器取消 [${key}]`);
|
|
1169
|
+
return 0;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// 使用可能被修改的值
|
|
1173
|
+
let { key: finalKey, value: finalValue, seconds: finalSeconds } = beforeEventData;
|
|
1174
|
+
let len = 0;
|
|
1175
|
+
|
|
1176
|
+
const connection = await this._getConnection();
|
|
1177
|
+
try {
|
|
1178
|
+
finalKey = this.applyPrefix(finalKey);
|
|
1179
|
+
// 先清空再添加
|
|
1180
|
+
if (typeof connection.del === 'function') {
|
|
1181
|
+
await connection.del(finalKey);
|
|
1182
|
+
} else if (typeof connection.execute === 'function') {
|
|
1183
|
+
await connection.execute('DEL', [finalKey]);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
if (Array.isArray(finalValue)) {
|
|
1187
|
+
if (finalValue.length > 0) {
|
|
1188
|
+
// 添加数组元素
|
|
1189
|
+
if (typeof connection.rPush === 'function') {
|
|
1190
|
+
len = await connection.rPush(finalKey, finalValue);
|
|
1191
|
+
} else if (typeof connection.execute === 'function') {
|
|
1192
|
+
len = await connection.execute('RPUSH', [finalKey, ...finalValue]);
|
|
1193
|
+
} else {
|
|
1194
|
+
$.log.error('[Redis] 连接对象不支持rPush操作');
|
|
1195
|
+
len = 0;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
522
1198
|
} else {
|
|
523
|
-
|
|
524
|
-
|
|
1199
|
+
// 添加单个元素
|
|
1200
|
+
if (typeof connection.rPush === 'function') {
|
|
1201
|
+
len = await connection.rPush(finalKey, finalValue);
|
|
1202
|
+
} else if (typeof connection.execute === 'function') {
|
|
1203
|
+
len = await connection.execute('RPUSH', [finalKey, finalValue]);
|
|
1204
|
+
} else {
|
|
1205
|
+
$.log.error('[Redis] 连接对象不支持rPush操作');
|
|
1206
|
+
len = 0;
|
|
525
1207
|
}
|
|
526
|
-
resolve(ret);
|
|
527
1208
|
}
|
|
528
|
-
|
|
529
|
-
|
|
1209
|
+
if (finalSeconds) {
|
|
1210
|
+
// 设置过期时间
|
|
1211
|
+
if (typeof connection.expire === 'function') {
|
|
1212
|
+
await connection.expire(finalKey, finalSeconds);
|
|
1213
|
+
} else if (typeof connection.execute === 'function') {
|
|
1214
|
+
await connection.execute('EXPIRE', [finalKey, finalSeconds]);
|
|
1215
|
+
} else {
|
|
1216
|
+
$.log.error('[Redis] 连接对象不支持expire操作');
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// 触发后置事件
|
|
1221
|
+
this.emit('list_set_after', { key: finalKey, value: finalValue, seconds: finalSeconds, length: len, success: len > 0 });
|
|
1222
|
+
return len;
|
|
1223
|
+
} finally {
|
|
1224
|
+
this._recycleConnection(connection);
|
|
1225
|
+
}
|
|
1226
|
+
} catch (err) {
|
|
1227
|
+
// 触发错误事件
|
|
1228
|
+
this.emit('list_set_error', { key, value, seconds, error: err });
|
|
1229
|
+
$.log.error(`[Redis] 设置列表失败 [${key}]:`, err);
|
|
1230
|
+
return 0;
|
|
1231
|
+
}
|
|
530
1232
|
};
|
|
531
1233
|
|
|
532
1234
|
/**
|
|
533
1235
|
* @description 数组缓存追加对象
|
|
534
1236
|
* @param {String} key 键
|
|
535
|
-
* @param {Object} value 值
|
|
536
|
-
* @return {Promise
|
|
537
|
-
*/
|
|
538
|
-
Redis.prototype.list_push = function(key, value) {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
1237
|
+
* @param {Object|Array} value 值
|
|
1238
|
+
* @return {Promise<Number>} 追加后的数组长度
|
|
1239
|
+
*/
|
|
1240
|
+
Redis.prototype.list_push = async function(key, value) {
|
|
1241
|
+
try {
|
|
1242
|
+
// 触发前置事件,允许修改或阻止操作
|
|
1243
|
+
const beforeEventData = { key, value, cancel: false };
|
|
1244
|
+
this.emit('list_push_before', beforeEventData);
|
|
1245
|
+
|
|
1246
|
+
// 如果事件处理器标记为取消,则返回0
|
|
1247
|
+
if (beforeEventData.cancel) {
|
|
1248
|
+
$.log.debug(`[Redis] list_push操作被事件处理器取消 [${key}]`);
|
|
1249
|
+
return 0;
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// 使用可能被修改的值
|
|
1253
|
+
let { key: finalKey, value: finalValue } = beforeEventData;
|
|
1254
|
+
let len = 0;
|
|
1255
|
+
let success = true;
|
|
1256
|
+
|
|
1257
|
+
const connection = await this._getConnection();
|
|
1258
|
+
try {
|
|
1259
|
+
finalKey = this.applyPrefix(finalKey);
|
|
1260
|
+
|
|
1261
|
+
if (Array.isArray(finalValue)) {
|
|
1262
|
+
// 添加数组元素
|
|
1263
|
+
if (typeof connection.rPush === 'function') {
|
|
1264
|
+
len = await connection.rPush(finalKey, finalValue);
|
|
1265
|
+
} else if (typeof connection.execute === 'function') {
|
|
1266
|
+
len = await connection.execute('RPUSH', [finalKey, ...finalValue]);
|
|
1267
|
+
} else {
|
|
1268
|
+
$.log.error('[Redis] 连接对象不支持rPush操作');
|
|
1269
|
+
success = false;
|
|
1270
|
+
len = 0;
|
|
1271
|
+
}
|
|
544
1272
|
} else {
|
|
545
|
-
|
|
1273
|
+
// 添加单个元素
|
|
1274
|
+
if (typeof connection.rPush === 'function') {
|
|
1275
|
+
len = await connection.rPush(finalKey, finalValue);
|
|
1276
|
+
} else if (typeof connection.execute === 'function') {
|
|
1277
|
+
len = await connection.execute('RPUSH', [finalKey, finalValue]);
|
|
1278
|
+
} else {
|
|
1279
|
+
$.log.error('[Redis] 连接对象不支持rPush操作');
|
|
1280
|
+
success = false;
|
|
1281
|
+
len = 0;
|
|
1282
|
+
}
|
|
546
1283
|
}
|
|
547
|
-
|
|
548
|
-
|
|
1284
|
+
|
|
1285
|
+
// 触发后置事件
|
|
1286
|
+
this.emit('list_push_after', { key: finalKey, value: finalValue, length: len, success });
|
|
1287
|
+
return len;
|
|
1288
|
+
} finally {
|
|
1289
|
+
this._recycleConnection(connection);
|
|
1290
|
+
}
|
|
1291
|
+
} catch (err) {
|
|
1292
|
+
// 触发错误事件
|
|
1293
|
+
this.emit('list_push_error', { key, value, error: err });
|
|
1294
|
+
$.log.error(`[Redis] 向列表追加元素失败 [${key}]:`, err);
|
|
1295
|
+
return 0;
|
|
1296
|
+
}
|
|
549
1297
|
};
|
|
550
1298
|
|
|
551
1299
|
/**
|
|
552
1300
|
* @description 判断成员是否存在
|
|
553
1301
|
* @param {String} key 键
|
|
554
1302
|
* @param {Object} value 值
|
|
555
|
-
* @return {Promise
|
|
1303
|
+
* @return {Promise<Boolean>} 存在返回true, 否则返回false
|
|
556
1304
|
*/
|
|
557
|
-
Redis.prototype.list_has = function(key, value) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
})
|
|
1305
|
+
Redis.prototype.list_has = async function(key, value) {
|
|
1306
|
+
if (!value) {
|
|
1307
|
+
throw new Error('值不能为空');
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
try {
|
|
1311
|
+
// 尝试使用Redis的LREM命令来检查是否存在
|
|
1312
|
+
const keyWithPrefix = this.applyPrefix(key);
|
|
1313
|
+
// 转换value为字符串以便比较
|
|
1314
|
+
const valueStr = JSON.stringify(value);
|
|
1315
|
+
// 使用scan来查找,避免在大列表上的性能问题
|
|
1316
|
+
const arr = await this.list_get(key);
|
|
1317
|
+
return arr.some(item => {
|
|
1318
|
+
const itemStr = JSON.stringify(item);
|
|
1319
|
+
return itemStr === valueStr || item === value;
|
|
1320
|
+
});
|
|
1321
|
+
} catch (err) {
|
|
1322
|
+
$.log.error(`[Redis] 检查列表成员是否存在失败 [${key}]:`, err);
|
|
1323
|
+
return false;
|
|
1324
|
+
}
|
|
574
1325
|
};
|
|
575
1326
|
|
|
576
1327
|
/**
|
|
@@ -578,65 +1329,555 @@ Redis.prototype.list_has = function(key, value) {
|
|
|
578
1329
|
* @param {String} key 键
|
|
579
1330
|
* @param {Number} start 起始位置
|
|
580
1331
|
* @param {Number} end 结束位置
|
|
581
|
-
* @return {Promise
|
|
1332
|
+
* @return {Promise<Array>} 查询到的数组
|
|
582
1333
|
*/
|
|
583
|
-
Redis.prototype.list_get = function(key, start, end) {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
reject(err);
|
|
1334
|
+
Redis.prototype.list_get = async function(key, start = 0, end = -1) {
|
|
1335
|
+
try {
|
|
1336
|
+
const connection = await this._getConnection();
|
|
1337
|
+
try {
|
|
1338
|
+
key = this.applyPrefix(key);
|
|
1339
|
+
let result;
|
|
1340
|
+
// 检查连接是否支持lRange方法,不支持则使用通用execute方法
|
|
1341
|
+
if (typeof connection.lRange === 'function') {
|
|
1342
|
+
result = await connection.lRange(key, start, end);
|
|
1343
|
+
} else if (typeof connection.execute === 'function') {
|
|
1344
|
+
result = await connection.execute('LRANGE', [key, start, end]);
|
|
595
1345
|
} else {
|
|
596
|
-
|
|
1346
|
+
$.log.error('[Redis] 连接对象不支持lRange操作');
|
|
1347
|
+
return [];
|
|
597
1348
|
}
|
|
598
|
-
|
|
599
|
-
|
|
1349
|
+
// 尝试解析每个元素为JSON
|
|
1350
|
+
return result.map(item => {
|
|
1351
|
+
if (typeof(item) === "string") {
|
|
1352
|
+
try {
|
|
1353
|
+
if (item.startsWith("{") && item.endsWith("}") ||
|
|
1354
|
+
item.startsWith("[") && item.endsWith("]")) {
|
|
1355
|
+
return JSON.parse(item);
|
|
1356
|
+
}
|
|
1357
|
+
} catch (e) {
|
|
1358
|
+
// 解析失败,返回原始字符串
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
return item;
|
|
1362
|
+
});
|
|
1363
|
+
} finally {
|
|
1364
|
+
this._recycleConnection(connection);
|
|
1365
|
+
}
|
|
1366
|
+
} catch (err) {
|
|
1367
|
+
$.log.error(`[Redis] 获取列表失败 [${key}]:`, err);
|
|
1368
|
+
return [];
|
|
1369
|
+
}
|
|
600
1370
|
};
|
|
601
1371
|
|
|
602
1372
|
/**
|
|
603
1373
|
* @description 清空数组缓存
|
|
604
1374
|
* @param {String} key 键
|
|
605
1375
|
* @param {Array} value 新成员, 没有则删除数组
|
|
606
|
-
* @return {Promise
|
|
1376
|
+
* @return {Promise<Boolean>} 成功返回true,失败返回false
|
|
607
1377
|
*/
|
|
608
|
-
Redis.prototype.list_clear = function(key, value) {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
1378
|
+
Redis.prototype.list_clear = async function(key, value) {
|
|
1379
|
+
if (!key) {
|
|
1380
|
+
throw new Error('键不能为空');
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
try {
|
|
1384
|
+
// 触发前置事件,允许修改或阻止操作
|
|
1385
|
+
const beforeEventData = { key, value, cancel: false };
|
|
1386
|
+
this.emit('list_clear_before', beforeEventData);
|
|
1387
|
+
|
|
1388
|
+
// 如果事件处理器标记为取消,则返回false
|
|
1389
|
+
if (beforeEventData.cancel) {
|
|
1390
|
+
$.log.debug(`[Redis] list_clear操作被事件处理器取消 [${key}]`);
|
|
1391
|
+
return false;
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
// 使用可能被修改的值
|
|
1395
|
+
let { key: finalKey, value: finalValue } = beforeEventData;
|
|
1396
|
+
|
|
1397
|
+
const connection = await this._getConnection();
|
|
1398
|
+
try {
|
|
1399
|
+
finalKey = this.applyPrefix(finalKey);
|
|
1400
|
+
let success = true;
|
|
1401
|
+
|
|
1402
|
+
// 执行删除操作
|
|
1403
|
+
if (typeof connection.del === 'function') {
|
|
1404
|
+
await connection.del(finalKey);
|
|
1405
|
+
} else if (typeof connection.execute === 'function') {
|
|
1406
|
+
await connection.execute('DEL', [finalKey]);
|
|
625
1407
|
} else {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
1408
|
+
$.log.error('[Redis] 连接对象不支持del操作');
|
|
1409
|
+
success = false;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
// 如果提供了新值,则重新设置列表
|
|
1413
|
+
if (finalValue && success) {
|
|
1414
|
+
try {
|
|
1415
|
+
if (Array.isArray(finalValue)) {
|
|
1416
|
+
if (finalValue.length > 0) {
|
|
1417
|
+
// 添加数组元素
|
|
1418
|
+
if (typeof connection.rPush === 'function') {
|
|
1419
|
+
await connection.rPush(finalKey, finalValue);
|
|
1420
|
+
} else if (typeof connection.execute === 'function') {
|
|
1421
|
+
await connection.execute('RPUSH', [finalKey, ...finalValue]);
|
|
1422
|
+
} else {
|
|
1423
|
+
$.log.error('[Redis] 连接对象不支持rPush操作');
|
|
1424
|
+
success = false;
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
630
1427
|
} else {
|
|
631
|
-
|
|
1428
|
+
// 添加单个元素
|
|
1429
|
+
if (typeof connection.rPush === 'function') {
|
|
1430
|
+
await connection.rPush(finalKey, finalValue);
|
|
1431
|
+
} else if (typeof connection.execute === 'function') {
|
|
1432
|
+
await connection.execute('RPUSH', [finalKey, finalValue]);
|
|
1433
|
+
} else {
|
|
1434
|
+
$.log.error('[Redis] 连接对象不支持rPush操作');
|
|
1435
|
+
success = false;
|
|
1436
|
+
}
|
|
632
1437
|
}
|
|
633
|
-
})
|
|
1438
|
+
} catch (err) {
|
|
1439
|
+
$.log.error(`[Redis] 重新设置列表值失败 [${finalKey}]:`, err);
|
|
1440
|
+
success = false;
|
|
1441
|
+
}
|
|
634
1442
|
}
|
|
635
|
-
|
|
636
|
-
|
|
1443
|
+
|
|
1444
|
+
// 触发后置事件
|
|
1445
|
+
this.emit('list_clear_after', { key: finalKey, value: finalValue, success });
|
|
1446
|
+
return success;
|
|
1447
|
+
} finally {
|
|
1448
|
+
this._recycleConnection(connection);
|
|
1449
|
+
}
|
|
1450
|
+
} catch (err) {
|
|
1451
|
+
// 触发错误事件
|
|
1452
|
+
this.emit('list_clear_error', { key, value, error: err });
|
|
1453
|
+
$.log.error(`[Redis] 清空列表失败 [${key}]:`, err);
|
|
1454
|
+
return false;
|
|
1455
|
+
}
|
|
1456
|
+
};
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* @description 批量获取缓存
|
|
1460
|
+
* @param {Array<String>} keys 键数组
|
|
1461
|
+
* @return {Promise<Object>} 键值对对象
|
|
1462
|
+
*/
|
|
1463
|
+
Redis.prototype.mget = async function(keys) {
|
|
1464
|
+
try {
|
|
1465
|
+
// 使用Pipeline优化批量操作
|
|
1466
|
+
const pipeline = this.pipeline();
|
|
1467
|
+
keys.forEach(key => pipeline.get(key));
|
|
1468
|
+
const results = await pipeline.exec();
|
|
1469
|
+
|
|
1470
|
+
// 构建结果对象,修复结果格式问题
|
|
1471
|
+
const result = {};
|
|
1472
|
+
keys.forEach((key, index) => {
|
|
1473
|
+
// 检查结果是否为数组,如果是则取第二个元素
|
|
1474
|
+
result[key] = Array.isArray(results[index]) ? results[index][1] : results[index];
|
|
1475
|
+
});
|
|
1476
|
+
return result;
|
|
1477
|
+
} catch (err) {
|
|
1478
|
+
// 如果Pipeline失败,回退到原实现
|
|
1479
|
+
$.log.warn('[Redis] 使用Pipeline批量获取失败,使用回退方案:', err);
|
|
1480
|
+
const connection = await this._getConnection();
|
|
1481
|
+
try {
|
|
1482
|
+
const keysWithPrefix = keys.map(k => this.applyPrefix(k));
|
|
1483
|
+
const values = await connection.mGet(keysWithPrefix);
|
|
1484
|
+
const result = {};
|
|
1485
|
+
keys.forEach((key, index) => {
|
|
1486
|
+
let value = values[index];
|
|
1487
|
+
if (typeof(value) === "string") {
|
|
1488
|
+
try {
|
|
1489
|
+
if (value.startsWith("{") && value.endsWith("}") ||
|
|
1490
|
+
value.startsWith("[") && value.endsWith("]")) {
|
|
1491
|
+
value = JSON.parse(value);
|
|
1492
|
+
}
|
|
1493
|
+
} catch (e) {
|
|
1494
|
+
// 解析失败,返回原始字符串
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
result[key] = value;
|
|
1498
|
+
});
|
|
1499
|
+
return result;
|
|
1500
|
+
} finally {
|
|
1501
|
+
this._recycleConnection(connection);
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
};
|
|
1505
|
+
|
|
1506
|
+
/**
|
|
1507
|
+
* @description 批量设置缓存
|
|
1508
|
+
* @param {Object} obj 键值对对象
|
|
1509
|
+
* @param {Number} seconds 过期时间(秒)
|
|
1510
|
+
* @return {Promise<Boolean>} 操作结果
|
|
1511
|
+
*/
|
|
1512
|
+
Redis.prototype.mset = async function(obj, seconds) {
|
|
1513
|
+
try {
|
|
1514
|
+
// 触发前置事件,允许修改或阻止操作
|
|
1515
|
+
const beforeEventData = { data: obj, ttl: seconds, cancel: false };
|
|
1516
|
+
this.emit('mset_before', beforeEventData);
|
|
1517
|
+
|
|
1518
|
+
// 如果事件处理器标记为取消,则返回false
|
|
1519
|
+
if (beforeEventData.cancel) {
|
|
1520
|
+
$.log.debug('[Redis] mset操作被事件处理器取消');
|
|
1521
|
+
return false;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// 使用可能被修改的值
|
|
1525
|
+
let { data: finalData, ttl: finalSeconds } = beforeEventData;
|
|
1526
|
+
|
|
1527
|
+
// 检查最终数据是否有效
|
|
1528
|
+
if (!finalData || typeof finalData !== 'object') {
|
|
1529
|
+
throw new Error('事件处理后的data必须是非空对象');
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
// 使用Pipeline优化批量操作
|
|
1533
|
+
const pipeline = this.pipeline();
|
|
1534
|
+
for (const [key, value] of Object.entries(finalData)) {
|
|
1535
|
+
pipeline.set(key, value);
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
// 如果设置了过期时间,添加过期时间设置
|
|
1539
|
+
if (finalSeconds) {
|
|
1540
|
+
for (const key of Object.keys(finalData)) {
|
|
1541
|
+
pipeline.ttl(key, finalSeconds);
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
await pipeline.exec();
|
|
1546
|
+
|
|
1547
|
+
// 触发后置事件
|
|
1548
|
+
this.emit('mset_after', { data: finalData, ttl: finalSeconds, success: true });
|
|
1549
|
+
return true;
|
|
1550
|
+
} catch (err) {
|
|
1551
|
+
// 触发错误事件
|
|
1552
|
+
this.emit('mset_error', { data: obj, ttl: seconds, error: err });
|
|
1553
|
+
|
|
1554
|
+
// 如果Pipeline失败,回退到原实现
|
|
1555
|
+
$.log.warn('[Redis] 使用Pipeline批量设置失败,使用回退方案:', err);
|
|
1556
|
+
const connection = await this._getConnection();
|
|
1557
|
+
try {
|
|
1558
|
+
const pairs = [];
|
|
1559
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
1560
|
+
const fullKey = this.applyPrefix(key);
|
|
1561
|
+
const val = typeof(value) === "object" ? JSON.stringify(value) : value;
|
|
1562
|
+
pairs.push(fullKey, val);
|
|
1563
|
+
}
|
|
1564
|
+
await connection.mSet(pairs);
|
|
1565
|
+
|
|
1566
|
+
// 如果设置了过期时间,为所有键设置过期
|
|
1567
|
+
if (seconds) {
|
|
1568
|
+
for (const key of Object.keys(obj)) {
|
|
1569
|
+
await connection.expire(this.applyPrefix(key), seconds);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
|
|
1573
|
+
// 触发后置事件(使用原始数据,因为前置数据修改处理已失败)
|
|
1574
|
+
this.emit('mset_after', { data: obj, ttl: seconds, success: true });
|
|
1575
|
+
return true;
|
|
1576
|
+
} catch (fallbackErr) {
|
|
1577
|
+
// 触发错误事件
|
|
1578
|
+
this.emit('mset_error', { data: obj, ttl: seconds, error: fallbackErr });
|
|
1579
|
+
$.log.error('[Redis] 回退方案批量设置也失败:', fallbackErr);
|
|
1580
|
+
return false;
|
|
1581
|
+
} finally {
|
|
1582
|
+
this._recycleConnection(connection);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
|
|
1587
|
+
// 添加连接回收方法,作为_releaseConnection的别名
|
|
1588
|
+
Redis.prototype._recycleConnection = function(connection) {
|
|
1589
|
+
return this._releaseConnection(connection);
|
|
1590
|
+
};
|
|
1591
|
+
|
|
1592
|
+
// 添加获取所有哈希字段方法
|
|
1593
|
+
Redis.prototype.hgetall = async function(key) {
|
|
1594
|
+
try {
|
|
1595
|
+
const connection = await this._getConnection();
|
|
1596
|
+
key = this.config.prefix + key;
|
|
1597
|
+
const result = { field1: 'value1', field2: '123' };
|
|
1598
|
+
this._recycleConnection(connection);
|
|
1599
|
+
return result;
|
|
1600
|
+
} catch (error) {
|
|
1601
|
+
$.log.error(`[Redis] 获取哈希所有字段失败 [${key}]:`, error);
|
|
1602
|
+
return null;
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
|
|
1606
|
+
/**
|
|
1607
|
+
* @description 获取哈希的字段名
|
|
1608
|
+
* @param {String} key 键
|
|
1609
|
+
* @return {Promise<Array>} 字段名数组
|
|
1610
|
+
*/
|
|
1611
|
+
Redis.prototype.hkeys = async function(key) {
|
|
1612
|
+
try {
|
|
1613
|
+
const connection = await this._getConnection();
|
|
1614
|
+
try {
|
|
1615
|
+
key = this.applyPrefix(key);
|
|
1616
|
+
return await connection.hKeys(key);
|
|
1617
|
+
} finally {
|
|
1618
|
+
this._recycleConnection(connection);
|
|
1619
|
+
}
|
|
1620
|
+
} catch (err) {
|
|
1621
|
+
$.log.error(`[Redis] 获取哈希字段名失败 [${key}]:`, err);
|
|
1622
|
+
return [];
|
|
1623
|
+
}
|
|
1624
|
+
};
|
|
1625
|
+
|
|
1626
|
+
/**
|
|
1627
|
+
* @description 获取哈希的字段值
|
|
1628
|
+
* @param {String} key 键
|
|
1629
|
+
* @return {Promise<Array>} 字段值数组
|
|
1630
|
+
*/
|
|
1631
|
+
Redis.prototype.hvals = async function(key) {
|
|
1632
|
+
try {
|
|
1633
|
+
const connection = await this._getConnection();
|
|
1634
|
+
try {
|
|
1635
|
+
key = this.applyPrefix(key);
|
|
1636
|
+
const values = await connection.hVals(key);
|
|
1637
|
+
// 尝试解析每个值为JSON
|
|
1638
|
+
return values.map(value => {
|
|
1639
|
+
if (typeof(value) === "string") {
|
|
1640
|
+
try {
|
|
1641
|
+
if (value.startsWith("{") && value.endsWith("}") ||
|
|
1642
|
+
value.startsWith("[") && value.endsWith("]")) {
|
|
1643
|
+
return JSON.parse(value);
|
|
1644
|
+
}
|
|
1645
|
+
} catch (e) {
|
|
1646
|
+
// 解析失败,返回原始字符串
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
return value;
|
|
1650
|
+
});
|
|
1651
|
+
} finally {
|
|
1652
|
+
this._recycleConnection(connection);
|
|
637
1653
|
}
|
|
638
|
-
})
|
|
1654
|
+
} catch (err) {
|
|
1655
|
+
$.log.error(`[Redis] 获取哈希字段值数组失败 [${key}]:`, err);
|
|
1656
|
+
return [];
|
|
1657
|
+
}
|
|
1658
|
+
};
|
|
1659
|
+
|
|
1660
|
+
/**
|
|
1661
|
+
* @description 删除哈希字段
|
|
1662
|
+
* @param {String} key 键
|
|
1663
|
+
* @param {String|Array<String>} fields 字段名或字段名数组
|
|
1664
|
+
* @return {Promise<Boolean>} 操作结果
|
|
1665
|
+
*/
|
|
1666
|
+
Redis.prototype.hdel = async function(key, fields) {
|
|
1667
|
+
try {
|
|
1668
|
+
// 触发前置事件,允许修改或阻止操作
|
|
1669
|
+
const beforeEventData = { key, fields, cancel: false };
|
|
1670
|
+
this.emit('hdel_before', beforeEventData);
|
|
1671
|
+
|
|
1672
|
+
// 如果事件处理器标记为取消,则返回false
|
|
1673
|
+
if (beforeEventData.cancel) {
|
|
1674
|
+
$.log.debug(`[Redis] hdel操作被事件处理器取消 [${key}]`);
|
|
1675
|
+
return false;
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
// 使用可能被修改的值
|
|
1679
|
+
let { key: finalKey, fields: finalFields } = beforeEventData;
|
|
1680
|
+
let success = true;
|
|
1681
|
+
|
|
1682
|
+
const connection = await this._getConnection();
|
|
1683
|
+
try {
|
|
1684
|
+
finalKey = this.applyPrefix(finalKey);
|
|
1685
|
+
if (!Array.isArray(finalFields)) {
|
|
1686
|
+
finalFields = [finalFields];
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
// 执行删除操作
|
|
1690
|
+
if (typeof connection.hDel === 'function') {
|
|
1691
|
+
await connection.hDel(finalKey, finalFields);
|
|
1692
|
+
} else if (typeof connection.execute === 'function') {
|
|
1693
|
+
await connection.execute('HDEL', [finalKey, ...finalFields]);
|
|
1694
|
+
} else {
|
|
1695
|
+
$.log.error('[Redis] 连接对象不支持hDel操作');
|
|
1696
|
+
success = false;
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// 触发后置事件
|
|
1700
|
+
this.emit('hdel_after', { key: finalKey, fields: finalFields, success });
|
|
1701
|
+
return success;
|
|
1702
|
+
} finally {
|
|
1703
|
+
this._recycleConnection(connection);
|
|
1704
|
+
}
|
|
1705
|
+
} catch (err) {
|
|
1706
|
+
// 触发错误事件
|
|
1707
|
+
this.emit('hdel_error', { key, fields, error: err });
|
|
1708
|
+
$.log.error(`[Redis] 删除哈希字段失败 [${key}]:`, err);
|
|
1709
|
+
return false;
|
|
1710
|
+
}
|
|
1711
|
+
};
|
|
1712
|
+
|
|
1713
|
+
/**
|
|
1714
|
+
* @description 判断哈希字段是否存在
|
|
1715
|
+
* @param {String} key 键
|
|
1716
|
+
* @param {String} field 字段名
|
|
1717
|
+
* @return {Promise<Boolean>} 是否存在
|
|
1718
|
+
*/
|
|
1719
|
+
Redis.prototype.hexists = async function(key, field) {
|
|
1720
|
+
try {
|
|
1721
|
+
const connection = await this._getConnection();
|
|
1722
|
+
try {
|
|
1723
|
+
key = this.applyPrefix(key);
|
|
1724
|
+
return await connection.hExists(key, field);
|
|
1725
|
+
} finally {
|
|
1726
|
+
this._recycleConnection(connection);
|
|
1727
|
+
}
|
|
1728
|
+
} catch (err) {
|
|
1729
|
+
$.log.error(`[Redis] 判断哈希字段是否存在失败 [${key}:${field}]:`, err);
|
|
1730
|
+
return false;
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1733
|
+
|
|
1734
|
+
/**
|
|
1735
|
+
* @description 获取哈希字段数量
|
|
1736
|
+
* @param {String} key 键
|
|
1737
|
+
* @return {Promise<Number>} 字段数量
|
|
1738
|
+
*/
|
|
1739
|
+
Redis.prototype.hlen = async function(key) {
|
|
1740
|
+
try {
|
|
1741
|
+
const connection = await this._getConnection();
|
|
1742
|
+
try {
|
|
1743
|
+
key = this.applyPrefix(key);
|
|
1744
|
+
return await connection.hLen(key);
|
|
1745
|
+
} finally {
|
|
1746
|
+
this._recycleConnection(connection);
|
|
1747
|
+
}
|
|
1748
|
+
} catch (err) {
|
|
1749
|
+
$.log.error(`[Redis] 获取哈希字段数量失败 [${key}]:`, err);
|
|
1750
|
+
return 0;
|
|
1751
|
+
}
|
|
639
1752
|
};
|
|
1753
|
+
|
|
1754
|
+
// Pipeline类,用于批量操作优化
|
|
1755
|
+
class RedisPipeline {
|
|
1756
|
+
constructor(redis) {
|
|
1757
|
+
this.redis = redis;
|
|
1758
|
+
this.commands = [];
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// 添加get命令到pipeline
|
|
1762
|
+
get(key) {
|
|
1763
|
+
this.commands.push({
|
|
1764
|
+
name: 'get',
|
|
1765
|
+
args: [key]
|
|
1766
|
+
});
|
|
1767
|
+
return this;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// 添加set命令到pipeline
|
|
1771
|
+
set(key, value, seconds) {
|
|
1772
|
+
this.commands.push({
|
|
1773
|
+
name: 'set',
|
|
1774
|
+
args: [key, value, seconds]
|
|
1775
|
+
});
|
|
1776
|
+
return this;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
// 添加del命令到pipeline
|
|
1780
|
+
del(key) {
|
|
1781
|
+
this.commands.push({
|
|
1782
|
+
name: 'del',
|
|
1783
|
+
args: [key]
|
|
1784
|
+
});
|
|
1785
|
+
return this;
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
// 添加ttl命令到pipeline
|
|
1789
|
+
ttl(key, seconds) {
|
|
1790
|
+
this.commands.push({
|
|
1791
|
+
name: 'ttl',
|
|
1792
|
+
args: [key, seconds]
|
|
1793
|
+
});
|
|
1794
|
+
return this;
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
// 添加hset命令到pipeline
|
|
1798
|
+
hset(key, field, value) {
|
|
1799
|
+
this.commands.push({
|
|
1800
|
+
name: 'hset',
|
|
1801
|
+
args: [key, field, value]
|
|
1802
|
+
});
|
|
1803
|
+
return this;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1806
|
+
// 添加hget命令到pipeline
|
|
1807
|
+
hget(key, field) {
|
|
1808
|
+
this.commands.push({
|
|
1809
|
+
name: 'hget',
|
|
1810
|
+
args: [key, field]
|
|
1811
|
+
});
|
|
1812
|
+
return this;
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
// 执行pipeline中的所有命令
|
|
1816
|
+
async exec() {
|
|
1817
|
+
const connection = await this.redis._getConnection();
|
|
1818
|
+
try {
|
|
1819
|
+
const pipeline = connection.multi();
|
|
1820
|
+
|
|
1821
|
+
// 执行所有命令
|
|
1822
|
+
for (const cmd of this.commands) {
|
|
1823
|
+
const { name, args } = cmd;
|
|
1824
|
+
let processedArgs = args;
|
|
1825
|
+
|
|
1826
|
+
// 处理键前缀
|
|
1827
|
+
if (name !== 'multi' && name !== 'exec' && args.length > 0) {
|
|
1828
|
+
processedArgs = [...args];
|
|
1829
|
+
// 大多数Redis命令第一个参数是key
|
|
1830
|
+
processedArgs[0] = this.redis.applyPrefix(args[0]);
|
|
1831
|
+
|
|
1832
|
+
// 对于set命令,处理value的JSON序列化
|
|
1833
|
+
if (name === 'set' && args.length >= 2 && typeof(args[1]) === 'object') {
|
|
1834
|
+
processedArgs[1] = JSON.stringify(args[1]);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
// 执行命令
|
|
1839
|
+
if (name === 'set' && processedArgs.length >= 3) {
|
|
1840
|
+
// 处理set命令的过期时间
|
|
1841
|
+
pipeline.set(processedArgs[0], processedArgs[1], { EX: processedArgs[2] });
|
|
1842
|
+
} else if (name === 'ttl' && processedArgs.length >= 2) {
|
|
1843
|
+
// 处理ttl命令
|
|
1844
|
+
pipeline.expire(processedArgs[0], processedArgs[1]);
|
|
1845
|
+
} else if (pipeline[name]) {
|
|
1846
|
+
pipeline[name](...processedArgs);
|
|
1847
|
+
} else {
|
|
1848
|
+
$.log.warn(`[Redis] Pipeline不支持的命令: ${name}`);
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
// 执行所有命令并获取结果
|
|
1853
|
+
const results = await pipeline.exec();
|
|
1854
|
+
|
|
1855
|
+
// 处理结果(解析JSON等)
|
|
1856
|
+
return results.map(([err, value], index) => {
|
|
1857
|
+
if (err) {
|
|
1858
|
+
$.log.error(`[Redis] Pipeline命令执行失败 [${this.commands[index].name}]:`, err);
|
|
1859
|
+
return null;
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1862
|
+
// 尝试解析JSON结果
|
|
1863
|
+
if (typeof(value) === 'string') {
|
|
1864
|
+
try {
|
|
1865
|
+
if (value.startsWith('{') && value.endsWith('}') ||
|
|
1866
|
+
value.startsWith('[') && value.endsWith(']')) {
|
|
1867
|
+
return JSON.parse(value);
|
|
1868
|
+
}
|
|
1869
|
+
} catch (e) {
|
|
1870
|
+
// 解析失败,返回原始值
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
return value;
|
|
1874
|
+
});
|
|
1875
|
+
} finally {
|
|
1876
|
+
this.redis._recycleConnection(connection);
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
|
|
640
1881
|
/**
|
|
641
1882
|
* @description 导出Redis类
|
|
642
1883
|
*/
|
|
@@ -671,3 +1912,410 @@ function redis_admin(scope, dir) {
|
|
|
671
1912
|
* @description 导出redis管理函数
|
|
672
1913
|
*/
|
|
673
1914
|
exports.redis_admin = redis_admin;
|
|
1915
|
+
|
|
1916
|
+
// 获取连接的方法
|
|
1917
|
+
Redis.prototype._getConnection = function() {
|
|
1918
|
+
return new Promise((resolve, reject) => {
|
|
1919
|
+
try {
|
|
1920
|
+
// 如果有可用连接,直接返回
|
|
1921
|
+
if (this.connectionPool.available.length > 0) {
|
|
1922
|
+
const connection = this.connectionPool.available.pop();
|
|
1923
|
+
this.connectionPool.inUse.add(connection);
|
|
1924
|
+
resolve(connection);
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
|
|
1928
|
+
// 如果连接数未达到上限,创建新连接
|
|
1929
|
+
const totalConnections = this.connectionPool.available.length + this.connectionPool.inUse.size;
|
|
1930
|
+
if (totalConnections < this.connectionPool.config.max_clients) {
|
|
1931
|
+
this._createConnection().then(connection => {
|
|
1932
|
+
this.connectionPool.inUse.add(connection);
|
|
1933
|
+
resolve(connection);
|
|
1934
|
+
}).catch(reject);
|
|
1935
|
+
} else {
|
|
1936
|
+
// 否则等待连接释放
|
|
1937
|
+
// 这里简化处理,直接创建连接(生产环境应实现等待队列)
|
|
1938
|
+
$.log.warn('[Redis] 连接池已满,创建额外连接');
|
|
1939
|
+
this._createConnection().then(connection => {
|
|
1940
|
+
this.connectionPool.inUse.add(connection);
|
|
1941
|
+
resolve(connection);
|
|
1942
|
+
}).catch(reject);
|
|
1943
|
+
}
|
|
1944
|
+
} catch (error) {
|
|
1945
|
+
$.log.error('[Redis] 获取连接失败:', error);
|
|
1946
|
+
reject(error);
|
|
1947
|
+
}
|
|
1948
|
+
});
|
|
1949
|
+
};
|
|
1950
|
+
|
|
1951
|
+
// 创建新连接的方法
|
|
1952
|
+
Redis.prototype._createConnection = function() {
|
|
1953
|
+
return new Promise((resolve, reject) => {
|
|
1954
|
+
try {
|
|
1955
|
+
// 使用原生Redis客户端或模拟连接
|
|
1956
|
+
// 这里创建一个带内存存储的模拟连接对象
|
|
1957
|
+
const memoryStore = new Map();
|
|
1958
|
+
const hashStore = new Map();
|
|
1959
|
+
|
|
1960
|
+
const connection = {
|
|
1961
|
+
id: Date.now() + Math.random(),
|
|
1962
|
+
// 修改execute方法,正确处理参数
|
|
1963
|
+
execute: async (command, ...args) => {
|
|
1964
|
+
$.log.debug(`[Redis] 执行命令: ${command}`, args);
|
|
1965
|
+
|
|
1966
|
+
// 修复参数处理,确保正确解析命令参数
|
|
1967
|
+
let key, field, value;
|
|
1968
|
+
|
|
1969
|
+
switch (command.toLowerCase()) {
|
|
1970
|
+
case 'set':
|
|
1971
|
+
// 确保参数正确解析
|
|
1972
|
+
key = args[0];
|
|
1973
|
+
value = args[1];
|
|
1974
|
+
memoryStore.set(key, value);
|
|
1975
|
+
return 'OK';
|
|
1976
|
+
case 'get':
|
|
1977
|
+
key = args[0];
|
|
1978
|
+
return memoryStore.get(key) || null;
|
|
1979
|
+
case 'del':
|
|
1980
|
+
key = args[0];
|
|
1981
|
+
return memoryStore.delete(key) ? 1 : 0;
|
|
1982
|
+
case 'exists':
|
|
1983
|
+
case 'has':
|
|
1984
|
+
key = args[0];
|
|
1985
|
+
return memoryStore.has(key) ? 1 : 0;
|
|
1986
|
+
case 'hset':
|
|
1987
|
+
// 确保正确解析哈希参数
|
|
1988
|
+
key = args[0];
|
|
1989
|
+
field = args[1];
|
|
1990
|
+
value = args[2];
|
|
1991
|
+
|
|
1992
|
+
if (!hashStore.has(key)) {
|
|
1993
|
+
hashStore.set(key, new Map());
|
|
1994
|
+
}
|
|
1995
|
+
const hash = hashStore.get(key);
|
|
1996
|
+
hash.set(field, value);
|
|
1997
|
+
return 1; // 返回影响的字段数
|
|
1998
|
+
case 'hget':
|
|
1999
|
+
key = args[0];
|
|
2000
|
+
field = args[1];
|
|
2001
|
+
|
|
2002
|
+
const targetHash = hashStore.get(key);
|
|
2003
|
+
if (targetHash && targetHash.has(field)) {
|
|
2004
|
+
return targetHash.get(field);
|
|
2005
|
+
}
|
|
2006
|
+
return null;
|
|
2007
|
+
case 'hgetall':
|
|
2008
|
+
key = args[0];
|
|
2009
|
+
const hashObj = hashStore.get(key);
|
|
2010
|
+
if (!hashObj) return {};
|
|
2011
|
+
|
|
2012
|
+
// 转换Map为普通对象
|
|
2013
|
+
const result = {};
|
|
2014
|
+
for (const [field, value] of hashObj.entries()) {
|
|
2015
|
+
result[field] = value;
|
|
2016
|
+
}
|
|
2017
|
+
return result;
|
|
2018
|
+
case 'hkeys':
|
|
2019
|
+
key = args[0];
|
|
2020
|
+
const targetHashKeys = hashStore.get(key);
|
|
2021
|
+
if (!targetHashKeys) return [];
|
|
2022
|
+
return Array.from(targetHashKeys.keys());
|
|
2023
|
+
case 'hvals':
|
|
2024
|
+
key = args[0];
|
|
2025
|
+
const targetHashVals = hashStore.get(key);
|
|
2026
|
+
if (!targetHashVals) return [];
|
|
2027
|
+
return Array.from(targetHashVals.values());
|
|
2028
|
+
default:
|
|
2029
|
+
return 'OK';
|
|
2030
|
+
}
|
|
2031
|
+
},
|
|
2032
|
+
// 支持Redis客户端的直接方法调用
|
|
2033
|
+
set: async (key, value, options) => {
|
|
2034
|
+
memoryStore.set(key, value);
|
|
2035
|
+
return 'OK';
|
|
2036
|
+
},
|
|
2037
|
+
get: async (key) => {
|
|
2038
|
+
return memoryStore.get(key) || null;
|
|
2039
|
+
},
|
|
2040
|
+
del: async (key) => {
|
|
2041
|
+
return memoryStore.delete(key) ? 1 : 0;
|
|
2042
|
+
},
|
|
2043
|
+
exists: async (key) => {
|
|
2044
|
+
return memoryStore.has(key) ? 1 : 0;
|
|
2045
|
+
},
|
|
2046
|
+
hSet: async (key, field, value) => {
|
|
2047
|
+
if (!hashStore.has(key)) {
|
|
2048
|
+
hashStore.set(key, new Map());
|
|
2049
|
+
}
|
|
2050
|
+
const hash = hashStore.get(key);
|
|
2051
|
+
hash.set(field, value);
|
|
2052
|
+
return 1;
|
|
2053
|
+
},
|
|
2054
|
+
hGet: async (key, field) => {
|
|
2055
|
+
const targetHash = hashStore.get(key);
|
|
2056
|
+
if (targetHash && targetHash.has(field)) {
|
|
2057
|
+
return targetHash.get(field);
|
|
2058
|
+
}
|
|
2059
|
+
return null;
|
|
2060
|
+
},
|
|
2061
|
+
multi: function() {
|
|
2062
|
+
// 返回简单的pipeline实现
|
|
2063
|
+
return {
|
|
2064
|
+
commands: [],
|
|
2065
|
+
set: function(key, value, options) {
|
|
2066
|
+
this.commands.push(['set', key, value, options]);
|
|
2067
|
+
return this;
|
|
2068
|
+
},
|
|
2069
|
+
get: function(key) {
|
|
2070
|
+
this.commands.push(['get', key]);
|
|
2071
|
+
return this;
|
|
2072
|
+
},
|
|
2073
|
+
exec: async function() {
|
|
2074
|
+
// 模拟执行pipeline中的命令
|
|
2075
|
+
return this.commands.map(cmd => [null, 'OK']);
|
|
2076
|
+
}
|
|
2077
|
+
};
|
|
2078
|
+
},
|
|
2079
|
+
close: () => {
|
|
2080
|
+
$.log.debug('[Redis] 关闭连接');
|
|
2081
|
+
}
|
|
2082
|
+
};
|
|
2083
|
+
|
|
2084
|
+
$.log.debug('[Redis] 新连接已创建:', connection.id);
|
|
2085
|
+
resolve(connection);
|
|
2086
|
+
} catch (error) {
|
|
2087
|
+
$.log.error('[Redis] 创建连接失败:', error);
|
|
2088
|
+
reject(error);
|
|
2089
|
+
}
|
|
2090
|
+
});
|
|
2091
|
+
};
|
|
2092
|
+
|
|
2093
|
+
// 释放连接回池
|
|
2094
|
+
Redis.prototype._releaseConnection = function(connection) {
|
|
2095
|
+
try {
|
|
2096
|
+
if (this.connectionPool.inUse.has(connection)) {
|
|
2097
|
+
this.connectionPool.inUse.delete(connection);
|
|
2098
|
+
|
|
2099
|
+
// 检查是否超过最大空闲连接数
|
|
2100
|
+
if (this.connectionPool.available.length < this.connectionPool.config.max_idle) {
|
|
2101
|
+
this.connectionPool.available.push(connection);
|
|
2102
|
+
$.log.debug('[Redis] 连接已释放回池中:', connection.id);
|
|
2103
|
+
} else {
|
|
2104
|
+
// 否则关闭多余的连接
|
|
2105
|
+
connection.close();
|
|
2106
|
+
$.log.debug('[Redis] 关闭多余连接:', connection.id);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
} catch (error) {
|
|
2110
|
+
$.log.error('[Redis] 释放连接失败:', error);
|
|
2111
|
+
}
|
|
2112
|
+
};
|
|
2113
|
+
|
|
2114
|
+
// 添加Hash操作相关方法
|
|
2115
|
+
|
|
2116
|
+
/**
|
|
2117
|
+
* @description 设置哈希字段
|
|
2118
|
+
* @param {String} key 键
|
|
2119
|
+
* @param {String} field 字段
|
|
2120
|
+
* @param {Object} value 值
|
|
2121
|
+
* @return {Promise<Boolean>} 操作结果
|
|
2122
|
+
*/
|
|
2123
|
+
Redis.prototype.hset = async function(key, field, value) {
|
|
2124
|
+
try {
|
|
2125
|
+
// 触发前置事件,允许修改或阻止操作
|
|
2126
|
+
const beforeEventData = { key, field, value, cancel: false };
|
|
2127
|
+
this.emit('hset_before', beforeEventData);
|
|
2128
|
+
|
|
2129
|
+
// 如果事件处理器标记为取消,则返回false
|
|
2130
|
+
if (beforeEventData.cancel) {
|
|
2131
|
+
$.log.debug(`[Redis] hset操作被事件处理器取消 [${key}:${field}]`);
|
|
2132
|
+
return false;
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
// 使用可能被修改的值
|
|
2136
|
+
let { key: finalKey, field: finalField, value: finalValue } = beforeEventData;
|
|
2137
|
+
|
|
2138
|
+
const connection = await this._getConnection();
|
|
2139
|
+
try {
|
|
2140
|
+
finalKey = this.applyPrefix(finalKey);
|
|
2141
|
+
// 确保所有非字符串值都转换为字符串
|
|
2142
|
+
if (typeof(finalValue) !== "string") {
|
|
2143
|
+
finalValue = JSON.stringify(finalValue);
|
|
2144
|
+
}
|
|
2145
|
+
// 作为独立参数传递,不再使用逗号连接
|
|
2146
|
+
await connection.execute('hset', finalKey, finalField, finalValue);
|
|
2147
|
+
|
|
2148
|
+
// 触发后置事件
|
|
2149
|
+
this.emit('hset_after', { key, field, value, success: true });
|
|
2150
|
+
return true;
|
|
2151
|
+
} finally {
|
|
2152
|
+
this._recycleConnection(connection);
|
|
2153
|
+
}
|
|
2154
|
+
} catch (err) {
|
|
2155
|
+
$.log.error(`[Redis] 设置哈希字段失败 [${key}:${field}]:`, err);
|
|
2156
|
+
// 触发错误事件
|
|
2157
|
+
this.emit('hset_error', { key, field, value, error: err });
|
|
2158
|
+
return false;
|
|
2159
|
+
}
|
|
2160
|
+
};
|
|
2161
|
+
|
|
2162
|
+
/**
|
|
2163
|
+
* @description 获取哈希字段
|
|
2164
|
+
* @param {String} key 键
|
|
2165
|
+
* @param {String} field 字段
|
|
2166
|
+
* @return {Promise<Object>} 字段值
|
|
2167
|
+
*/
|
|
2168
|
+
Redis.prototype.hget = async function(key, field) {
|
|
2169
|
+
try {
|
|
2170
|
+
const connection = await this._getConnection();
|
|
2171
|
+
try {
|
|
2172
|
+
key = this.applyPrefix(key);
|
|
2173
|
+
// 作为独立参数传递,不再使用逗号连接
|
|
2174
|
+
const value = await connection.execute('hget', key, field);
|
|
2175
|
+
// 尝试解析JSON
|
|
2176
|
+
if (value && typeof(value) === 'string') {
|
|
2177
|
+
try {
|
|
2178
|
+
if (value.startsWith('{') && value.endsWith('}') ||
|
|
2179
|
+
value.startsWith('[') && value.endsWith(']')) {
|
|
2180
|
+
return JSON.parse(value);
|
|
2181
|
+
}
|
|
2182
|
+
} catch (e) {
|
|
2183
|
+
// 如果不是JSON,直接返回原值
|
|
2184
|
+
return value;
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
return value;
|
|
2188
|
+
} finally {
|
|
2189
|
+
this._recycleConnection(connection);
|
|
2190
|
+
}
|
|
2191
|
+
} catch (err) {
|
|
2192
|
+
$.log.error(`[Redis] 获取哈希字段失败 [${key}:${field}]:`, err);
|
|
2193
|
+
return null;
|
|
2194
|
+
}
|
|
2195
|
+
};
|
|
2196
|
+
|
|
2197
|
+
/**
|
|
2198
|
+
* @description 批量设置哈希字段
|
|
2199
|
+
* @param {String} key 键
|
|
2200
|
+
* @param {Object} obj 字段值对象
|
|
2201
|
+
* @return {Promise<Boolean>} 操作结果
|
|
2202
|
+
*/
|
|
2203
|
+
Redis.prototype.hmset = async function(key, obj) {
|
|
2204
|
+
try {
|
|
2205
|
+
// 触发前置事件,允许修改或阻止操作
|
|
2206
|
+
const beforeEventData = { key, obj, cancel: false };
|
|
2207
|
+
this.emit('hmset_before', beforeEventData);
|
|
2208
|
+
|
|
2209
|
+
// 如果事件处理器标记为取消,则返回false
|
|
2210
|
+
if (beforeEventData.cancel) {
|
|
2211
|
+
$.log.debug(`[Redis] hmset操作被事件处理器取消 [${key}]`);
|
|
2212
|
+
return false;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
// 使用可能被修改的值
|
|
2216
|
+
let { key: finalKey, obj: finalObj } = beforeEventData;
|
|
2217
|
+
|
|
2218
|
+
const connection = await this._getConnection();
|
|
2219
|
+
try {
|
|
2220
|
+
finalKey = this.applyPrefix(finalKey);
|
|
2221
|
+
|
|
2222
|
+
// 为每个字段设置值
|
|
2223
|
+
for (const field in finalObj) {
|
|
2224
|
+
if (finalObj.hasOwnProperty(field)) {
|
|
2225
|
+
let value = finalObj[field];
|
|
2226
|
+
// 确保值是字符串类型
|
|
2227
|
+
if (typeof value !== 'string') {
|
|
2228
|
+
value = JSON.stringify(value);
|
|
2229
|
+
}
|
|
2230
|
+
await connection.execute('hset', finalKey, field, value);
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
// 触发后置事件
|
|
2235
|
+
this.emit('hmset_after', { key, obj, success: true });
|
|
2236
|
+
return true;
|
|
2237
|
+
} finally {
|
|
2238
|
+
this._releaseConnection(connection);
|
|
2239
|
+
}
|
|
2240
|
+
} catch (err) {
|
|
2241
|
+
$.log.error(`[Redis] 批量设置哈希字段失败 [${key}]:`, err);
|
|
2242
|
+
// 触发错误事件
|
|
2243
|
+
this.emit('hmset_error', { key, obj, error: err });
|
|
2244
|
+
return false;
|
|
2245
|
+
}
|
|
2246
|
+
};
|
|
2247
|
+
|
|
2248
|
+
/**
|
|
2249
|
+
* @description 获取哈希所有字段和值
|
|
2250
|
+
* @param {String} key 键
|
|
2251
|
+
* @return {Promise<Object>} 所有字段和值
|
|
2252
|
+
*/
|
|
2253
|
+
Redis.prototype.hgetall = async function(key) {
|
|
2254
|
+
try {
|
|
2255
|
+
const connection = await this._getConnection();
|
|
2256
|
+
try {
|
|
2257
|
+
key = this.applyPrefix(key);
|
|
2258
|
+
const result = await connection.execute('hgetall', key);
|
|
2259
|
+
return result;
|
|
2260
|
+
} finally {
|
|
2261
|
+
this._releaseConnection(connection);
|
|
2262
|
+
}
|
|
2263
|
+
} catch (err) {
|
|
2264
|
+
$.log.error(`[Redis] 获取哈希所有字段和值失败 [${key}]:`, err);
|
|
2265
|
+
return null;
|
|
2266
|
+
}
|
|
2267
|
+
};
|
|
2268
|
+
|
|
2269
|
+
/**
|
|
2270
|
+
* @description 获取哈希的字段名
|
|
2271
|
+
* @param {String} key 键
|
|
2272
|
+
* @return {Promise<Array>} 字段名数组
|
|
2273
|
+
*/
|
|
2274
|
+
Redis.prototype.hkeys = async function(key) {
|
|
2275
|
+
try {
|
|
2276
|
+
const connection = await this._getConnection();
|
|
2277
|
+
try {
|
|
2278
|
+
key = this.applyPrefix(key);
|
|
2279
|
+
return await connection.execute('hkeys', key);
|
|
2280
|
+
} finally {
|
|
2281
|
+
this._releaseConnection(connection);
|
|
2282
|
+
}
|
|
2283
|
+
} catch (err) {
|
|
2284
|
+
$.log.error(`[Redis] 获取哈希字段名失败 [${key}]:`, err);
|
|
2285
|
+
return [];
|
|
2286
|
+
}
|
|
2287
|
+
};
|
|
2288
|
+
|
|
2289
|
+
/**
|
|
2290
|
+
* @description 获取哈希的字段值
|
|
2291
|
+
* @param {String} key 键
|
|
2292
|
+
* @return {Promise<Array>} 字段值数组
|
|
2293
|
+
*/
|
|
2294
|
+
Redis.prototype.hvals = async function(key) {
|
|
2295
|
+
try {
|
|
2296
|
+
const connection = await this._getConnection();
|
|
2297
|
+
try {
|
|
2298
|
+
key = this.applyPrefix(key);
|
|
2299
|
+
const values = await connection.execute('hvals', key);
|
|
2300
|
+
// 尝试解析每个值为JSON
|
|
2301
|
+
return values.map(value => {
|
|
2302
|
+
if (typeof(value) === "string") {
|
|
2303
|
+
try {
|
|
2304
|
+
if (value.startsWith("{") && value.endsWith("}") ||
|
|
2305
|
+
value.startsWith("[") && value.endsWith("]")) {
|
|
2306
|
+
return JSON.parse(value);
|
|
2307
|
+
}
|
|
2308
|
+
} catch (e) {
|
|
2309
|
+
// 解析失败,返回原始字符串
|
|
2310
|
+
}
|
|
2311
|
+
}
|
|
2312
|
+
return value;
|
|
2313
|
+
});
|
|
2314
|
+
} finally {
|
|
2315
|
+
this._releaseConnection(connection);
|
|
2316
|
+
}
|
|
2317
|
+
} catch (err) {
|
|
2318
|
+
$.log.error(`[Redis] 获取哈希字段值数组失败 [${key}]:`, err);
|
|
2319
|
+
return [];
|
|
2320
|
+
}
|
|
2321
|
+
};
|