mm_mongodb 1.4.5 → 1.4.7
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 +154 -215
- package/compatibility_table.md +122 -0
- package/index.js +1494 -75
- package/package.json +1 -1
- package/test2.js +22 -14
- package/test_redis_api.js +66 -0
- package/test_redis_compatibility.js +123 -0
- package/cache.js +0 -2866
package/index.js
CHANGED
|
@@ -87,6 +87,11 @@ class MongoDB extends eventEmitter {
|
|
|
87
87
|
this.size = 0;
|
|
88
88
|
// 连接状态
|
|
89
89
|
this.connected = false;
|
|
90
|
+
// 连接Promise缓存
|
|
91
|
+
this.connectPromise = null;
|
|
92
|
+
// 连接重试次数
|
|
93
|
+
this.connectAttempts = 0;
|
|
94
|
+
this.maxConnectAttempts = 3;
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
/**
|
|
@@ -154,6 +159,1308 @@ class MongoDB extends eventEmitter {
|
|
|
154
159
|
// 所有重试都失败,抛出最后一个错误
|
|
155
160
|
throw lastError;
|
|
156
161
|
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* @description 确保数据库连接已建立
|
|
165
|
+
* @private
|
|
166
|
+
* @return {Promise<void>}
|
|
167
|
+
*/
|
|
168
|
+
async _ensureConnected() {
|
|
169
|
+
// 如果已有连接Promise,等待其完成
|
|
170
|
+
if (this.connectPromise) {
|
|
171
|
+
return this.connectPromise;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// 创建新的连接Promise
|
|
175
|
+
this.connectPromise = (async () => {
|
|
176
|
+
try {
|
|
177
|
+
// 检查连接状态
|
|
178
|
+
if (!this.db || !this.connected) {
|
|
179
|
+
// 记录连接尝试
|
|
180
|
+
this.connectAttempts++;
|
|
181
|
+
|
|
182
|
+
// 尝试连接
|
|
183
|
+
await this.open();
|
|
184
|
+
|
|
185
|
+
// 检查连接是否成功
|
|
186
|
+
if (this.db && this.db.collection) {
|
|
187
|
+
this.connected = true;
|
|
188
|
+
this.connectAttempts = 0;
|
|
189
|
+
|
|
190
|
+
// 确保索引已创建
|
|
191
|
+
await this._ensureIndexes();
|
|
192
|
+
|
|
193
|
+
$.log.info(`[MongoDB] 成功连接到数据库,表名: ${this.table}`);
|
|
194
|
+
} else {
|
|
195
|
+
throw new Error('数据库连接失败,db对象为空');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
} catch (error) {
|
|
199
|
+
$.log.error(`[MongoDB] 连接失败 (尝试 ${this.connectAttempts}/${this.maxConnectAttempts}):`, error.message);
|
|
200
|
+
|
|
201
|
+
// 重置连接Promise,允许下次重试
|
|
202
|
+
this.connectPromise = null;
|
|
203
|
+
|
|
204
|
+
// 如果尝试次数未超过限制,抛出错误让上层处理
|
|
205
|
+
if (this.connectAttempts < this.maxConnectAttempts) {
|
|
206
|
+
throw error;
|
|
207
|
+
} else {
|
|
208
|
+
// 超过重试次数,记录严重错误
|
|
209
|
+
$.log.error('[MongoDB] 达到最大连接重试次数,连接失败');
|
|
210
|
+
throw new Error('数据库连接失败,已达到最大重试次数');
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
})();
|
|
214
|
+
|
|
215
|
+
return this.connectPromise;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* @description 确保索引已创建
|
|
220
|
+
* @private
|
|
221
|
+
* @return {Promise<void>}
|
|
222
|
+
*/
|
|
223
|
+
async _ensureIndexes() {
|
|
224
|
+
try {
|
|
225
|
+
// 确保db对象有效
|
|
226
|
+
if (!this.db || !this.db.collection) {
|
|
227
|
+
throw new Error('数据库连接无效,无法创建索引');
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// 创建TTL索引用于自动过期
|
|
231
|
+
await this.db.collection(this.table).createIndex(
|
|
232
|
+
{ expireAt: 1 },
|
|
233
|
+
{ expireAfterSeconds: 0 }
|
|
234
|
+
);
|
|
235
|
+
// 创建_id索引(通常自动创建,但显式指定更清晰)
|
|
236
|
+
await this.db.collection(this.table).createIndex({ _id: 1 });
|
|
237
|
+
} catch (error) {
|
|
238
|
+
// 忽略索引已存在的错误
|
|
239
|
+
if (error.code !== 85) {
|
|
240
|
+
$.log.error('[MongoDB] 创建索引失败:', error);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* @description Redis兼容API - 获取缓存值
|
|
247
|
+
* @param {String} key 键名
|
|
248
|
+
* @return {Promise<any>} 缓存值
|
|
249
|
+
*/
|
|
250
|
+
async get(key) {
|
|
251
|
+
try {
|
|
252
|
+
await this._ensureConnected();
|
|
253
|
+
const result = await this.findOne({ key: key });
|
|
254
|
+
return result ? result.value : null;
|
|
255
|
+
} catch (error) {
|
|
256
|
+
$.log.error('[MongoDB] 获取缓存失败:', error);
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* @description Redis兼容API - 清空所有缓存
|
|
263
|
+
* @return {Promise<Boolean>} 是否成功
|
|
264
|
+
*/
|
|
265
|
+
async clear() {
|
|
266
|
+
try {
|
|
267
|
+
await this._ensureConnected();
|
|
268
|
+
await this.del({});
|
|
269
|
+
return true;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
$.log.error('[MongoDB] 清空缓存失败:', error);
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* @description Redis兼容API - 获取所有键
|
|
278
|
+
* @param {String} pattern 匹配模式
|
|
279
|
+
* @return {Promise<Array>} 键名数组
|
|
280
|
+
*/
|
|
281
|
+
async keys(pattern = '*') {
|
|
282
|
+
try {
|
|
283
|
+
await this._ensureConnected();
|
|
284
|
+
let query = {};
|
|
285
|
+
if (pattern !== '*') {
|
|
286
|
+
// 简单的通配符支持
|
|
287
|
+
const regexPattern = pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
288
|
+
query.key = { $regex: new RegExp(`^${regexPattern}$`) };
|
|
289
|
+
}
|
|
290
|
+
const results = await this.find(query, { projection: { key: 1 } });
|
|
291
|
+
return results.map(item => item.key);
|
|
292
|
+
} catch (error) {
|
|
293
|
+
$.log.error('[MongoDB] 获取键列表失败:', error);
|
|
294
|
+
return [];
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* @description Redis兼容API - 设置过期时间
|
|
300
|
+
* @param {String} key 键名
|
|
301
|
+
* @param {Number} seconds 过期时间(秒)
|
|
302
|
+
* @return {Promise<Boolean>} 是否成功
|
|
303
|
+
*/
|
|
304
|
+
async ttl(key, seconds) {
|
|
305
|
+
try {
|
|
306
|
+
await this._ensureConnected();
|
|
307
|
+
if (!seconds && seconds !== 0) {
|
|
308
|
+
// 获取剩余过期时间
|
|
309
|
+
const item = await this.findOne({ key: key });
|
|
310
|
+
if (!item || !item.expireAt) return -1;
|
|
311
|
+
const ttl = Math.floor((item.expireAt.getTime() - Date.now()) / 1000);
|
|
312
|
+
return ttl > 0 ? ttl : -2; // -2 表示已过期
|
|
313
|
+
} else {
|
|
314
|
+
// 设置过期时间
|
|
315
|
+
const expireAt = seconds === 0 ? null : new Date(Date.now() + seconds * 1000);
|
|
316
|
+
const result = await this._set({ key: key }, { expireAt: expireAt });
|
|
317
|
+
return result.modifiedCount > 0;
|
|
318
|
+
}
|
|
319
|
+
} catch (error) {
|
|
320
|
+
$.log.error('[MongoDB] 设置TTL失败:', error);
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* @description Redis兼容API - 添加缓存
|
|
327
|
+
* @param {String} key 键名
|
|
328
|
+
* @param {*} value 值
|
|
329
|
+
* @param {Number} expire 过期时间(秒)
|
|
330
|
+
* @return {Promise<Boolean>} 是否成功
|
|
331
|
+
*/
|
|
332
|
+
async _add(key, value, expire) {
|
|
333
|
+
try {
|
|
334
|
+
await this._ensureConnected();
|
|
335
|
+
const data = {
|
|
336
|
+
key: key,
|
|
337
|
+
value: value
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
if (expire) {
|
|
341
|
+
data.expireAt = new Date(Date.now() + expire * 1000);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 检查是否已存在
|
|
345
|
+
const existing = await this.findOne({ key: key });
|
|
346
|
+
if (existing) {
|
|
347
|
+
await this._set({ key: key }, data);
|
|
348
|
+
} else {
|
|
349
|
+
await this.addObj(data);
|
|
350
|
+
}
|
|
351
|
+
return true;
|
|
352
|
+
} catch (error) {
|
|
353
|
+
$.log.error('[MongoDB] 添加缓存失败:', error);
|
|
354
|
+
return false;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* @description Redis兼容API - 添加缓存
|
|
360
|
+
* @param {String} key 键名
|
|
361
|
+
* @param {*} value 值
|
|
362
|
+
* @param {Number} expire 过期时间(秒)
|
|
363
|
+
* @return {Promise<Boolean>} 是否成功
|
|
364
|
+
*/
|
|
365
|
+
async add(key, value, expire) {
|
|
366
|
+
return this._add(key, value, expire);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* @description Redis兼容API - 增加整数值
|
|
371
|
+
* @param {String} key 键名
|
|
372
|
+
* @param {Number} value 增加值
|
|
373
|
+
* @return {Promise<Number>} 增加后的值
|
|
374
|
+
*/
|
|
375
|
+
async addInt(key, value = 1) {
|
|
376
|
+
try {
|
|
377
|
+
await this._ensureConnected();
|
|
378
|
+
const item = await this.findOne({ key: key });
|
|
379
|
+
let currentValue = 0;
|
|
380
|
+
|
|
381
|
+
if (item && typeof item.value === 'number') {
|
|
382
|
+
currentValue = item.value;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const newValue = currentValue + value;
|
|
386
|
+
await this._add(key, newValue);
|
|
387
|
+
return newValue;
|
|
388
|
+
} catch (error) {
|
|
389
|
+
$.log.error('[MongoDB] 增加整数值失败:', error);
|
|
390
|
+
return undefined;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* @description Redis兼容API - 增加浮点数值
|
|
396
|
+
* @param {String} key 键名
|
|
397
|
+
* @param {Number} value 增加值
|
|
398
|
+
* @return {Promise<Number>} 增加后的值
|
|
399
|
+
*/
|
|
400
|
+
async addFloat(key, value = 1.0) {
|
|
401
|
+
return this.addInt(key, value); // 复用addInt方法
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* @description Redis兼容API - 添加字符串
|
|
406
|
+
* @param {String} key 键名
|
|
407
|
+
* @param {String} value 字符串值
|
|
408
|
+
* @return {Promise<Boolean>} 是否成功
|
|
409
|
+
*/
|
|
410
|
+
async addStr(key, value) {
|
|
411
|
+
return this._add(key, String(value));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* @description Redis兼容API - 排序
|
|
416
|
+
* @param {String} key 键名
|
|
417
|
+
* @param {Object} options 排序选项
|
|
418
|
+
* @return {Promise<Array>} 排序结果
|
|
419
|
+
*/
|
|
420
|
+
async sort(key, options = {}) {
|
|
421
|
+
try {
|
|
422
|
+
await this._ensureConnected();
|
|
423
|
+
// 假设value是数组
|
|
424
|
+
const item = await this.findOne({ key: key });
|
|
425
|
+
if (!item || !Array.isArray(item.value)) return [];
|
|
426
|
+
|
|
427
|
+
const sorted = [...item.value];
|
|
428
|
+
|
|
429
|
+
// 处理排序选项
|
|
430
|
+
if (options.desc) {
|
|
431
|
+
sorted.sort((a, b) => b - a);
|
|
432
|
+
} else {
|
|
433
|
+
sorted.sort((a, b) => a - b);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// 处理限制
|
|
437
|
+
if (options.limit) {
|
|
438
|
+
const offset = options.offset || 0;
|
|
439
|
+
return sorted.slice(offset, offset + options.limit);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return sorted;
|
|
443
|
+
} catch (error) {
|
|
444
|
+
$.log.error('[MongoDB] 排序失败:', error);
|
|
445
|
+
return [];
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* @description Redis兼容API - 哈希表设置字段
|
|
451
|
+
* @param {String} key 键名
|
|
452
|
+
* @param {String} field 字段名
|
|
453
|
+
* @param {*} value 值
|
|
454
|
+
* @return {Promise<Boolean>} 是否成功
|
|
455
|
+
*/
|
|
456
|
+
async hset(key, field, value) {
|
|
457
|
+
try {
|
|
458
|
+
await this._ensureConnected();
|
|
459
|
+
const item = await this.findOne({ key: key });
|
|
460
|
+
let hash = item && typeof item.value === 'object' && item.value !== null ? item.value : {};
|
|
461
|
+
|
|
462
|
+
hash[field] = value;
|
|
463
|
+
await this._add(key, hash);
|
|
464
|
+
return true;
|
|
465
|
+
} catch (error) {
|
|
466
|
+
$.log.error('[MongoDB] 哈希表设置失败:', error);
|
|
467
|
+
return false;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* @description Redis兼容API - 哈希表获取字段
|
|
473
|
+
* @param {String} key 键名
|
|
474
|
+
* @param {String} field 字段名
|
|
475
|
+
* @return {Promise<any>} 字段值
|
|
476
|
+
*/
|
|
477
|
+
async hget(key, field) {
|
|
478
|
+
try {
|
|
479
|
+
await this._ensureConnected();
|
|
480
|
+
const item = await this.findOne({ key: key });
|
|
481
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
484
|
+
return item.value[field] !== undefined ? item.value[field] : null;
|
|
485
|
+
} catch (error) {
|
|
486
|
+
$.log.error('[MongoDB] 哈希表获取失败:', error);
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* @description Redis兼容API - 哈希表获取所有字段和值
|
|
493
|
+
* @param {String} key 键名
|
|
494
|
+
* @return {Promise<Object>} 哈希表对象
|
|
495
|
+
*/
|
|
496
|
+
async hgetall(key) {
|
|
497
|
+
try {
|
|
498
|
+
const item = await this.findOne({ key: key });
|
|
499
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
500
|
+
return {};
|
|
501
|
+
}
|
|
502
|
+
return item.value;
|
|
503
|
+
} catch (error) {
|
|
504
|
+
$.log.error('[MongoDB] 哈希表获取全部失败:', error);
|
|
505
|
+
return {};
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* @description Redis兼容API - 哈希表删除字段
|
|
511
|
+
* @param {String} key 键名
|
|
512
|
+
* @param {String} field 字段名
|
|
513
|
+
* @return {Promise<Boolean>} 是否成功
|
|
514
|
+
*/
|
|
515
|
+
async hdel(key, field) {
|
|
516
|
+
try {
|
|
517
|
+
const item = await this.findOne({ key: key });
|
|
518
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
if (item.value[field] === undefined) {
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
delete item.value[field];
|
|
527
|
+
await this._set({ key: key }, { value: item.value });
|
|
528
|
+
return true;
|
|
529
|
+
} catch (error) {
|
|
530
|
+
$.log.error('[MongoDB] 哈希表删除失败:', error);
|
|
531
|
+
return false;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* @description Redis兼容API - 检查键是否存在
|
|
537
|
+
* @param {String} key 键名
|
|
538
|
+
* @return {Promise<Boolean>} 是否存在
|
|
539
|
+
*/
|
|
540
|
+
async has(key) {
|
|
541
|
+
try {
|
|
542
|
+
const result = await this.findOne({ key: key });
|
|
543
|
+
return result !== null;
|
|
544
|
+
} catch (error) {
|
|
545
|
+
$.log.error('[MongoDB] 检查键是否存在失败:', error);
|
|
546
|
+
return false;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/**
|
|
551
|
+
* @description Redis兼容API - 设置键过期时间(映射到ttl)
|
|
552
|
+
* @param {String} key 键名
|
|
553
|
+
* @param {Number} seconds 过期时间(秒)
|
|
554
|
+
* @return {Promise<Boolean>} 是否成功
|
|
555
|
+
*/
|
|
556
|
+
async expire(key, seconds) {
|
|
557
|
+
return this.ttl(key, seconds);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* @description Redis兼容API - 递增(通过addInt模拟)
|
|
562
|
+
* @param {String} key 键名
|
|
563
|
+
* @return {Promise<Number>} 递增后的值
|
|
564
|
+
*/
|
|
565
|
+
async incr(key) {
|
|
566
|
+
try {
|
|
567
|
+
await this._ensureConnected();
|
|
568
|
+
const result = await this.addInt(key, 1);
|
|
569
|
+
return result !== undefined ? result : 0;
|
|
570
|
+
} catch (error) {
|
|
571
|
+
$.log.error('[MongoDB] 递增操作失败:', error);
|
|
572
|
+
return 0;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* @description Redis兼容API - 递减(通过addInt模拟)
|
|
578
|
+
* @param {String} key 键名
|
|
579
|
+
* @return {Promise<Number>} 递减后的值
|
|
580
|
+
*/
|
|
581
|
+
async decr(key) {
|
|
582
|
+
try {
|
|
583
|
+
await this._ensureConnected();
|
|
584
|
+
const result = await this.addInt(key, -1);
|
|
585
|
+
return result !== undefined ? result : 0;
|
|
586
|
+
} catch (error) {
|
|
587
|
+
$.log.error('[MongoDB] 递减操作失败:', error);
|
|
588
|
+
return 0;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* @description Redis兼容API - 检查键是否存在(别名)
|
|
594
|
+
* @param {String} key 键名
|
|
595
|
+
* @return {Promise<Boolean>} 是否存在
|
|
596
|
+
*/
|
|
597
|
+
async exists(key) {
|
|
598
|
+
try {
|
|
599
|
+
await this._ensureConnected();
|
|
600
|
+
return this.has(key);
|
|
601
|
+
} catch (error) {
|
|
602
|
+
$.log.error('[MongoDB] 检查键是否存在失败:', error);
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* @description Redis兼容API - 设置键值
|
|
609
|
+
* @param {String} key 键名
|
|
610
|
+
* @param {*} value 值
|
|
611
|
+
* @return {Promise<Boolean>} 是否成功
|
|
612
|
+
*/
|
|
613
|
+
async _set(key, value) {
|
|
614
|
+
try {
|
|
615
|
+
await this._ensureConnected();
|
|
616
|
+
// 直接使用已有的add方法,它已正确处理查询条件格式
|
|
617
|
+
return await this._add(key, value);
|
|
618
|
+
} catch (error) {
|
|
619
|
+
$.log.error('[MongoDB] 设置键值失败:', error);
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* @description Redis兼容API - 设置键值
|
|
626
|
+
* @param {String} key 键名
|
|
627
|
+
* @param {*} value 值
|
|
628
|
+
* @return {Promise<Boolean>} 是否成功
|
|
629
|
+
*/
|
|
630
|
+
async set(key, value) {
|
|
631
|
+
return this._set(key, value);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* @description Redis兼容API - 哈希表批量设置
|
|
636
|
+
* @param {String} key 键名
|
|
637
|
+
* @param {Object} fieldValues 字段值对象
|
|
638
|
+
* @return {Promise<Boolean>} 是否成功
|
|
639
|
+
*/
|
|
640
|
+
async hmset(key, fieldValues) {
|
|
641
|
+
try {
|
|
642
|
+
await this._ensureConnected();
|
|
643
|
+
const item = await this.findOne({ key: key });
|
|
644
|
+
let hash = item && typeof item.value === 'object' && item.value !== null ? item.value : {};
|
|
645
|
+
|
|
646
|
+
// 合并字段值
|
|
647
|
+
Object.assign(hash, fieldValues);
|
|
648
|
+
await this._add(key, hash);
|
|
649
|
+
return true;
|
|
650
|
+
} catch (error) {
|
|
651
|
+
$.log.error('[MongoDB] 哈希表批量设置失败:', error);
|
|
652
|
+
return false;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/**
|
|
657
|
+
* @description Redis兼容API - 哈希表批量获取
|
|
658
|
+
* @param {String} key 键名
|
|
659
|
+
* @param {Array} fields 字段名数组
|
|
660
|
+
* @return {Promise<Object>} 字段值对象
|
|
661
|
+
*/
|
|
662
|
+
async hmget(key, fields) {
|
|
663
|
+
try {
|
|
664
|
+
await this._ensureConnected();
|
|
665
|
+
const item = await this.findOne({ key: key });
|
|
666
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
667
|
+
return {};
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
const result = {};
|
|
671
|
+
fields.forEach(field => {
|
|
672
|
+
result[field] = item.value[field] !== undefined ? item.value[field] : null;
|
|
673
|
+
});
|
|
674
|
+
return result;
|
|
675
|
+
} catch (error) {
|
|
676
|
+
$.log.error('[MongoDB] 哈希表批量获取失败:', error);
|
|
677
|
+
return {};
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/**
|
|
682
|
+
* @description Redis兼容API - 哈希表批量删除字段
|
|
683
|
+
* @param {String} key 键名
|
|
684
|
+
* @param {Array} fields 字段名数组
|
|
685
|
+
* @return {Promise<Boolean>} 是否成功
|
|
686
|
+
*/
|
|
687
|
+
async hdelMulti(key, fields) {
|
|
688
|
+
try {
|
|
689
|
+
await this._ensureConnected();
|
|
690
|
+
const item = await this.findOne({ key: key });
|
|
691
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
let deleted = false;
|
|
696
|
+
fields.forEach(field => {
|
|
697
|
+
if (item.value[field] !== undefined) {
|
|
698
|
+
delete item.value[field];
|
|
699
|
+
deleted = true;
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
if (deleted) {
|
|
704
|
+
await this._set({ key: key }, { value: item.value });
|
|
705
|
+
}
|
|
706
|
+
return deleted;
|
|
707
|
+
} catch (error) {
|
|
708
|
+
$.log.error('[MongoDB] 哈希表批量删除失败:', error);
|
|
709
|
+
return false;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
/**
|
|
714
|
+
* @description Redis兼容API - 哈希表获取所有值
|
|
715
|
+
* @param {String} key 键名
|
|
716
|
+
* @return {Promise<Array>} 值数组
|
|
717
|
+
*/
|
|
718
|
+
async hvals(key) {
|
|
719
|
+
try {
|
|
720
|
+
await this._ensureConnected();
|
|
721
|
+
const item = await this.findOne({ key: key });
|
|
722
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
723
|
+
return [];
|
|
724
|
+
}
|
|
725
|
+
return Object.values(item.value);
|
|
726
|
+
} catch (error) {
|
|
727
|
+
$.log.error('[MongoDB] 哈希表获取所有值失败:', error);
|
|
728
|
+
return [];
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/**
|
|
733
|
+
* @description Redis兼容API - 哈希表获取所有字段名
|
|
734
|
+
* @param {String} key 键名
|
|
735
|
+
* @return {Promise<Array>} 字段名数组
|
|
736
|
+
*/
|
|
737
|
+
async hkeys(key) {
|
|
738
|
+
try {
|
|
739
|
+
await this._ensureConnected();
|
|
740
|
+
const item = await this.findOne({ key: key });
|
|
741
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
742
|
+
return [];
|
|
743
|
+
}
|
|
744
|
+
return Object.keys(item.value);
|
|
745
|
+
} catch (error) {
|
|
746
|
+
$.log.error('[MongoDB] 哈希表获取所有字段名失败:', error);
|
|
747
|
+
return [];
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* @description Redis兼容API - 哈希表字段数量
|
|
753
|
+
* @param {String} key 键名
|
|
754
|
+
* @return {Promise<Number>} 字段数量
|
|
755
|
+
*/
|
|
756
|
+
async hlen(key) {
|
|
757
|
+
try {
|
|
758
|
+
await this._ensureConnected();
|
|
759
|
+
const item = await this.findOne({ key: key });
|
|
760
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
761
|
+
return 0;
|
|
762
|
+
}
|
|
763
|
+
return Object.keys(item.value).length;
|
|
764
|
+
} catch (error) {
|
|
765
|
+
$.log.error('[MongoDB] 哈希表字段数量失败:', error);
|
|
766
|
+
return 0;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/**
|
|
771
|
+
* @description Redis兼容API - 哈希表字段是否存在
|
|
772
|
+
* @param {String} key 键名
|
|
773
|
+
* @param {String} field 字段名
|
|
774
|
+
* @return {Promise<Boolean>} 是否存在
|
|
775
|
+
*/
|
|
776
|
+
async hexists(key, field) {
|
|
777
|
+
try {
|
|
778
|
+
await this._ensureConnected();
|
|
779
|
+
const item = await this.findOne({ key: key });
|
|
780
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
781
|
+
return false;
|
|
782
|
+
}
|
|
783
|
+
return field in item.value;
|
|
784
|
+
} catch (error) {
|
|
785
|
+
$.log.error('[MongoDB] 哈希表字段是否存在失败:', error);
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* @description Redis兼容API - 列表尾部添加元素
|
|
792
|
+
* @param {String} key 键名
|
|
793
|
+
* @param {*} value 值
|
|
794
|
+
* @return {Promise<Number>} 添加后列表长度
|
|
795
|
+
*/
|
|
796
|
+
async rpush(key, value) {
|
|
797
|
+
try {
|
|
798
|
+
await this._ensureConnected();
|
|
799
|
+
const item = await this.findOne({ key: key });
|
|
800
|
+
let list = item && Array.isArray(item.value) ? item.value : [];
|
|
801
|
+
|
|
802
|
+
list.push(value);
|
|
803
|
+
await this._add(key, list);
|
|
804
|
+
return list.length;
|
|
805
|
+
} catch (error) {
|
|
806
|
+
$.log.error('[MongoDB] 列表尾部添加失败:', error);
|
|
807
|
+
return 0;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
/**
|
|
812
|
+
* @description Redis兼容API - 列表头部添加元素
|
|
813
|
+
* @param {String} key 键名
|
|
814
|
+
* @param {*} value 值
|
|
815
|
+
* @return {Promise<Number>} 添加后列表长度
|
|
816
|
+
*/
|
|
817
|
+
async lpush(key, value) {
|
|
818
|
+
try {
|
|
819
|
+
await this._ensureConnected();
|
|
820
|
+
const item = await this.findOne({ key: key });
|
|
821
|
+
let list = item && Array.isArray(item.value) ? item.value : [];
|
|
822
|
+
|
|
823
|
+
list.unshift(value);
|
|
824
|
+
await this._add(key, list);
|
|
825
|
+
return list.length;
|
|
826
|
+
} catch (error) {
|
|
827
|
+
$.log.error('[MongoDB] 列表头部添加失败:', error);
|
|
828
|
+
return 0;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* @description Redis兼容API - 列表范围查询
|
|
834
|
+
* @param {String} key 键名
|
|
835
|
+
* @param {Number} start 起始索引
|
|
836
|
+
* @param {Number} stop 结束索引
|
|
837
|
+
* @return {Promise<Array>} 元素数组
|
|
838
|
+
*/
|
|
839
|
+
async lrange(key, start, stop) {
|
|
840
|
+
try {
|
|
841
|
+
await this._ensureConnected();
|
|
842
|
+
const item = await this.findOne({ key: key });
|
|
843
|
+
if (!item || !Array.isArray(item.value)) {
|
|
844
|
+
return [];
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// 处理负数索引
|
|
848
|
+
let startIndex = start < 0 ? item.value.length + start : start;
|
|
849
|
+
let endIndex = stop < 0 ? item.value.length + stop : stop;
|
|
850
|
+
|
|
851
|
+
// 确保索引有效
|
|
852
|
+
startIndex = Math.max(0, Math.min(startIndex, item.value.length - 1));
|
|
853
|
+
endIndex = Math.max(startIndex, Math.min(endIndex, item.value.length - 1));
|
|
854
|
+
|
|
855
|
+
return item.value.slice(startIndex, endIndex + 1);
|
|
856
|
+
} catch (error) {
|
|
857
|
+
$.log.error('[MongoDB] 列表范围查询失败:', error);
|
|
858
|
+
return [];
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* @description Redis兼容API - 获取列表指定索引元素
|
|
864
|
+
* @param {String} key 键名
|
|
865
|
+
* @param {Number} index 索引
|
|
866
|
+
* @return {Promise<any>} 元素值
|
|
867
|
+
*/
|
|
868
|
+
async lindex(key, index) {
|
|
869
|
+
try {
|
|
870
|
+
await this._ensureConnected();
|
|
871
|
+
const item = await this.findOne({ key: key });
|
|
872
|
+
if (!item || !Array.isArray(item.value)) {
|
|
873
|
+
return null;
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
// 处理负数索引
|
|
877
|
+
const realIndex = index < 0 ? item.value.length + index : index;
|
|
878
|
+
|
|
879
|
+
if (realIndex < 0 || realIndex >= item.value.length) {
|
|
880
|
+
return null;
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
return item.value[realIndex];
|
|
884
|
+
} catch (error) {
|
|
885
|
+
$.log.error('[MongoDB] 获取列表索引元素失败:', error);
|
|
886
|
+
return null;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* @description Redis兼容API - 设置列表指定索引元素
|
|
892
|
+
* @param {String} key 键名
|
|
893
|
+
* @param {Number} index 索引
|
|
894
|
+
* @param {*} value 值
|
|
895
|
+
* @return {Promise<Boolean>} 是否成功
|
|
896
|
+
*/
|
|
897
|
+
async lset(key, index, value) {
|
|
898
|
+
try {
|
|
899
|
+
await this._ensureConnected();
|
|
900
|
+
const item = await this.findOne({ key: key });
|
|
901
|
+
if (!item || !Array.isArray(item.value)) {
|
|
902
|
+
return false;
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// 处理负数索引
|
|
906
|
+
const realIndex = index < 0 ? item.value.length + index : index;
|
|
907
|
+
|
|
908
|
+
if (realIndex < 0 || realIndex >= item.value.length) {
|
|
909
|
+
return false;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
item.value[realIndex] = value;
|
|
913
|
+
await this._set({ key: key }, { value: item.value });
|
|
914
|
+
return true;
|
|
915
|
+
} catch (error) {
|
|
916
|
+
$.log.error('[MongoDB] 设置列表索引元素失败:', error);
|
|
917
|
+
return false;
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* @description Redis兼容API - 删除列表元素
|
|
923
|
+
* @param {String} key 键名
|
|
924
|
+
* @param {Number} count 删除数量
|
|
925
|
+
* @param {*} value 要删除的值
|
|
926
|
+
* @return {Promise<Number>} 删除的元素数量
|
|
927
|
+
*/
|
|
928
|
+
async lrem(key, count, value) {
|
|
929
|
+
try {
|
|
930
|
+
await this._ensureConnected();
|
|
931
|
+
const item = await this.findOne({ key: key });
|
|
932
|
+
if (!item || !Array.isArray(item.value)) {
|
|
933
|
+
return 0;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
let list = [...item.value];
|
|
937
|
+
let removedCount = 0;
|
|
938
|
+
|
|
939
|
+
if (count > 0) {
|
|
940
|
+
// 从头部删除count个等于value的元素
|
|
941
|
+
for (let i = 0; i < list.length && removedCount < count; i++) {
|
|
942
|
+
if (JSON.stringify(list[i]) === JSON.stringify(value)) {
|
|
943
|
+
list.splice(i, 1);
|
|
944
|
+
removedCount++;
|
|
945
|
+
i--; // 调整索引
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
} else if (count < 0) {
|
|
949
|
+
// 从尾部删除|count|个等于value的元素
|
|
950
|
+
count = Math.abs(count);
|
|
951
|
+
for (let i = list.length - 1; i >= 0 && removedCount < count; i--) {
|
|
952
|
+
if (JSON.stringify(list[i]) === JSON.stringify(value)) {
|
|
953
|
+
list.splice(i, 1);
|
|
954
|
+
removedCount++;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
} else {
|
|
958
|
+
// 删除所有等于value的元素
|
|
959
|
+
list = list.filter(item => JSON.stringify(item) !== JSON.stringify(value));
|
|
960
|
+
removedCount = item.value.length - list.length;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
if (removedCount > 0) {
|
|
964
|
+
await this._set({ key: key }, { value: list });
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
return removedCount;
|
|
968
|
+
} catch (error) {
|
|
969
|
+
$.log.error('[MongoDB] 删除列表元素失败:', error);
|
|
970
|
+
return 0;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
/**
|
|
975
|
+
* @description Redis兼容API - 裁剪列表
|
|
976
|
+
* @param {String} key 键名
|
|
977
|
+
* @param {Number} start 起始索引
|
|
978
|
+
* @param {Number} stop 结束索引
|
|
979
|
+
* @return {Promise<Boolean>} 是否成功
|
|
980
|
+
*/
|
|
981
|
+
async ltrim(key, start, stop) {
|
|
982
|
+
try {
|
|
983
|
+
await this._ensureConnected();
|
|
984
|
+
const item = await this.findOne({ key: key });
|
|
985
|
+
if (!item || !Array.isArray(item.value)) {
|
|
986
|
+
return false;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// 使用lrange的逻辑获取裁剪后的列表
|
|
990
|
+
const trimmed = await this.lrange(key, start, stop);
|
|
991
|
+
await this._set({ key: key }, { value: trimmed });
|
|
992
|
+
return true;
|
|
993
|
+
} catch (error) {
|
|
994
|
+
$.log.error('[MongoDB] 裁剪列表失败:', error);
|
|
995
|
+
return false;
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/**
|
|
1000
|
+
* @description Redis兼容API - 列表长度
|
|
1001
|
+
* @param {String} key 键名
|
|
1002
|
+
* @return {Promise<Number>} 列表长度
|
|
1003
|
+
*/
|
|
1004
|
+
async llen(key) {
|
|
1005
|
+
try {
|
|
1006
|
+
await this._ensureConnected();
|
|
1007
|
+
const item = await this.findOne({ key: key });
|
|
1008
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1009
|
+
return 0;
|
|
1010
|
+
}
|
|
1011
|
+
return item.value.length;
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
$.log.error('[MongoDB] 获取列表长度失败:', error);
|
|
1014
|
+
return 0;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* @description Redis兼容API - 集合批量添加
|
|
1020
|
+
* @param {String} key 键名
|
|
1021
|
+
* @param {Array} members 成员数组
|
|
1022
|
+
* @return {Promise<Number>} 添加的成员数量
|
|
1023
|
+
*/
|
|
1024
|
+
async saddMulti(key, members) {
|
|
1025
|
+
try {
|
|
1026
|
+
await this._ensureConnected();
|
|
1027
|
+
const item = await this.findOne({ key: key });
|
|
1028
|
+
let set = item && Array.isArray(item.value) ? [...new Set(item.value)] : [];
|
|
1029
|
+
let addedCount = 0;
|
|
1030
|
+
|
|
1031
|
+
members.forEach(member => {
|
|
1032
|
+
if (!set.some(existing => JSON.stringify(existing) === JSON.stringify(member))) {
|
|
1033
|
+
set.push(member);
|
|
1034
|
+
addedCount++;
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
|
|
1038
|
+
if (addedCount > 0) {
|
|
1039
|
+
await this._add(key, set);
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
return addedCount;
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
$.log.error('[MongoDB] 集合并发添加失败:', error);
|
|
1045
|
+
return 0;
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/**
|
|
1050
|
+
* @description Redis兼容API - 集合批量删除
|
|
1051
|
+
* @param {String} key 键名
|
|
1052
|
+
* @param {Array} members 成员数组
|
|
1053
|
+
* @return {Promise<Number>} 删除的成员数量
|
|
1054
|
+
*/
|
|
1055
|
+
async sremMulti(key, members) {
|
|
1056
|
+
try {
|
|
1057
|
+
await this._ensureConnected();
|
|
1058
|
+
const item = await this.findOne({ key: key });
|
|
1059
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1060
|
+
return 0;
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
let set = [...new Set(item.value)];
|
|
1064
|
+
const originalLength = set.length;
|
|
1065
|
+
|
|
1066
|
+
// 创建要删除的值的字符串表示集合用于快速查找
|
|
1067
|
+
const membersToRemove = new Set(members.map(m => JSON.stringify(m)));
|
|
1068
|
+
set = set.filter(member => !membersToRemove.has(JSON.stringify(member)));
|
|
1069
|
+
|
|
1070
|
+
const removedCount = originalLength - set.length;
|
|
1071
|
+
if (removedCount > 0) {
|
|
1072
|
+
await this._add(key, set);
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
return removedCount;
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
$.log.error('[MongoDB] 集合并发删除失败:', error);
|
|
1078
|
+
return 0;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/**
|
|
1083
|
+
* @description Redis兼容API - 集合差集
|
|
1084
|
+
* @param {String} key1 第一个集合键
|
|
1085
|
+
* @param {String} key2 第二个集合键
|
|
1086
|
+
* @return {Promise<Array>} 差集数组
|
|
1087
|
+
*/
|
|
1088
|
+
async sdiff(key1, key2) {
|
|
1089
|
+
try {
|
|
1090
|
+
await this._ensureConnected();
|
|
1091
|
+
const item1 = await this.findOne({ key: key1 });
|
|
1092
|
+
const item2 = await this.findOne({ key: key2 });
|
|
1093
|
+
|
|
1094
|
+
const set1 = item1 && Array.isArray(item1.value) ? [...new Set(item1.value)] : [];
|
|
1095
|
+
const set2 = item2 && Array.isArray(item2.value) ? [...new Set(item2.value)] : [];
|
|
1096
|
+
|
|
1097
|
+
// 创建第二个集合值的字符串表示用于快速查找
|
|
1098
|
+
const set2Str = new Set(set2.map(m => JSON.stringify(m)));
|
|
1099
|
+
|
|
1100
|
+
// 找出在set1中但不在set2中的元素
|
|
1101
|
+
return set1.filter(member => !set2Str.has(JSON.stringify(member)));
|
|
1102
|
+
} catch (error) {
|
|
1103
|
+
$.log.error('[MongoDB] 集合差集失败:', error);
|
|
1104
|
+
return [];
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* @description Redis兼容API - 集合交集
|
|
1110
|
+
* @param {String} key1 第一个集合键
|
|
1111
|
+
* @param {String} key2 第二个集合键
|
|
1112
|
+
* @return {Promise<Array>} 交集数组
|
|
1113
|
+
*/
|
|
1114
|
+
async sinter(key1, key2) {
|
|
1115
|
+
try {
|
|
1116
|
+
await this._ensureConnected();
|
|
1117
|
+
const item1 = await this.findOne({ key: key1 });
|
|
1118
|
+
const item2 = await this.findOne({ key: key2 });
|
|
1119
|
+
|
|
1120
|
+
const set1 = item1 && Array.isArray(item1.value) ? [...new Set(item1.value)] : [];
|
|
1121
|
+
const set2 = item2 && Array.isArray(item2.value) ? [...new Set(item2.value)] : [];
|
|
1122
|
+
|
|
1123
|
+
// 创建较小集合的字符串表示用于快速查找,优化性能
|
|
1124
|
+
let smallerSet, largerSet;
|
|
1125
|
+
if (set1.length <= set2.length) {
|
|
1126
|
+
smallerSet = set1;
|
|
1127
|
+
largerSet = set2;
|
|
1128
|
+
} else {
|
|
1129
|
+
smallerSet = set2;
|
|
1130
|
+
largerSet = set1;
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
const largerSetStr = new Set(largerSet.map(m => JSON.stringify(m)));
|
|
1134
|
+
|
|
1135
|
+
// 找出同时存在于两个集合中的元素
|
|
1136
|
+
return smallerSet.filter(member => largerSetStr.has(JSON.stringify(member)));
|
|
1137
|
+
} catch (error) {
|
|
1138
|
+
$.log.error('[MongoDB] 集合并集失败:', error);
|
|
1139
|
+
return [];
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* @description Redis兼容API - 集合并集
|
|
1145
|
+
* @param {String} key1 第一个集合键
|
|
1146
|
+
* @param {String} key2 第二个集合键
|
|
1147
|
+
* @return {Promise<Array>} 并集数组
|
|
1148
|
+
*/
|
|
1149
|
+
async sunion(key1, key2) {
|
|
1150
|
+
try {
|
|
1151
|
+
await this._ensureConnected();
|
|
1152
|
+
const item1 = await this.findOne({ key: key1 });
|
|
1153
|
+
const item2 = await this.findOne({ key: key2 });
|
|
1154
|
+
|
|
1155
|
+
const set1 = item1 && Array.isArray(item1.value) ? item1.value : [];
|
|
1156
|
+
const set2 = item2 && Array.isArray(item2.value) ? item2.value : [];
|
|
1157
|
+
|
|
1158
|
+
// 合并并去重
|
|
1159
|
+
const combined = [...set1, ...set2];
|
|
1160
|
+
// 使用Map进行对象去重,基于JSON字符串表示
|
|
1161
|
+
const uniqueMap = new Map();
|
|
1162
|
+
|
|
1163
|
+
combined.forEach(item => {
|
|
1164
|
+
const key = JSON.stringify(item);
|
|
1165
|
+
if (!uniqueMap.has(key)) {
|
|
1166
|
+
uniqueMap.set(key, item);
|
|
1167
|
+
}
|
|
1168
|
+
});
|
|
1169
|
+
|
|
1170
|
+
return Array.from(uniqueMap.values());
|
|
1171
|
+
} catch (error) {
|
|
1172
|
+
$.log.error('[MongoDB] 集合并集失败:', error);
|
|
1173
|
+
return [];
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* @description Redis兼容API - 集合成员数量
|
|
1179
|
+
* @param {String} key 键名
|
|
1180
|
+
* @return {Promise<Number>} 成员数量
|
|
1181
|
+
*/
|
|
1182
|
+
async scard(key) {
|
|
1183
|
+
try {
|
|
1184
|
+
await this._ensureConnected();
|
|
1185
|
+
const item = await this.findOne({ key: key });
|
|
1186
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1187
|
+
return 0;
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
// 使用Set去重并计算大小
|
|
1191
|
+
const uniqueItems = new Set(item.value.map(m => JSON.stringify(m)));
|
|
1192
|
+
return uniqueItems.size;
|
|
1193
|
+
} catch (error) {
|
|
1194
|
+
$.log.error('[MongoDB] 获取集合成员数量失败:', error);
|
|
1195
|
+
return 0;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
/**
|
|
1200
|
+
* @description Redis兼容API - 检查是否是集合成员
|
|
1201
|
+
* @param {String} key 键名
|
|
1202
|
+
* @param {*} member 成员
|
|
1203
|
+
* @return {Promise<Boolean>} 是否是成员
|
|
1204
|
+
*/
|
|
1205
|
+
async sismember(key, member) {
|
|
1206
|
+
try {
|
|
1207
|
+
await this._ensureConnected();
|
|
1208
|
+
const item = await this.findOne({ key: key });
|
|
1209
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1210
|
+
return false;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
// 检查是否存在相同的成员
|
|
1214
|
+
const memberStr = JSON.stringify(member);
|
|
1215
|
+
return item.value.some(m => JSON.stringify(m) === memberStr);
|
|
1216
|
+
} catch (error) {
|
|
1217
|
+
$.log.error('[MongoDB] 检查集合成员失败:', error);
|
|
1218
|
+
return false;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
/**
|
|
1223
|
+
* @description Redis兼容API - 批量获取
|
|
1224
|
+
* @param {...String} keys 键名列表
|
|
1225
|
+
* @return {Promise<Array>} 与键顺序对应的值数组,不存在返回null
|
|
1226
|
+
*/
|
|
1227
|
+
async mget(...keys) {
|
|
1228
|
+
try {
|
|
1229
|
+
await this._ensureConnected();
|
|
1230
|
+
// 确保keys是数组
|
|
1231
|
+
const keyArray = Array.isArray(keys[0]) ? keys[0] : keys;
|
|
1232
|
+
const results = await this.find({ key: { $in: keyArray } });
|
|
1233
|
+
const resultMap = {};
|
|
1234
|
+
results.forEach(item => {
|
|
1235
|
+
resultMap[item.key] = item.value;
|
|
1236
|
+
});
|
|
1237
|
+
|
|
1238
|
+
// 返回与输入键顺序对应的数组,不存在的键对应位置返回null
|
|
1239
|
+
return keyArray.map(key => resultMap.hasOwnProperty(key) ? resultMap[key] : null);
|
|
1240
|
+
} catch (error) {
|
|
1241
|
+
$.log.error('[MongoDB] 批量获取失败:', error);
|
|
1242
|
+
return [];
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
/**
|
|
1247
|
+
* @description Redis兼容API - 批量设置
|
|
1248
|
+
* @param {Object} keyValues 键值对象
|
|
1249
|
+
* @return {Promise<Boolean>} 是否成功
|
|
1250
|
+
*/
|
|
1251
|
+
async mset(keyValues) {
|
|
1252
|
+
try {
|
|
1253
|
+
await this._ensureConnected();
|
|
1254
|
+
for (const [key, value] of Object.entries(keyValues)) {
|
|
1255
|
+
await this._add(key, value);
|
|
1256
|
+
}
|
|
1257
|
+
return true;
|
|
1258
|
+
} catch (error) {
|
|
1259
|
+
$.log.error('[MongoDB] 批量设置失败:', error);
|
|
1260
|
+
return false;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* @description Redis兼容API - 有序集合添加成员
|
|
1266
|
+
* @param {String} key 键名
|
|
1267
|
+
* @param {Number} score 分数
|
|
1268
|
+
* @param {*} member 成员
|
|
1269
|
+
* @return {Promise<Boolean>} 是否成功
|
|
1270
|
+
*/
|
|
1271
|
+
async zadd(key, score, member) {
|
|
1272
|
+
try {
|
|
1273
|
+
await this._ensureConnected();
|
|
1274
|
+
const item = await this.findOne({ key: key });
|
|
1275
|
+
let sortedSet = item && Array.isArray(item.value) ? item.value : [];
|
|
1276
|
+
|
|
1277
|
+
// 检查成员是否已存在,如果存在则更新分数
|
|
1278
|
+
const memberStr = JSON.stringify(member);
|
|
1279
|
+
const existingIndex = sortedSet.findIndex(item => JSON.stringify(item.member) === memberStr);
|
|
1280
|
+
|
|
1281
|
+
if (existingIndex >= 0) {
|
|
1282
|
+
sortedSet[existingIndex].score = score;
|
|
1283
|
+
} else {
|
|
1284
|
+
sortedSet.push({ score, member });
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
// 按分数排序
|
|
1288
|
+
sortedSet.sort((a, b) => a.score - b.score);
|
|
1289
|
+
await this._add(key, sortedSet);
|
|
1290
|
+
return true;
|
|
1291
|
+
} catch (error) {
|
|
1292
|
+
$.log.error('[MongoDB] 有序集合添加成员失败:', error);
|
|
1293
|
+
return false;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
/**
|
|
1298
|
+
* @description Redis兼容API - 有序集合范围查询(按分数)
|
|
1299
|
+
* @param {String} key 键名
|
|
1300
|
+
* @param {Number} min 最小分数
|
|
1301
|
+
* @param {Number} max 最大分数
|
|
1302
|
+
* @return {Promise<Array>} 成员数组
|
|
1303
|
+
*/
|
|
1304
|
+
async zrangebyscore(key, min, max) {
|
|
1305
|
+
try {
|
|
1306
|
+
await this._ensureConnected();
|
|
1307
|
+
const item = await this.findOne({ key: key });
|
|
1308
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1309
|
+
return [];
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
// 过滤分数范围内的成员
|
|
1313
|
+
return item.value
|
|
1314
|
+
.filter(item => item.score >= min && item.score <= max)
|
|
1315
|
+
.map(item => item.member);
|
|
1316
|
+
} catch (error) {
|
|
1317
|
+
$.log.error('[MongoDB] 有序集合范围查询失败:', error);
|
|
1318
|
+
return [];
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
/**
|
|
1323
|
+
* @description Redis兼容API - 有序集合成员数量
|
|
1324
|
+
* @param {String} key 键名
|
|
1325
|
+
* @return {Promise<Number>} 成员数量
|
|
1326
|
+
*/
|
|
1327
|
+
async zcard(key) {
|
|
1328
|
+
try {
|
|
1329
|
+
await this._ensureConnected();
|
|
1330
|
+
const item = await this.findOne({ key: key });
|
|
1331
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1332
|
+
return 0;
|
|
1333
|
+
}
|
|
1334
|
+
return item.value.length;
|
|
1335
|
+
} catch (error) {
|
|
1336
|
+
$.log.error('[MongoDB] 有序集合成员数量失败:', error);
|
|
1337
|
+
return 0;
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
/**
|
|
1342
|
+
* @description Redis兼容API - 有序集合删除成员
|
|
1343
|
+
* @param {String} key 键名
|
|
1344
|
+
* @param {*} member 成员
|
|
1345
|
+
* @return {Promise<Number>} 删除的成员数量
|
|
1346
|
+
*/
|
|
1347
|
+
async zrem(key, member) {
|
|
1348
|
+
try {
|
|
1349
|
+
await this._ensureConnected();
|
|
1350
|
+
const item = await this.findOne({ key: key });
|
|
1351
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1352
|
+
return 0;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
const memberStr = JSON.stringify(member);
|
|
1356
|
+
const filtered = item.value.filter(item => JSON.stringify(item.member) !== memberStr);
|
|
1357
|
+
const removedCount = item.value.length - filtered.length;
|
|
1358
|
+
|
|
1359
|
+
if (removedCount > 0) {
|
|
1360
|
+
await this._add(key, filtered);
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
return removedCount;
|
|
1364
|
+
} catch (error) {
|
|
1365
|
+
$.log.error('[MongoDB] 有序集合删除成员失败:', error);
|
|
1366
|
+
return 0;
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
/**
|
|
1371
|
+
* @description Redis兼容API - 获取成员分数
|
|
1372
|
+
* @param {String} key 键名
|
|
1373
|
+
* @param {*} member 成员
|
|
1374
|
+
* @return {Promise<Number|null>} 分数
|
|
1375
|
+
*/
|
|
1376
|
+
async zscore(key, member) {
|
|
1377
|
+
try {
|
|
1378
|
+
await this._ensureConnected();
|
|
1379
|
+
const item = await this.findOne({ key: key });
|
|
1380
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1381
|
+
return null;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
const memberStr = JSON.stringify(member);
|
|
1385
|
+
const found = item.value.find(item => JSON.stringify(item.member) === memberStr);
|
|
1386
|
+
return found ? found.score : null;
|
|
1387
|
+
} catch (error) {
|
|
1388
|
+
$.log.error('[MongoDB] 获取成员分数失败:', error);
|
|
1389
|
+
return null;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* @description Redis兼容API - 列表弹出头部元素
|
|
1395
|
+
* @param {String} key 键名
|
|
1396
|
+
* @return {Promise<any|null>} 弹出的元素
|
|
1397
|
+
*/
|
|
1398
|
+
async lpop(key) {
|
|
1399
|
+
try {
|
|
1400
|
+
await this._ensureConnected();
|
|
1401
|
+
const item = await this.findOne({ key: key });
|
|
1402
|
+
if (!item || !Array.isArray(item.value) || item.value.length === 0) {
|
|
1403
|
+
return null;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
const popped = item.value.shift();
|
|
1407
|
+
await this._set({ key: key }, { value: item.value });
|
|
1408
|
+
return popped;
|
|
1409
|
+
} catch (error) {
|
|
1410
|
+
$.log.error('[MongoDB] 列表弹出头部元素失败:', error);
|
|
1411
|
+
return null;
|
|
1412
|
+
}
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
/**
|
|
1416
|
+
* @description Redis兼容API - 列表弹出尾部元素
|
|
1417
|
+
* @param {String} key 键名
|
|
1418
|
+
* @return {Promise<any|null>} 弹出的元素
|
|
1419
|
+
*/
|
|
1420
|
+
async rpop(key) {
|
|
1421
|
+
try {
|
|
1422
|
+
await this._ensureConnected();
|
|
1423
|
+
const item = await this.findOne({ key: key });
|
|
1424
|
+
if (!item || !Array.isArray(item.value) || item.value.length === 0) {
|
|
1425
|
+
return null;
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
const popped = item.value.pop();
|
|
1429
|
+
await this._set({ key: key }, { value: item.value });
|
|
1430
|
+
return popped;
|
|
1431
|
+
} catch (error) {
|
|
1432
|
+
$.log.error('[MongoDB] 列表弹出尾部元素失败:', error);
|
|
1433
|
+
return null;
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/**
|
|
1438
|
+
* @description Redis兼容API - 获取集合所有成员
|
|
1439
|
+
* @param {String} key 键名
|
|
1440
|
+
* @return {Promise<Array>} 成员数组
|
|
1441
|
+
*/
|
|
1442
|
+
async smembers(key) {
|
|
1443
|
+
try {
|
|
1444
|
+
await this._ensureConnected();
|
|
1445
|
+
const item = await this.findOne({ key: key });
|
|
1446
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1447
|
+
return [];
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// 去重并返回
|
|
1451
|
+
const uniqueMap = new Map();
|
|
1452
|
+
item.value.forEach(member => {
|
|
1453
|
+
const keyStr = JSON.stringify(member);
|
|
1454
|
+
if (!uniqueMap.has(keyStr)) {
|
|
1455
|
+
uniqueMap.set(keyStr, member);
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
1458
|
+
return Array.from(uniqueMap.values());
|
|
1459
|
+
} catch (error) {
|
|
1460
|
+
$.log.error('[MongoDB] 获取集合所有成员失败:', error);
|
|
1461
|
+
return [];
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
157
1464
|
}
|
|
158
1465
|
|
|
159
1466
|
/**
|
|
@@ -237,11 +1544,12 @@ MongoDB.prototype.close = async function () {
|
|
|
237
1544
|
};
|
|
238
1545
|
|
|
239
1546
|
/**
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
MongoDB.prototype.addTable = function (table) {
|
|
1547
|
+
* @description 创建数据表
|
|
1548
|
+
* @param {Object} table 表名
|
|
1549
|
+
* @return {Promise|Object} 执行结果
|
|
1550
|
+
*/
|
|
1551
|
+
MongoDB.prototype.addTable = async function (table) {
|
|
1552
|
+
await this._ensureConnected();
|
|
245
1553
|
if (table) {
|
|
246
1554
|
this.table = table;
|
|
247
1555
|
} else {
|
|
@@ -250,28 +1558,6 @@ MongoDB.prototype.addTable = function (table) {
|
|
|
250
1558
|
return this.db.createCollection(table);
|
|
251
1559
|
};
|
|
252
1560
|
|
|
253
|
-
/**
|
|
254
|
-
* @description 创建TTL索引
|
|
255
|
-
* @param {String} table 表名
|
|
256
|
-
* @param {String} field 过期字段名
|
|
257
|
-
* @param {Number} expireAfterSeconds 过期时间(秒)
|
|
258
|
-
* @return {Promise<Object>} 执行结果
|
|
259
|
-
*/
|
|
260
|
-
MongoDB.prototype.createTTLIndex = async function (table, field, expireAfterSeconds) {
|
|
261
|
-
if (!table) table = this.table;
|
|
262
|
-
return await this.db.collection(table).createIndex(
|
|
263
|
-
{ [field]: 1 },
|
|
264
|
-
{ expireAfterSeconds: expireAfterSeconds || 0 }
|
|
265
|
-
);
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* @description 创建普通索引
|
|
270
|
-
* @param {String} table 表名
|
|
271
|
-
* @param {Object} keys 索引字段对象
|
|
272
|
-
* @param {Object} options 索引选项
|
|
273
|
-
* @return {Promise<Object>} 执行结果
|
|
274
|
-
*/
|
|
275
1561
|
/**
|
|
276
1562
|
* @description 创建索引
|
|
277
1563
|
* @param {Object} keys 索引键配置 {field: 1或-1}
|
|
@@ -279,6 +1565,7 @@ MongoDB.prototype.createTTLIndex = async function (table, field, expireAfterSeco
|
|
|
279
1565
|
* @returns {Promise<Object>} 创建索引的结果
|
|
280
1566
|
*/
|
|
281
1567
|
MongoDB.prototype.createIndex = async function (keys, options = {}) {
|
|
1568
|
+
await this._ensureConnected();
|
|
282
1569
|
try {
|
|
283
1570
|
if (!this.db || !this.table) {
|
|
284
1571
|
throw new Error('数据库未连接或未指定表名');
|
|
@@ -300,6 +1587,7 @@ MongoDB.prototype.createIndex = async function (keys, options = {}) {
|
|
|
300
1587
|
* @returns {Promise<Object>} 创建索引的结果
|
|
301
1588
|
*/
|
|
302
1589
|
MongoDB.prototype.createTTLIndex = async function (keys, expireAfterSeconds) {
|
|
1590
|
+
await this._ensureConnected();
|
|
303
1591
|
try {
|
|
304
1592
|
if (!this.db || !this.table) {
|
|
305
1593
|
throw new Error('数据库未连接或未指定表名');
|
|
@@ -320,13 +1608,14 @@ MongoDB.prototype.createTTLIndex = async function (keys, expireAfterSeconds) {
|
|
|
320
1608
|
};
|
|
321
1609
|
|
|
322
1610
|
/**
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
1611
|
+
* @description 获取集合的所有索引
|
|
1612
|
+
* @returns {Promise<Array>} 索引信息数组
|
|
1613
|
+
*/
|
|
326
1614
|
MongoDB.prototype.getIndexes = async function () {
|
|
1615
|
+
await this._ensureConnected();
|
|
327
1616
|
try {
|
|
328
|
-
if (!this.
|
|
329
|
-
throw new Error('
|
|
1617
|
+
if (!this.table) {
|
|
1618
|
+
throw new Error('未指定表名');
|
|
330
1619
|
}
|
|
331
1620
|
|
|
332
1621
|
$.log.info(`[MongoDB] 获取索引: ${this.table}`);
|
|
@@ -343,9 +1632,7 @@ MongoDB.prototype.getIndexes = async function () {
|
|
|
343
1632
|
* @returns {Promise<Boolean>} 是否删除成功
|
|
344
1633
|
*/
|
|
345
1634
|
MongoDB.prototype.dropIndex = async function (indexName) {
|
|
346
|
-
|
|
347
|
-
throw new Error('数据库未连接');
|
|
348
|
-
}
|
|
1635
|
+
await this._ensureConnected();
|
|
349
1636
|
if (!this.table) {
|
|
350
1637
|
throw new Error('未指定表名');
|
|
351
1638
|
}
|
|
@@ -367,9 +1654,7 @@ MongoDB.prototype.dropIndex = async function (indexName) {
|
|
|
367
1654
|
* @returns {Promise<Boolean>} 是否删除成功
|
|
368
1655
|
*/
|
|
369
1656
|
MongoDB.prototype.dropTable = async function () {
|
|
370
|
-
|
|
371
|
-
throw new Error('数据库未连接');
|
|
372
|
-
}
|
|
1657
|
+
await this._ensureConnected();
|
|
373
1658
|
if (!this.table) {
|
|
374
1659
|
throw new Error('未指定表名');
|
|
375
1660
|
}
|
|
@@ -388,103 +1673,230 @@ MongoDB.prototype.dropTable = async function () {
|
|
|
388
1673
|
* @param {Object} obj 对象
|
|
389
1674
|
* @return {Promise|Object} 执行结果
|
|
390
1675
|
*/
|
|
391
|
-
MongoDB.prototype.addObj = async function(obj) {
|
|
1676
|
+
MongoDB.prototype.addObj = async function (obj) {
|
|
1677
|
+
await this._ensureConnected();
|
|
392
1678
|
try {
|
|
393
1679
|
// 触发前置事件
|
|
394
|
-
this.emit('
|
|
395
|
-
|
|
1680
|
+
this.emit('addObj_before', { table: this.table, data: obj });
|
|
1681
|
+
|
|
396
1682
|
// 执行插入操作
|
|
397
1683
|
const result = await this.db.collection(this.table).insertOne(obj);
|
|
398
|
-
|
|
1684
|
+
|
|
399
1685
|
// 触发后置事件
|
|
400
|
-
this.emit('
|
|
401
|
-
|
|
1686
|
+
this.emit('addObj_after', { table: this.table, data: obj, result });
|
|
1687
|
+
|
|
402
1688
|
return result;
|
|
403
1689
|
} catch (error) {
|
|
404
1690
|
// 触发错误事件
|
|
405
|
-
this.emit('
|
|
1691
|
+
this.emit('addObj_error', { table: this.table, data: obj, error });
|
|
406
1692
|
throw error;
|
|
407
1693
|
}
|
|
408
1694
|
};
|
|
409
1695
|
|
|
1696
|
+
/**
|
|
1697
|
+
* @description Redis兼容API - 根据键获取值
|
|
1698
|
+
* @param {String} key 键名
|
|
1699
|
+
* @return {Promise<any|null>} 存储的值,如果不存在则返回null
|
|
1700
|
+
*/
|
|
1701
|
+
MongoDB.prototype.get = async function (key) {
|
|
1702
|
+
await this._ensureConnected();
|
|
1703
|
+
try {
|
|
1704
|
+
const result = await this.db.collection(this.table).findOne({ key: key });
|
|
1705
|
+
return result ? result.value : null;
|
|
1706
|
+
} catch (error) {
|
|
1707
|
+
$.log.error(`[MongoDB] 获取键值失败: ${error.message}`);
|
|
1708
|
+
return null;
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
|
|
1712
|
+
/**
|
|
1713
|
+
* @description Redis兼容API - 删除键
|
|
1714
|
+
* @param {String} key 键名
|
|
1715
|
+
* @return {Promise<Boolean>} 是否删除成功
|
|
1716
|
+
*/
|
|
1717
|
+
MongoDB.prototype.del = async function (key) {
|
|
1718
|
+
await this._ensureConnected();
|
|
1719
|
+
try {
|
|
1720
|
+
// 触发前置事件
|
|
1721
|
+
this.emit('del_before', { table: this.table, key: key });
|
|
1722
|
+
|
|
1723
|
+
const result = await this.db.collection(this.table).deleteOne({ key: key });
|
|
1724
|
+
|
|
1725
|
+
// 触发后置事件
|
|
1726
|
+
this.emit('del_after', { table: this.table, key: key, result });
|
|
1727
|
+
|
|
1728
|
+
return result.deletedCount > 0;
|
|
1729
|
+
} catch (error) {
|
|
1730
|
+
// 触发错误事件
|
|
1731
|
+
this.emit('del_error', { table: this.table, key: key, error });
|
|
1732
|
+
$.log.error(`[MongoDB] 删除键失败: ${error.message}`);
|
|
1733
|
+
return false;
|
|
1734
|
+
}
|
|
1735
|
+
};
|
|
1736
|
+
|
|
1737
|
+
/**
|
|
1738
|
+
* @description Redis兼容API - 添加键值对
|
|
1739
|
+
* @param {String} key 键名
|
|
1740
|
+
* @param {any} value 值
|
|
1741
|
+
* @return {Promise<Boolean>} 是否添加成功(如果键已存在则返回false)
|
|
1742
|
+
*/
|
|
1743
|
+
MongoDB.prototype.add = async function (key, value) {
|
|
1744
|
+
await this._ensureConnected();
|
|
1745
|
+
try {
|
|
1746
|
+
// 检查键是否已存在
|
|
1747
|
+
const exists = await this.db.collection(this.table).findOne({ key: key });
|
|
1748
|
+
if (exists) {
|
|
1749
|
+
return false;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
const data = { key: key, value: value };
|
|
1753
|
+
|
|
1754
|
+
// 触发前置事件
|
|
1755
|
+
this.emit('add_before', { table: this.table, key: key, value: value });
|
|
1756
|
+
|
|
1757
|
+
// 添加键值对
|
|
1758
|
+
const result = await this.db.collection(this.table).insertOne(data);
|
|
1759
|
+
|
|
1760
|
+
// 触发后置事件
|
|
1761
|
+
this.emit('add_after', { table: this.table, key: key, value: value, result });
|
|
1762
|
+
|
|
1763
|
+
return result.acknowledged;
|
|
1764
|
+
} catch (error) {
|
|
1765
|
+
// 触发错误事件
|
|
1766
|
+
this.emit('add_error', { table: this.table, key: key, value: value, error });
|
|
1767
|
+
$.log.error(`[MongoDB] 添加键值失败: ${error.message}`);
|
|
1768
|
+
return false;
|
|
1769
|
+
}
|
|
1770
|
+
};
|
|
1771
|
+
|
|
1772
|
+
/**
|
|
1773
|
+
* @description Redis兼容API - 设置键值对
|
|
1774
|
+
* @param {String} key 键名
|
|
1775
|
+
* @param {any} value 值
|
|
1776
|
+
* @return {Promise<Boolean>} 是否设置成功
|
|
1777
|
+
*/
|
|
1778
|
+
MongoDB.prototype.set = async function (key, value) {
|
|
1779
|
+
await this._ensureConnected();
|
|
1780
|
+
try {
|
|
1781
|
+
// 触发前置事件
|
|
1782
|
+
this.emit('set_before', { table: this.table, key: key, value: value });
|
|
1783
|
+
|
|
1784
|
+
// 更新或插入键值对
|
|
1785
|
+
const result = await this.db.collection(this.table).updateOne(
|
|
1786
|
+
{ key: key },
|
|
1787
|
+
{ $set: { value: value } },
|
|
1788
|
+
{ upsert: true }
|
|
1789
|
+
);
|
|
1790
|
+
|
|
1791
|
+
// 触发后置事件
|
|
1792
|
+
this.emit('set_after', { table: this.table, key: key, value: value, result });
|
|
1793
|
+
|
|
1794
|
+
return result.acknowledged;
|
|
1795
|
+
} catch (error) {
|
|
1796
|
+
// 触发错误事件
|
|
1797
|
+
this.emit('set_error', { table: this.table, key: key, value: value, error });
|
|
1798
|
+
$.log.error(`[MongoDB] 设置键值失败: ${error.message}`);
|
|
1799
|
+
return false;
|
|
1800
|
+
}
|
|
1801
|
+
};
|
|
1802
|
+
|
|
410
1803
|
/**
|
|
411
1804
|
* @description 增加多条缓存
|
|
412
1805
|
* @param {Array} list 对象数组
|
|
413
1806
|
* @return {Promise|Object} 执行结果
|
|
414
1807
|
*/
|
|
415
|
-
MongoDB.prototype.addList = async function(list) {
|
|
1808
|
+
MongoDB.prototype.addList = async function (list) {
|
|
1809
|
+
await this._ensureConnected();
|
|
416
1810
|
try {
|
|
417
1811
|
// 触发前置事件
|
|
418
|
-
this.emit('
|
|
419
|
-
|
|
1812
|
+
this.emit('addList_before', { table: this.table, data: list });
|
|
1813
|
+
|
|
420
1814
|
// 执行插入操作
|
|
421
1815
|
const result = await this.db.collection(this.table).insertMany(list);
|
|
422
|
-
|
|
1816
|
+
|
|
423
1817
|
// 触发后置事件
|
|
424
|
-
this.emit('
|
|
425
|
-
|
|
1818
|
+
this.emit('addList_after', { table: this.table, data: list, result });
|
|
1819
|
+
|
|
426
1820
|
return result;
|
|
427
1821
|
} catch (error) {
|
|
428
1822
|
// 触发错误事件
|
|
429
|
-
this.emit('
|
|
1823
|
+
this.emit('addList_error', { table: this.table, data: list, error });
|
|
430
1824
|
throw error;
|
|
431
1825
|
}
|
|
432
1826
|
};
|
|
433
1827
|
|
|
434
1828
|
/**
|
|
435
1829
|
* @description 删除缓存
|
|
436
|
-
* @param {Object} query
|
|
1830
|
+
* @param {Object|String} query 查询条件对象或键名
|
|
437
1831
|
* @return {Promise|Object} 执行结果
|
|
438
1832
|
*/
|
|
439
|
-
MongoDB.prototype.
|
|
1833
|
+
MongoDB.prototype.delete = async function (query) {
|
|
1834
|
+
await this._ensureConnected();
|
|
440
1835
|
try {
|
|
1836
|
+
// 如果query是字符串,则作为键名处理
|
|
1837
|
+
const safeQuery = typeof query === 'string' ? { key: query } : query;
|
|
1838
|
+
|
|
441
1839
|
// 触发前置事件
|
|
442
|
-
this.emit('
|
|
443
|
-
|
|
1840
|
+
this.emit('delete_before', { table: this.table, query: safeQuery });
|
|
1841
|
+
|
|
444
1842
|
// 执行删除操作
|
|
445
|
-
const result = await this.db.collection(this.table).deleteMany(
|
|
446
|
-
|
|
1843
|
+
const result = await this.db.collection(this.table).deleteMany(safeQuery);
|
|
1844
|
+
|
|
447
1845
|
// 触发后置事件
|
|
448
|
-
this.emit('
|
|
449
|
-
|
|
1846
|
+
this.emit('delete_after', { table: this.table, query: safeQuery, result });
|
|
1847
|
+
|
|
450
1848
|
return result;
|
|
451
1849
|
} catch (error) {
|
|
452
1850
|
// 触发错误事件
|
|
453
|
-
this.emit('
|
|
1851
|
+
this.emit('delete_error', { table: this.table, query, error });
|
|
454
1852
|
throw error;
|
|
455
1853
|
}
|
|
456
1854
|
};
|
|
457
1855
|
|
|
458
1856
|
/**
|
|
459
1857
|
* @description 修改缓存
|
|
460
|
-
* @param {Object} query
|
|
1858
|
+
* @param {Object|String} query 查询条件对象或键名
|
|
461
1859
|
* @param {Object} body 修改对象
|
|
462
1860
|
* @param {Object} options 附加选项
|
|
463
1861
|
* @return {Promise|Object} 执行结果
|
|
464
1862
|
*/
|
|
465
|
-
MongoDB.prototype.
|
|
1863
|
+
MongoDB.prototype.setting = async function (query, body, options = {}) {
|
|
1864
|
+
await this._ensureConnected();
|
|
466
1865
|
try {
|
|
1866
|
+
// 处理Redis兼容模式:如果query是字符串,则将query作为键名
|
|
1867
|
+
const safeQuery = typeof query === 'string' ? { key: query } : query;
|
|
1868
|
+
|
|
467
1869
|
// 默认使用$set操作符进行部分更新
|
|
468
|
-
|
|
469
|
-
|
|
1870
|
+
let updateBody;
|
|
1871
|
+
if (typeof query === 'string') {
|
|
1872
|
+
// Redis兼容模式:如果是字符串键名,直接设置value
|
|
1873
|
+
updateBody = { $set: { value: body } };
|
|
1874
|
+
} else if (!body.$set && !body.$inc && !body.$push) {
|
|
1875
|
+
updateBody = { $set: body };
|
|
1876
|
+
} else {
|
|
1877
|
+
updateBody = body;
|
|
470
1878
|
}
|
|
1879
|
+
|
|
471
1880
|
// 默认更新多条
|
|
472
1881
|
const updateOptions = Object.assign({ multi: true }, options);
|
|
473
|
-
|
|
1882
|
+
|
|
474
1883
|
// 触发前置事件
|
|
475
|
-
this.emit('
|
|
476
|
-
|
|
1884
|
+
this.emit('setting_before', { table: this.table, query: safeQuery, update: updateBody, options: updateOptions });
|
|
1885
|
+
|
|
477
1886
|
// 执行更新操作
|
|
478
|
-
const result = await this.db.collection(this.table).updateMany(
|
|
479
|
-
|
|
1887
|
+
const result = await this.db.collection(this.table).updateMany(safeQuery, updateBody, updateOptions);
|
|
1888
|
+
|
|
480
1889
|
// 触发后置事件
|
|
481
|
-
this.emit('
|
|
482
|
-
|
|
483
|
-
|
|
1890
|
+
this.emit('setting_after', { table: this.table, query: safeQuery, update: updateBody, options: updateOptions, result });
|
|
1891
|
+
|
|
1892
|
+
// Redis兼容模式返回布尔值,否则返回完整结果
|
|
1893
|
+
return typeof query === 'string' ? result.modifiedCount > 0 : result;
|
|
484
1894
|
} catch (error) {
|
|
485
1895
|
// 触发错误事件
|
|
486
|
-
this.emit('
|
|
487
|
-
|
|
1896
|
+
this.emit('setting_error', { table: this.table, query, update: body, options, error });
|
|
1897
|
+
|
|
1898
|
+
// Redis兼容模式返回布尔值
|
|
1899
|
+
return typeof query === 'string' ? false : null;
|
|
488
1900
|
}
|
|
489
1901
|
};
|
|
490
1902
|
|
|
@@ -495,6 +1907,7 @@ MongoDB.prototype.set = async function(query, body, options = {}) {
|
|
|
495
1907
|
* @return {Promise|Array} 查询结果数组
|
|
496
1908
|
*/
|
|
497
1909
|
MongoDB.prototype.find = async function (query, options = {}) {
|
|
1910
|
+
await this._ensureConnected();
|
|
498
1911
|
try {
|
|
499
1912
|
// 安全处理查询条件,避免注入
|
|
500
1913
|
const safeQuery = this._sanitizeQuery(query);
|
|
@@ -513,6 +1926,7 @@ MongoDB.prototype.find = async function (query, options = {}) {
|
|
|
513
1926
|
* @return {Promise<Object|null>} 查询结果
|
|
514
1927
|
*/
|
|
515
1928
|
MongoDB.prototype.findOne = async function (query, options = {}) {
|
|
1929
|
+
await this._ensureConnected();
|
|
516
1930
|
try {
|
|
517
1931
|
const safeQuery = this._sanitizeQuery(query);
|
|
518
1932
|
return await this.db.collection(this.table).findOne(safeQuery, options);
|
|
@@ -528,6 +1942,7 @@ MongoDB.prototype.findOne = async function (query, options = {}) {
|
|
|
528
1942
|
* @return {Promise<Number>} 文档数量
|
|
529
1943
|
*/
|
|
530
1944
|
MongoDB.prototype.count = async function (query = {}) {
|
|
1945
|
+
await this._ensureConnected();
|
|
531
1946
|
try {
|
|
532
1947
|
const safeQuery = this._sanitizeQuery(query);
|
|
533
1948
|
return await this.db.collection(this.table).countDocuments(safeQuery);
|
|
@@ -564,6 +1979,7 @@ MongoDB.prototype._sanitizeQuery = function (query) {
|
|
|
564
1979
|
* @return {Promise<Object>} 会话对象
|
|
565
1980
|
*/
|
|
566
1981
|
MongoDB.prototype.startTransaction = async function () {
|
|
1982
|
+
await this._ensureConnected();
|
|
567
1983
|
try {
|
|
568
1984
|
const session = this.conn.startSession();
|
|
569
1985
|
session.startTransaction();
|
|
@@ -637,6 +2053,9 @@ function mongoDB_admin(scope, dir) {
|
|
|
637
2053
|
return obj;
|
|
638
2054
|
}
|
|
639
2055
|
|
|
2056
|
+
|
|
2057
|
+
|
|
2058
|
+
|
|
640
2059
|
/**
|
|
641
2060
|
* @module 导出MongoDB管理器
|
|
642
2061
|
*/
|