mm_redis 1.4.2 → 1.6.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 +379 -0
- package/index.js +2021 -382
- package/package.json +33 -33
- package/test.js +83 -87
- package/index_new.js +0 -585
- 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,9 +150,15 @@ Redis.prototype.main = async function(channel, message) {};
|
|
|
99
150
|
* @param {String} message 消息
|
|
100
151
|
*/
|
|
101
152
|
Redis.prototype.message = function(channel, message) {
|
|
153
|
+
// 移除前缀
|
|
154
|
+
channel = this.removePrefix(channel);
|
|
102
155
|
var func = this.func_list[channel];
|
|
103
156
|
if (func) {
|
|
104
|
-
|
|
157
|
+
try {
|
|
158
|
+
func(message);
|
|
159
|
+
} catch (err) {
|
|
160
|
+
$.log.error(`[Redis] 处理频道 ${channel} 的消息时出错:`, err);
|
|
161
|
+
}
|
|
105
162
|
} else {
|
|
106
163
|
this.main(channel, message);
|
|
107
164
|
}
|
|
@@ -111,196 +168,583 @@ Redis.prototype.message = function(channel, message) {
|
|
|
111
168
|
* @description 设置配置
|
|
112
169
|
* @param {Object} cg 配置对象或配置路径
|
|
113
170
|
*/
|
|
171
|
+
// 修复setConfig方法,移除其中的_initPool定义
|
|
114
172
|
Redis.prototype.setConfig = function(cg) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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] 连接池已初始化');
|
|
123
213
|
};
|
|
124
214
|
|
|
125
215
|
/**
|
|
126
216
|
* @description 连接Redis数据库
|
|
217
|
+
* @return {Promise<Boolean>} 连接结果
|
|
127
218
|
*/
|
|
128
|
-
Redis.prototype.open = function() {
|
|
129
|
-
|
|
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
|
-
this.message(channel, message);
|
|
155
|
-
});
|
|
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();
|
|
156
245
|
});
|
|
157
|
-
|
|
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;
|
|
158
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);
|
|
159
307
|
};
|
|
160
308
|
|
|
161
309
|
/**
|
|
162
310
|
* @description 关闭连接
|
|
311
|
+
* @return {Promise<Boolean>} 关闭结果
|
|
163
312
|
*/
|
|
313
|
+
// 修复close方法,正确处理连接池
|
|
164
314
|
Redis.prototype.close = function() {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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;
|
|
170
418
|
};
|
|
171
419
|
|
|
172
420
|
/**
|
|
173
421
|
* @description 查询或是设置缓存过期时间
|
|
174
422
|
* @param {String} key 键
|
|
175
423
|
* @param {Number} seconds 秒, 为空则查询有效时长
|
|
176
|
-
* @return {Promise
|
|
177
|
-
*/
|
|
178
|
-
Redis.prototype.ttl = function(key, seconds) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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]);
|
|
186
438
|
} else {
|
|
187
|
-
|
|
439
|
+
$.log.error('[Redis] 连接对象不支持expire操作');
|
|
440
|
+
return false;
|
|
188
441
|
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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]);
|
|
194
448
|
} else {
|
|
195
|
-
|
|
449
|
+
$.log.error('[Redis] 连接对象不支持persist操作');
|
|
450
|
+
return false;
|
|
196
451
|
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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]);
|
|
203
459
|
} else {
|
|
204
|
-
|
|
460
|
+
$.log.error('[Redis] 连接对象不支持ttl操作');
|
|
461
|
+
return -2; // 默认返回键不存在
|
|
205
462
|
}
|
|
206
|
-
}
|
|
463
|
+
}
|
|
464
|
+
} finally {
|
|
465
|
+
this._recycleConnection(connection);
|
|
207
466
|
}
|
|
208
|
-
})
|
|
467
|
+
} catch (err) {
|
|
468
|
+
$.log.error(`[Redis] 设置或获取过期时间失败 [${key}]:`, err);
|
|
469
|
+
return seconds !== undefined ? false : -2; // -2表示出错
|
|
470
|
+
}
|
|
209
471
|
};
|
|
210
472
|
|
|
211
|
-
/**
|
|
473
|
+
/**
|
|
212
474
|
* @description 增加整数值(负数为减)
|
|
213
475
|
* @param {String} key 键
|
|
214
476
|
* @param {Number} num 数值
|
|
215
|
-
* @return {Promise
|
|
477
|
+
* @return {Promise<Number>} 计算后的结果
|
|
216
478
|
*/
|
|
217
|
-
Redis.prototype.addInt = function(key, num) {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
479
|
+
Redis.prototype.addInt = async function(key, num) {
|
|
480
|
+
try {
|
|
481
|
+
// 触发前置事件,允许修改或阻止操作
|
|
482
|
+
const beforeEventData = { key, value: 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, value: 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]);
|
|
224
504
|
} else {
|
|
225
|
-
|
|
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;
|
|
226
515
|
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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]);
|
|
232
522
|
} else {
|
|
233
|
-
|
|
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;
|
|
234
533
|
}
|
|
235
|
-
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
// 触发后置事件
|
|
537
|
+
this.emit('addInt_after', { key: finalKey, value: finalNum, result, success: true });
|
|
538
|
+
return result;
|
|
539
|
+
} finally {
|
|
540
|
+
this._recycleConnection(connection);
|
|
236
541
|
}
|
|
237
|
-
})
|
|
542
|
+
} catch (err) {
|
|
543
|
+
// 触发错误事件
|
|
544
|
+
this.emit('addInt_error', { key, value: num, error: err });
|
|
545
|
+
$.log.error(`[Redis] 增加整数值失败 [${key}]:`, err);
|
|
546
|
+
throw err;
|
|
547
|
+
}
|
|
238
548
|
};
|
|
239
549
|
|
|
240
550
|
/**
|
|
241
551
|
* @description 增加浮点数值(负数为减)
|
|
242
552
|
* @param {String} key 键
|
|
243
553
|
* @param {Number} num 数值
|
|
244
|
-
* @return {Promise
|
|
554
|
+
* @return {Promise<Number>} 计算后的结果
|
|
245
555
|
*/
|
|
246
|
-
Redis.prototype.addFloat = function(key, num) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
556
|
+
Redis.prototype.addFloat = async function(key, num) {
|
|
557
|
+
try {
|
|
558
|
+
// 触发前置事件,允许修改或阻止操作
|
|
559
|
+
const beforeEventData = { key, value: 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, value: 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);
|
|
263
591
|
}
|
|
264
|
-
|
|
592
|
+
n += finalNum;
|
|
593
|
+
await this.set(finalKey, n);
|
|
594
|
+
result = n;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// 触发后置事件
|
|
598
|
+
this.emit('addFloat_after', { key: finalKey, value: finalNum, result, success: true });
|
|
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, value: finalNum, result: n, success: true });
|
|
613
|
+
return n;
|
|
614
|
+
} finally {
|
|
615
|
+
this._recycleConnection(connection);
|
|
265
616
|
}
|
|
266
|
-
})
|
|
617
|
+
} catch (err) {
|
|
618
|
+
// 触发错误事件
|
|
619
|
+
this.emit('addFloat_error', { key, value: num, error: err });
|
|
620
|
+
$.log.error(`[Redis] 增加浮点数值失败 [${key}]:`, err);
|
|
621
|
+
throw err;
|
|
622
|
+
}
|
|
267
623
|
};
|
|
268
624
|
|
|
269
625
|
/**
|
|
270
626
|
* @description 增加字符串值到指定缓存
|
|
271
627
|
* @param {String} key 键
|
|
272
628
|
* @param {String} str 添加的字符串
|
|
273
|
-
* @return {Promise
|
|
274
|
-
*/
|
|
275
|
-
Redis.prototype.addStr = function(key, str) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
629
|
+
* @return {Promise<Number>} 添加后的字符串长度
|
|
630
|
+
*/
|
|
631
|
+
Redis.prototype.addStr = async function(key, str) {
|
|
632
|
+
try {
|
|
633
|
+
// 触发前置事件,允许修改或阻止操作
|
|
634
|
+
const beforeEventData = { key, value: 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, value: 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);
|
|
283
654
|
}
|
|
284
|
-
|
|
285
|
-
|
|
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, value: finalStr, result, success: true });
|
|
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, value: finalStr, result: value.length, success: true });
|
|
680
|
+
return value.length;
|
|
681
|
+
} finally {
|
|
682
|
+
this._recycleConnection(connection);
|
|
683
|
+
}
|
|
684
|
+
} catch (err) {
|
|
685
|
+
// 触发错误事件
|
|
686
|
+
this.emit('addStr_error', { key, value: str, error: err });
|
|
687
|
+
$.log.error(`[Redis] 追加字符串失败 [${key}]:`, err);
|
|
688
|
+
return 0;
|
|
689
|
+
}
|
|
286
690
|
};
|
|
287
691
|
|
|
288
692
|
/**
|
|
289
693
|
* @description 删除缓存
|
|
290
|
-
* @param {String} key
|
|
291
|
-
* @return {Promise
|
|
292
|
-
*/
|
|
293
|
-
Redis.prototype.del = function(key) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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 deleted = [];
|
|
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
|
+
deleted.push(k);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
// 触发后置事件
|
|
729
|
+
this.emit('del_after', { key: finalKey, success, deleted });
|
|
299
730
|
} else {
|
|
300
|
-
|
|
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, deleted: success ? [finalKey] : [] });
|
|
301
737
|
}
|
|
302
|
-
|
|
303
|
-
|
|
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
|
+
}
|
|
304
748
|
};
|
|
305
749
|
|
|
306
750
|
/**
|
|
@@ -308,102 +752,150 @@ Redis.prototype.del = function(key) {
|
|
|
308
752
|
* @param {String} key 键
|
|
309
753
|
* @param {Object} value 值
|
|
310
754
|
* @param {Number} seconds 秒
|
|
311
|
-
* @return {
|
|
755
|
+
* @return {Promise<Boolean>} 操作结果
|
|
312
756
|
*/
|
|
313
|
-
Redis.prototype.set = function(key, value, seconds) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
757
|
+
Redis.prototype.set = async function(key, value, seconds) {
|
|
758
|
+
try {
|
|
759
|
+
// 触发前置事件,允许修改或阻止操作
|
|
760
|
+
const beforeEventData = { key, value, ttl: 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;
|
|
318
767
|
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
768
|
+
|
|
769
|
+
// 使用可能被修改的值
|
|
770
|
+
let { key: finalKey, value: finalValue, ttl: 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
|
+
});
|
|
322
783
|
} else {
|
|
323
|
-
|
|
324
|
-
_this.conn.expire(key, seconds);
|
|
325
|
-
}
|
|
326
|
-
resolve(ret === "OK");
|
|
784
|
+
await connection.set(finalKey, finalValue);
|
|
327
785
|
}
|
|
328
|
-
|
|
329
|
-
|
|
786
|
+
|
|
787
|
+
// 触发后置事件
|
|
788
|
+
this.emit('set_after', { key, value, ttl: 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, error: err });
|
|
797
|
+
return false;
|
|
798
|
+
}
|
|
330
799
|
};
|
|
331
800
|
|
|
332
801
|
/**
|
|
333
|
-
* @description
|
|
802
|
+
* @description 仅当键不存在时设置缓存
|
|
334
803
|
* @param {String} key 键
|
|
335
804
|
* @param {Object} value 值
|
|
336
805
|
* @param {Number} seconds 秒
|
|
337
|
-
* @return {Promise
|
|
338
|
-
*/
|
|
339
|
-
Redis.prototype.add = function(key, value, seconds) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
});
|
|
360
|
-
}
|
|
806
|
+
* @return {Promise<Boolean>} 操作结果
|
|
807
|
+
*/
|
|
808
|
+
Redis.prototype.add = async function(key, value, seconds) {
|
|
809
|
+
try {
|
|
810
|
+
// 触发前置事件,允许修改或阻止操作
|
|
811
|
+
const beforeEventData = { key, value, ttl: 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, ttl: 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);
|
|
361
828
|
}
|
|
362
|
-
|
|
363
|
-
|
|
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: finalKey, value: finalValue, ttl: finalSeconds, 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, ttl: seconds, error: err });
|
|
844
|
+
return false;
|
|
845
|
+
}
|
|
364
846
|
};
|
|
365
847
|
|
|
366
848
|
/**
|
|
367
849
|
* @description 查询缓存
|
|
368
850
|
* @param {String} key 键
|
|
369
|
-
* @return {Promise
|
|
370
|
-
*/
|
|
371
|
-
Redis.prototype.get = function(key) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
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);
|
|
383
865
|
}
|
|
866
|
+
} catch (e) {
|
|
867
|
+
// 解析失败,返回原始字符串
|
|
384
868
|
}
|
|
385
|
-
resolve(value);
|
|
386
869
|
}
|
|
387
|
-
|
|
388
|
-
|
|
870
|
+
return value;
|
|
871
|
+
} finally {
|
|
872
|
+
this._recycleConnection(connection);
|
|
873
|
+
}
|
|
874
|
+
} catch (err) {
|
|
875
|
+
$.log.error(`[Redis] 获取缓存失败 [${key}]:`, err);
|
|
876
|
+
return null;
|
|
877
|
+
}
|
|
389
878
|
};
|
|
390
879
|
|
|
391
880
|
/**
|
|
392
881
|
* @description 判断键是否存在
|
|
393
882
|
* @param {String} key 键
|
|
394
|
-
* @return {Promise
|
|
395
|
-
*/
|
|
396
|
-
Redis.prototype.has = function(key) {
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
406
|
-
})
|
|
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
|
+
}
|
|
407
899
|
};
|
|
408
900
|
|
|
409
901
|
/**
|
|
@@ -411,19 +903,40 @@ Redis.prototype.has = function(key) {
|
|
|
411
903
|
* @param {String} key 键
|
|
412
904
|
* @param {Number} start 开始位置
|
|
413
905
|
* @param {Number} end 结束位置
|
|
414
|
-
* @return {Promise
|
|
415
|
-
*/
|
|
416
|
-
Redis.prototype.getrange = function(key, start, end) {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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);
|
|
424
920
|
}
|
|
425
|
-
|
|
426
|
-
|
|
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);
|
|
427
940
|
};
|
|
428
941
|
|
|
429
942
|
/**
|
|
@@ -431,155 +944,384 @@ Redis.prototype.getrange = function(key, start, end) {
|
|
|
431
944
|
* @param {String} key 键
|
|
432
945
|
* @param {Number} index 开始位置
|
|
433
946
|
* @param {String} value 变更的值
|
|
434
|
-
* @return {Promise
|
|
435
|
-
*/
|
|
436
|
-
Redis.prototype.setrange = function(key, index, value) {
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
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);
|
|
444
972
|
}
|
|
445
|
-
|
|
446
|
-
|
|
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, 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, result: 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
|
+
}
|
|
447
1016
|
};
|
|
448
1017
|
|
|
449
1018
|
/**
|
|
450
1019
|
* @description 清空缓存
|
|
451
1020
|
* @param {String} key 键, 为空则清空所有
|
|
452
|
-
* @return {Promise
|
|
1021
|
+
* @return {Promise<Boolean>} 执行结果
|
|
453
1022
|
*/
|
|
454
|
-
Redis.prototype.clear = function(key) {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
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;
|
|
469
1052
|
} else {
|
|
470
|
-
|
|
1053
|
+
$.log.error('[Redis] 连接对象不支持flushDb操作');
|
|
1054
|
+
success = false;
|
|
471
1055
|
}
|
|
472
|
-
}
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
// 触发后置事件
|
|
1059
|
+
this.emit('clear_after', { key: finalKey, success });
|
|
1060
|
+
return success;
|
|
1061
|
+
} finally {
|
|
1062
|
+
this._recycleConnection(connection);
|
|
473
1063
|
}
|
|
474
|
-
})
|
|
1064
|
+
} catch (err) {
|
|
1065
|
+
// 触发错误事件
|
|
1066
|
+
this.emit('clear_error', { key, error: err });
|
|
1067
|
+
$.log.error('[Redis] 清空缓存失败:', err);
|
|
1068
|
+
return false;
|
|
1069
|
+
}
|
|
475
1070
|
};
|
|
476
1071
|
|
|
477
1072
|
/**
|
|
478
1073
|
* @description 排序
|
|
479
1074
|
* @param {String} key 键
|
|
480
|
-
* @param {
|
|
481
|
-
* @
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
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');
|
|
490
1093
|
} else {
|
|
491
|
-
|
|
1094
|
+
sortOptions.push('ASC');
|
|
492
1095
|
}
|
|
493
|
-
|
|
494
|
-
|
|
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
|
+
}
|
|
495
1128
|
};
|
|
496
1129
|
|
|
497
1130
|
/**
|
|
498
1131
|
* @description 获取所有键名
|
|
499
|
-
* @param {String}
|
|
500
|
-
* @return {Promise
|
|
1132
|
+
* @param {String} pattern 键模式 支持*号
|
|
1133
|
+
* @return {Promise<Array>} 键数组
|
|
501
1134
|
*/
|
|
502
|
-
Redis.prototype.keys = function(
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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 [];
|
|
506
1150
|
}
|
|
507
|
-
return new Promise((resolve, reject) => {
|
|
508
|
-
_this.conn.keys(key, function(err, ret) {
|
|
509
|
-
if (err) {
|
|
510
|
-
reject(err);
|
|
511
|
-
} else {
|
|
512
|
-
resolve(ret);
|
|
513
|
-
}
|
|
514
|
-
});
|
|
515
|
-
});
|
|
516
1151
|
};
|
|
517
1152
|
|
|
518
1153
|
/**
|
|
519
1154
|
* @description 修改数组缓存
|
|
520
1155
|
* @param {String} key 键
|
|
521
|
-
* @param {Object} value 值
|
|
1156
|
+
* @param {Object|Array} value 值
|
|
522
1157
|
* @param {Number} seconds 秒
|
|
523
|
-
* @return {Promise
|
|
524
|
-
*/
|
|
525
|
-
Redis.prototype.list_set = function(key, value, seconds) {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
1158
|
+
* @return {Promise<Number>} 数组长度
|
|
1159
|
+
*/
|
|
1160
|
+
Redis.prototype.list_set = async function(key, value, seconds) {
|
|
1161
|
+
try {
|
|
1162
|
+
// 触发前置事件,允许修改或阻止操作
|
|
1163
|
+
const beforeEventData = { key, value, ttl: 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, ttl: 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
|
+
}
|
|
531
1198
|
} else {
|
|
532
|
-
|
|
533
|
-
|
|
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;
|
|
534
1207
|
}
|
|
535
|
-
resolve(ret);
|
|
536
1208
|
}
|
|
537
|
-
|
|
538
|
-
|
|
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, ttl: finalSeconds, result: 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, error: err });
|
|
1229
|
+
$.log.error(`[Redis] 设置列表失败 [${key}]:`, err);
|
|
1230
|
+
return 0;
|
|
1231
|
+
}
|
|
539
1232
|
};
|
|
540
1233
|
|
|
541
1234
|
/**
|
|
542
1235
|
* @description 数组缓存追加对象
|
|
543
1236
|
* @param {String} key 键
|
|
544
|
-
* @param {Object} value 值
|
|
545
|
-
* @return {Promise
|
|
546
|
-
*/
|
|
547
|
-
Redis.prototype.list_push = function(key, value) {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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
|
+
}
|
|
553
1272
|
} else {
|
|
554
|
-
|
|
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
|
+
}
|
|
555
1283
|
}
|
|
556
|
-
|
|
557
|
-
|
|
1284
|
+
|
|
1285
|
+
// 触发后置事件
|
|
1286
|
+
this.emit('list_push_after', { key: finalKey, value: finalValue, result: 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
|
+
}
|
|
558
1297
|
};
|
|
559
1298
|
|
|
560
1299
|
/**
|
|
561
1300
|
* @description 判断成员是否存在
|
|
562
1301
|
* @param {String} key 键
|
|
563
1302
|
* @param {Object} value 值
|
|
564
|
-
* @return {Promise
|
|
1303
|
+
* @return {Promise<Boolean>} 存在返回true, 否则返回false
|
|
565
1304
|
*/
|
|
566
|
-
Redis.prototype.list_has = function(key, value) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
})
|
|
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
|
+
}
|
|
583
1325
|
};
|
|
584
1326
|
|
|
585
1327
|
/**
|
|
@@ -587,65 +1329,555 @@ Redis.prototype.list_has = function(key, value) {
|
|
|
587
1329
|
* @param {String} key 键
|
|
588
1330
|
* @param {Number} start 起始位置
|
|
589
1331
|
* @param {Number} end 结束位置
|
|
590
|
-
* @return {Promise
|
|
1332
|
+
* @return {Promise<Array>} 查询到的数组
|
|
591
1333
|
*/
|
|
592
|
-
Redis.prototype.list_get = function(key, start, end) {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
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]);
|
|
604
1345
|
} else {
|
|
605
|
-
|
|
1346
|
+
$.log.error('[Redis] 连接对象不支持lRange操作');
|
|
1347
|
+
return [];
|
|
606
1348
|
}
|
|
607
|
-
|
|
608
|
-
|
|
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
|
+
}
|
|
609
1370
|
};
|
|
610
1371
|
|
|
611
1372
|
/**
|
|
612
1373
|
* @description 清空数组缓存
|
|
613
1374
|
* @param {String} key 键
|
|
614
1375
|
* @param {Array} value 新成员, 没有则删除数组
|
|
615
|
-
* @return {Promise
|
|
1376
|
+
* @return {Promise<Boolean>} 成功返回true,失败返回false
|
|
616
1377
|
*/
|
|
617
|
-
Redis.prototype.list_clear = function(key, value) {
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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]);
|
|
634
1407
|
} else {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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
|
+
}
|
|
639
1427
|
} else {
|
|
640
|
-
|
|
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
|
+
}
|
|
641
1437
|
}
|
|
642
|
-
})
|
|
1438
|
+
} catch (err) {
|
|
1439
|
+
$.log.error(`[Redis] 重新设置列表值失败 [${finalKey}]:`, err);
|
|
1440
|
+
success = false;
|
|
1441
|
+
}
|
|
643
1442
|
}
|
|
644
|
-
|
|
645
|
-
|
|
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 = { value: 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 { value: finalData, ttl: finalSeconds } = beforeEventData;
|
|
1526
|
+
|
|
1527
|
+
// 检查最终数据是否有效
|
|
1528
|
+
if (!finalData || typeof finalData !== 'object') {
|
|
1529
|
+
throw new Error('事件处理后的value必须是非空对象');
|
|
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', { value: finalData, ttl: finalSeconds, success: true });
|
|
1549
|
+
return true;
|
|
1550
|
+
} catch (err) {
|
|
1551
|
+
// 触发错误事件
|
|
1552
|
+
this.emit('mset_error', { value: 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', { value: obj, ttl: seconds, success: true });
|
|
1575
|
+
return true;
|
|
1576
|
+
} catch (fallbackErr) {
|
|
1577
|
+
// 触发错误事件
|
|
1578
|
+
this.emit('mset_error', { value: 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);
|
|
1653
|
+
}
|
|
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, value: 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;
|
|
646
1676
|
}
|
|
647
|
-
|
|
1677
|
+
|
|
1678
|
+
// 使用可能被修改的值
|
|
1679
|
+
let { key: finalKey, value: 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, value: finalFields, success });
|
|
1701
|
+
return success;
|
|
1702
|
+
} finally {
|
|
1703
|
+
this._recycleConnection(connection);
|
|
1704
|
+
}
|
|
1705
|
+
} catch (err) {
|
|
1706
|
+
// 触发错误事件
|
|
1707
|
+
this.emit('hdel_error', { key, value: fields, error: err });
|
|
1708
|
+
$.log.error(`[Redis] 删除哈希字段失败 [${key}]:`, err);
|
|
1709
|
+
return false;
|
|
1710
|
+
}
|
|
648
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
|
+
}
|
|
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
|
+
|
|
649
1881
|
/**
|
|
650
1882
|
* @description 导出Redis类
|
|
651
1883
|
*/
|
|
@@ -679,4 +1911,411 @@ function redis_admin(scope, dir) {
|
|
|
679
1911
|
/**
|
|
680
1912
|
* @description 导出redis管理函数
|
|
681
1913
|
*/
|
|
682
|
-
exports.redis_admin = redis_admin;
|
|
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: finalKey, field: finalField, value: finalValue, 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, value: 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, value: 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: finalKey, value: finalObj, success: true });
|
|
2236
|
+
return true;
|
|
2237
|
+
} finally {
|
|
2238
|
+
this._recycleConnection(connection);
|
|
2239
|
+
}
|
|
2240
|
+
} catch (err) {
|
|
2241
|
+
$.log.error(`[Redis] 批量设置哈希字段失败 [${key}]:`, err);
|
|
2242
|
+
// 触发错误事件
|
|
2243
|
+
this.emit('hmset_error', { key, value: 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
|
+
};
|