mm_mongodb 2.0.3 → 2.0.4

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