mm_mongodb 1.4.3 → 1.4.5
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 +565 -231
- package/cache.js +2680 -173
- package/index.js +483 -64
- package/package.json +23 -16
- package/test.js +6 -8
package/cache.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileOverview
|
|
2
|
+
* @fileOverview MongoDB缓存帮助类函数
|
|
3
3
|
* @author <a href="http://qww.elins.cn">邱文武</a>
|
|
4
4
|
* @version 1.2
|
|
5
5
|
*/
|
|
6
|
-
const {
|
|
7
|
-
MongoDB
|
|
8
|
-
} = require('./index.js');
|
|
6
|
+
const { MongoDB } = require('./index.js');
|
|
9
7
|
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
/**
|
|
9
|
+
* 确保全局$对象存在
|
|
10
|
+
*/
|
|
11
|
+
if (!global.$) {
|
|
12
|
+
global.$ = {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @description MongoDB缓存帮助类
|
|
17
|
+
*/
|
|
12
18
|
class Mongodb_cache extends MongoDB {
|
|
13
19
|
/**
|
|
14
20
|
* @description 创建Mongodb_cache帮助类函数 (构造函数)
|
|
@@ -18,127 +24,442 @@ class Mongodb_cache extends MongoDB {
|
|
|
18
24
|
*/
|
|
19
25
|
constructor(scope, dir, config) {
|
|
20
26
|
super(scope, dir, config);
|
|
27
|
+
// 默认使用键值对表
|
|
28
|
+
this.table = 'keyValue';
|
|
29
|
+
// 数据库连接状态
|
|
30
|
+
this.connected = false;
|
|
31
|
+
// 连接Promise缓存
|
|
32
|
+
this.connectPromise = null;
|
|
33
|
+
// 连接重试次数
|
|
34
|
+
this.connectAttempts = 0;
|
|
35
|
+
this.maxConnectAttempts = 3;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @description 确保数据库连接已建立
|
|
40
|
+
* @private
|
|
41
|
+
* @return {Promise<void>}
|
|
42
|
+
*/
|
|
43
|
+
async _ensureConnected() {
|
|
44
|
+
// 如果已有连接Promise,等待其完成
|
|
45
|
+
if (this.connectPromise) {
|
|
46
|
+
return this.connectPromise;
|
|
47
|
+
}
|
|
21
48
|
|
|
22
|
-
//
|
|
23
|
-
this.
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
49
|
+
// 创建新的连接Promise
|
|
50
|
+
this.connectPromise = (async () => {
|
|
51
|
+
try {
|
|
52
|
+
// 检查连接状态
|
|
53
|
+
if (!this.db || !this.connected) {
|
|
54
|
+
// 记录连接尝试
|
|
55
|
+
this.connectAttempts++;
|
|
56
|
+
|
|
57
|
+
// 尝试连接
|
|
58
|
+
await super.open();
|
|
59
|
+
|
|
60
|
+
// 检查连接是否成功
|
|
61
|
+
if (this.db && this.db.collection) {
|
|
62
|
+
this.connected = true;
|
|
63
|
+
this.connectAttempts = 0;
|
|
64
|
+
|
|
65
|
+
// 确保索引已创建
|
|
66
|
+
await this._ensureIndexes();
|
|
67
|
+
|
|
68
|
+
$.log.info(`[MongoDB cache] 成功连接到数据库,表名: ${this.table}`);
|
|
69
|
+
} else {
|
|
70
|
+
throw new Error('数据库连接失败,db对象为空');
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
$.log.error(`[MongoDB cache] 连接失败 (尝试 ${this.connectAttempts}/${this.maxConnectAttempts}):`, error.message);
|
|
75
|
+
|
|
76
|
+
// 重置连接Promise,允许下次重试
|
|
77
|
+
this.connectPromise = null;
|
|
78
|
+
|
|
79
|
+
// 如果尝试次数未超过限制,抛出错误让上层处理
|
|
80
|
+
if (this.connectAttempts < this.maxConnectAttempts) {
|
|
81
|
+
throw error;
|
|
82
|
+
} else {
|
|
83
|
+
// 超过重试次数,记录严重错误
|
|
84
|
+
$.log.error('[MongoDB cache] 达到最大连接重试次数,连接失败');
|
|
85
|
+
throw new Error('数据库连接失败,已达到最大重试次数');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
})();
|
|
89
|
+
|
|
90
|
+
return this.connectPromise;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @description 确保索引已创建
|
|
95
|
+
* @private
|
|
96
|
+
* @return {Promise<void>}
|
|
97
|
+
*/
|
|
98
|
+
async _ensureIndexes() {
|
|
99
|
+
try {
|
|
100
|
+
// 确保db对象有效
|
|
101
|
+
if (!this.db || !this.db.collection) {
|
|
102
|
+
throw new Error('数据库连接无效,无法创建索引');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// 创建TTL索引用于自动过期
|
|
106
|
+
await this.db.collection(this.table).createIndex(
|
|
107
|
+
{ expireAt: 1 },
|
|
108
|
+
{ expireAfterSeconds: 0 }
|
|
109
|
+
);
|
|
110
|
+
// 创建_id索引(通常自动创建,但显式指定更清晰)
|
|
111
|
+
await this.db.collection(this.table).createIndex({ _id: 1 });
|
|
112
|
+
} catch (error) {
|
|
113
|
+
// 忽略索引已存在的错误
|
|
114
|
+
if (error.code !== 85) {
|
|
115
|
+
$.log.error('[MongoDB cache] 创建索引失败:', error);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
27
118
|
}
|
|
28
119
|
}
|
|
29
120
|
|
|
30
121
|
/**
|
|
31
|
-
*
|
|
32
|
-
* @param {String}
|
|
33
|
-
* @param {
|
|
122
|
+
* @description 增加整数值
|
|
123
|
+
* @param {String} key 键
|
|
124
|
+
* @param {Number} value 增量值
|
|
125
|
+
* @return {Promise|Number} 增加后的值
|
|
34
126
|
*/
|
|
35
|
-
Mongodb_cache.prototype.
|
|
36
|
-
|
|
37
|
-
|
|
127
|
+
Mongodb_cache.prototype.addInt = async function(key, value) {
|
|
128
|
+
try {
|
|
129
|
+
// 触发before事件
|
|
130
|
+
this.emit('addInt_before', { key, value });
|
|
131
|
+
|
|
132
|
+
// 确保连接已建立
|
|
133
|
+
await this._ensureConnected();
|
|
134
|
+
|
|
135
|
+
// 验证参数
|
|
136
|
+
if (!key || typeof value !== 'number' || !Number.isInteger(value)) {
|
|
137
|
+
throw new Error('参数无效:key不能为空且value必须是整数');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 使用$inc操作符增加整数值
|
|
141
|
+
const result = await this.db.collection(this.table).findOneAndUpdate(
|
|
142
|
+
{ _id: key },
|
|
143
|
+
{ $inc: { value: value } },
|
|
144
|
+
{ upsert: true, returnDocument: 'after', projection: { value: 1 } }
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const returnValue = result.value ? result.value.value : value;
|
|
148
|
+
|
|
149
|
+
// 触发after事件
|
|
150
|
+
this.emit('addInt_after', { key, value, result: returnValue });
|
|
151
|
+
|
|
152
|
+
return returnValue;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
// 触发error事件
|
|
155
|
+
this.emit('addInt_error', { key, value, error });
|
|
156
|
+
$.log.error('[MongoDB cache] 增加整数值失败:', error);
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
38
159
|
};
|
|
39
160
|
|
|
40
161
|
/**
|
|
41
|
-
*
|
|
42
|
-
* @param {String}
|
|
162
|
+
* @description 增加浮点数值
|
|
163
|
+
* @param {String} key 键
|
|
164
|
+
* @param {Number} value 增量值
|
|
165
|
+
* @return {Promise|Number} 增加后的值
|
|
43
166
|
*/
|
|
44
|
-
Mongodb_cache.prototype.
|
|
45
|
-
|
|
46
|
-
|
|
167
|
+
Mongodb_cache.prototype.addFloat = async function(key, value) {
|
|
168
|
+
try {
|
|
169
|
+
// 触发before事件
|
|
170
|
+
this.emit('addFloat_before', { key, value });
|
|
171
|
+
|
|
172
|
+
// 确保连接已建立
|
|
173
|
+
await this._ensureConnected();
|
|
174
|
+
|
|
175
|
+
// 验证参数
|
|
176
|
+
if (!key || typeof value !== 'number') {
|
|
177
|
+
throw new Error('参数无效:key不能为空且value必须是数字');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 使用$inc操作符增加浮点数值
|
|
181
|
+
const result = await this.db.collection(this.table).findOneAndUpdate(
|
|
182
|
+
{ _id: key },
|
|
183
|
+
{ $inc: { value: value } },
|
|
184
|
+
{ upsert: true, returnDocument: 'after', projection: { value: 1 } }
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
const returnValue = result.value ? result.value.value : value;
|
|
188
|
+
|
|
189
|
+
// 触发after事件
|
|
190
|
+
this.emit('addFloat_after', { key, value, result: returnValue });
|
|
191
|
+
|
|
192
|
+
return returnValue;
|
|
193
|
+
} catch (error) {
|
|
194
|
+
// 触发error事件
|
|
195
|
+
this.emit('addFloat_error', { key, value, error });
|
|
196
|
+
$.log.error('[MongoDB cache] 增加浮点数值失败:', error);
|
|
197
|
+
throw error;
|
|
198
|
+
}
|
|
47
199
|
};
|
|
48
200
|
|
|
49
201
|
/**
|
|
50
|
-
*
|
|
51
|
-
* @param {String}
|
|
52
|
-
* @param {String}
|
|
53
|
-
* @
|
|
202
|
+
* @description 增加字符串值
|
|
203
|
+
* @param {String} key 键
|
|
204
|
+
* @param {String} value 要增加的字符串
|
|
205
|
+
* @return {Promise|String} 增加后的字符串
|
|
54
206
|
*/
|
|
55
|
-
Mongodb_cache.prototype.
|
|
56
|
-
|
|
207
|
+
Mongodb_cache.prototype.addStr = async function(key, value) {
|
|
208
|
+
try {
|
|
209
|
+
// 触发before事件
|
|
210
|
+
this.emit('addStr_before', { key, value });
|
|
211
|
+
|
|
212
|
+
// 确保连接已建立
|
|
213
|
+
await this._ensureConnected();
|
|
214
|
+
|
|
215
|
+
// 验证参数
|
|
216
|
+
if (!key || typeof value !== 'string') {
|
|
217
|
+
throw new Error('参数无效:key不能为空且value必须是字符串');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 获取现有值
|
|
221
|
+
const current = await this.db.collection(this.table).findOne({ _id: key }, { projection: { value: 1 } });
|
|
222
|
+
const currentValue = current ? current.value : '';
|
|
223
|
+
|
|
224
|
+
// 确保当前值是字符串
|
|
225
|
+
if (typeof currentValue !== 'string') {
|
|
226
|
+
throw new Error('现有值不是字符串类型,无法追加');
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// 更新字符串
|
|
230
|
+
const newValue = currentValue + value;
|
|
231
|
+
await this.db.collection(this.table).updateOne(
|
|
232
|
+
{ _id: key },
|
|
233
|
+
{ $set: { value: newValue } },
|
|
234
|
+
{ upsert: true }
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
// 触发after事件
|
|
238
|
+
this.emit('addStr_after', { key, value, result: newValue });
|
|
239
|
+
|
|
240
|
+
return newValue;
|
|
241
|
+
} catch (error) {
|
|
242
|
+
// 触发error事件
|
|
243
|
+
this.emit('addStr_error', { key, value, error });
|
|
244
|
+
$.log.error('[MongoDB cache] 增加字符串值失败:', error);
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
57
247
|
};
|
|
58
248
|
|
|
59
249
|
/**
|
|
60
|
-
*
|
|
61
|
-
* @param {String} channel
|
|
62
|
-
* @param {String} message
|
|
63
|
-
*/
|
|
64
|
-
Mongodb_cache.prototype.main = async function(channel, message) {};
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* 消息通知
|
|
68
|
-
* @param {String} channel 频道
|
|
69
|
-
* @param {String} message 消息
|
|
70
|
-
*/
|
|
71
|
-
Mongodb_cache.prototype.message = function(channel, message) {
|
|
72
|
-
// console.log("我接收到信息了" + message);
|
|
73
|
-
var func = this.func_list[channel];
|
|
74
|
-
if (func) {
|
|
75
|
-
func(message);
|
|
76
|
-
} else {
|
|
77
|
-
this.main(channel, message);
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
/**z
|
|
82
|
-
* @description 增加整数值(负数为减)
|
|
250
|
+
* @description 删除缓存
|
|
83
251
|
* @param {String} key 键
|
|
84
|
-
* @
|
|
85
|
-
* @return {Promise|Number} 计算后的结果
|
|
252
|
+
* @return {Promise|Boolean} 成功返回true,失败返回false
|
|
86
253
|
*/
|
|
87
|
-
Mongodb_cache.prototype.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
254
|
+
Mongodb_cache.prototype.del = async function(key) {
|
|
255
|
+
try {
|
|
256
|
+
// 触发before事件
|
|
257
|
+
this.emit('del_before', { key });
|
|
258
|
+
|
|
259
|
+
// 确保连接已建立
|
|
260
|
+
await this._ensureConnected();
|
|
261
|
+
|
|
262
|
+
// 验证参数
|
|
263
|
+
if (!key) {
|
|
264
|
+
throw new Error('参数无效:key不能为空');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const result = await this.db.collection(this.table).deleteOne({ _id: key });
|
|
268
|
+
const success = result.deletedCount > 0;
|
|
269
|
+
|
|
270
|
+
// 触发after事件
|
|
271
|
+
this.emit('del_after', { key, result: success });
|
|
272
|
+
|
|
273
|
+
return success;
|
|
274
|
+
} catch (error) {
|
|
275
|
+
// 触发error事件
|
|
276
|
+
this.emit('del_error', { key, error });
|
|
277
|
+
$.log.error('[MongoDB cache] 删除缓存失败:', error);
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
93
280
|
};
|
|
94
281
|
|
|
95
282
|
/**
|
|
96
|
-
* @description
|
|
97
|
-
* @param {String}
|
|
98
|
-
* @
|
|
99
|
-
* @return {Promise|Number} 计算后的结果
|
|
283
|
+
* @description 批量删除缓存 (Redis风格)
|
|
284
|
+
* @param {String[]} keys 键数组
|
|
285
|
+
* @return {Promise|Number} 删除的键数量
|
|
100
286
|
*/
|
|
101
|
-
Mongodb_cache.prototype.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
287
|
+
Mongodb_cache.prototype.delKeys = async function(...keys) {
|
|
288
|
+
try {
|
|
289
|
+
// 触发before事件
|
|
290
|
+
this.emit('delKeys_before', { keys });
|
|
291
|
+
|
|
292
|
+
// 确保连接已建立
|
|
293
|
+
await this._ensureConnected();
|
|
294
|
+
|
|
295
|
+
if (!keys || keys.length === 0) {
|
|
296
|
+
// 触发after事件
|
|
297
|
+
this.emit('delKeys_after', { keys, result: 0 });
|
|
298
|
+
return 0;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const result = await this.db.collection(this.table).deleteMany({
|
|
302
|
+
_id: { $in: keys }
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// 触发after事件
|
|
306
|
+
this.emit('delKeys_after', { keys, result: result.deletedCount });
|
|
307
|
+
|
|
308
|
+
return result.deletedCount;
|
|
309
|
+
} catch (error) {
|
|
310
|
+
// 触发error事件
|
|
311
|
+
this.emit('delKeys_error', { keys, error });
|
|
312
|
+
$.log.error('[MongoDB cache] 批量删除缓存失败:', error);
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
107
315
|
};
|
|
108
316
|
|
|
109
317
|
/**
|
|
110
|
-
* @description
|
|
318
|
+
* @description 修改缓存
|
|
111
319
|
* @param {String} key 键
|
|
112
|
-
* @param {
|
|
113
|
-
* @
|
|
320
|
+
* @param {Object} value 值
|
|
321
|
+
* @param {Number} seconds 过期时间(秒)
|
|
322
|
+
* @return {Promise|Object} 修改后的值
|
|
114
323
|
*/
|
|
115
|
-
Mongodb_cache.prototype.
|
|
116
|
-
|
|
324
|
+
Mongodb_cache.prototype.set = async function(key, value, seconds) {
|
|
325
|
+
try {
|
|
326
|
+
// 触发before事件
|
|
327
|
+
this.emit('set_before', { key, value, seconds });
|
|
328
|
+
|
|
329
|
+
// 确保连接已建立
|
|
330
|
+
await this._ensureConnected();
|
|
331
|
+
|
|
332
|
+
// 验证参数
|
|
333
|
+
if (!key) {
|
|
334
|
+
throw new Error('参数无效:key不能为空');
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 准备更新数据
|
|
338
|
+
const updateData = { $set: { value } };
|
|
339
|
+
|
|
340
|
+
// 设置过期时间
|
|
341
|
+
if (seconds) {
|
|
342
|
+
const expireAt = new Date(Date.now() + seconds * 1000);
|
|
343
|
+
updateData.$set.expireAt = expireAt;
|
|
344
|
+
} else {
|
|
345
|
+
// 如果没有设置过期时间,移除可能存在的expireAt字段
|
|
346
|
+
updateData.$unset = { expireAt: 1 };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
await this.db.collection(this.table).updateOne(
|
|
350
|
+
{ _id: key },
|
|
351
|
+
updateData,
|
|
352
|
+
{ upsert: true }
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
// 触发after事件
|
|
356
|
+
this.emit('set_after', { key, value, seconds, result: value });
|
|
357
|
+
|
|
358
|
+
return value;
|
|
359
|
+
} catch (error) {
|
|
360
|
+
// 触发error事件
|
|
361
|
+
this.emit('set_error', { key, value, seconds, error });
|
|
362
|
+
$.log.error('[MongoDB cache] 修改缓存失败:', error);
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
117
365
|
};
|
|
118
366
|
|
|
119
367
|
/**
|
|
120
|
-
* @description
|
|
368
|
+
* @description 条件设置缓存 (Redis SETNX)
|
|
121
369
|
* @param {String} key 键
|
|
122
|
-
* @
|
|
370
|
+
* @param {Object} value 值
|
|
371
|
+
* @return {Promise|Boolean} 如果键不存在且设置成功返回true,否则返回false
|
|
123
372
|
*/
|
|
124
|
-
Mongodb_cache.prototype.
|
|
125
|
-
|
|
373
|
+
Mongodb_cache.prototype.setnx = async function(key, value) {
|
|
374
|
+
try {
|
|
375
|
+
// 触发before事件
|
|
376
|
+
this.emit('setnx_before', { key, value });
|
|
377
|
+
|
|
378
|
+
// 确保连接已建立
|
|
379
|
+
await this._ensureConnected();
|
|
380
|
+
|
|
381
|
+
// 验证参数
|
|
382
|
+
if (!key) {
|
|
383
|
+
throw new Error('参数无效:key不能为空');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// 检查键是否存在
|
|
387
|
+
const exists = await this.db.collection(this.table).findOne({
|
|
388
|
+
_id: key,
|
|
389
|
+
$or: [
|
|
390
|
+
{ expireAt: { $exists: false } },
|
|
391
|
+
{ expireAt: { $gt: new Date() } }
|
|
392
|
+
]
|
|
393
|
+
}, { projection: { _id: 1 } });
|
|
394
|
+
|
|
395
|
+
// 如果键存在,返回false
|
|
396
|
+
if (exists) {
|
|
397
|
+
// 触发after事件
|
|
398
|
+
this.emit('setnx_after', { key, value, result: false });
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// 否则设置值
|
|
403
|
+
await this.db.collection(this.table).insertOne({
|
|
404
|
+
_id: key,
|
|
405
|
+
value
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
// 触发after事件
|
|
409
|
+
this.emit('setnx_after', { key, value, result: true });
|
|
410
|
+
|
|
411
|
+
return true;
|
|
412
|
+
} catch (error) {
|
|
413
|
+
// 处理重复键错误
|
|
414
|
+
if (error.code === 11000) {
|
|
415
|
+
// 触发after事件
|
|
416
|
+
this.emit('setnx_after', { key, value, result: false });
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
// 触发error事件
|
|
420
|
+
this.emit('setnx_error', { key, value, error });
|
|
421
|
+
$.log.error('[MongoDB cache] 条件设置缓存失败:', error);
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
126
424
|
};
|
|
127
425
|
|
|
128
426
|
/**
|
|
129
|
-
* @description
|
|
427
|
+
* @description 设置带过期时间的缓存 (Redis SETEX)
|
|
130
428
|
* @param {String} key 键
|
|
429
|
+
* @param {Number} seconds 过期时间(秒)
|
|
131
430
|
* @param {Object} value 值
|
|
132
|
-
* @
|
|
133
|
-
* @return {Object} 值
|
|
431
|
+
* @return {Promise|Boolean} 是否设置成功
|
|
134
432
|
*/
|
|
135
|
-
Mongodb_cache.prototype.
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
433
|
+
Mongodb_cache.prototype.setex = async function(key, seconds, value) {
|
|
434
|
+
try {
|
|
435
|
+
// 触发before事件
|
|
436
|
+
this.emit('setex_before', { key, seconds, value });
|
|
437
|
+
|
|
438
|
+
// 确保连接已建立
|
|
439
|
+
await this._ensureConnected();
|
|
440
|
+
|
|
441
|
+
// 验证参数
|
|
442
|
+
if (!key || typeof seconds !== 'number' || seconds <= 0) {
|
|
443
|
+
throw new Error('参数无效:key不能为空且seconds必须是正数');
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const expireAt = new Date(Date.now() + seconds * 1000);
|
|
447
|
+
await this.db.collection(this.table).updateOne(
|
|
448
|
+
{ _id: key },
|
|
449
|
+
{ $set: { value, expireAt } },
|
|
450
|
+
{ upsert: true }
|
|
451
|
+
);
|
|
452
|
+
|
|
453
|
+
// 触发after事件
|
|
454
|
+
this.emit('setex_after', { key, seconds, value, result: true });
|
|
455
|
+
|
|
456
|
+
return true;
|
|
457
|
+
} catch (error) {
|
|
458
|
+
// 触发error事件
|
|
459
|
+
this.emit('setex_error', { key, seconds, value, error });
|
|
460
|
+
$.log.error('[MongoDB cache] 设置带过期时间的缓存失败:', error);
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
142
463
|
};
|
|
143
464
|
|
|
144
465
|
/**
|
|
@@ -149,15 +470,51 @@ Mongodb_cache.prototype.set = async function(key, value, seconds) {
|
|
|
149
470
|
* @return {Promise|Object} 值
|
|
150
471
|
*/
|
|
151
472
|
Mongodb_cache.prototype.add = async function(key, value, seconds) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
473
|
+
try {
|
|
474
|
+
// 触发before事件
|
|
475
|
+
this.emit('add_before', { key, value, seconds });
|
|
476
|
+
|
|
477
|
+
// 验证参数
|
|
478
|
+
if (!key) {
|
|
479
|
+
throw new Error('参数无效:key不能为空');
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const exists = await this.db.collection(this.table).findOne({ _id: key }, { projection: { _id: 1, expireAt: 1 } });
|
|
483
|
+
|
|
484
|
+
// 检查是否存在且未过期
|
|
485
|
+
if (exists) {
|
|
486
|
+
// 检查是否过期
|
|
487
|
+
if (exists.expireAt && new Date(exists.expireAt) < new Date()) {
|
|
488
|
+
// 过期则删除旧数据
|
|
489
|
+
await this.db.collection(this.table).deleteOne({ _id: key });
|
|
490
|
+
} else {
|
|
491
|
+
// 触发after事件
|
|
492
|
+
this.emit('add_after', { key, value, seconds, result: value });
|
|
493
|
+
// 未过期则返回现有值
|
|
494
|
+
return value;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
const data = { _id: key, value };
|
|
499
|
+
|
|
500
|
+
// 设置过期时间
|
|
501
|
+
if (seconds) {
|
|
502
|
+
const expireAt = new Date(Date.now() + seconds * 1000);
|
|
503
|
+
data.expireAt = expireAt;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
await this.db.collection(this.table).insertOne(data);
|
|
507
|
+
|
|
508
|
+
// 触发after事件
|
|
509
|
+
this.emit('add_after', { key, value, seconds, result: value });
|
|
510
|
+
|
|
511
|
+
return value;
|
|
512
|
+
} catch (error) {
|
|
513
|
+
// 触发error事件
|
|
514
|
+
this.emit('add_error', { key, value, seconds, error });
|
|
515
|
+
$.log.error('[MongoDB cache] 添加缓存失败:', error);
|
|
516
|
+
throw error;
|
|
517
|
+
}
|
|
161
518
|
};
|
|
162
519
|
|
|
163
520
|
/**
|
|
@@ -165,8 +522,108 @@ Mongodb_cache.prototype.add = async function(key, value, seconds) {
|
|
|
165
522
|
* @param {String} key 键
|
|
166
523
|
* @return {Promise|Object} 查询值
|
|
167
524
|
*/
|
|
168
|
-
Mongodb_cache.prototype.get = function(key) {
|
|
169
|
-
|
|
525
|
+
Mongodb_cache.prototype.get = async function(key) {
|
|
526
|
+
try {
|
|
527
|
+
// 确保连接已建立
|
|
528
|
+
await this._ensureConnected();
|
|
529
|
+
|
|
530
|
+
// 验证参数
|
|
531
|
+
if (!key) {
|
|
532
|
+
throw new Error('参数无效:key不能为空');
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const result = await this.db.collection(this.table).findOne({ _id: key });
|
|
536
|
+
|
|
537
|
+
let returnValue;
|
|
538
|
+
// 检查是否过期
|
|
539
|
+
if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
|
|
540
|
+
// 过期则删除并返回null
|
|
541
|
+
await this.db.collection(this.table).deleteOne({ _id: key });
|
|
542
|
+
returnValue = null;
|
|
543
|
+
} else {
|
|
544
|
+
returnValue = result ? result.value : null;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return returnValue;
|
|
548
|
+
} catch (error) {
|
|
549
|
+
$.log.error('[MongoDB cache] 查询缓存失败:', error);
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* @description 批量查询缓存 (Redis MGET)
|
|
556
|
+
* @param {String[]} keys 键数组
|
|
557
|
+
* @return {Promise|Array} 值数组,顺序与键对应
|
|
558
|
+
*/
|
|
559
|
+
Mongodb_cache.prototype.mget = async function(...keys) {
|
|
560
|
+
try {
|
|
561
|
+
if (!keys || keys.length === 0) {
|
|
562
|
+
return [];
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const results = await this.db.collection(this.table).find({
|
|
566
|
+
_id: { $in: keys },
|
|
567
|
+
$or: [
|
|
568
|
+
{ expireAt: { $exists: false } },
|
|
569
|
+
{ expireAt: { $gt: new Date() } }
|
|
570
|
+
]
|
|
571
|
+
}).toArray();
|
|
572
|
+
|
|
573
|
+
// 创建结果映射
|
|
574
|
+
const resultMap = {};
|
|
575
|
+
results.forEach(item => {
|
|
576
|
+
resultMap[item._id] = item.value;
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
// 按输入顺序返回结果
|
|
580
|
+
const resultArray = keys.map(key => resultMap[key] !== undefined ? resultMap[key] : null);
|
|
581
|
+
return resultArray;
|
|
582
|
+
} catch (error) {
|
|
583
|
+
$.log.error('[MongoDB cache] 批量查询缓存失败:', error);
|
|
584
|
+
return [];
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* @description 批量设置缓存 (Redis MSET)
|
|
590
|
+
* @param {Object} keyValuePairs 键值对对象
|
|
591
|
+
* @return {Promise|Boolean} 是否设置成功
|
|
592
|
+
*/
|
|
593
|
+
Mongodb_cache.prototype.mset = async function(keyValuePairs) {
|
|
594
|
+
try {
|
|
595
|
+
// 触发before事件
|
|
596
|
+
this.emit('mset_before', { keyValuePairs });
|
|
597
|
+
|
|
598
|
+
if (!keyValuePairs || typeof keyValuePairs !== 'object') {
|
|
599
|
+
// 触发after事件
|
|
600
|
+
this.emit('mset_after', { keyValuePairs, result: false });
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// 准备批量操作
|
|
605
|
+
const bulkOps = Object.entries(keyValuePairs).map(([key, value]) => ({
|
|
606
|
+
updateOne: {
|
|
607
|
+
filter: { _id: key },
|
|
608
|
+
update: { $set: { value }, $unset: { expireAt: 1 } },
|
|
609
|
+
upsert: true
|
|
610
|
+
}
|
|
611
|
+
}));
|
|
612
|
+
|
|
613
|
+
if (bulkOps.length > 0) {
|
|
614
|
+
await this.db.collection(this.table).bulkWrite(bulkOps);
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// 触发after事件
|
|
618
|
+
this.emit('mset_after', { keyValuePairs, result: true });
|
|
619
|
+
|
|
620
|
+
return true;
|
|
621
|
+
} catch (error) {
|
|
622
|
+
// 触发error事件
|
|
623
|
+
this.emit('mset_error', { keyValuePairs, error });
|
|
624
|
+
$.log.error('[MongoDB cache] 批量设置缓存失败:', error);
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
170
627
|
};
|
|
171
628
|
|
|
172
629
|
/**
|
|
@@ -174,8 +631,42 @@ Mongodb_cache.prototype.get = function(key) {
|
|
|
174
631
|
* @param {String} key 键
|
|
175
632
|
* @return {Promise|Boolean} 有返回true, 没有返回false
|
|
176
633
|
*/
|
|
177
|
-
Mongodb_cache.prototype.has = function(key) {
|
|
178
|
-
|
|
634
|
+
Mongodb_cache.prototype.has = async function(key) {
|
|
635
|
+
try {
|
|
636
|
+
// 触发before事件
|
|
637
|
+
this.emit('has_before', { key });
|
|
638
|
+
|
|
639
|
+
// 确保连接已建立
|
|
640
|
+
await this._ensureConnected();
|
|
641
|
+
|
|
642
|
+
// 验证参数
|
|
643
|
+
if (!key) {
|
|
644
|
+
throw new Error('参数无效:key不能为空');
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const result = await this.db.collection(this.table).findOne({ _id: key }, { projection: { _id: 1, expireAt: 1 } });
|
|
648
|
+
// 检查是否过期
|
|
649
|
+
if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
|
|
650
|
+
// 过期则删除并返回false
|
|
651
|
+
await this.db.collection(this.table).deleteOne({ _id: key });
|
|
652
|
+
|
|
653
|
+
// 触发after事件
|
|
654
|
+
this.emit('has_after', { key, result: false });
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const hasResult = !!result;
|
|
659
|
+
|
|
660
|
+
// 触发after事件
|
|
661
|
+
this.emit('has_after', { key, result: hasResult });
|
|
662
|
+
|
|
663
|
+
return hasResult;
|
|
664
|
+
} catch (error) {
|
|
665
|
+
// 触发error事件
|
|
666
|
+
this.emit('has_error', { key, error });
|
|
667
|
+
$.log.error('[MongoDB cache] 判断键是否存在失败:', error);
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
179
670
|
};
|
|
180
671
|
|
|
181
672
|
/**
|
|
@@ -185,8 +676,32 @@ Mongodb_cache.prototype.has = function(key) {
|
|
|
185
676
|
* @param {Number} end 结束位置
|
|
186
677
|
* @return {Promise|String} 查询值
|
|
187
678
|
*/
|
|
188
|
-
Mongodb_cache.prototype.getrange = function(key, start, end) {
|
|
189
|
-
|
|
679
|
+
Mongodb_cache.prototype.getrange = async function(key, start, end) {
|
|
680
|
+
try {
|
|
681
|
+
// 触发before事件
|
|
682
|
+
this.emit('getrange_before', { key, start, end });
|
|
683
|
+
|
|
684
|
+
// 验证参数
|
|
685
|
+
if (!key || typeof start !== 'number' || typeof end !== 'number') {
|
|
686
|
+
throw new Error('参数无效:key不能为空且start和end必须是数字');
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
const value = await this.get(key);
|
|
690
|
+
let result = '';
|
|
691
|
+
if (typeof value === 'string') {
|
|
692
|
+
result = value.substring(start, end + 1);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// 触发after事件
|
|
696
|
+
this.emit('getrange_after', { key, start, end, result: result });
|
|
697
|
+
|
|
698
|
+
return result;
|
|
699
|
+
} catch (error) {
|
|
700
|
+
// 触发error事件
|
|
701
|
+
this.emit('getrange_error', { key, start, end, error });
|
|
702
|
+
$.log.error('[MongoDB cache] 查询字符串范围失败:', error);
|
|
703
|
+
return '';
|
|
704
|
+
}
|
|
190
705
|
};
|
|
191
706
|
|
|
192
707
|
/**
|
|
@@ -196,55 +711,238 @@ Mongodb_cache.prototype.getrange = function(key, start, end) {
|
|
|
196
711
|
* @param {String} value 变更的值
|
|
197
712
|
* @return {Promise|Number} 字符串长度
|
|
198
713
|
*/
|
|
199
|
-
Mongodb_cache.prototype.setrange = function(key, index, value) {
|
|
200
|
-
|
|
714
|
+
Mongodb_cache.prototype.setrange = async function(key, index, value) {
|
|
715
|
+
try {
|
|
716
|
+
// 触发before事件
|
|
717
|
+
this.emit('setrange_before', { key, index, value });
|
|
718
|
+
|
|
719
|
+
// 验证参数
|
|
720
|
+
if (!key || typeof index !== 'number' || typeof value !== 'string') {
|
|
721
|
+
throw new Error('参数无效:key不能为空,index必须是数字,value必须是字符串');
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// 获取当前值
|
|
725
|
+
let currentValue = await this.get(key) || '';
|
|
726
|
+
|
|
727
|
+
// 确保当前值是字符串
|
|
728
|
+
if (typeof currentValue !== 'string') {
|
|
729
|
+
currentValue = '';
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// 处理index超出范围的情况
|
|
733
|
+
if (index > currentValue.length) {
|
|
734
|
+
// 补空格
|
|
735
|
+
currentValue = currentValue.padEnd(index, ' ');
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// 构建新字符串
|
|
739
|
+
const newValue = currentValue.substring(0, index) + value + currentValue.substring(index + value.length);
|
|
740
|
+
|
|
741
|
+
// 保存新值
|
|
742
|
+
await this.set(key, newValue);
|
|
743
|
+
|
|
744
|
+
const resultLength = newValue.length;
|
|
745
|
+
// 触发after事件
|
|
746
|
+
this.emit('setrange_after', { key, index, value, result: resultLength });
|
|
747
|
+
|
|
748
|
+
return resultLength;
|
|
749
|
+
} catch (error) {
|
|
750
|
+
// 触发error事件
|
|
751
|
+
this.emit('setrange_error', { key, index, value, error });
|
|
752
|
+
$.log.error('[MongoDB cache] 设置字符串范围失败:', error);
|
|
753
|
+
return 0;
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* @description 获取字符串长度 (Redis STRLEN)
|
|
759
|
+
* @param {String} key 键
|
|
760
|
+
* @return {Promise|Number} 字符串长度,非字符串返回0
|
|
761
|
+
*/
|
|
762
|
+
Mongodb_cache.prototype.strlen = async function(key) {
|
|
763
|
+
try {
|
|
764
|
+
// 触发before事件
|
|
765
|
+
this.emit('strlen_before', { key });
|
|
766
|
+
|
|
767
|
+
const value = await this.get(key);
|
|
768
|
+
const result = typeof value === 'string' ? value.length : 0;
|
|
769
|
+
|
|
770
|
+
// 触发after事件
|
|
771
|
+
this.emit('strlen_after', { key, result: result });
|
|
772
|
+
|
|
773
|
+
return result;
|
|
774
|
+
} catch (error) {
|
|
775
|
+
// 触发error事件
|
|
776
|
+
this.emit('strlen_error', { key, error });
|
|
777
|
+
$.log.error('[MongoDB cache] 获取字符串长度失败:', error);
|
|
778
|
+
return 0;
|
|
779
|
+
}
|
|
201
780
|
};
|
|
202
781
|
|
|
203
782
|
/**
|
|
204
783
|
* @description 清空缓存
|
|
205
|
-
* @param {String}
|
|
206
|
-
* @return {Promise|
|
|
784
|
+
* @param {String} prefix 键前缀
|
|
785
|
+
* @return {Promise|Boolean} 成功返回true,失败返回false
|
|
207
786
|
*/
|
|
208
|
-
Mongodb_cache.prototype.clear = function(
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
787
|
+
Mongodb_cache.prototype.clear = async function(prefix) {
|
|
788
|
+
try {
|
|
789
|
+
// 触发before事件
|
|
790
|
+
this.emit('clear_before', { prefix });
|
|
791
|
+
|
|
792
|
+
let query = {};
|
|
793
|
+
|
|
794
|
+
// 如果提供了前缀,使用正则匹配
|
|
795
|
+
if (prefix) {
|
|
796
|
+
query._id = { $regex: `^${prefix}` };
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const result = await this.db.collection(this.table).deleteMany(query);
|
|
800
|
+
$.log.info(`[MongoDB cache] 清空缓存成功,删除了${result.deletedCount}条记录`);
|
|
801
|
+
|
|
802
|
+
// 触发after事件
|
|
803
|
+
this.emit('clear_after', { prefix, result: true, deletedCount: result.deletedCount });
|
|
804
|
+
|
|
805
|
+
return true;
|
|
806
|
+
} catch (error) {
|
|
807
|
+
// 触发error事件
|
|
808
|
+
this.emit('clear_error', { prefix, error });
|
|
809
|
+
$.log.error('[MongoDB cache] 清空缓存失败:', error);
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
214
812
|
};
|
|
215
813
|
|
|
216
814
|
/**
|
|
217
815
|
* @description 排序
|
|
218
|
-
* @param {String}
|
|
219
|
-
* @param {
|
|
220
|
-
* @param {
|
|
221
|
-
* @return {Promise|Array}
|
|
816
|
+
* @param {String} prefix 键前缀
|
|
817
|
+
* @param {Number} count 数量
|
|
818
|
+
* @param {Number} type 类型 1: 从小到大,0: 从大到小
|
|
819
|
+
* @return {Promise|Array} 执行结果
|
|
222
820
|
*/
|
|
223
|
-
Mongodb_cache.prototype.sort = function(
|
|
224
|
-
|
|
821
|
+
Mongodb_cache.prototype.sort = async function(prefix, count, type) {
|
|
822
|
+
try {
|
|
823
|
+
// 触发before事件
|
|
824
|
+
this.emit('sort_before', { prefix, count, type });
|
|
825
|
+
|
|
826
|
+
// 验证参数
|
|
827
|
+
if (typeof count !== 'number' || (type !== 0 && type !== 1)) {
|
|
828
|
+
throw new Error('参数无效:count必须是数字,type必须是0或1');
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
// 构建查询
|
|
832
|
+
let query = {};
|
|
833
|
+
if (prefix) {
|
|
834
|
+
query._id = { $regex: `^${prefix}` };
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// 排除过期的数据
|
|
838
|
+
query.$or = [
|
|
839
|
+
{ expireAt: { $exists: false } },
|
|
840
|
+
{ expireAt: { $gt: new Date() } }
|
|
841
|
+
];
|
|
842
|
+
|
|
843
|
+
// 执行查询并排序
|
|
844
|
+
const cursor = this.db.collection(this.table)
|
|
845
|
+
.find(query)
|
|
846
|
+
.sort({ value: type === 1 ? 1 : -1 })
|
|
847
|
+
.limit(count);
|
|
848
|
+
|
|
849
|
+
const results = await cursor.toArray();
|
|
850
|
+
|
|
851
|
+
// 转换为所需格式
|
|
852
|
+
const formattedResults = results.map(item => [item._id, item.value]);
|
|
853
|
+
|
|
854
|
+
// 触发after事件
|
|
855
|
+
this.emit('sort_after', { prefix, count, type, result: formattedResults });
|
|
856
|
+
|
|
857
|
+
return formattedResults;
|
|
858
|
+
} catch (error) {
|
|
859
|
+
// 触发error事件
|
|
860
|
+
this.emit('sort_error', { prefix, count, type, error });
|
|
861
|
+
$.log.error('[MongoDB cache] 排序失败:', error);
|
|
862
|
+
return [];
|
|
863
|
+
}
|
|
225
864
|
};
|
|
226
865
|
|
|
227
866
|
/**
|
|
228
|
-
* @description
|
|
229
|
-
* @param {String}
|
|
230
|
-
* @return {Promise|Array}
|
|
867
|
+
* @description 获取键名
|
|
868
|
+
* @param {String} pattern 匹配模式
|
|
869
|
+
* @return {Promise|Array} 键名数组
|
|
231
870
|
*/
|
|
232
|
-
Mongodb_cache.prototype.keys = function(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
871
|
+
Mongodb_cache.prototype.keys = async function(pattern) {
|
|
872
|
+
try {
|
|
873
|
+
// 触发before事件
|
|
874
|
+
this.emit('keys_before', { pattern });
|
|
875
|
+
|
|
876
|
+
// 构建查询
|
|
877
|
+
let query = {};
|
|
878
|
+
|
|
879
|
+
// 处理通配符
|
|
880
|
+
if (pattern) {
|
|
881
|
+
// 将Redis风格的通配符转换为正则表达式
|
|
882
|
+
const regexPattern = pattern
|
|
883
|
+
.replace(/\*/g, '.*') // * 转换为 .*
|
|
884
|
+
.replace(/\?/g, '.'); // ? 转换为 .
|
|
885
|
+
query._id = { $regex: `^${regexPattern}$` };
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// 排除过期的数据
|
|
889
|
+
query.$or = [
|
|
890
|
+
{ expireAt: { $exists: false } },
|
|
891
|
+
{ expireAt: { $gt: new Date() } }
|
|
892
|
+
];
|
|
893
|
+
|
|
894
|
+
// 只获取_id字段
|
|
895
|
+
const cursor = this.db.collection(this.table)
|
|
896
|
+
.find(query)
|
|
897
|
+
.project({ _id: 1 });
|
|
898
|
+
|
|
899
|
+
const results = await cursor.toArray();
|
|
900
|
+
|
|
901
|
+
// 提取键名
|
|
902
|
+
const keysList = results.map(item => item._id);
|
|
903
|
+
|
|
904
|
+
// 触发after事件
|
|
905
|
+
this.emit('keys_after', { pattern, result: keysList });
|
|
906
|
+
|
|
907
|
+
return keysList;
|
|
908
|
+
} catch (error) {
|
|
909
|
+
// 触发error事件
|
|
910
|
+
this.emit('keys_error', { pattern, error });
|
|
911
|
+
$.log.error('[MongoDB cache] 获取键名失败:', error);
|
|
912
|
+
return [];
|
|
913
|
+
}
|
|
237
914
|
};
|
|
238
915
|
|
|
239
916
|
/**
|
|
240
917
|
* @description 修改数组缓存
|
|
241
918
|
* @param {String} key 键
|
|
242
|
-
* @param {
|
|
919
|
+
* @param {Array} value 值
|
|
243
920
|
* @param {Number} seconds 秒
|
|
244
921
|
* @return {Promise|Array} 执行结果
|
|
245
922
|
*/
|
|
246
|
-
Mongodb_cache.prototype.list_set = function(key, value, seconds) {
|
|
247
|
-
|
|
923
|
+
Mongodb_cache.prototype.list_set = async function(key, value, seconds) {
|
|
924
|
+
try {
|
|
925
|
+
// 触发before事件
|
|
926
|
+
this.emit('list_set_before', { key, value, seconds });
|
|
927
|
+
|
|
928
|
+
// 验证参数
|
|
929
|
+
if (!key) {
|
|
930
|
+
throw new Error('参数无效:key不能为空');
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const finalValue = Array.isArray(value) ? value : [value];
|
|
934
|
+
const result = await this.set(key, finalValue, seconds);
|
|
935
|
+
|
|
936
|
+
// 触发after事件
|
|
937
|
+
this.emit('list_set_after', { key, value: finalValue, seconds, result });
|
|
938
|
+
|
|
939
|
+
return result;
|
|
940
|
+
} catch (error) {
|
|
941
|
+
// 触发error事件
|
|
942
|
+
this.emit('list_set_error', { key, value, seconds, error });
|
|
943
|
+
$.log.error('[MongoDB cache] 修改数组缓存失败:', error);
|
|
944
|
+
throw error;
|
|
945
|
+
}
|
|
248
946
|
};
|
|
249
947
|
|
|
250
948
|
/**
|
|
@@ -253,8 +951,186 @@ Mongodb_cache.prototype.list_set = function(key, value, seconds) {
|
|
|
253
951
|
* @param {Object} value 值
|
|
254
952
|
* @return {Promise|String} 追加后的数组
|
|
255
953
|
*/
|
|
256
|
-
Mongodb_cache.prototype.list_push = function(key, value) {
|
|
257
|
-
|
|
954
|
+
Mongodb_cache.prototype.list_push = async function(key, value) {
|
|
955
|
+
try {
|
|
956
|
+
// 触发before事件
|
|
957
|
+
this.emit('list_push_before', { key, value });
|
|
958
|
+
|
|
959
|
+
// 验证参数
|
|
960
|
+
if (!key) {
|
|
961
|
+
throw new Error('参数无效:key不能为空');
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// 获取现有值
|
|
965
|
+
let currentValue = await this.get(key);
|
|
966
|
+
|
|
967
|
+
// 如果不存在或不是数组,则初始化为空数组
|
|
968
|
+
if (!Array.isArray(currentValue)) {
|
|
969
|
+
currentValue = [];
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// 追加新值
|
|
973
|
+
currentValue.push(value);
|
|
974
|
+
|
|
975
|
+
// 保存更新后的值
|
|
976
|
+
await this.set(key, currentValue);
|
|
977
|
+
|
|
978
|
+
// 触发after事件
|
|
979
|
+
this.emit('list_push_after', { key, value, result: currentValue });
|
|
980
|
+
|
|
981
|
+
return currentValue;
|
|
982
|
+
} catch (error) {
|
|
983
|
+
// 触发error事件
|
|
984
|
+
this.emit('list_push_error', { key, value, error });
|
|
985
|
+
$.log.error('[MongoDB cache] 数组缓存追加对象失败:', error);
|
|
986
|
+
throw error;
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* @description 在数组头部添加元素 (Redis LPUSH)
|
|
992
|
+
* @param {String} key 键
|
|
993
|
+
* @param {Object} value 值
|
|
994
|
+
* @return {Promise|Array} 添加后的数组
|
|
995
|
+
*/
|
|
996
|
+
Mongodb_cache.prototype.lpush = async function(key, value) {
|
|
997
|
+
try {
|
|
998
|
+
// 触发before事件
|
|
999
|
+
this.emit('lpush_before', { key, value });
|
|
1000
|
+
|
|
1001
|
+
// 验证参数
|
|
1002
|
+
if (!key) {
|
|
1003
|
+
throw new Error('参数无效:key不能为空');
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// 获取现有值
|
|
1007
|
+
let currentValue = await this.get(key);
|
|
1008
|
+
|
|
1009
|
+
// 如果不存在或不是数组,则初始化为空数组
|
|
1010
|
+
if (!Array.isArray(currentValue)) {
|
|
1011
|
+
currentValue = [];
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// 在头部添加新值
|
|
1015
|
+
currentValue.unshift(value);
|
|
1016
|
+
|
|
1017
|
+
// 保存更新后的值
|
|
1018
|
+
await this.set(key, currentValue);
|
|
1019
|
+
|
|
1020
|
+
// 触发after事件
|
|
1021
|
+
this.emit('lpush_after', { key, value, result: currentValue });
|
|
1022
|
+
|
|
1023
|
+
return currentValue;
|
|
1024
|
+
} catch (error) {
|
|
1025
|
+
// 触发error事件
|
|
1026
|
+
this.emit('lpush_error', { key, value, error });
|
|
1027
|
+
$.log.error('[MongoDB cache] 在数组头部添加元素失败:', error);
|
|
1028
|
+
throw error;
|
|
1029
|
+
}
|
|
1030
|
+
};
|
|
1031
|
+
|
|
1032
|
+
/**
|
|
1033
|
+
* @description 从数组尾部弹出元素 (Redis RPOP)
|
|
1034
|
+
* @param {String} key 键
|
|
1035
|
+
* @return {Promise|*} 弹出的元素,数组不存在或为空返回null
|
|
1036
|
+
*/
|
|
1037
|
+
Mongodb_cache.prototype.rpop = async function(key) {
|
|
1038
|
+
try {
|
|
1039
|
+
// 触发before事件
|
|
1040
|
+
this.emit('rpop_before', { key });
|
|
1041
|
+
|
|
1042
|
+
// 验证参数
|
|
1043
|
+
if (!key) {
|
|
1044
|
+
throw new Error('参数无效:key不能为空');
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
// 获取现有值
|
|
1048
|
+
let currentValue = await this.get(key);
|
|
1049
|
+
|
|
1050
|
+
// 如果不存在或不是数组或为空,返回null
|
|
1051
|
+
if (!Array.isArray(currentValue) || currentValue.length === 0) {
|
|
1052
|
+
// 触发after事件
|
|
1053
|
+
this.emit('rpop_after', { key, result: null });
|
|
1054
|
+
return null;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// 弹出最后一个元素
|
|
1058
|
+
const popped = currentValue.pop();
|
|
1059
|
+
|
|
1060
|
+
// 保存更新后的值
|
|
1061
|
+
await this.set(key, currentValue);
|
|
1062
|
+
|
|
1063
|
+
// 触发after事件
|
|
1064
|
+
this.emit('rpop_after', { key, result: popped });
|
|
1065
|
+
|
|
1066
|
+
return popped;
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
// 触发error事件
|
|
1069
|
+
this.emit('rpop_error', { key, error });
|
|
1070
|
+
$.log.error('[MongoDB cache] 从数组尾部弹出元素失败:', error);
|
|
1071
|
+
return null;
|
|
1072
|
+
}
|
|
1073
|
+
};
|
|
1074
|
+
|
|
1075
|
+
/**
|
|
1076
|
+
* @description 从数组头部弹出元素 (Redis LPOP)
|
|
1077
|
+
* @param {String} key 键
|
|
1078
|
+
* @return {Promise|*} 弹出的元素,数组不存在或为空返回null
|
|
1079
|
+
*/
|
|
1080
|
+
Mongodb_cache.prototype.lpop = async function(key) {
|
|
1081
|
+
try {
|
|
1082
|
+
// 触发before事件
|
|
1083
|
+
this.emit('lpop_before', { key });
|
|
1084
|
+
|
|
1085
|
+
// 验证参数
|
|
1086
|
+
if (!key) {
|
|
1087
|
+
throw new Error('参数无效:key不能为空');
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
// 获取现有值
|
|
1091
|
+
let currentValue = await this.get(key);
|
|
1092
|
+
|
|
1093
|
+
// 如果不存在或不是数组或为空,返回null
|
|
1094
|
+
if (!Array.isArray(currentValue) || currentValue.length === 0) {
|
|
1095
|
+
// 触发after事件
|
|
1096
|
+
this.emit('lpop_after', { key, result: null });
|
|
1097
|
+
return null;
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
// 弹出第一个元素
|
|
1101
|
+
const popped = currentValue.shift();
|
|
1102
|
+
|
|
1103
|
+
// 保存更新后的值
|
|
1104
|
+
await this.set(key, currentValue);
|
|
1105
|
+
|
|
1106
|
+
// 触发after事件
|
|
1107
|
+
this.emit('lpop_after', { key, result: popped });
|
|
1108
|
+
|
|
1109
|
+
return popped;
|
|
1110
|
+
} catch (error) {
|
|
1111
|
+
// 触发error事件
|
|
1112
|
+
this.emit('lpop_error', { key, error });
|
|
1113
|
+
$.log.error('[MongoDB cache] 从数组头部弹出元素失败:', error);
|
|
1114
|
+
return null;
|
|
1115
|
+
}
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
/**
|
|
1119
|
+
* @description 获取数组长度 (Redis LLEN)
|
|
1120
|
+
* @param {String} key 键
|
|
1121
|
+
* @return {Promise|Number} 数组长度,不存在或不是数组返回0
|
|
1122
|
+
*/
|
|
1123
|
+
Mongodb_cache.prototype.llen = async function(key) {
|
|
1124
|
+
try {
|
|
1125
|
+
// 确保连接已建立
|
|
1126
|
+
await this._ensureConnected();
|
|
1127
|
+
|
|
1128
|
+
const value = await this.get(key);
|
|
1129
|
+
return Array.isArray(value) ? value.length : 0;
|
|
1130
|
+
} catch (error) {
|
|
1131
|
+
$.log.error('[MongoDB cache] 获取数组长度失败:', error);
|
|
1132
|
+
return 0;
|
|
1133
|
+
}
|
|
258
1134
|
};
|
|
259
1135
|
|
|
260
1136
|
/**
|
|
@@ -264,19 +1140,40 @@ Mongodb_cache.prototype.list_push = function(key, value) {
|
|
|
264
1140
|
* @return {Promise|Boolean} 存在返回true, 否则返回false
|
|
265
1141
|
*/
|
|
266
1142
|
Mongodb_cache.prototype.list_has = async function(key, value) {
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
1143
|
+
if (!value) {
|
|
1144
|
+
throw '值不能为空';
|
|
1145
|
+
}
|
|
1146
|
+
try {
|
|
1147
|
+
// 触发before事件
|
|
1148
|
+
this.emit('list_has_before', { key, value });
|
|
1149
|
+
|
|
1150
|
+
// 确保连接已建立
|
|
1151
|
+
await this._ensureConnected();
|
|
1152
|
+
|
|
1153
|
+
// 验证参数
|
|
1154
|
+
if (!key) {
|
|
1155
|
+
throw new Error('参数无效:key不能为空');
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
const arr = await this.get(key) || [];
|
|
1159
|
+
if (!Array.isArray(arr)) {
|
|
1160
|
+
// 触发after事件
|
|
1161
|
+
this.emit('list_has_after', { key, value, result: false });
|
|
1162
|
+
return false;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
const result = arr.some(item => item === value || JSON.stringify(item) === JSON.stringify(value));
|
|
1166
|
+
|
|
1167
|
+
// 触发after事件
|
|
1168
|
+
this.emit('list_has_after', { key, value, result });
|
|
1169
|
+
|
|
1170
|
+
return result;
|
|
1171
|
+
} catch (error) {
|
|
1172
|
+
// 触发error事件
|
|
1173
|
+
this.emit('list_has_error', { key, value, error });
|
|
1174
|
+
$.log.error('[MongoDB cache] 判断成员是否存在失败:', error);
|
|
1175
|
+
return false;
|
|
1176
|
+
}
|
|
280
1177
|
};
|
|
281
1178
|
|
|
282
1179
|
/**
|
|
@@ -286,41 +1183,1648 @@ Mongodb_cache.prototype.list_has = async function(key, value) {
|
|
|
286
1183
|
* @param {Number} end 结束位置
|
|
287
1184
|
* @return {Promise|Array} 查询到的数组
|
|
288
1185
|
*/
|
|
289
|
-
Mongodb_cache.prototype.list_get = function(key, start, end) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
1186
|
+
Mongodb_cache.prototype.list_get = async function(key, start, end) {
|
|
1187
|
+
try {
|
|
1188
|
+
// 确保连接已建立
|
|
1189
|
+
await this._ensureConnected();
|
|
1190
|
+
|
|
1191
|
+
// 验证参数
|
|
1192
|
+
if (!key) {
|
|
1193
|
+
throw new Error('参数无效:key不能为空');
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
const arr = await this.get(key) || [];
|
|
1197
|
+
if (!Array.isArray(arr)) {
|
|
1198
|
+
return [];
|
|
1199
|
+
}
|
|
1200
|
+
if (start === undefined) {
|
|
1201
|
+
start = 0;
|
|
1202
|
+
}
|
|
1203
|
+
if (end === undefined) {
|
|
1204
|
+
end = -1;
|
|
1205
|
+
}
|
|
1206
|
+
// 处理负数索引
|
|
1207
|
+
if (start < 0) start = Math.max(0, arr.length + start);
|
|
1208
|
+
if (end < 0) end = arr.length + end;
|
|
1209
|
+
|
|
1210
|
+
const result = arr.slice(start, end + 1);
|
|
1211
|
+
|
|
1212
|
+
return result;
|
|
1213
|
+
} catch (error) {
|
|
1214
|
+
$.log.error('[MongoDB cache] 查询数组缓存失败:', error);
|
|
1215
|
+
return [];
|
|
1216
|
+
}
|
|
297
1217
|
};
|
|
298
1218
|
|
|
299
1219
|
/**
|
|
300
1220
|
* @description 清空数组缓存
|
|
301
1221
|
* @param {String} key 键
|
|
302
1222
|
* @param {Array} value 新成员, 没有则删除数组
|
|
303
|
-
* @return {Promise|Boolean} 成功返回true
|
|
1223
|
+
* @return {Promise|Boolean} 成功返回true,失败返回false
|
|
304
1224
|
*/
|
|
305
1225
|
Mongodb_cache.prototype.list_clear = async function(key, value) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
1226
|
+
try {
|
|
1227
|
+
// 触发before事件
|
|
1228
|
+
this.emit('list_clear_before', { key, value });
|
|
1229
|
+
|
|
1230
|
+
// 确保连接已建立
|
|
1231
|
+
await this._ensureConnected();
|
|
1232
|
+
|
|
1233
|
+
if (!key) {
|
|
1234
|
+
throw '键不能为空';
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
let result;
|
|
1238
|
+
if (value) {
|
|
1239
|
+
// 设置新的数组值
|
|
1240
|
+
await this.set(key, Array.isArray(value) ? value : [value]);
|
|
1241
|
+
result = true;
|
|
1242
|
+
} else {
|
|
1243
|
+
// 直接删除键
|
|
1244
|
+
result = await this.del(key);
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// 触发after事件
|
|
1248
|
+
this.emit('list_clear_after', { key, value, result });
|
|
1249
|
+
|
|
1250
|
+
return result;
|
|
1251
|
+
} catch (error) {
|
|
1252
|
+
// 触发error事件
|
|
1253
|
+
this.emit('list_clear_error', { key, value, error });
|
|
1254
|
+
$.log.error('[MongoDB cache] 清空数组缓存失败:', error);
|
|
1255
|
+
return false;
|
|
1256
|
+
}
|
|
1257
|
+
};
|
|
314
1258
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
1259
|
+
/**
|
|
1260
|
+
* @description 设置缓存过期时间
|
|
1261
|
+
* @param {String} key 键
|
|
1262
|
+
* @param {Number} seconds 过期时间(秒)
|
|
1263
|
+
* @return {Promise|Boolean} 成功返回true,失败返回false
|
|
1264
|
+
*/
|
|
1265
|
+
Mongodb_cache.prototype.expire = async function(key, seconds) {
|
|
1266
|
+
try {
|
|
1267
|
+
// 触发before事件
|
|
1268
|
+
this.emit('expire_before', { key, seconds });
|
|
1269
|
+
|
|
1270
|
+
// 确保连接已建立
|
|
1271
|
+
await this._ensureConnected();
|
|
1272
|
+
|
|
1273
|
+
// 验证参数
|
|
1274
|
+
if (!key || typeof seconds !== 'number' || seconds < 0) {
|
|
1275
|
+
throw new Error('参数无效:key不能为空且seconds必须是非负数字');
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
const expireAt = new Date(Date.now() + seconds * 1000);
|
|
1279
|
+
const result = await this.db.collection(this.table).updateOne(
|
|
1280
|
+
{ _id: key },
|
|
1281
|
+
{ $set: { expireAt } }
|
|
1282
|
+
);
|
|
1283
|
+
|
|
1284
|
+
const success = result.modifiedCount > 0;
|
|
1285
|
+
|
|
1286
|
+
// 触发after事件
|
|
1287
|
+
this.emit('expire_after', { key, seconds, result: success });
|
|
1288
|
+
|
|
1289
|
+
return success;
|
|
1290
|
+
} catch (error) {
|
|
1291
|
+
// 触发error事件
|
|
1292
|
+
this.emit('expire_error', { key, seconds, error });
|
|
1293
|
+
$.log.error('[MongoDB cache] 设置过期时间失败:', error);
|
|
1294
|
+
return false;
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
/**
|
|
1299
|
+
* @description 获取缓存剩余过期时间
|
|
1300
|
+
* @param {String} key 键
|
|
1301
|
+
* @return {Promise|Number} 剩余过期时间(秒),-1表示永不过期,-2表示不存在
|
|
1302
|
+
*/
|
|
1303
|
+
Mongodb_cache.prototype.ttl = async function(key) {
|
|
1304
|
+
try {
|
|
1305
|
+
// 触发before事件
|
|
1306
|
+
this.emit('ttl_before', { key });
|
|
1307
|
+
|
|
1308
|
+
// 确保连接已建立
|
|
1309
|
+
await this._ensureConnected();
|
|
1310
|
+
|
|
1311
|
+
// 验证参数
|
|
1312
|
+
if (!key) {
|
|
1313
|
+
throw new Error('参数无效:key不能为空');
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
const result = await this.db.collection(this.table).findOne({ _id: key }, { projection: { expireAt: 1 } });
|
|
1317
|
+
|
|
1318
|
+
let ttlValue;
|
|
1319
|
+
if (!result) {
|
|
1320
|
+
ttlValue = -2; // 键不存在
|
|
1321
|
+
} else if (!result.expireAt) {
|
|
1322
|
+
ttlValue = -1; // 永不过期
|
|
1323
|
+
} else {
|
|
1324
|
+
const now = new Date();
|
|
1325
|
+
const expireTime = new Date(result.expireAt);
|
|
1326
|
+
ttlValue = Math.ceil((expireTime - now) / 1000);
|
|
1327
|
+
ttlValue = Math.max(0, ttlValue); // 返回剩余秒数,最少为0
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
// 触发after事件
|
|
1331
|
+
this.emit('ttl_after', { key, result: ttlValue });
|
|
1332
|
+
|
|
1333
|
+
return ttlValue;
|
|
1334
|
+
} catch (error) {
|
|
1335
|
+
// 触发error事件
|
|
1336
|
+
this.emit('ttl_error', { key, error });
|
|
1337
|
+
$.log.error('[MongoDB cache] 获取过期时间失败:', error);
|
|
1338
|
+
return -2; // 出错时默认返回不存在
|
|
1339
|
+
}
|
|
1340
|
+
};
|
|
1341
|
+
|
|
1342
|
+
/**
|
|
1343
|
+
* @description 移除过期时间 (Redis PERSIST)
|
|
1344
|
+
* @param {String} key 键
|
|
1345
|
+
* @return {Promise|Boolean} 成功返回true,失败返回false
|
|
1346
|
+
*/
|
|
1347
|
+
Mongodb_cache.prototype.persist = async function(key) {
|
|
1348
|
+
try {
|
|
1349
|
+
// 触发before事件
|
|
1350
|
+
this.emit('persist_before', { key });
|
|
1351
|
+
|
|
1352
|
+
// 确保连接已建立
|
|
1353
|
+
await this._ensureConnected();
|
|
1354
|
+
|
|
1355
|
+
// 验证参数
|
|
1356
|
+
if (!key) {
|
|
1357
|
+
throw new Error('参数无效:key不能为空');
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
const result = await this.db.collection(this.table).updateOne(
|
|
1361
|
+
{ _id: key },
|
|
1362
|
+
{ $unset: { expireAt: 1 } }
|
|
1363
|
+
);
|
|
1364
|
+
|
|
1365
|
+
const success = result.modifiedCount > 0;
|
|
1366
|
+
|
|
1367
|
+
// 触发after事件
|
|
1368
|
+
this.emit('persist_after', { key, result: success });
|
|
1369
|
+
|
|
1370
|
+
return success;
|
|
1371
|
+
} catch (error) {
|
|
1372
|
+
// 触发error事件
|
|
1373
|
+
this.emit('persist_error', { key, error });
|
|
1374
|
+
$.log.error('[MongoDB cache] 移除过期时间失败:', error);
|
|
1375
|
+
return false;
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
|
|
1379
|
+
// 哈希操作方法
|
|
1380
|
+
|
|
1381
|
+
/**
|
|
1382
|
+
* @description 设置哈希字段 (Redis HSET)
|
|
1383
|
+
* @param {String} key 键
|
|
1384
|
+
* @param {String} field 字段名
|
|
1385
|
+
* @param {*} value 字段值
|
|
1386
|
+
* @return {Promise|Boolean} 是否设置成功
|
|
1387
|
+
*/
|
|
1388
|
+
Mongodb_cache.prototype.hset = async function(key, field, value) {
|
|
1389
|
+
try {
|
|
1390
|
+
// 触发before事件
|
|
1391
|
+
this.emit('hset_before', { key, field, value });
|
|
1392
|
+
|
|
1393
|
+
// 确保连接已建立
|
|
1394
|
+
await this._ensureConnected();
|
|
1395
|
+
|
|
1396
|
+
// 验证参数
|
|
1397
|
+
if (!key || !field) {
|
|
1398
|
+
throw new Error('参数无效:key和field不能为空');
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
// 准备更新对象
|
|
1402
|
+
const updateField = `fields.${field}`;
|
|
1403
|
+
const updateData = { $set: {} };
|
|
1404
|
+
updateData.$set[updateField] = value;
|
|
1405
|
+
|
|
1406
|
+
// 执行更新
|
|
1407
|
+
await this.db.collection(this.table).updateOne(
|
|
1408
|
+
{ _id: key },
|
|
1409
|
+
updateData,
|
|
1410
|
+
{ upsert: true }
|
|
1411
|
+
);
|
|
1412
|
+
|
|
1413
|
+
// 触发after事件
|
|
1414
|
+
this.emit('hset_after', { key, field, value, result: true });
|
|
1415
|
+
|
|
1416
|
+
return true;
|
|
1417
|
+
} catch (error) {
|
|
1418
|
+
// 触发error事件
|
|
1419
|
+
this.emit('hset_error', { key, field, value, error });
|
|
1420
|
+
$.log.error('[MongoDB cache] 设置哈希字段失败:', error);
|
|
1421
|
+
return false;
|
|
1422
|
+
}
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
/**
|
|
1426
|
+
* @description 获取哈希字段值 (Redis HGET)
|
|
1427
|
+
* @param {String} key 键
|
|
1428
|
+
* @param {String} field 字段名
|
|
1429
|
+
* @return {Promise|null} 字段值,不存在返回null
|
|
1430
|
+
*/
|
|
1431
|
+
Mongodb_cache.prototype.hget = async function(key, field) {
|
|
1432
|
+
try {
|
|
1433
|
+
// 确保连接已建立
|
|
1434
|
+
await this._ensureConnected();
|
|
1435
|
+
|
|
1436
|
+
// 验证参数
|
|
1437
|
+
if (!key || !field) {
|
|
1438
|
+
throw new Error('参数无效:key和field不能为空');
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
const result = await this.db.collection(this.table).findOne(
|
|
1442
|
+
{ _id: key },
|
|
1443
|
+
{ projection: { [`fields.${field}`]: 1 } }
|
|
1444
|
+
);
|
|
1445
|
+
|
|
1446
|
+
// 检查是否过期
|
|
1447
|
+
if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
|
|
1448
|
+
// 过期则删除并返回null
|
|
1449
|
+
await this.db.collection(this.table).deleteOne({ _id: key });
|
|
1450
|
+
// 触发after事件
|
|
1451
|
+
this.emit('hget_after', { key, field, result: null });
|
|
1452
|
+
return null;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
// 确保字段存在才返回值,否则返回null
|
|
1456
|
+
const fieldValue = result && result.fields && field in result.fields ? result.fields[field] : null;
|
|
1457
|
+
|
|
1458
|
+
return fieldValue;
|
|
1459
|
+
} catch (error) {
|
|
1460
|
+
$.log.error('[MongoDB cache] 获取哈希字段值失败:', error);
|
|
1461
|
+
return null;
|
|
1462
|
+
}
|
|
1463
|
+
};
|
|
1464
|
+
|
|
1465
|
+
/**
|
|
1466
|
+
* @description 获取哈希所有字段和值 (Redis HGETALL)
|
|
1467
|
+
* @param {String} key 键
|
|
1468
|
+
* @return {Promise|Object} 所有字段和值,不存在返回{}
|
|
1469
|
+
*/
|
|
1470
|
+
Mongodb_cache.prototype.hgetall = async function(key) {
|
|
1471
|
+
try {
|
|
1472
|
+
// 确保连接已建立
|
|
1473
|
+
await this._ensureConnected();
|
|
1474
|
+
|
|
1475
|
+
// 验证参数
|
|
1476
|
+
if (!key) {
|
|
1477
|
+
throw new Error('参数无效:key不能为空');
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
const result = await this.db.collection(this.table).findOne(
|
|
1481
|
+
{ _id: key },
|
|
1482
|
+
{ projection: { fields: 1, expireAt: 1 } }
|
|
1483
|
+
);
|
|
1484
|
+
|
|
1485
|
+
// 检查是否过期
|
|
1486
|
+
if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
|
|
1487
|
+
// 过期则删除并返回{}
|
|
1488
|
+
await this.db.collection(this.table).deleteOne({ _id: key });
|
|
1489
|
+
return {};
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
const fieldsData = result && result.fields ? result.fields : {};
|
|
1493
|
+
|
|
1494
|
+
return fieldsData;
|
|
1495
|
+
} catch (error) {
|
|
1496
|
+
$.log.error('[MongoDB cache] 获取哈希所有字段和值失败:', error);
|
|
1497
|
+
return {};
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
|
|
1501
|
+
/**
|
|
1502
|
+
* @description 删除哈希字段 (Redis HDEL)
|
|
1503
|
+
* @param {String} key 键
|
|
1504
|
+
* @param {String} field 字段名
|
|
1505
|
+
* @return {Promise|null} 删除成功返回null
|
|
1506
|
+
*/
|
|
1507
|
+
Mongodb_cache.prototype.hdel = async function(key, field) {
|
|
1508
|
+
try {
|
|
1509
|
+
// 触发before事件
|
|
1510
|
+
this.emit('hdel_before', { key, field });
|
|
1511
|
+
|
|
1512
|
+
// 确保连接已建立
|
|
1513
|
+
await this._ensureConnected();
|
|
1514
|
+
|
|
1515
|
+
// 验证参数
|
|
1516
|
+
if (!key || !field) {
|
|
1517
|
+
throw new Error('参数无效:key和field不能为空');
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
// 准备更新对象
|
|
1521
|
+
const unsetField = `fields.${field}`;
|
|
1522
|
+
const updateData = { $unset: {} };
|
|
1523
|
+
updateData.$unset[unsetField] = '';
|
|
1524
|
+
|
|
1525
|
+
// 执行更新
|
|
1526
|
+
await this.db.collection(this.table).updateOne(
|
|
1527
|
+
{ _id: key },
|
|
1528
|
+
updateData
|
|
1529
|
+
);
|
|
1530
|
+
|
|
1531
|
+
// 触发after事件
|
|
1532
|
+
this.emit('hdel_after', { key, field, result: null });
|
|
1533
|
+
|
|
1534
|
+
// 返回null以匹配Redis行为
|
|
1535
|
+
return null;
|
|
1536
|
+
} catch (error) {
|
|
1537
|
+
// 触发error事件
|
|
1538
|
+
this.emit('hdel_error', { key, field, error });
|
|
1539
|
+
$.log.error('[MongoDB cache] 删除哈希字段失败:', error);
|
|
1540
|
+
return null;
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
|
|
1544
|
+
/**
|
|
1545
|
+
* @description 判断哈希字段是否存在 (Redis HEXISTS)
|
|
1546
|
+
* @param {String} key 键
|
|
1547
|
+
* @param {String} field 字段名
|
|
1548
|
+
* @return {Promise|Boolean} 存在返回true,不存在返回false
|
|
1549
|
+
*/
|
|
1550
|
+
Mongodb_cache.prototype.hexists = async function(key, field) {
|
|
1551
|
+
try {
|
|
1552
|
+
// 触发before事件
|
|
1553
|
+
this.emit('hexists_before', { key, field });
|
|
1554
|
+
|
|
1555
|
+
// 确保连接已建立
|
|
1556
|
+
await this._ensureConnected();
|
|
1557
|
+
|
|
1558
|
+
// 验证参数
|
|
1559
|
+
if (!key || !field) {
|
|
1560
|
+
throw new Error('参数无效:key和field不能为空');
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
const result = await this.db.collection(this.table).findOne(
|
|
1564
|
+
{
|
|
1565
|
+
_id: key,
|
|
1566
|
+
$or: [
|
|
1567
|
+
{ expireAt: { $exists: false } },
|
|
1568
|
+
{ expireAt: { $gt: new Date() } }
|
|
1569
|
+
],
|
|
1570
|
+
[`fields.${field}`]: { $exists: true }
|
|
1571
|
+
},
|
|
1572
|
+
{ projection: { _id: 1 } }
|
|
1573
|
+
);
|
|
1574
|
+
|
|
1575
|
+
const exists = !!result;
|
|
1576
|
+
|
|
1577
|
+
// 触发after事件
|
|
1578
|
+
this.emit('hexists_after', { key, field, result: exists });
|
|
1579
|
+
|
|
1580
|
+
return exists;
|
|
1581
|
+
} catch (error) {
|
|
1582
|
+
// 触发error事件
|
|
1583
|
+
this.emit('hexists_error', { key, field, error });
|
|
1584
|
+
$.log.error('[MongoDB cache] 判断哈希字段是否存在失败:', error);
|
|
1585
|
+
return false;
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
|
|
1589
|
+
/**
|
|
1590
|
+
* @description 获取哈希所有字段名 (Redis HKEYS)
|
|
1591
|
+
* @param {String} key 键
|
|
1592
|
+
* @return {Promise|Array} 字段名数组
|
|
1593
|
+
*/
|
|
1594
|
+
Mongodb_cache.prototype.hkeys = async function(key) {
|
|
1595
|
+
try {
|
|
1596
|
+
// 触发before事件
|
|
1597
|
+
this.emit('hkeys_before', { key });
|
|
1598
|
+
|
|
1599
|
+
// 确保连接已建立
|
|
1600
|
+
await this._ensureConnected();
|
|
1601
|
+
|
|
1602
|
+
// 验证参数
|
|
1603
|
+
if (!key) {
|
|
1604
|
+
throw new Error('参数无效:key不能为空');
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
const result = await this.db.collection(this.table).findOne(
|
|
1608
|
+
{ _id: key },
|
|
1609
|
+
{ projection: { fields: 1, expireAt: 1 } }
|
|
1610
|
+
);
|
|
1611
|
+
|
|
1612
|
+
// 检查是否过期
|
|
1613
|
+
if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
|
|
1614
|
+
// 过期则删除并返回[]
|
|
1615
|
+
await this.db.collection(this.table).deleteOne({ _id: key });
|
|
1616
|
+
// 触发after事件
|
|
1617
|
+
this.emit('hkeys_after', { key, result: [] });
|
|
1618
|
+
return [];
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
const fieldsList = result && result.fields ? Object.keys(result.fields) : [];
|
|
1622
|
+
|
|
1623
|
+
// 触发after事件
|
|
1624
|
+
this.emit('hkeys_after', { key, result: fieldsList });
|
|
1625
|
+
|
|
1626
|
+
return fieldsList;
|
|
1627
|
+
} catch (error) {
|
|
1628
|
+
// 触发error事件
|
|
1629
|
+
this.emit('hkeys_error', { key, error });
|
|
1630
|
+
$.log.error('[MongoDB cache] 获取哈希所有字段名失败:', error);
|
|
1631
|
+
return [];
|
|
1632
|
+
}
|
|
1633
|
+
};
|
|
1634
|
+
|
|
1635
|
+
/**
|
|
1636
|
+
* @description 获取哈希字段数量 (Redis HLEN)
|
|
1637
|
+
* @param {String} key 键
|
|
1638
|
+
* @return {Promise|Number} 字段数量
|
|
1639
|
+
*/
|
|
1640
|
+
Mongodb_cache.prototype.hlen = async function(key) {
|
|
1641
|
+
try {
|
|
1642
|
+
// 触发before事件
|
|
1643
|
+
this.emit('hlen_before', { key });
|
|
1644
|
+
|
|
1645
|
+
// 确保连接已建立
|
|
1646
|
+
await this._ensureConnected();
|
|
1647
|
+
|
|
1648
|
+
// 验证参数
|
|
1649
|
+
if (!key) {
|
|
1650
|
+
throw new Error('参数无效:key不能为空');
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
const result = await this.db.collection(this.table).findOne(
|
|
1654
|
+
{ _id: key },
|
|
1655
|
+
{ projection: { fields: 1, expireAt: 1 } }
|
|
1656
|
+
);
|
|
1657
|
+
|
|
1658
|
+
// 检查是否过期
|
|
1659
|
+
if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
|
|
1660
|
+
// 过期则删除并返回0
|
|
1661
|
+
await this.db.collection(this.table).deleteOne({ _id: key });
|
|
1662
|
+
// 触发after事件
|
|
1663
|
+
this.emit('hlen_after', { key, result: 0 });
|
|
1664
|
+
return 0;
|
|
1665
|
+
}
|
|
1666
|
+
|
|
1667
|
+
const length = result && result.fields ? Object.keys(result.fields).length : 0;
|
|
1668
|
+
|
|
1669
|
+
// 触发after事件
|
|
1670
|
+
this.emit('hlen_after', { key, result: length });
|
|
1671
|
+
|
|
1672
|
+
return length;
|
|
1673
|
+
} catch (error) {
|
|
1674
|
+
// 触发error事件
|
|
1675
|
+
this.emit('hlen_error', { key, error });
|
|
1676
|
+
$.log.error('[MongoDB cache] 获取哈希字段数量失败:', error);
|
|
1677
|
+
return 0;
|
|
1678
|
+
}
|
|
1679
|
+
};
|
|
1680
|
+
|
|
1681
|
+
// 集合操作方法
|
|
1682
|
+
|
|
1683
|
+
/**
|
|
1684
|
+
* @description 向集合添加成员 (Redis SADD)
|
|
1685
|
+
* @param {String} key 键
|
|
1686
|
+
* @param {*} member 成员
|
|
1687
|
+
* @return {Promise|Number} 添加的成员数量
|
|
1688
|
+
*/
|
|
1689
|
+
Mongodb_cache.prototype.sadd = async function(key, member) {
|
|
1690
|
+
try {
|
|
1691
|
+
// 触发before事件
|
|
1692
|
+
this.emit('sadd_before', { key, member });
|
|
1693
|
+
|
|
1694
|
+
// 确保连接已建立
|
|
1695
|
+
await this._ensureConnected();
|
|
1696
|
+
|
|
1697
|
+
// 验证参数
|
|
1698
|
+
if (!key) {
|
|
1699
|
+
throw new Error('参数无效:key不能为空');
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
// 准备更新数据
|
|
1703
|
+
const updateData = {
|
|
1704
|
+
$addToSet: { setMembers: member }
|
|
1705
|
+
};
|
|
1706
|
+
|
|
1707
|
+
// 执行更新
|
|
1708
|
+
const result = await this.db.collection(this.table).updateOne(
|
|
1709
|
+
{ _id: key },
|
|
1710
|
+
updateData,
|
|
1711
|
+
{ upsert: true }
|
|
1712
|
+
);
|
|
1713
|
+
|
|
1714
|
+
// 检查是否新增了成员
|
|
1715
|
+
const doc = await this.db.collection(this.table).findOne(
|
|
1716
|
+
{ _id: key },
|
|
1717
|
+
{ projection: { setMembers: 1 } }
|
|
1718
|
+
);
|
|
1719
|
+
|
|
1720
|
+
const addedCount = 1; // MongoDB不直接返回是否添加成功,这里假设成功
|
|
1721
|
+
|
|
1722
|
+
// 触发after事件
|
|
1723
|
+
this.emit('sadd_after', { key, member, result: addedCount });
|
|
1724
|
+
|
|
1725
|
+
return addedCount;
|
|
1726
|
+
} catch (error) {
|
|
1727
|
+
// 触发error事件
|
|
1728
|
+
this.emit('sadd_error', { key, member, error });
|
|
1729
|
+
$.log.error('[MongoDB cache] 向集合添加成员失败:', error);
|
|
1730
|
+
return 0;
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1733
|
+
|
|
1734
|
+
/**
|
|
1735
|
+
* @description 从集合移除成员 (Redis SREM)
|
|
1736
|
+
* @param {String} key 键
|
|
1737
|
+
* @param {*} member 成员
|
|
1738
|
+
* @return {Promise|Number} 移除的成员数量
|
|
1739
|
+
*/
|
|
1740
|
+
Mongodb_cache.prototype.srem = async function(key, member) {
|
|
1741
|
+
try {
|
|
1742
|
+
// 触发before事件
|
|
1743
|
+
this.emit('srem_before', { key, member });
|
|
1744
|
+
|
|
1745
|
+
// 确保连接已建立
|
|
1746
|
+
await this._ensureConnected();
|
|
1747
|
+
|
|
1748
|
+
// 验证参数
|
|
1749
|
+
if (!key) {
|
|
1750
|
+
throw new Error('参数无效:key不能为空');
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// 准备更新数据
|
|
1754
|
+
const updateData = {
|
|
1755
|
+
$pull: { setMembers: member }
|
|
1756
|
+
};
|
|
1757
|
+
|
|
1758
|
+
// 执行更新
|
|
1759
|
+
const result = await this.db.collection(this.table).updateOne(
|
|
1760
|
+
{ _id: key },
|
|
1761
|
+
updateData
|
|
1762
|
+
);
|
|
1763
|
+
|
|
1764
|
+
const removedCount = result.modifiedCount;
|
|
1765
|
+
|
|
1766
|
+
// 触发after事件
|
|
1767
|
+
this.emit('srem_after', { key, member, result: removedCount });
|
|
1768
|
+
|
|
1769
|
+
return removedCount;
|
|
1770
|
+
} catch (error) {
|
|
1771
|
+
// 触发error事件
|
|
1772
|
+
this.emit('srem_error', { key, member, error });
|
|
1773
|
+
$.log.error('[MongoDB cache] 从集合移除成员失败:', error);
|
|
1774
|
+
return 0;
|
|
1775
|
+
}
|
|
1776
|
+
};
|
|
1777
|
+
|
|
1778
|
+
/**
|
|
1779
|
+
* @description 获取集合所有成员 (Redis SMEMBERS)
|
|
1780
|
+
* @param {String} key 键
|
|
1781
|
+
* @return {Promise|Array} 成员数组
|
|
1782
|
+
*/
|
|
1783
|
+
Mongodb_cache.prototype.smembers = async function(key) {
|
|
1784
|
+
try {
|
|
1785
|
+
// 确保连接已建立
|
|
1786
|
+
await this._ensureConnected();
|
|
1787
|
+
|
|
1788
|
+
// 验证参数
|
|
1789
|
+
if (!key) {
|
|
1790
|
+
throw new Error('参数无效:key不能为空');
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
const result = await this.db.collection(this.table).findOne(
|
|
1794
|
+
{ _id: key },
|
|
1795
|
+
{ projection: { setMembers: 1, expireAt: 1 } }
|
|
1796
|
+
);
|
|
1797
|
+
|
|
1798
|
+
// 检查是否过期
|
|
1799
|
+
if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
|
|
1800
|
+
// 过期则删除并返回[]
|
|
1801
|
+
await this.db.collection(this.table).deleteOne({ _id: key });
|
|
1802
|
+
return [];
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
const members = result && Array.isArray(result.setMembers) ? result.setMembers : [];
|
|
1806
|
+
|
|
1807
|
+
return members;
|
|
1808
|
+
} catch (error) {
|
|
1809
|
+
$.log.error('[MongoDB cache] 获取集合所有成员失败:', error);
|
|
1810
|
+
return [];
|
|
1811
|
+
}
|
|
1812
|
+
};
|
|
1813
|
+
|
|
1814
|
+
/**
|
|
1815
|
+
* @description 判断成员是否在集合中 (Redis SISMEMBER)
|
|
1816
|
+
* @param {String} key 键
|
|
1817
|
+
* @param {*} member 成员
|
|
1818
|
+
* @return {Promise|Boolean} 存在返回true,不存在返回false
|
|
1819
|
+
*/
|
|
1820
|
+
Mongodb_cache.prototype.sismember = async function(key, member) {
|
|
1821
|
+
try {
|
|
1822
|
+
// 确保连接已建立
|
|
1823
|
+
await this._ensureConnected();
|
|
1824
|
+
|
|
1825
|
+
// 验证参数
|
|
1826
|
+
if (!key) {
|
|
1827
|
+
throw new Error('参数无效:key不能为空');
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
const result = await this.db.collection(this.table).findOne(
|
|
1831
|
+
{
|
|
1832
|
+
_id: key,
|
|
1833
|
+
$or: [
|
|
1834
|
+
{ expireAt: { $exists: false } },
|
|
1835
|
+
{ expireAt: { $gt: new Date() } }
|
|
1836
|
+
],
|
|
1837
|
+
setMembers: member
|
|
1838
|
+
},
|
|
1839
|
+
{ projection: { _id: 1 } }
|
|
1840
|
+
);
|
|
1841
|
+
|
|
1842
|
+
const exists = !!result;
|
|
1843
|
+
|
|
1844
|
+
return exists;
|
|
1845
|
+
} catch (error) {
|
|
1846
|
+
$.log.error('[MongoDB cache] 判断成员是否在集合中失败:', error);
|
|
1847
|
+
return false;
|
|
1848
|
+
}
|
|
1849
|
+
};
|
|
1850
|
+
|
|
1851
|
+
/**
|
|
1852
|
+
* @description 获取集合成员数量 (Redis SCARD)
|
|
1853
|
+
* @param {String} key 键
|
|
1854
|
+
* @return {Promise|Number} 成员数量
|
|
1855
|
+
*/
|
|
1856
|
+
Mongodb_cache.prototype.scard = async function(key) {
|
|
1857
|
+
try {
|
|
1858
|
+
// 确保连接已建立
|
|
1859
|
+
await this._ensureConnected();
|
|
1860
|
+
|
|
1861
|
+
// 验证参数
|
|
1862
|
+
if (!key) {
|
|
1863
|
+
throw new Error('参数无效:key不能为空');
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
const result = await this.db.collection(this.table).findOne(
|
|
1867
|
+
{ _id: key },
|
|
1868
|
+
{ projection: { setMembers: 1, expireAt: 1 } }
|
|
1869
|
+
);
|
|
1870
|
+
|
|
1871
|
+
// 检查是否过期
|
|
1872
|
+
if (result && result.expireAt && new Date(result.expireAt) < new Date()) {
|
|
1873
|
+
// 过期则删除并返回0
|
|
1874
|
+
await this.db.collection(this.table).deleteOne({ _id: key });
|
|
1875
|
+
return 0;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
const count = result && Array.isArray(result.setMembers) ? result.setMembers.length : 0;
|
|
1879
|
+
|
|
1880
|
+
return count;
|
|
1881
|
+
} catch (error) {
|
|
1882
|
+
$.log.error('[MongoDB cache] 获取集合成员数量失败:', error);
|
|
1883
|
+
return 0;
|
|
1884
|
+
}
|
|
1885
|
+
};
|
|
1886
|
+
|
|
1887
|
+
// 基本操作补充方法
|
|
1888
|
+
|
|
1889
|
+
/**
|
|
1890
|
+
* @description 原子递增 (Redis INCR)
|
|
1891
|
+
* @param {String} key 键
|
|
1892
|
+
* @return {Promise|Number} 递增后的值
|
|
1893
|
+
*/
|
|
1894
|
+
Mongodb_cache.prototype.incr = async function(key) {
|
|
1895
|
+
try {
|
|
1896
|
+
// 触发before事件
|
|
1897
|
+
this.emit('incr_before', { key });
|
|
1898
|
+
|
|
1899
|
+
// 确保连接已建立
|
|
1900
|
+
await this._ensureConnected();
|
|
1901
|
+
|
|
1902
|
+
// 验证参数
|
|
1903
|
+
if (!key) {
|
|
1904
|
+
throw new Error('参数无效:key不能为空');
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1907
|
+
// 使用$inc操作符增加1
|
|
1908
|
+
const result = await this.db.collection(this.table).findOneAndUpdate(
|
|
1909
|
+
{ _id: key },
|
|
1910
|
+
{ $inc: { value: 1 } },
|
|
1911
|
+
{ upsert: true, returnDocument: 'after', projection: { value: 1 } }
|
|
1912
|
+
);
|
|
1913
|
+
|
|
1914
|
+
const newValue = result.value ? result.value.value : 1;
|
|
1915
|
+
|
|
1916
|
+
// 触发after事件
|
|
1917
|
+
this.emit('incr_after', { key, result: newValue });
|
|
1918
|
+
|
|
1919
|
+
return newValue;
|
|
1920
|
+
} catch (error) {
|
|
1921
|
+
// 触发error事件
|
|
1922
|
+
this.emit('incr_error', { key, error });
|
|
1923
|
+
$.log.error('[MongoDB cache] 原子递增失败:', error);
|
|
1924
|
+
throw error;
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1927
|
+
|
|
1928
|
+
/**
|
|
1929
|
+
* @description 原子递减 (Redis DECR)
|
|
1930
|
+
* @param {String} key 键
|
|
1931
|
+
* @return {Promise|Number} 递减后的值
|
|
1932
|
+
*/
|
|
1933
|
+
Mongodb_cache.prototype.decr = async function(key) {
|
|
1934
|
+
try {
|
|
1935
|
+
// 触发before事件
|
|
1936
|
+
this.emit('decr_before', { key });
|
|
1937
|
+
|
|
1938
|
+
// 确保连接已建立
|
|
1939
|
+
await this._ensureConnected();
|
|
1940
|
+
|
|
1941
|
+
// 验证参数
|
|
1942
|
+
if (!key) {
|
|
1943
|
+
throw new Error('参数无效:key不能为空');
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// 使用$inc操作符减少1
|
|
1947
|
+
const result = await this.db.collection(this.table).findOneAndUpdate(
|
|
1948
|
+
{ _id: key },
|
|
1949
|
+
{ $inc: { value: -1 } },
|
|
1950
|
+
{ upsert: true, returnDocument: 'after', projection: { value: 1 } }
|
|
1951
|
+
);
|
|
1952
|
+
|
|
1953
|
+
const newValue = result.value ? result.value.value : -1;
|
|
1954
|
+
|
|
1955
|
+
// 触发after事件
|
|
1956
|
+
this.emit('decr_after', { key, result: newValue });
|
|
1957
|
+
|
|
1958
|
+
return newValue;
|
|
1959
|
+
} catch (error) {
|
|
1960
|
+
// 触发error事件
|
|
1961
|
+
this.emit('decr_error', { key, error });
|
|
1962
|
+
$.log.error('[MongoDB cache] 原子递减失败:', error);
|
|
1963
|
+
throw error;
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
|
|
1967
|
+
/**
|
|
1968
|
+
* @description 原子递增指定值 (Redis INCRBY)
|
|
1969
|
+
* @param {String} key 键
|
|
1970
|
+
* @param {Number} value 递增值
|
|
1971
|
+
* @return {Promise|Number} 递增后的值
|
|
1972
|
+
*/
|
|
1973
|
+
Mongodb_cache.prototype.incrby = async function(key, value) {
|
|
1974
|
+
try {
|
|
1975
|
+
// 触发before事件
|
|
1976
|
+
this.emit('incrby_before', { key, value });
|
|
1977
|
+
|
|
1978
|
+
// 确保连接已建立
|
|
1979
|
+
await this._ensureConnected();
|
|
1980
|
+
|
|
1981
|
+
// 验证参数
|
|
1982
|
+
if (!key || typeof value !== 'number' || !Number.isInteger(value)) {
|
|
1983
|
+
throw new Error('参数无效:key不能为空且value必须是整数');
|
|
1984
|
+
}
|
|
1985
|
+
|
|
1986
|
+
const result = await this.addInt(key, value);
|
|
1987
|
+
|
|
1988
|
+
// 触发after事件
|
|
1989
|
+
this.emit('incrby_after', { key, value, result });
|
|
1990
|
+
|
|
1991
|
+
return result;
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
// 触发error事件
|
|
1994
|
+
this.emit('incrby_error', { key, value, error });
|
|
1995
|
+
$.log.error('[MongoDB cache] 原子递增指定值失败:', error);
|
|
1996
|
+
throw error;
|
|
1997
|
+
}
|
|
1998
|
+
};
|
|
1999
|
+
|
|
2000
|
+
/**
|
|
2001
|
+
* @description 原子递减指定值 (Redis DECRBY)
|
|
2002
|
+
* @param {String} key 键
|
|
2003
|
+
* @param {Number} value 递减值
|
|
2004
|
+
* @return {Promise|Number} 递减后的值
|
|
2005
|
+
*/
|
|
2006
|
+
Mongodb_cache.prototype.decrby = async function(key, value) {
|
|
2007
|
+
try {
|
|
2008
|
+
// 触发before事件
|
|
2009
|
+
this.emit('decrby_before', { key, value });
|
|
2010
|
+
|
|
2011
|
+
// 确保连接已建立
|
|
2012
|
+
await this._ensureConnected();
|
|
2013
|
+
|
|
2014
|
+
// 验证参数
|
|
2015
|
+
if (!key || typeof value !== 'number' || !Number.isInteger(value)) {
|
|
2016
|
+
throw new Error('参数无效:key不能为空且value必须是整数');
|
|
2017
|
+
}
|
|
2018
|
+
|
|
2019
|
+
// 递减就是增加负值
|
|
2020
|
+
const result = await this.addInt(key, -value);
|
|
2021
|
+
|
|
2022
|
+
// 触发after事件
|
|
2023
|
+
this.emit('decrby_after', { key, value, result });
|
|
2024
|
+
|
|
2025
|
+
return result;
|
|
2026
|
+
} catch (error) {
|
|
2027
|
+
// 触发error事件
|
|
2028
|
+
this.emit('decrby_error', { key, value, error });
|
|
2029
|
+
$.log.error('[MongoDB cache] 原子递减指定值失败:', error);
|
|
2030
|
+
throw error;
|
|
2031
|
+
}
|
|
2032
|
+
};
|
|
2033
|
+
|
|
2034
|
+
/**
|
|
2035
|
+
* @description 追加字符串 (Redis APPEND)
|
|
2036
|
+
* @param {String} key 键
|
|
2037
|
+
* @param {String} value 要追加的字符串
|
|
2038
|
+
* @return {Promise|Number} 追加后的字符串长度
|
|
2039
|
+
*/
|
|
2040
|
+
Mongodb_cache.prototype.append = async function(key, value) {
|
|
2041
|
+
try {
|
|
2042
|
+
// 触发before事件
|
|
2043
|
+
this.emit('append_before', { key, value });
|
|
2044
|
+
|
|
2045
|
+
// 确保连接已建立
|
|
2046
|
+
await this._ensureConnected();
|
|
2047
|
+
|
|
2048
|
+
// 验证参数
|
|
2049
|
+
if (!key || typeof value !== 'string') {
|
|
2050
|
+
throw new Error('参数无效:key不能为空且value必须是字符串');
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
// 使用addStr方法追加字符串
|
|
2054
|
+
const newValue = await this.addStr(key, value);
|
|
2055
|
+
const length = newValue.length;
|
|
2056
|
+
|
|
2057
|
+
// 触发after事件
|
|
2058
|
+
this.emit('append_after', { key, value, result: length });
|
|
2059
|
+
|
|
2060
|
+
return length;
|
|
2061
|
+
} catch (error) {
|
|
2062
|
+
// 触发error事件
|
|
2063
|
+
this.emit('append_error', { key, value, error });
|
|
2064
|
+
$.log.error('[MongoDB cache] 追加字符串失败:', error);
|
|
2065
|
+
return 0;
|
|
2066
|
+
}
|
|
2067
|
+
};
|
|
2068
|
+
|
|
2069
|
+
/**
|
|
2070
|
+
* @description 重命名键 (Redis RENAME)
|
|
2071
|
+
* @param {String} oldKey 旧键名
|
|
2072
|
+
* @param {String} newKey 新键名
|
|
2073
|
+
* @return {Promise|Boolean} 是否重命名成功
|
|
2074
|
+
*/
|
|
2075
|
+
Mongodb_cache.prototype.rename = async function(oldKey, newKey) {
|
|
2076
|
+
try {
|
|
2077
|
+
// 触发before事件
|
|
2078
|
+
this.emit('rename_before', { oldKey, newKey });
|
|
2079
|
+
|
|
2080
|
+
// 确保连接已建立
|
|
2081
|
+
await this._ensureConnected();
|
|
2082
|
+
|
|
2083
|
+
// 验证参数
|
|
2084
|
+
if (!oldKey || !newKey) {
|
|
2085
|
+
throw new Error('参数无效:旧键和新键不能为空');
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
// 获取旧键的值
|
|
2089
|
+
const result = await this.db.collection(this.table).findOne({ _id: oldKey });
|
|
2090
|
+
if (!result) {
|
|
2091
|
+
// 触发after事件
|
|
2092
|
+
this.emit('rename_after', { oldKey, newKey, result: false });
|
|
2093
|
+
return false;
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
// 创建新键
|
|
2097
|
+
const newDoc = { ...result, _id: newKey };
|
|
2098
|
+
delete newDoc._id; // 移除旧的_id字段
|
|
2099
|
+
|
|
2100
|
+
// 先删除可能存在的新键
|
|
2101
|
+
await this.db.collection(this.table).deleteOne({ _id: newKey });
|
|
2102
|
+
|
|
2103
|
+
// 插入新键
|
|
2104
|
+
await this.db.collection(this.table).insertOne({ ...newDoc, _id: newKey });
|
|
2105
|
+
|
|
2106
|
+
// 删除旧键
|
|
2107
|
+
await this.db.collection(this.table).deleteOne({ _id: oldKey });
|
|
2108
|
+
|
|
2109
|
+
// 触发after事件
|
|
2110
|
+
this.emit('rename_after', { oldKey, newKey, result: true });
|
|
2111
|
+
|
|
2112
|
+
return true;
|
|
2113
|
+
} catch (error) {
|
|
2114
|
+
// 触发error事件
|
|
2115
|
+
this.emit('rename_error', { oldKey, newKey, error });
|
|
2116
|
+
$.log.error('[MongoDB cache] 重命名键失败:', error);
|
|
2117
|
+
return false;
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
2120
|
+
|
|
2121
|
+
/**
|
|
2122
|
+
* @description 条件重命名键 (Redis RENAMENX)
|
|
2123
|
+
* @param {String} oldKey 旧键名
|
|
2124
|
+
* @param {String} newKey 新键名
|
|
2125
|
+
* @return {Promise|Boolean} 如果新键不存在且重命名成功返回true,否则返回false
|
|
2126
|
+
*/
|
|
2127
|
+
Mongodb_cache.prototype.renamenx = async function(oldKey, newKey) {
|
|
2128
|
+
try {
|
|
2129
|
+
// 触发before事件
|
|
2130
|
+
this.emit('renamenx_before', { oldKey, newKey });
|
|
2131
|
+
|
|
2132
|
+
// 确保连接已建立
|
|
2133
|
+
await this._ensureConnected();
|
|
2134
|
+
|
|
2135
|
+
// 验证参数
|
|
2136
|
+
if (!oldKey || !newKey) {
|
|
2137
|
+
throw new Error('参数无效:旧键和新键不能为空');
|
|
2138
|
+
}
|
|
2139
|
+
|
|
2140
|
+
// 检查新键是否存在
|
|
2141
|
+
const newKeyExists = await this.db.collection(this.table).findOne({ _id: newKey }, { projection: { _id: 1 } });
|
|
2142
|
+
if (newKeyExists) {
|
|
2143
|
+
// 触发after事件
|
|
2144
|
+
this.emit('renamenx_after', { oldKey, newKey, result: false });
|
|
2145
|
+
return false;
|
|
2146
|
+
}
|
|
2147
|
+
|
|
2148
|
+
// 获取旧键的值
|
|
2149
|
+
const result = await this.db.collection(this.table).findOne({ _id: oldKey });
|
|
2150
|
+
if (!result) {
|
|
2151
|
+
// 触发after事件
|
|
2152
|
+
this.emit('renamenx_after', { oldKey, newKey, result: false });
|
|
2153
|
+
return false;
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
// 创建新键
|
|
2157
|
+
const newDoc = { ...result };
|
|
2158
|
+
delete newDoc._id; // 移除旧的_id字段
|
|
2159
|
+
|
|
2160
|
+
// 插入新键
|
|
2161
|
+
await this.db.collection(this.table).insertOne({ ...newDoc, _id: newKey });
|
|
2162
|
+
|
|
2163
|
+
// 删除旧键
|
|
2164
|
+
await this.db.collection(this.table).deleteOne({ _id: oldKey });
|
|
2165
|
+
|
|
2166
|
+
// 触发after事件
|
|
2167
|
+
this.emit('renamenx_after', { oldKey, newKey, result: true });
|
|
2168
|
+
|
|
2169
|
+
return true;
|
|
2170
|
+
} catch (error) {
|
|
2171
|
+
// 触发error事件
|
|
2172
|
+
this.emit('renamenx_error', { oldKey, newKey, error });
|
|
2173
|
+
$.log.error('[MongoDB cache] 条件重命名键失败:', error);
|
|
2174
|
+
return false;
|
|
2175
|
+
}
|
|
2176
|
+
};
|
|
2177
|
+
|
|
2178
|
+
/**
|
|
2179
|
+
* @description 获取数据库中的键数量 (Redis DBSIZE)
|
|
2180
|
+
* @return {Promise|Number} 键的数量
|
|
2181
|
+
*/
|
|
2182
|
+
Mongodb_cache.prototype.dbsize = async function() {
|
|
2183
|
+
try {
|
|
2184
|
+
// 确保连接已建立
|
|
2185
|
+
await this._ensureConnected();
|
|
2186
|
+
|
|
2187
|
+
// 查询有效(未过期)的键数量
|
|
2188
|
+
const count = await this.db.collection(this.table).countDocuments({
|
|
2189
|
+
$or: [
|
|
2190
|
+
{ expireAt: { $exists: false } },
|
|
2191
|
+
{ expireAt: { $gt: new Date() } }
|
|
2192
|
+
]
|
|
2193
|
+
});
|
|
2194
|
+
|
|
2195
|
+
return count;
|
|
2196
|
+
} catch (error) {
|
|
2197
|
+
$.log.error('[MongoDB cache] 获取键数量失败:', error);
|
|
2198
|
+
return 0;
|
|
2199
|
+
}
|
|
2200
|
+
};
|
|
2201
|
+
|
|
2202
|
+
// 列表操作补充方法
|
|
2203
|
+
|
|
2204
|
+
/**
|
|
2205
|
+
* @description 从右侧推入元素 (Redis RPUSH)
|
|
2206
|
+
* @param {String} key 键
|
|
2207
|
+
* @param {*} value 值
|
|
2208
|
+
* @return {Promise|Array} 操作后的数组
|
|
2209
|
+
*/
|
|
2210
|
+
Mongodb_cache.prototype.rpush = async function(key, value) {
|
|
2211
|
+
try {
|
|
2212
|
+
// 触发before事件
|
|
2213
|
+
this.emit('rpush_before', { key, value });
|
|
2214
|
+
|
|
2215
|
+
// 验证参数
|
|
2216
|
+
if (!key) {
|
|
2217
|
+
throw new Error('参数无效:key不能为空');
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
// 使用现有的list_push方法
|
|
2221
|
+
const result = await this.list_push(key, value);
|
|
2222
|
+
|
|
2223
|
+
// 触发after事件
|
|
2224
|
+
this.emit('rpush_after', { key, value, result });
|
|
2225
|
+
|
|
2226
|
+
return result;
|
|
2227
|
+
} catch (error) {
|
|
2228
|
+
// 触发error事件
|
|
2229
|
+
this.emit('rpush_error', { key, value, error });
|
|
2230
|
+
$.log.error('[MongoDB cache] 从右侧推入元素失败:', error);
|
|
2231
|
+
throw error;
|
|
2232
|
+
}
|
|
2233
|
+
};
|
|
2234
|
+
|
|
2235
|
+
/**
|
|
2236
|
+
* @description 获取列表指定范围的元素 (Redis LRANGE)
|
|
2237
|
+
* @param {String} key 键
|
|
2238
|
+
* @param {Number} start 起始位置
|
|
2239
|
+
* @param {Number} stop 结束位置
|
|
2240
|
+
* @return {Promise|Array} 指定范围的元素数组
|
|
2241
|
+
*/
|
|
2242
|
+
Mongodb_cache.prototype.lrange = async function(key, start, stop) {
|
|
2243
|
+
try {
|
|
2244
|
+
// 验证参数
|
|
2245
|
+
if (!key) {
|
|
2246
|
+
throw new Error('参数无效:key不能为空');
|
|
2247
|
+
}
|
|
2248
|
+
|
|
2249
|
+
// 使用现有的list_get方法
|
|
2250
|
+
const result = await this.list_get(key, start, stop);
|
|
2251
|
+
|
|
2252
|
+
return result;
|
|
2253
|
+
} catch (error) {
|
|
2254
|
+
$.log.error('[MongoDB cache] 获取列表范围失败:', error);
|
|
2255
|
+
return [];
|
|
2256
|
+
}
|
|
2257
|
+
};
|
|
2258
|
+
|
|
2259
|
+
/**
|
|
2260
|
+
* @description 获取列表指定索引的元素 (Redis LINDEX)
|
|
2261
|
+
* @param {String} key 键
|
|
2262
|
+
* @param {Number} index 索引
|
|
2263
|
+
* @return {Promise|*} 索引处的元素,不存在返回null
|
|
2264
|
+
*/
|
|
2265
|
+
Mongodb_cache.prototype.lindex = async function(key, index) {
|
|
2266
|
+
try {
|
|
2267
|
+
// 验证参数
|
|
2268
|
+
if (!key || typeof index !== 'number') {
|
|
2269
|
+
throw new Error('参数无效:key不能为空且index必须是数字');
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
// 获取列表
|
|
2273
|
+
const list = await this.get(key) || [];
|
|
2274
|
+
if (!Array.isArray(list)) {
|
|
2275
|
+
return null;
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
// 处理负数索引
|
|
2279
|
+
if (index < 0) {
|
|
2280
|
+
index = list.length + index;
|
|
2281
|
+
}
|
|
2282
|
+
|
|
2283
|
+
// 检查索引是否有效
|
|
2284
|
+
let result = null;
|
|
2285
|
+
if (index >= 0 && index < list.length) {
|
|
2286
|
+
result = list[index];
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
return result;
|
|
2290
|
+
} catch (error) {
|
|
2291
|
+
$.log.error('[MongoDB cache] 获取列表索引元素失败:', error);
|
|
2292
|
+
return null;
|
|
2293
|
+
}
|
|
2294
|
+
};
|
|
2295
|
+
|
|
2296
|
+
/**
|
|
2297
|
+
* @description 设置列表指定索引的元素 (Redis LSET)
|
|
2298
|
+
* @param {String} key 键
|
|
2299
|
+
* @param {Number} index 索引
|
|
2300
|
+
* @param {*} value 新值
|
|
2301
|
+
* @return {Promise|Boolean} 是否设置成功
|
|
2302
|
+
*/
|
|
2303
|
+
Mongodb_cache.prototype.lset = async function(key, index, value) {
|
|
2304
|
+
try {
|
|
2305
|
+
// 触发before事件
|
|
2306
|
+
this.emit('lset_before', { key, index, value });
|
|
2307
|
+
|
|
2308
|
+
// 验证参数
|
|
2309
|
+
if (!key || typeof index !== 'number') {
|
|
2310
|
+
throw new Error('参数无效:key不能为空且index必须是数字');
|
|
2311
|
+
}
|
|
2312
|
+
|
|
2313
|
+
// 获取列表
|
|
2314
|
+
let list = await this.get(key) || [];
|
|
2315
|
+
if (!Array.isArray(list)) {
|
|
2316
|
+
// 触发after事件
|
|
2317
|
+
this.emit('lset_after', { key, index, value, result: false });
|
|
2318
|
+
return false;
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2321
|
+
// 处理负数索引
|
|
2322
|
+
if (index < 0) {
|
|
2323
|
+
index = list.length + index;
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
// 检查索引是否有效
|
|
2327
|
+
if (index < 0 || index >= list.length) {
|
|
2328
|
+
// 触发after事件
|
|
2329
|
+
this.emit('lset_after', { key, index, value, result: false });
|
|
2330
|
+
return false;
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
// 设置新值
|
|
2334
|
+
list[index] = value;
|
|
2335
|
+
|
|
2336
|
+
// 保存更新后的列表
|
|
2337
|
+
await this.set(key, list);
|
|
2338
|
+
|
|
2339
|
+
// 触发after事件
|
|
2340
|
+
this.emit('lset_after', { key, index, value, result: true });
|
|
2341
|
+
|
|
2342
|
+
return true;
|
|
2343
|
+
} catch (error) {
|
|
2344
|
+
// 触发error事件
|
|
2345
|
+
this.emit('lset_error', { key, index, value, error });
|
|
2346
|
+
$.log.error('[MongoDB cache] 设置列表索引元素失败:', error);
|
|
2347
|
+
return false;
|
|
2348
|
+
}
|
|
2349
|
+
};
|
|
2350
|
+
|
|
2351
|
+
/**
|
|
2352
|
+
* @description 从列表中删除指定元素 (Redis LREM)
|
|
2353
|
+
* @param {String} key 键
|
|
2354
|
+
* @param {Number} count 计数
|
|
2355
|
+
* @param {*} value 要删除的元素
|
|
2356
|
+
* @return {Promise|Number} 删除的元素数量
|
|
2357
|
+
*/
|
|
2358
|
+
Mongodb_cache.prototype.lrem = async function(key, count, value) {
|
|
2359
|
+
try {
|
|
2360
|
+
// 触发before事件
|
|
2361
|
+
this.emit('lrem_before', { key, count, value });
|
|
2362
|
+
|
|
2363
|
+
// 验证参数
|
|
2364
|
+
if (!key || typeof count !== 'number') {
|
|
2365
|
+
throw new Error('参数无效:key不能为空且count必须是数字');
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
// 获取列表
|
|
2369
|
+
let list = await this.get(key) || [];
|
|
2370
|
+
if (!Array.isArray(list)) {
|
|
2371
|
+
// 触发after事件
|
|
2372
|
+
this.emit('lrem_after', { key, count, value, result: 0 });
|
|
2373
|
+
return 0;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
let deletedCount = 0;
|
|
2377
|
+
const valueStr = JSON.stringify(value);
|
|
2378
|
+
const newList = [];
|
|
2379
|
+
|
|
2380
|
+
if (count === 0) {
|
|
2381
|
+
// 删除所有匹配元素
|
|
2382
|
+
for (let i = 0; i < list.length; i++) {
|
|
2383
|
+
if (JSON.stringify(list[i]) !== valueStr) {
|
|
2384
|
+
newList.push(list[i]);
|
|
2385
|
+
} else {
|
|
2386
|
+
deletedCount++;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
2389
|
+
} else if (count > 0) {
|
|
2390
|
+
// 从左侧删除count个匹配元素
|
|
2391
|
+
for (let i = 0; i < list.length; i++) {
|
|
2392
|
+
if (JSON.stringify(list[i]) === valueStr && deletedCount < count) {
|
|
2393
|
+
deletedCount++;
|
|
2394
|
+
} else {
|
|
2395
|
+
newList.push(list[i]);
|
|
2396
|
+
}
|
|
2397
|
+
}
|
|
2398
|
+
} else {
|
|
2399
|
+
// 从右侧删除count的绝对值个匹配元素
|
|
2400
|
+
count = Math.abs(count);
|
|
2401
|
+
const tempList = [];
|
|
2402
|
+
for (let i = list.length - 1; i >= 0; i--) {
|
|
2403
|
+
if (JSON.stringify(list[i]) === valueStr && deletedCount < count) {
|
|
2404
|
+
deletedCount++;
|
|
2405
|
+
} else {
|
|
2406
|
+
tempList.unshift(list[i]);
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
newList.push(...tempList);
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
// 如果有元素被删除,更新列表
|
|
2413
|
+
if (deletedCount > 0) {
|
|
2414
|
+
await this.set(key, newList);
|
|
2415
|
+
}
|
|
2416
|
+
|
|
2417
|
+
// 触发after事件
|
|
2418
|
+
this.emit('lrem_after', { key, count, value, result: deletedCount });
|
|
2419
|
+
|
|
2420
|
+
return deletedCount;
|
|
2421
|
+
} catch (error) {
|
|
2422
|
+
// 触发error事件
|
|
2423
|
+
this.emit('lrem_error', { key, count, value, error });
|
|
2424
|
+
$.log.error('[MongoDB cache] 删除列表元素失败:', error);
|
|
2425
|
+
return 0;
|
|
2426
|
+
}
|
|
2427
|
+
};
|
|
2428
|
+
|
|
2429
|
+
/**
|
|
2430
|
+
* @description 修剪列表 (Redis LTRIM)
|
|
2431
|
+
* @param {String} key 键
|
|
2432
|
+
* @param {Number} start 起始位置
|
|
2433
|
+
* @param {Number} stop 结束位置
|
|
2434
|
+
* @return {Promise|Boolean} 是否修剪成功
|
|
2435
|
+
*/
|
|
2436
|
+
Mongodb_cache.prototype.ltrim = async function(key, start, stop) {
|
|
2437
|
+
try {
|
|
2438
|
+
// 触发before事件
|
|
2439
|
+
this.emit('ltrim_before', { key, start, stop });
|
|
2440
|
+
|
|
2441
|
+
// 验证参数
|
|
2442
|
+
if (!key || typeof start !== 'number' || typeof stop !== 'number') {
|
|
2443
|
+
throw new Error('参数无效:key不能为空且start和stop必须是数字');
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
// 获取列表
|
|
2447
|
+
let list = await this.get(key) || [];
|
|
2448
|
+
if (!Array.isArray(list)) {
|
|
2449
|
+
// 触发after事件
|
|
2450
|
+
this.emit('ltrim_after', { key, start, stop, result: false });
|
|
2451
|
+
return false;
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
// 处理负数索引
|
|
2455
|
+
if (start < 0) start = Math.max(0, list.length + start);
|
|
2456
|
+
if (stop < 0) stop = list.length + stop;
|
|
2457
|
+
|
|
2458
|
+
// 确保索引有效
|
|
2459
|
+
start = Math.max(0, start);
|
|
2460
|
+
stop = Math.min(list.length - 1, stop);
|
|
2461
|
+
|
|
2462
|
+
// 修剪列表
|
|
2463
|
+
const trimmedList = list.slice(start, stop + 1);
|
|
2464
|
+
|
|
2465
|
+
// 保存修剪后的列表
|
|
2466
|
+
await this.set(key, trimmedList);
|
|
2467
|
+
|
|
2468
|
+
// 触发after事件
|
|
2469
|
+
this.emit('ltrim_after', { key, start, stop, result: true });
|
|
2470
|
+
|
|
2471
|
+
return true;
|
|
2472
|
+
} catch (error) {
|
|
2473
|
+
// 触发error事件
|
|
2474
|
+
this.emit('ltrim_error', { key, start, stop, error });
|
|
2475
|
+
$.log.error('[MongoDB cache] 修剪列表失败:', error);
|
|
2476
|
+
return false;
|
|
2477
|
+
}
|
|
2478
|
+
};
|
|
2479
|
+
|
|
2480
|
+
// 集合操作补充方法
|
|
2481
|
+
|
|
2482
|
+
/**
|
|
2483
|
+
* @description 批量添加集合成员 (Redis SADD 多成员)
|
|
2484
|
+
* @param {String} key 键
|
|
2485
|
+
* @param {...*} members 成员列表
|
|
2486
|
+
* @return {Promise|Number} 添加的成员数量
|
|
2487
|
+
*/
|
|
2488
|
+
Mongodb_cache.prototype.saddMulti = async function(key, ...members) {
|
|
2489
|
+
try {
|
|
2490
|
+
// 触发before事件
|
|
2491
|
+
this.emit('saddMulti_before', { key, members });
|
|
2492
|
+
|
|
2493
|
+
// 确保连接已建立
|
|
2494
|
+
await this._ensureConnected();
|
|
2495
|
+
|
|
2496
|
+
// 验证参数
|
|
2497
|
+
if (!key || members.length === 0) {
|
|
2498
|
+
// 触发after事件
|
|
2499
|
+
this.emit('saddMulti_after', { key, members, result: 0 });
|
|
2500
|
+
return 0;
|
|
2501
|
+
}
|
|
2502
|
+
|
|
2503
|
+
// 获取当前成员
|
|
2504
|
+
const doc = await this.db.collection(this.table).findOne(
|
|
2505
|
+
{ _id: key },
|
|
2506
|
+
{ projection: { setMembers: 1 } }
|
|
2507
|
+
);
|
|
2508
|
+
const currentMembers = doc && Array.isArray(doc.setMembers) ? doc.setMembers : [];
|
|
2509
|
+
|
|
2510
|
+
// 找出新成员
|
|
2511
|
+
let addedCount = 0;
|
|
2512
|
+
const newMembers = [];
|
|
2513
|
+
|
|
2514
|
+
for (const member of members) {
|
|
2515
|
+
if (!currentMembers.some(m => JSON.stringify(m) === JSON.stringify(member))) {
|
|
2516
|
+
newMembers.push(member);
|
|
2517
|
+
addedCount++;
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
if (addedCount > 0) {
|
|
2522
|
+
// 添加新成员
|
|
2523
|
+
await this.db.collection(this.table).updateOne(
|
|
2524
|
+
{ _id: key },
|
|
2525
|
+
{ $addToSet: { setMembers: { $each: newMembers } } },
|
|
2526
|
+
{ upsert: true }
|
|
2527
|
+
);
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
// 触发after事件
|
|
2531
|
+
this.emit('saddMulti_after', { key, members, result: addedCount });
|
|
2532
|
+
|
|
2533
|
+
return addedCount;
|
|
2534
|
+
} catch (error) {
|
|
2535
|
+
// 触发error事件
|
|
2536
|
+
this.emit('saddMulti_error', { key, members, error });
|
|
2537
|
+
$.log.error('[MongoDB cache] 批量添加集合成员失败:', error);
|
|
2538
|
+
return 0;
|
|
2539
|
+
}
|
|
2540
|
+
};
|
|
2541
|
+
|
|
2542
|
+
/**
|
|
2543
|
+
* @description 批量移除集合成员 (Redis SREM 多成员)
|
|
2544
|
+
* @param {String} key 键
|
|
2545
|
+
* @param {...*} members 成员列表
|
|
2546
|
+
* @return {Promise|Number} 移除的成员数量
|
|
2547
|
+
*/
|
|
2548
|
+
Mongodb_cache.prototype.sremMulti = async function(key, ...members) {
|
|
2549
|
+
try {
|
|
2550
|
+
// 触发before事件
|
|
2551
|
+
this.emit('sremMulti_before', { key, members });
|
|
2552
|
+
|
|
2553
|
+
// 确保连接已建立
|
|
2554
|
+
await this._ensureConnected();
|
|
2555
|
+
|
|
2556
|
+
// 验证参数
|
|
2557
|
+
if (!key || members.length === 0) {
|
|
2558
|
+
// 触发after事件
|
|
2559
|
+
this.emit('sremMulti_after', { key, members, result: 0 });
|
|
2560
|
+
return 0;
|
|
2561
|
+
}
|
|
2562
|
+
|
|
2563
|
+
// 批量移除成员
|
|
2564
|
+
const result = await this.db.collection(this.table).updateOne(
|
|
2565
|
+
{ _id: key },
|
|
2566
|
+
{ $pull: { setMembers: { $in: members } } }
|
|
2567
|
+
);
|
|
2568
|
+
|
|
2569
|
+
// 触发after事件
|
|
2570
|
+
this.emit('sremMulti_after', { key, members, result: result.modifiedCount });
|
|
2571
|
+
|
|
2572
|
+
return result.modifiedCount;
|
|
2573
|
+
} catch (error) {
|
|
2574
|
+
// 触发error事件
|
|
2575
|
+
this.emit('sremMulti_error', { key, members, error });
|
|
2576
|
+
$.log.error('[MongoDB cache] 批量移除集合成员失败:', error);
|
|
2577
|
+
return 0;
|
|
2578
|
+
}
|
|
2579
|
+
};
|
|
2580
|
+
|
|
2581
|
+
/**
|
|
2582
|
+
* @description 获取两个集合的差集 (Redis SDIFF)
|
|
2583
|
+
* @param {String} key1 第一个集合键
|
|
2584
|
+
* @param {String} key2 第二个集合键
|
|
2585
|
+
* @return {Promise|Array} 差集成员数组
|
|
2586
|
+
*/
|
|
2587
|
+
Mongodb_cache.prototype.sdiff = async function(key1, key2) {
|
|
2588
|
+
try {
|
|
2589
|
+
// 确保连接已建立
|
|
2590
|
+
await this._ensureConnected();
|
|
2591
|
+
|
|
2592
|
+
// 验证参数
|
|
2593
|
+
if (!key1 || !key2) {
|
|
2594
|
+
throw new Error('参数无效:key1和key2不能为空');
|
|
2595
|
+
}
|
|
2596
|
+
|
|
2597
|
+
// 获取两个集合的成员
|
|
2598
|
+
const set1 = await this.smembers(key1);
|
|
2599
|
+
const set2 = await this.smembers(key2);
|
|
2600
|
+
|
|
2601
|
+
// 计算差集
|
|
2602
|
+
const set2Str = new Set(set2.map(item => JSON.stringify(item)));
|
|
2603
|
+
const result = set1.filter(item => !set2Str.has(JSON.stringify(item)));
|
|
2604
|
+
|
|
2605
|
+
return result;
|
|
2606
|
+
} catch (error) {
|
|
2607
|
+
$.log.error('[MongoDB cache] 获取集合差集失败:', error);
|
|
2608
|
+
return [];
|
|
2609
|
+
}
|
|
2610
|
+
};
|
|
2611
|
+
|
|
2612
|
+
/**
|
|
2613
|
+
* @description 获取两个集合的交集 (Redis SINTER)
|
|
2614
|
+
* @param {String} key1 第一个集合键
|
|
2615
|
+
* @param {String} key2 第二个集合键
|
|
2616
|
+
* @return {Promise|Array} 交集成员数组
|
|
2617
|
+
*/
|
|
2618
|
+
Mongodb_cache.prototype.sinter = async function(key1, key2) {
|
|
2619
|
+
try {
|
|
2620
|
+
// 确保连接已建立
|
|
2621
|
+
await this._ensureConnected();
|
|
2622
|
+
|
|
2623
|
+
// 验证参数
|
|
2624
|
+
if (!key1 || !key2) {
|
|
2625
|
+
throw new Error('参数无效:key1和key2不能为空');
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2628
|
+
// 获取两个集合的成员
|
|
2629
|
+
const set1 = await this.smembers(key1);
|
|
2630
|
+
const set2 = await this.smembers(key2);
|
|
2631
|
+
|
|
2632
|
+
// 计算交集
|
|
2633
|
+
const set2Str = new Set(set2.map(item => JSON.stringify(item)));
|
|
2634
|
+
const result = set1.filter(item => set2Str.has(JSON.stringify(item)));
|
|
2635
|
+
|
|
2636
|
+
return result;
|
|
2637
|
+
} catch (error) {
|
|
2638
|
+
$.log.error('[MongoDB cache] 获取集合交集失败:', error);
|
|
2639
|
+
return [];
|
|
2640
|
+
}
|
|
2641
|
+
};
|
|
2642
|
+
|
|
2643
|
+
/**
|
|
2644
|
+
* @description 获取两个集合的并集 (Redis SUNION)
|
|
2645
|
+
* @param {String} key1 第一个集合键
|
|
2646
|
+
* @param {String} key2 第二个集合键
|
|
2647
|
+
* @return {Promise|Array} 并集成员数组
|
|
2648
|
+
*/
|
|
2649
|
+
Mongodb_cache.prototype.sunion = async function(key1, key2) {
|
|
2650
|
+
try {
|
|
2651
|
+
// 确保连接已建立
|
|
2652
|
+
await this._ensureConnected();
|
|
2653
|
+
|
|
2654
|
+
// 验证参数
|
|
2655
|
+
if (!key1 || !key2) {
|
|
2656
|
+
throw new Error('参数无效:key1和key2不能为空');
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
// 获取两个集合的成员
|
|
2660
|
+
const set1 = await this.smembers(key1);
|
|
2661
|
+
const set2 = await this.smembers(key2);
|
|
2662
|
+
|
|
2663
|
+
// 计算并集
|
|
2664
|
+
const result = [];
|
|
2665
|
+
const seen = new Set();
|
|
2666
|
+
|
|
2667
|
+
for (const item of [...set1, ...set2]) {
|
|
2668
|
+
const itemStr = JSON.stringify(item);
|
|
2669
|
+
if (!seen.has(itemStr)) {
|
|
2670
|
+
seen.add(itemStr);
|
|
2671
|
+
result.push(item);
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
|
|
2675
|
+
return result;
|
|
2676
|
+
} catch (error) {
|
|
2677
|
+
$.log.error('[MongoDB cache] 获取集合并集失败:', error);
|
|
2678
|
+
return [];
|
|
2679
|
+
}
|
|
2680
|
+
};
|
|
2681
|
+
|
|
2682
|
+
/**
|
|
2683
|
+
* @description 哈希操作补充方法 - 批量设置哈希字段 (Redis HMSET)
|
|
2684
|
+
* @param {String} key 键
|
|
2685
|
+
* @param {Object} fieldValuePairs 字段值对象
|
|
2686
|
+
* @return {Promise|Boolean} 是否设置成功
|
|
2687
|
+
*/
|
|
2688
|
+
Mongodb_cache.prototype.hmset = async function(key, fieldValuePairs) {
|
|
2689
|
+
try {
|
|
2690
|
+
// 触发before事件
|
|
2691
|
+
this.emit('hmset_before', { key, fieldValuePairs });
|
|
2692
|
+
|
|
2693
|
+
// 确保连接已建立
|
|
2694
|
+
await this._ensureConnected();
|
|
2695
|
+
|
|
2696
|
+
// 验证参数
|
|
2697
|
+
if (!key || !fieldValuePairs || typeof fieldValuePairs !== 'object') {
|
|
2698
|
+
throw new Error('参数无效:key不能为空且fieldValuePairs必须是对象');
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
// 准备更新对象
|
|
2702
|
+
const updateData = { $set: {} };
|
|
2703
|
+
Object.entries(fieldValuePairs).forEach(([field, value]) => {
|
|
2704
|
+
updateData.$set[`fields.${field}`] = value;
|
|
2705
|
+
});
|
|
2706
|
+
|
|
2707
|
+
// 执行更新
|
|
2708
|
+
await this.db.collection(this.table).updateOne(
|
|
2709
|
+
{ _id: key },
|
|
2710
|
+
updateData,
|
|
2711
|
+
{ upsert: true }
|
|
2712
|
+
);
|
|
2713
|
+
|
|
2714
|
+
// 触发after事件
|
|
2715
|
+
this.emit('hmset_after', { key, fieldValuePairs, result: true });
|
|
2716
|
+
|
|
2717
|
+
return true;
|
|
2718
|
+
} catch (error) {
|
|
2719
|
+
// 触发error事件
|
|
2720
|
+
this.emit('hmset_error', { key, fieldValuePairs, error });
|
|
2721
|
+
$.log.error('[MongoDB cache] 批量设置哈希字段失败:', error);
|
|
2722
|
+
return false;
|
|
2723
|
+
}
|
|
2724
|
+
};
|
|
2725
|
+
|
|
2726
|
+
/**
|
|
2727
|
+
* @description 哈希操作补充方法 - 批量获取哈希字段值 (Redis HMGET)
|
|
2728
|
+
* @param {String} key 键
|
|
2729
|
+
* @param {...String} fields 字段名列表
|
|
2730
|
+
* @return {Promise|Array} 字段值数组,顺序与字段名对应
|
|
2731
|
+
*/
|
|
2732
|
+
Mongodb_cache.prototype.hmget = async function(key, ...fields) {
|
|
2733
|
+
try {
|
|
2734
|
+
// 确保连接已建立
|
|
2735
|
+
await this._ensureConnected();
|
|
2736
|
+
|
|
2737
|
+
// 验证参数
|
|
2738
|
+
if (!key || fields.length === 0) {
|
|
2739
|
+
return [];
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
// 获取所有字段值
|
|
2743
|
+
const allFields = await this.hgetall(key);
|
|
2744
|
+
|
|
2745
|
+
// 按字段名顺序返回值
|
|
2746
|
+
const result = fields.map(field => allFields[field] !== undefined ? allFields[field] : null);
|
|
2747
|
+
|
|
2748
|
+
return result;
|
|
2749
|
+
} catch (error) {
|
|
2750
|
+
$.log.error('[MongoDB cache] 批量获取哈希字段值失败:', error);
|
|
2751
|
+
return [];
|
|
2752
|
+
}
|
|
2753
|
+
};
|
|
2754
|
+
|
|
2755
|
+
/**
|
|
2756
|
+
* @description 哈希操作补充方法 - 批量删除哈希字段 (Redis HDEL 多字段)
|
|
2757
|
+
* @param {String} key 键
|
|
2758
|
+
* @param {...String} fields 字段名列表
|
|
2759
|
+
* @return {Promise|Number} 删除的字段数量
|
|
2760
|
+
*/
|
|
2761
|
+
Mongodb_cache.prototype.hdelMulti = async function(key, ...fields) {
|
|
2762
|
+
try {
|
|
2763
|
+
// 触发before事件
|
|
2764
|
+
this.emit('hdelMulti_before', { key, fields });
|
|
2765
|
+
|
|
2766
|
+
// 确保连接已建立
|
|
2767
|
+
await this._ensureConnected();
|
|
2768
|
+
|
|
2769
|
+
// 验证参数
|
|
2770
|
+
if (!key || fields.length === 0) {
|
|
2771
|
+
// 触发after事件
|
|
2772
|
+
this.emit('hdelMulti_after', { key, fields, result: 0 });
|
|
2773
|
+
return 0;
|
|
2774
|
+
}
|
|
2775
|
+
|
|
2776
|
+
// 准备更新对象
|
|
2777
|
+
const updateData = { $unset: {} };
|
|
2778
|
+
fields.forEach(field => {
|
|
2779
|
+
updateData.$unset[`fields.${field}`] = '';
|
|
2780
|
+
});
|
|
2781
|
+
|
|
2782
|
+
// 执行更新
|
|
2783
|
+
const result = await this.db.collection(this.table).updateOne(
|
|
2784
|
+
{ _id: key },
|
|
2785
|
+
updateData
|
|
2786
|
+
);
|
|
2787
|
+
|
|
2788
|
+
const deletedCount = result.modifiedCount ? fields.length : 0;
|
|
2789
|
+
|
|
2790
|
+
// 触发after事件
|
|
2791
|
+
this.emit('hdelMulti_after', { key, fields, result: deletedCount });
|
|
2792
|
+
|
|
2793
|
+
return deletedCount;
|
|
2794
|
+
} catch (error) {
|
|
2795
|
+
// 触发error事件
|
|
2796
|
+
this.emit('hdelMulti_error', { key, fields, error });
|
|
2797
|
+
$.log.error('[MongoDB cache] 批量删除哈希字段失败:', error);
|
|
2798
|
+
return 0;
|
|
2799
|
+
}
|
|
2800
|
+
};
|
|
2801
|
+
|
|
2802
|
+
/**
|
|
2803
|
+
* @description 获取哈希所有值 (Redis HVALS)
|
|
2804
|
+
* @param {String} key 键
|
|
2805
|
+
* @return {Promise|Array} 值数组
|
|
2806
|
+
*/
|
|
2807
|
+
Mongodb_cache.prototype.hvals = async function(key) {
|
|
2808
|
+
try {
|
|
2809
|
+
// 确保连接已建立
|
|
2810
|
+
await this._ensureConnected();
|
|
2811
|
+
|
|
2812
|
+
// 验证参数
|
|
2813
|
+
if (!key) {
|
|
2814
|
+
throw new Error('参数无效:key不能为空');
|
|
2815
|
+
}
|
|
2816
|
+
|
|
2817
|
+
// 获取所有字段值
|
|
2818
|
+
const fields = await this.hgetall(key);
|
|
2819
|
+
|
|
2820
|
+
// 返回值数组
|
|
2821
|
+
const result = Object.values(fields);
|
|
2822
|
+
|
|
2823
|
+
return result;
|
|
2824
|
+
} catch (error) {
|
|
2825
|
+
$.log.error('[MongoDB cache] 获取哈希所有值失败:', error);
|
|
2826
|
+
return [];
|
|
2827
|
+
}
|
|
324
2828
|
};
|
|
325
2829
|
|
|
326
2830
|
/**
|
|
@@ -331,6 +2835,9 @@ exports.Mongodb_cache = Mongodb_cache;
|
|
|
331
2835
|
/**
|
|
332
2836
|
* @description mongodb_cache连接池
|
|
333
2837
|
*/
|
|
2838
|
+
if (!$.pool) {
|
|
2839
|
+
$.pool = {};
|
|
2840
|
+
}
|
|
334
2841
|
if (!$.pool.mongodb_cache) {
|
|
335
2842
|
$.pool.mongodb_cache = {};
|
|
336
2843
|
}
|
|
@@ -343,7 +2850,7 @@ if (!$.pool.mongodb_cache) {
|
|
|
343
2850
|
*/
|
|
344
2851
|
function mongodb_cache_admin(scope, dir) {
|
|
345
2852
|
if (!scope) {
|
|
346
|
-
scope = $.val.scope
|
|
2853
|
+
scope = ($.val && $.val.scope) || 'default';
|
|
347
2854
|
}
|
|
348
2855
|
var obj = $.pool.mongodb_cache[scope];
|
|
349
2856
|
if (!obj) {
|
|
@@ -356,4 +2863,4 @@ function mongodb_cache_admin(scope, dir) {
|
|
|
356
2863
|
/**
|
|
357
2864
|
* @description 导出mongodb_cache管理函数
|
|
358
2865
|
*/
|
|
359
|
-
exports.mongodb_cache_admin = mongodb_cache_admin;
|
|
2866
|
+
exports.mongodb_cache_admin = mongodb_cache_admin;
|