mm_mongodb 1.4.8 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -3,36 +3,25 @@
3
3
  * @author <a href="http://qww.elins.cn">邱文武</a>
4
4
  * @version 1.2.1
5
5
  */
6
- const eventEmitter = require('events');
7
- require('mm_logs');
6
+ const {
7
+ BaseService
8
+ } = require('mm_base_service');
8
9
  const MongoClient = require('mongodb').MongoClient;
9
10
 
10
- /**
11
- * 确保全局$对象存在
12
- */
13
- if (!global.$) {
14
- global.$ = {};
15
- }
16
-
17
11
  /**
18
12
  * @description MongoDB帮助类
19
13
  */
20
- class MongoDB extends eventEmitter {
14
+ class MongoDB extends BaseService {
21
15
  /**
22
16
  * @description 创建MongoDB帮助类函数 (构造函数)
23
- * @param {String} scope 作用域
24
17
  * @param {Object} config 配置参数
25
18
  * @constructor
26
19
  */
27
- constructor(scope, config) {
20
+ constructor(config) {
28
21
  super();
29
- // 作用域
30
- this.scope = scope || ($.val && $.val.scope ? String($.val.scope) : 'default');
31
22
 
32
- // 从环境变量加载配置
33
- const envConfig = this._loadEnvConfig();
34
-
35
- this.config = Object.assign({
23
+ // 合并配置,确保使用对象扩展运算符正确合并
24
+ this.config = {
36
25
  // 服务器地址
37
26
  host: "localhost",
38
27
  // 端口号
@@ -45,16 +34,10 @@ class MongoDB extends eventEmitter {
45
34
  password: "",
46
35
  // 连接选项
47
36
  options: {
48
- // 移除已废弃的选项
49
- // 使用MongoDB驱动推荐的配置
50
- // 连接池配置
51
- minPoolSize: 5,
52
- maxPoolSize: 20,
53
- // 连接超时时间
37
+ minPoolSize: 10,
38
+ maxPoolSize: 100,
54
39
  connectTimeoutMS: 10000,
55
- // 服务器选择超时
56
40
  serverSelectionTimeoutMS: 5000,
57
- // 重试策略
58
41
  retryWrites: true
59
42
  },
60
43
  // 重试配置
@@ -62,11 +45,7 @@ class MongoDB extends eventEmitter {
62
45
  maxAttempts: 3,
63
46
  delayMS: 1000
64
47
  }
65
- }, envConfig);
66
-
67
- this.setConfig(config);
68
- // 验证配置
69
- this._validateConfig();
48
+ };
70
49
 
71
50
  // 连接地址
72
51
  this.url = null;
@@ -91,1395 +70,2589 @@ class MongoDB extends eventEmitter {
91
70
  // 连接重试次数
92
71
  this.connectAttempts = 0;
93
72
  this.maxConnectAttempts = 3;
94
- }
95
-
96
- /**
97
- * @private
98
- * @description 从环境变量加载配置
99
- * @return {Object} 配置对象
100
- */
101
- _loadEnvConfig() {
102
- const envConfig = {};
103
73
 
104
- if (process.env.MONGODB_HOST) envConfig.host = process.env.MONGODB_HOST;
105
- if (process.env.MONGODB_PORT) envConfig.port = parseInt(process.env.MONGODB_PORT, 10);
106
- if (process.env.MONGODB_DATABASE) envConfig.database = process.env.MONGODB_DATABASE;
107
- if (process.env.MONGODB_USER) envConfig.user = process.env.MONGODB_USER;
108
- if (process.env.MONGODB_PASSWORD) envConfig.password = process.env.MONGODB_PASSWORD;
109
-
110
- return envConfig;
74
+ this.setConfig(config);
75
+ // 验证配置
76
+ this._validateConfig();
111
77
  }
78
+ }
112
79
 
113
- /**
114
- * @private
115
- * @description 验证配置有效性
116
- * @throws {Error} 配置无效时抛出错误
117
- */
118
- _validateConfig() {
119
- if (!this.config.host || typeof this.config.host !== 'string') {
120
- throw new Error('MongoDB配置无效:host必须是有效的字符串');
121
- }
80
+ /**
81
+ * @private
82
+ * @description 验证配置有效性
83
+ * @throws {Error} 配置无效时抛出错误
84
+ */
85
+ MongoDB.prototype._validateConfig = function () {
86
+ if (!this.config.host || typeof this.config.host !== 'string') {
87
+ throw new Error('MongoDB配置无效:host必须是有效的字符串');
88
+ }
122
89
 
123
- if (!Number.isInteger(this.config.port) || this.config.port < 1 || this.config.port > 65535) {
124
- throw new Error('MongoDB配置无效:port必须是1-65535之间的整数');
125
- }
90
+ if (!Number.isInteger(this.config.port) || this.config.port < 1 || this.config.port > 65535) {
91
+ throw new Error('MongoDB配置无效:port必须是1-65535之间的整数');
92
+ }
126
93
 
127
- if (!this.config.database || typeof this.config.database !== 'string') {
128
- throw new Error('MongoDB配置无效:database必须是有效的字符串');
129
- }
94
+ if (!this.config.database || typeof this.config.database !== 'string') {
95
+ throw new Error('MongoDB配置无效:database必须是有效的字符串');
130
96
  }
97
+ };
131
98
 
132
- /**
133
- * @private
134
- * @description 执行带重试的操作
135
- * @param {Function} operation 要执行的操作
136
- * @param {String} operationName 操作名称
137
- * @return {Promise} 操作结果
138
- */
139
- async _withRetry(operation, operationName) {
140
- const maxAttempts = this.config.retry?.maxAttempts || 3;
141
- const delayMS = this.config.retry?.delayMS || 1000;
142
- let lastError;
99
+ /**
100
+ * @private
101
+ * @description 执行带重试的操作
102
+ * @param {Function} operation 要执行的操作
103
+ * @param {String} operationName 操作名称
104
+ * @return {Promise} 操作结果
105
+ */
106
+ MongoDB.prototype._withRetry = async function (operation, operationName) {
107
+ const maxAttempts = this.config.retry?.maxAttempts || 3;
108
+ const delayMS = this.config.retry?.delayMS || 1000;
109
+ let lastError;
143
110
 
144
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
145
- try {
146
- return await operation();
147
- } catch (error) {
148
- lastError = error;
149
- $.log.error(`[MongoDB] ${operationName} 第${attempt}次尝试失败:`, error.message);
111
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
112
+ try {
113
+ return await operation();
114
+ } catch (error) {
115
+ lastError = error;
116
+ $.log.error(`[MongoDB] ${operationName} 第${attempt}次尝试失败:`, error.message);
150
117
 
151
- // 如果不是最后一次尝试,则等待后重试
152
- if (attempt < maxAttempts) {
153
- await new Promise(resolve => setTimeout(resolve, delayMS));
154
- }
118
+ // 如果不是最后一次尝试,则等待后重试
119
+ if (attempt < maxAttempts) {
120
+ await new Promise(resolve => setTimeout(resolve, delayMS));
155
121
  }
156
122
  }
157
-
158
- // 所有重试都失败,抛出最后一个错误
159
- throw lastError;
160
123
  }
161
124
 
162
- /**
163
- * @description 确保数据库连接已建立
164
- * @private
165
- * @return {Promise<void>}
166
- */
167
- async _ensureConnected() {
168
- // 如果已有连接Promise,等待其完成
169
- if (this.connectPromise) {
170
- return this.connectPromise;
171
- }
172
-
173
- // 创建新的连接Promise
174
- this.connectPromise = (async () => {
175
- try {
176
- // 检查连接状态
177
- if (!this.db || !this.connected) {
178
- // 记录连接尝试
179
- this.connectAttempts++;
180
-
181
- // 尝试连接
182
- await this.open();
125
+ // 所有重试都失败,抛出最后一个错误
126
+ throw lastError;
127
+ };
128
+ /**
129
+ * @description 确保数据库连接已建立
130
+ * @private
131
+ * @return {Promise<void>}
132
+ */
133
+ MongoDB.prototype._ensureConnected = async function () {
134
+ // 如果已有连接Promise,等待其完成
135
+ if (this.connectPromise) {
136
+ return this.connectPromise;
137
+ }
183
138
 
184
- // 检查连接是否成功
185
- if (this.db && this.db.collection) {
186
- this.connected = true;
187
- this.connectAttempts = 0;
139
+ // 创建新的连接Promise
140
+ this.connectPromise = (async () => {
141
+ try {
142
+ // 检查连接状态
143
+ if (!this.db || !this.connected) {
144
+ // 记录连接尝试
145
+ this.connectAttempts++;
146
+
147
+ // 尝试连接
148
+ await this.open();
149
+
150
+ // 检查连接是否成功
151
+ if (this.db && this.db.collection) {
152
+ $.log.debug(`[MongoDB] [_ensureConnected] 连接成功,db对象存在,开始初始化collection,table=${this.table}`);
153
+ this.connected = true;
154
+ this.connectAttempts = 0;
155
+
156
+ // 保存collection引用到实例变量
157
+ try {
158
+ this.collection = this.db.collection(this.table);
159
+ $.log.debug(`[MongoDB] [_ensureConnected] 成功初始化collection: ${this.table}`);
160
+ } catch (err) {
161
+ $.log.error(`[MongoDB] [_ensureConnected] 初始化collection失败:`, err.message);
162
+ this.collection = null;
163
+ }
188
164
 
189
- // 确保索引已创建
165
+ // 确保索引已创建
166
+ if (this.collection) {
190
167
  await this._ensureIndexes();
191
-
192
- $.log.info(`[MongoDB] 成功连接到数据库,表名: ${this.table}`);
193
- } else {
194
- throw new Error('数据库连接失败,db对象为空');
195
168
  }
196
- }
197
- } catch (error) {
198
- $.log.error(`[MongoDB] 连接失败 (尝试 ${this.connectAttempts}/${this.maxConnectAttempts}):`, error.message);
199
-
200
- // 重置连接Promise,允许下次重试
201
- this.connectPromise = null;
202
169
 
203
- // 如果尝试次数未超过限制,抛出错误让上层处理
204
- if (this.connectAttempts < this.maxConnectAttempts) {
205
- throw error;
170
+ $.log.info(`[MongoDB] 成功连接到数据库,表名: ${this.table}`);
206
171
  } else {
207
- // 超过重试次数,记录严重错误
208
- $.log.error('[MongoDB] 达到最大连接重试次数,连接失败');
209
- throw new Error('数据库连接失败,已达到最大重试次数');
172
+ $.log.error(`[MongoDB] [_ensureConnected] 连接失败,db对象为空或不具有collection方法`);
173
+ throw new Error('数据库连接失败,db对象为空');
210
174
  }
211
175
  }
212
- })();
176
+ } catch (error) {
177
+ $.log.error(`[MongoDB] 连接失败 (尝试 ${this.connectAttempts}/${this.maxConnectAttempts}):`,
178
+ error.message);
213
179
 
214
- return this.connectPromise;
215
- }
180
+ // 重置连接Promise,允许下次重试
181
+ this.connectPromise = null;
216
182
 
217
- /**
218
- * @description 确保索引已创建
219
- * @private
220
- * @return {Promise<void>}
221
- */
222
- async _ensureIndexes() {
223
- try {
224
- // 确保db对象有效
225
- if (!this.db || !this.db.collection) {
226
- throw new Error('数据库连接无效,无法创建索引');
183
+ // 如果尝试次数未超过限制,抛出错误让上层处理
184
+ if (this.connectAttempts < this.maxConnectAttempts) {
185
+ throw error;
186
+ } else {
187
+ // 超过重试次数,记录严重错误
188
+ $.log.error('[MongoDB] 达到最大连接重试次数,连接失败');
189
+ throw new Error('数据库连接失败,已达到最大重试次数');
227
190
  }
191
+ }
192
+ })();
228
193
 
229
- // 创建TTL索引用于自动过期
230
- await this.db.collection(this.table).createIndex(
231
- { expireAt: 1 },
232
- { expireAfterSeconds: 0 }
233
- );
234
- // 创建_id索引(通常自动创建,但显式指定更清晰)
235
- await this.db.collection(this.table).createIndex({ _id: 1 });
236
- } catch (error) {
237
- // 忽略索引已存在的错误
238
- if (error.code !== 85) {
239
- $.log.error('[MongoDB] 创建索引失败:', error);
240
- }
194
+ return this.connectPromise;
195
+ };
196
+
197
+ /**
198
+ * @description 确保索引已创建
199
+ * @private
200
+ * @return {Promise<void>}
201
+ */
202
+ MongoDB.prototype._ensureIndexes = async function () {
203
+ try {
204
+ // 确保db对象有效
205
+ if (!this.db || !this.db.collection) {
206
+ throw new Error('数据库连接无效,无法创建索引');
241
207
  }
242
- }
243
208
 
244
- /**
245
- * @description Redis兼容API - 获取缓存值
246
- * @param {String} key 键名
247
- * @return {Promise<any>} 缓存值
248
- */
249
- async get(key) {
250
- try {
251
- await this._ensureConnected();
252
- const result = await this.findOne({ key: key });
253
- return result ? result.value : null;
254
- } catch (error) {
255
- $.log.error('[MongoDB] 获取缓存失败:', error);
256
- return null;
209
+ // 创建TTL索引用于自动过期
210
+ await this.db.collection(this.table).createIndex({
211
+ expire_at: 1
212
+ }, {
213
+ expireAfterSeconds: 0
214
+ });
215
+ // 创建_id索引(通常自动创建,但显式指定更清晰)
216
+ await this.db.collection(this.table).createIndex({
217
+ _id: 1
218
+ });
219
+ } catch (error) {
220
+ // 忽略索引已存在的错误
221
+ if (error.code !== 85) {
222
+ $.log.error('[MongoDB] 创建索引失败:', error);
257
223
  }
258
224
  }
225
+ };
259
226
 
260
- /**
261
- * @description Redis兼容API - 清空所有缓存
262
- * @return {Promise<Boolean>} 是否成功
263
- */
264
- async clear() {
265
- try {
266
- await this._ensureConnected();
267
- await this.del({});
268
- return true;
269
- } catch (error) {
270
- $.log.error('[MongoDB] 清空缓存失败:', error);
271
- return false;
272
- }
227
+ /**
228
+ * @description Redis兼容API - 获取缓存值
229
+ * @param {String} key 键名
230
+ * @return {Promise<any>} 返回键值,不存在返回undefined
231
+ */
232
+ MongoDB.prototype.get = async function (key) {
233
+ try {
234
+ await this._ensureConnected();
235
+ const result = await this.findOne({
236
+ key: key
237
+ });
238
+ return result ? result.value : undefined;
239
+ } catch (error) {
240
+ $.log.error('[MongoDB] 获取缓存失败:', error);
241
+ throw error;
273
242
  }
243
+ };
274
244
 
275
- /**
276
- * @description Redis兼容API - 获取所有键
277
- * @param {String} pattern 匹配模式
278
- * @return {Promise<Array>} 键名数组
279
- */
280
- async keys(pattern = '*') {
281
- try {
282
- await this._ensureConnected();
283
- let query = {};
284
- if (pattern !== '*') {
285
- // 简单的通配符支持
286
- const regexPattern = pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
287
- query.key = { $regex: new RegExp(`^${regexPattern}$`) };
288
- }
289
- const results = await this.find(query, { projection: { key: 1 } });
290
- return results.map(item => item.key);
291
- } catch (error) {
292
- $.log.error('[MongoDB] 获取键列表失败:', error);
293
- return [];
245
+ /**
246
+ * @description Redis兼容API - 清空所有缓存或匹配模式的缓存
247
+ * @param {string} pattern - 可选的键匹配模式
248
+ * @return {Promise<Boolean>} 是否成功
249
+ */
250
+ MongoDB.prototype.clear = async function (pattern) {
251
+ try {
252
+ // 触发前置事件
253
+ this.emit('clear_before', {
254
+ pattern
255
+ });
256
+
257
+ await this._ensureConnected();
258
+
259
+ if (pattern) {
260
+ // 支持通配符匹配删除
261
+ const regex = new RegExp(pattern.replace(/\*/g, '.*'));
262
+ const result = await this.delete({
263
+ key: {
264
+ $regex: regex
265
+ }
266
+ });
267
+ this.emit('clear_after', {
268
+ pattern,
269
+ result
270
+ });
271
+ return result.acknowledged;
272
+ } else {
273
+ // 清空所有数据,直接使用deleteMany操作
274
+ const result = await this.db.collection(this.table).deleteMany({});
275
+ this.emit('clear_after', {
276
+ result
277
+ });
278
+ return result.acknowledged;
294
279
  }
280
+ } catch (error) {
281
+ this.emit('clear_error', {
282
+ error,
283
+ pattern
284
+ });
285
+ $.log.error('[MongoDB] 清空缓存失败:', error);
286
+ return false;
295
287
  }
288
+ };
296
289
 
297
- /**
298
- * @description Redis兼容API - 设置过期时间
299
- * @param {String} key 键名
300
- * @param {Number} seconds 过期时间(秒)
301
- * @return {Promise<Boolean>} 是否成功
302
- */
303
- async ttl(key, seconds) {
304
- try {
305
- await this._ensureConnected();
306
- if (!seconds && seconds !== 0) {
307
- // 获取剩余过期时间
308
- const item = await this.findOne({ key: key });
309
- if (!item || !item.expireAt) return -1;
310
- const ttl = Math.floor((item.expireAt.getTime() - Date.now()) / 1000);
311
- return ttl > 0 ? ttl : -2; // -2 表示已过期
312
- } else {
313
- // 设置过期时间
314
- const expireAt = seconds === 0 ? null : new Date(Date.now() + seconds * 1000);
315
- const result = await this._set({ key: key }, { expireAt: expireAt });
316
- return result.modifiedCount > 0;
317
- }
318
- } catch (error) {
319
- $.log.error('[MongoDB] 设置TTL失败:', error);
320
- return false;
290
+ /**
291
+ * @description Redis兼容API - 获取所有键
292
+ * @param {String} pattern 匹配模式
293
+ * @return {Promise<Array>} 键名数组
294
+ */
295
+ MongoDB.prototype.keys = async function (pattern = '*') {
296
+ try {
297
+ await this._ensureConnected();
298
+ let query = {};
299
+ if (pattern !== '*') {
300
+ // 简单的通配符支持
301
+ const regexPattern = pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
302
+ query.key = {
303
+ $regex: new RegExp(`^${regexPattern}$`)
304
+ };
321
305
  }
306
+ const results = await this.find(query, {
307
+ projection: {
308
+ key: 1
309
+ }
310
+ });
311
+ return results.map(item => item.key);
312
+ } catch (error) {
313
+ $.log.error('[MongoDB] 获取键列表失败:', error);
314
+ return [];
322
315
  }
316
+ };
323
317
 
324
- /**
325
- * @description Redis兼容API - 添加缓存
326
- * @param {String} key 键名
327
- * @param {*} value 值
328
- * @param {Number} expire 过期时间(秒)
329
- * @return {Promise<Boolean>} 是否成功
330
- */
331
- async _add(key, value, expire) {
332
- try {
333
- await this._ensureConnected();
334
- const data = {
335
- key: key,
336
- value: value
318
+ /**
319
+ * @description Redis兼容API - 获取或设置过期时间
320
+ * @param {String} key 键名
321
+ * @param {Number} [seconds] 过期时间(秒),不传此参数则获取剩余过期时间
322
+ * @return {Promise<Number|Boolean>} 不传seconds时返回剩余过期时间(-1表示无过期设置,-2表示已过期或不存在),传seconds时返回是否设置成功
323
+ */
324
+ MongoDB.prototype.ttl = async function (key, seconds) {
325
+ try {
326
+ // 参数验证
327
+ if (!key || typeof key !== 'string') {
328
+ return arguments.length === 1 ? -2 : false;
329
+ }
330
+
331
+ // 设置模式才需要触发事件
332
+ if (seconds !== undefined && seconds !== null) {
333
+ // 触发前置事件
334
+ const beforeEventData = {
335
+ key,
336
+ seconds,
337
+ cancel: false
337
338
  };
339
+ this.emit('ttl_before', beforeEventData);
338
340
 
339
- if (expire) {
340
- data.expireAt = new Date(Date.now() + expire * 1000);
341
+ if (beforeEventData.cancel) {
342
+ return false;
341
343
  }
342
344
 
343
- // 检查是否已存在
344
- const existing = await this.findOne({ key: key });
345
- if (existing) {
346
- await this._set({ key: key }, data);
347
- } else {
348
- await this.addObj(data);
345
+ let { key: finalKey, seconds: finalSeconds } = beforeEventData;
346
+
347
+ await this._ensureConnected();
348
+
349
+ // 确保连接和collection存在
350
+ if (!this.db) {
351
+ await this.open();
349
352
  }
350
- return true;
351
- } catch (error) {
352
- $.log.error('[MongoDB] 添加缓存失败:', error);
353
- return false;
354
- }
355
- }
356
353
 
357
- /**
358
- * @description Redis兼容API - 添加缓存
359
- * @param {String} key 键名
360
- * @param {*} value 值
361
- * @param {Number} expire 过期时间(秒)
362
- * @return {Promise<Boolean>} 是否成功
363
- */
364
- async add(key, value, expire) {
365
- return this._add(key, value, expire);
366
- }
354
+ // 直接尝试获取collection,使用实例的table属性
355
+ if (!this.collection && this.db && this.db.collection) {
356
+ $.log.debug(`[MongoDB] [ttl] 设置模式:直接获取collection, table=${this.table}`);
357
+ try {
358
+ this.collection = this.db.collection(this.table);
359
+ $.log.debug(`[MongoDB] [ttl] 设置模式:成功获取collection`);
360
+ } catch (err) {
361
+ $.log.error(`[MongoDB] [ttl] 设置模式:获取collection失败:`, err.message);
362
+ this.collection = null;
363
+ }
364
+ }
367
365
 
368
- /**
369
- * @description Redis兼容API - 增加整数值
370
- * @param {String} key 键名
371
- * @param {Number} value 增加值
372
- * @return {Promise<Number>} 增加后的值
373
- */
374
- async addInt(key, value = 1) {
375
- try {
376
- await this._ensureConnected();
377
- const item = await this.findOne({ key: key });
378
- let currentValue = 0;
366
+ // 检查collection是否存在
367
+ if (!this.collection) {
368
+ this.emit('ttl_after', {
369
+ key: finalKey,
370
+ seconds: finalSeconds,
371
+ result: false
372
+ });
373
+ return false;
374
+ }
379
375
 
380
- if (item && typeof item.value === 'number') {
381
- currentValue = item.value;
376
+ // 检查键是否存在
377
+ const count = await this.collection.countDocuments({ key: finalKey });
378
+ if (count === 0) {
379
+ this.emit('ttl_after', {
380
+ key: finalKey,
381
+ seconds: finalSeconds,
382
+ result: false
383
+ });
384
+ return false;
382
385
  }
383
386
 
384
- const newValue = currentValue + value;
385
- await this._add(key, newValue);
386
- return newValue;
387
- } catch (error) {
388
- $.log.error('[MongoDB] 增加整数值失败:', error);
389
- return undefined;
387
+ // 设置过期时间
388
+ const updateData = {};
389
+ if (finalSeconds === 0 || finalSeconds === -1) {
390
+ // 移除过期时间
391
+ updateData.$unset = { expire_at: 1 };
392
+ } else if (finalSeconds > 0) {
393
+ // 设置新的过期时间
394
+ updateData.$set = { expire_at: new Date(Date.now() + finalSeconds * 1000) };
395
+ } else {
396
+ // 设置为已过期
397
+ updateData.$set = { expire_at: new Date(0) };
398
+ }
399
+
400
+ await this.collection.updateOne(
401
+ { key: finalKey },
402
+ updateData,
403
+ { upsert: false }
404
+ );
405
+
406
+ this.emit('ttl_after', {
407
+ key: finalKey,
408
+ seconds: finalSeconds,
409
+ result: true
410
+ });
411
+ return true;
390
412
  }
391
- }
413
+ // 获取模式:检查键的过期时间(不需要事件)
414
+ else {
415
+ await this._ensureConnected();
392
416
 
393
- /**
394
- * @description Redis兼容API - 增加浮点数值
395
- * @param {String} key 键名
396
- * @param {Number} value 增加值
397
- * @return {Promise<Number>} 增加后的值
398
- */
399
- async addFloat(key, value = 1.0) {
400
- return this.addInt(key, value); // 复用addInt方法
401
- }
417
+ // 确保连接和collection存在
418
+ if (!this.db) {
419
+ await this.open();
420
+ }
402
421
 
403
- /**
404
- * @description Redis兼容API - 添加字符串
405
- * @param {String} key 键名
406
- * @param {String} value 字符串值
407
- * @return {Promise<Boolean>} 是否成功
408
- */
409
- async addStr(key, value) {
410
- return this._add(key, String(value));
411
- }
422
+ // 直接尝试获取collection,使用实例的table属性
423
+ if (!this.collection && this.db && this.db.collection) {
424
+ $.log.debug(`[MongoDB] [ttl] 直接获取collection, table=${this.table}`);
425
+ try {
426
+ this.collection = this.db.collection(this.table);
427
+ $.log.debug(`[MongoDB] [ttl] 成功获取collection`);
428
+ } catch (err) {
429
+ $.log.error(`[MongoDB] [ttl] 获取collection失败:`, err.message);
430
+ this.collection = null;
431
+ }
432
+ }
412
433
 
413
- /**
414
- * @description Redis兼容API - 排序
415
- * @param {String} key 键名
416
- * @param {Object} options 排序选项
417
- * @return {Promise<Array>} 排序结果
418
- */
419
- async sort(key, options = {}) {
420
- try {
421
- await this._ensureConnected();
422
- // 假设value是数组
423
- const item = await this.findOne({ key: key });
424
- if (!item || !Array.isArray(item.value)) return [];
434
+ // 再次检查collection
435
+ if (!this.collection) {
436
+ return -2;
437
+ }
425
438
 
426
- const sorted = [...item.value];
439
+ // 查询键是否存在及过期时间
440
+ const item = await this.collection.findOne({ key });
441
+ $.log.debug(`[MongoDB] [ttl] 查询键结果: item=${JSON.stringify(item)}`);
427
442
 
428
- // 处理排序选项
429
- if (options.desc) {
430
- sorted.sort((a, b) => b - a);
431
- } else {
432
- sorted.sort((a, b) => a - b);
443
+ if (!item) {
444
+ // 键不存在返回-2
445
+ $.log.debug(`[MongoDB] [ttl] 键 ${key} 不存在,返回-2`);
446
+ return -2;
433
447
  }
434
448
 
435
- // 处理限制
436
- if (options.limit) {
437
- const offset = options.offset || 0;
438
- return sorted.slice(offset, offset + options.limit);
449
+ if (!item.expire_at) {
450
+ // 没有过期时间返回-1
451
+ $.log.debug(`[MongoDB] [ttl] 键 ${key} 没有过期时间,返回-1`);
452
+ return -1;
439
453
  }
440
454
 
441
- return sorted;
442
- } catch (error) {
443
- $.log.error('[MongoDB] 排序失败:', error);
444
- return [];
445
- }
446
- }
455
+ // 计算剩余过期时间
456
+ const expireTime = new Date(item.expire_at).getTime();
457
+ const currentTime = Date.now();
458
+ const remainingSeconds = Math.ceil((expireTime - currentTime) / 1000);
447
459
 
448
- /**
449
- * @description Redis兼容API - 哈希表设置字段
450
- * @param {String} key 键名
451
- * @param {String} field 字段名
452
- * @param {*} value 值
453
- * @return {Promise<Boolean>} 是否成功
454
- */
455
- async hset(key, field, value) {
456
- try {
457
- await this._ensureConnected();
458
- const item = await this.findOne({ key: key });
459
- let hash = item && typeof item.value === 'object' && item.value !== null ? item.value : {};
460
+ if (remainingSeconds <= 0) {
461
+ // 已过期返回-2
462
+ return -2;
463
+ }
460
464
 
461
- hash[field] = value;
462
- await this._add(key, hash);
463
- return true;
464
- } catch (error) {
465
- $.log.error('[MongoDB] 哈希表设置失败:', error);
466
- return false;
465
+ // 返回剩余过期时间
466
+ return remainingSeconds;
467
467
  }
468
+ } catch (error) {
469
+ this.emit('ttl_error', { key, seconds, error });
470
+ $.log.error(`[MongoDB] [ttl] 操作失败:`, error.message);
471
+ return arguments.length === 1 ? -2 : false;
468
472
  }
473
+ };
469
474
 
470
- /**
471
- * @description Redis兼容API - 哈希表获取字段
472
- * @param {String} key 键名
473
- * @param {String} field 字段名
474
- * @return {Promise<any>} 字段值
475
- */
476
- async hget(key, field) {
477
- try {
478
- await this._ensureConnected();
479
- const item = await this.findOne({ key: key });
480
- if (!item || typeof item.value !== 'object' || item.value === null) {
481
- return null;
482
- }
483
- return item.value[field] !== undefined ? item.value[field] : null;
484
- } catch (error) {
485
- $.log.error('[MongoDB] 哈希表获取失败:', error);
486
- return null;
475
+ /**
476
+ * @description Redis兼容API - 移除键的过期时间
477
+ * @param {String} key 键名
478
+ * @return {Promise<boolean>} 移除成功返回true,键不存在或没有过期时间返回false
479
+ */
480
+ MongoDB.prototype.persist = async function (key) {
481
+ try {
482
+ // 参数验证
483
+ if (!key || typeof key !== 'string') {
484
+ return false;
487
485
  }
488
- }
489
486
 
490
- /**
491
- * @description Redis兼容API - 哈希表获取所有字段和值
492
- * @param {String} key 键名
493
- * @return {Promise<Object>} 哈希表对象
494
- */
495
- async hgetall(key) {
496
- try {
497
- const item = await this.findOne({ key: key });
498
- if (!item || typeof item.value !== 'object' || item.value === null) {
499
- return {};
500
- }
501
- return item.value;
502
- } catch (error) {
503
- $.log.error('[MongoDB] 哈希表获取全部失败:', error);
504
- return {};
487
+ // 触发前置事件
488
+ const beforeEventData = {
489
+ key,
490
+ cancel: false
491
+ };
492
+ this.emit('persist_before', beforeEventData);
493
+
494
+ if (beforeEventData.cancel) {
495
+ return false;
505
496
  }
506
- }
507
497
 
508
- /**
509
- * @description Redis兼容API - 哈希表删除字段
510
- * @param {String} key 键名
511
- * @param {String} field 字段名
512
- * @return {Promise<Boolean>} 是否成功
513
- */
514
- async hdel(key, field) {
515
- try {
516
- const item = await this.findOne({ key: key });
517
- if (!item || typeof item.value !== 'object' || item.value === null) {
518
- return false;
519
- }
498
+ const { key: finalKey } = beforeEventData;
520
499
 
521
- if (item.value[field] === undefined) {
522
- return false;
500
+ // 确保连接和collection存在
501
+ if (!this.db) {
502
+ await this.open();
503
+ }
504
+
505
+ // 直接尝试获取collection,不依赖_ensureConnected的复杂逻辑
506
+ if (!this.collection && this.db && this.db.collection) {
507
+ $.log.debug(`[MongoDB] [persist] 直接获取collection, table=${this.table}`);
508
+ try {
509
+ this.collection = this.db.collection(this.table);
510
+ $.log.debug(`[MongoDB] [persist] 成功获取collection`);
511
+ } catch (err) {
512
+ $.log.error(`[MongoDB] [persist] 获取collection失败:`, err.message);
513
+ this.collection = null;
523
514
  }
515
+ }
524
516
 
525
- delete item.value[field];
526
- await this._set({ key: key }, { value: item.value });
527
- return true;
528
- } catch (error) {
529
- $.log.error('[MongoDB] 哈希表删除失败:', error);
517
+ // 再次检查collection
518
+ if (!this.collection) {
519
+ $.log.error('[MongoDB] persist: collection is undefined');
520
+ this.emit('persist_error', {
521
+ key: finalKey,
522
+ error: new Error('collection is undefined')
523
+ });
530
524
  return false;
531
525
  }
532
- }
533
526
 
534
- /**
535
- * @description Redis兼容API - 检查键是否存在
536
- * @param {String} key 键名
537
- * @return {Promise<Boolean>} 是否存在
538
- */
539
- async has(key) {
540
- try {
541
- const result = await this.findOne({ key: key });
542
- return result !== null;
543
- } catch (error) {
544
- $.log.error('[MongoDB] 检查键是否存在失败:', error);
527
+ // 检查键是否存在
528
+ const item = await this.collection.findOne({ key: finalKey });
529
+ if (!item) {
530
+ // 键不存在返回false
531
+ this.emit('persist_after', {
532
+ key: finalKey,
533
+ success: false
534
+ });
545
535
  return false;
546
536
  }
537
+
538
+ // 如果没有过期时间,直接返回true
539
+ if (!item.expire_at) {
540
+ this.emit('persist_after', {
541
+ key: finalKey,
542
+ success: true
543
+ });
544
+ return true;
545
+ }
546
+
547
+ // 从文档中移除过期时间
548
+ const result = await this.collection.updateOne(
549
+ { key: finalKey },
550
+ { $unset: { expire_at: 1 } },
551
+ { upsert: false }
552
+ );
553
+
554
+ // 检查更新是否成功
555
+ const success = result.modifiedCount > 0;
556
+
557
+ // 触发后置事件
558
+ this.emit('persist_after', {
559
+ key: finalKey,
560
+ success: success
561
+ });
562
+ return success;
563
+ } catch (error) {
564
+ $.log.error('[MongoDB] persist error:', error.message);
565
+ this.emit('persist_error', {
566
+ key,
567
+ error
568
+ });
569
+ return false;
547
570
  }
571
+ };
548
572
 
549
- /**
550
- * @description Redis兼容API - 设置键过期时间(映射到ttl)
551
- * @param {String} key 键名
552
- * @param {Number} seconds 过期时间(秒)
553
- * @return {Promise<Boolean>} 是否成功
554
- */
555
- async expire(key, seconds) {
556
- return this.ttl(key, seconds);
573
+ /**
574
+ * @param {String} key 键名
575
+ * @param {*} value 值
576
+ * @param {Number} expire 过期时间(秒)
577
+ * @return {Promise<Boolean>} 是否成功
578
+ */
579
+ MongoDB.prototype._add = async function (key, value, expire) {
580
+ try {
581
+ await this._ensureConnected();
582
+ const data = {
583
+ key: key,
584
+ value: value
585
+ };
586
+
587
+ if (expire) {
588
+ data.expire_at = new Date(Date.now() + expire * 1000);
589
+ }
590
+
591
+ // 检查是否已存在
592
+ const existing = await this.findOne({
593
+ key: key
594
+ });
595
+ if (existing) {
596
+ // 调用_set方法时传递过期时间参数
597
+ await this._set(key, value, expire);
598
+ } else {
599
+ await this.addObj(data);
600
+ }
601
+ return true;
602
+ } catch (error) {
603
+ $.log.error('[MongoDB] 添加缓存失败:', error);
604
+ return false;
557
605
  }
606
+ };
558
607
 
559
- /**
560
- * @description Redis兼容API - 递增(通过addInt模拟)
561
- * @param {String} key 键名
562
- * @return {Promise<Number>} 递增后的值
563
- */
564
- async incr(key) {
565
- try {
566
- await this._ensureConnected();
567
- const result = await this.addInt(key, 1);
568
- return result !== undefined ? result : 0;
569
- } catch (error) {
570
- $.log.error('[MongoDB] 递增操作失败:', error);
608
+ /**
609
+ * @description 仅当键不存在时设置缓存
610
+ * @param {String} key 键
611
+ * @param {*} value 值
612
+ * @param {Number} seconds 过期时间(秒)
613
+ * @return {Promise<Boolean>} 操作结果,键不存在时设置成功返回true,键已存在返回false
614
+ */
615
+ MongoDB.prototype.add = async function (key, value, seconds) {
616
+ try {
617
+ // 触发前置事件,允许修改或阻止操作
618
+ const beforeEventData = {
619
+ key,
620
+ value,
621
+ ttl: seconds,
622
+ cancel: false
623
+ };
624
+ this.emit('add_before', beforeEventData);
625
+
626
+ // 如果事件处理器标记为取消,则返回false
627
+ if (beforeEventData.cancel) {
628
+ $.log.debug(`[MongoDB] add操作被事件处理器取消 [${key}]`);
629
+ return false;
630
+ }
631
+
632
+ // 使用可能被修改的值
633
+ let {
634
+ key: finalKey,
635
+ value: finalValue,
636
+ ttl: finalSeconds
637
+ } = beforeEventData;
638
+
639
+ await this._ensureConnected();
640
+
641
+ // 检查键是否已存在
642
+ const existing = await this.findOne({
643
+ key: finalKey
644
+ });
645
+ if (existing) {
646
+ // 键已存在,返回false
647
+ this.emit('add_after', {
648
+ key: finalKey,
649
+ value: finalValue,
650
+ ttl: finalSeconds,
651
+ success: false
652
+ });
653
+ return false;
654
+ }
655
+
656
+ // 键不存在,设置值
657
+ const data = {
658
+ key: finalKey,
659
+ value: finalValue
660
+ };
661
+
662
+ if (finalSeconds) {
663
+ data.expire_at = new Date(Date.now() + finalSeconds * 1000);
664
+ }
665
+
666
+ await this.addObj(data);
667
+
668
+ // 触发后置事件
669
+ this.emit('add_after', {
670
+ key: finalKey,
671
+ value: finalValue,
672
+ ttl: finalSeconds,
673
+ success: true
674
+ });
675
+ return true;
676
+ } catch (error) {
677
+ $.log.error(`[MongoDB] 添加缓存失败 [${key}]:`, error);
678
+ // 触发错误事件
679
+ this.emit('add_error', {
680
+ key,
681
+ value,
682
+ ttl: seconds,
683
+ error
684
+ });
685
+ return false;
686
+ }
687
+ };
688
+
689
+
690
+ /**
691
+ * @description Redis兼容API - 增加整数值
692
+ * @param {String} key 键名
693
+ * @param {Number} value 增加值
694
+ * @return {Promise<Number>} 增加后的值
695
+ */
696
+ MongoDB.prototype._addNum = async function (key, value = 1) {
697
+ try {
698
+ await this._ensureConnected();
699
+ const item = await this.findOne({
700
+ key: key
701
+ });
702
+ let currentValue = 0;
703
+
704
+ if (item && typeof item.value === 'number') {
705
+ currentValue = item.value;
706
+ }
707
+
708
+ const newValue = currentValue + value;
709
+ await this._add(key, newValue);
710
+ return newValue;
711
+ } catch (error) {
712
+ $.log.error('[MongoDB] 增加整数值失败:', error);
713
+ return undefined;
714
+ }
715
+ };
716
+
717
+ /**
718
+ * @description Redis兼容API - 增加整数值
719
+ * @param {String} key 键名
720
+ * @param {Number} value 增加值
721
+ * @return {Promise<Number>} 增加后的值
722
+ */
723
+ /**
724
+ * @description Redis兼容API - 增加整数值
725
+ * @param {String} key 键名
726
+ * @param {Number} value 增加值
727
+ * @return {Promise<Number>} 增加后的值
728
+ */
729
+ MongoDB.prototype.addInt = async function (key, value = 1, seconds = 0) {
730
+ try {
731
+ // 触发前置事件,允许修改或阻止操作
732
+ const beforeEventData = {
733
+ key,
734
+ value,
735
+ seconds,
736
+ cancel: false
737
+ };
738
+ this.emit('addInt_before', beforeEventData);
739
+
740
+ // 如果事件处理器标记为取消,则返回默认值
741
+ if (beforeEventData.cancel) {
742
+ $.log.debug('[MongoDB] addInt操作被事件处理器取消');
743
+ return 0;
744
+ }
745
+
746
+ // 使用可能被修改的值
747
+ let {
748
+ key: finalKey,
749
+ value: finalValue,
750
+ seconds: finalSeconds
751
+ } = beforeEventData;
752
+
753
+ // 参数验证
754
+ if (!finalKey || typeof finalKey !== 'string') {
755
+ $.log.warn('[MongoDB] addInt: key必须是非空字符串');
571
756
  return 0;
572
757
  }
758
+
759
+ finalValue = Number(finalValue) || 0;
760
+
761
+ await this._ensureConnected();
762
+ // 获取当前值
763
+ const item = await this.findOne({
764
+ key: finalKey
765
+ });
766
+ let currentValue = 0;
767
+
768
+ if (item) {
769
+ // 确保转换为数字
770
+ currentValue = Number(item.value) || 0;
771
+ }
772
+
773
+ const newValue = Math.floor(currentValue + finalValue); // 确保返回整数
774
+ await this._add(finalKey, newValue, finalSeconds);
775
+
776
+ // 触发后置事件
777
+ this.emit('addInt_after', {
778
+ key: finalKey,
779
+ value: finalValue,
780
+ seconds: finalSeconds,
781
+ result: newValue,
782
+ success: true
783
+ });
784
+ return newValue;
785
+ } catch (error) {
786
+ // 触发错误事件
787
+ this.emit('addInt_error', {
788
+ key,
789
+ value,
790
+ seconds,
791
+ error
792
+ });
793
+ $.log.error('[MongoDB] 增加整数值失败:', error);
794
+ // 与CacheBase保持一致,错误时返回默认值而不是抛出异常
795
+ return 0;
573
796
  }
797
+ };
574
798
 
575
- /**
576
- * @description Redis兼容API - 递减(通过addInt模拟)
577
- * @param {String} key 键名
578
- * @return {Promise<Number>} 递减后的值
579
- */
580
- async decr(key) {
581
- try {
582
- await this._ensureConnected();
583
- const result = await this.addInt(key, -1);
584
- return result !== undefined ? result : 0;
585
- } catch (error) {
586
- $.log.error('[MongoDB] 递减操作失败:', error);
799
+ /**
800
+ * @description Redis兼容API - 增加浮点数值
801
+ * @param {String} key 键名
802
+ * @param {Number} value 增加值
803
+ * @return {Promise<Number>} 增加后的值
804
+ */
805
+ MongoDB.prototype.addFloat = async function (key, value = 1.0, seconds = 0) {
806
+ try {
807
+ // 触发前置事件,允许修改或阻止操作
808
+ const beforeEventData = {
809
+ key,
810
+ value,
811
+ seconds,
812
+ cancel: false
813
+ };
814
+ this.emit('addFloat_before', beforeEventData);
815
+
816
+ // 如果事件处理器标记为取消,则返回默认值
817
+ if (beforeEventData.cancel) {
818
+ $.log.debug('[MongoDB] addFloat操作被事件处理器取消');
819
+ return 0.0;
820
+ }
821
+
822
+ // 使用可能被修改的值
823
+ let {
824
+ key: finalKey,
825
+ value: finalValue,
826
+ seconds: finalSeconds
827
+ } = beforeEventData;
828
+
829
+ // 参数验证
830
+ if (!finalKey || typeof finalKey !== 'string') {
831
+ $.log.warn('[MongoDB] addFloat: key必须是非空字符串');
832
+ return 0.0;
833
+ }
834
+
835
+ finalValue = Number(finalValue) || 0.0;
836
+
837
+ await this._ensureConnected();
838
+ // 获取当前值
839
+ const item = await this.findOne({
840
+ key: finalKey
841
+ });
842
+ let currentValue = 0.0;
843
+
844
+ if (item) {
845
+ // 确保转换为数字
846
+ currentValue = Number(item.value) || 0.0;
847
+ }
848
+
849
+ const newValue = currentValue + finalValue;
850
+ await this._add(finalKey, newValue, finalSeconds);
851
+
852
+ // 触发后置事件
853
+ this.emit('addFloat_after', {
854
+ key: finalKey,
855
+ value: finalValue,
856
+ seconds: finalSeconds,
857
+ result: newValue,
858
+ success: true
859
+ });
860
+ return newValue;
861
+ } catch (error) {
862
+ // 触发错误事件
863
+ this.emit('addFloat_error', {
864
+ key,
865
+ value,
866
+ seconds,
867
+ error
868
+ });
869
+ $.log.error('[MongoDB] 增加浮点数值失败:', error);
870
+ // 与CacheBase保持一致,错误时返回默认值而不是抛出异常
871
+ return 0.0;
872
+ }
873
+ };
874
+
875
+ /**
876
+ * @description Redis兼容API - 添加字符串
877
+ * @param {String} key 键名
878
+ * @param {String} value 字符串值
879
+ * @return {Promise<Number>} 添加后的字符串长度
880
+ */
881
+ MongoDB.prototype.addStr = async function (key, value) {
882
+ try {
883
+ // 触发前置事件
884
+ this.emit('addStr_before', {
885
+ key,
886
+ value
887
+ });
888
+
889
+ // 参数验证
890
+ if (!key || typeof key !== 'string') {
891
+ throw new Error('key必须是非空字符串');
892
+ }
893
+
894
+ if (value === undefined || value === null) {
895
+ value = '';
896
+ }
897
+
898
+ await this._ensureConnected();
899
+ // 获取当前值
900
+ const item = await this.findOne({
901
+ key: key
902
+ });
903
+ const currentValue = item ? String(item.value) : '';
904
+ const newValue = currentValue + String(value);
905
+ // 设置新值
906
+ await this._add(key, newValue);
907
+
908
+ // 触发后置事件
909
+ this.emit('addStr_after', {
910
+ key,
911
+ value,
912
+ result: newValue.length
913
+ });
914
+ // 返回添加后的字符串长度
915
+ return newValue.length;
916
+ } catch (error) {
917
+ // 触发错误事件
918
+ this.emit('addStr_error', {
919
+ key,
920
+ value,
921
+ error
922
+ });
923
+ $.log.error('[MongoDB] 添加字符串失败:', error);
924
+ return 0;
925
+ }
926
+ };
927
+
928
+ /**
929
+ * @description Redis兼容API - 排序
930
+ * @param {String} key 键名
931
+ * @param {Object} options 排序选项
932
+ * @return {Promise<Array>} 排序结果
933
+ */
934
+ MongoDB.prototype.sort = async function (key, options = {}) {
935
+ try {
936
+ await this._ensureConnected();
937
+ // 假设value是数组
938
+ const item = await this.findOne({
939
+ key: key
940
+ });
941
+ if (!item || !Array.isArray(item.value)) return [];
942
+
943
+ const sorted = [...item.value];
944
+
945
+ // 处理排序选项
946
+ if (options.desc) {
947
+ sorted.sort((a, b) => b - a);
948
+ } else {
949
+ sorted.sort((a, b) => a - b);
950
+ }
951
+
952
+ // 处理限制
953
+ if (options.limit) {
954
+ const offset = options.offset || 0;
955
+ return sorted.slice(offset, offset + options.limit);
956
+ }
957
+
958
+ return sorted;
959
+ } catch (error) {
960
+ $.log.error('[MongoDB] 排序失败:', error);
961
+ return [];
962
+ }
963
+ };
964
+
965
+ /**
966
+ * @description Redis兼容API - 哈希表设置字段
967
+ * @param {String} key 键名
968
+ * @param {String} field 字段名
969
+ * @param {*} value 值
970
+ * @return {Promise<number>} 返回1表示新建字段,0表示更新字段
971
+ */
972
+ MongoDB.prototype.hset = async function (key, field, value) {
973
+ try {
974
+ await this._ensureConnected();
975
+ const item = await this.findOne({
976
+ key: key
977
+ });
978
+ let hash = item && typeof item.value === 'object' && item.value !== null ? item.value : {};
979
+
980
+ // 检查字段是否存在,以确定是新建还是更新
981
+ const isNewField = !hash.hasOwnProperty(field);
982
+ hash[field] = value;
983
+ await this._add(key, hash);
984
+ return isNewField ? 1 : 0;
985
+ } catch (error) {
986
+ $.log.error('[MongoDB] 哈希表设置失败:', error);
987
+ throw error;
988
+ }
989
+ };
990
+
991
+ /**
992
+ * @description Redis兼容API - 哈希表获取字段
993
+ * @param {String} key 键名
994
+ * @param {String} field 字段名
995
+ * @return {Promise<any>} 返回字段值,不存在返回undefined
996
+ */
997
+ MongoDB.prototype.hget = async function (key, field) {
998
+ try {
999
+ await this._ensureConnected();
1000
+ const item = await this.findOne({
1001
+ key: key
1002
+ });
1003
+ if (!item || typeof item.value !== 'object' || item.value === null) {
1004
+ return undefined;
1005
+ }
1006
+ return item.value[field] !== undefined ? item.value[field] : undefined;
1007
+ } catch (error) {
1008
+ $.log.error('[MongoDB] 哈希表获取失败:', error);
1009
+ throw error;
1010
+ }
1011
+ };
1012
+
1013
+ /**
1014
+ * @description Redis兼容API - 哈希表获取所有字段和值
1015
+ * @param {String} key 键名
1016
+ * @return {Promise<Object>} 哈希表对象
1017
+ */
1018
+ MongoDB.prototype.hgetall = async function (key) {
1019
+ try {
1020
+ const item = await this.findOne({
1021
+ key: key
1022
+ });
1023
+ if (!item || typeof item.value !== 'object' || item.value === null) {
1024
+ return {};
1025
+ }
1026
+ return item.value;
1027
+ } catch (error) {
1028
+ $.log.error('[MongoDB] 哈希表获取全部失败:', error);
1029
+ return {};
1030
+ }
1031
+ };
1032
+
1033
+ /**
1034
+ * @description Redis兼容API - 哈希表删除字段
1035
+ * @param {String} key 键名
1036
+ * @param {String|Array<String>} field 字段名或字段名数组
1037
+ * @return {Promise<boolean>} 返回true表示成功删除,false表示未删除
1038
+ */
1039
+ MongoDB.prototype.hdel = async function (key, fields) {
1040
+ try {
1041
+ // 参数验证
1042
+ if (!key || typeof key !== 'string') {
1043
+ return 0;
1044
+ }
1045
+
1046
+ // 统一将fields转换为数组格式
1047
+ const fieldArray = Array.isArray(fields) ? fields : [fields];
1048
+ if (fieldArray.length === 0) {
1049
+ return 0;
1050
+ }
1051
+
1052
+ await this._ensureConnected();
1053
+
1054
+ // 查找是否存在该键
1055
+ const item = await this.findOne({ key: key });
1056
+
1057
+ // 如果键不存在或不是哈希表格式,返回0
1058
+ if (!item || typeof item.value !== 'object' || item.value === null || Array.isArray(item.value)) {
587
1059
  return 0;
588
1060
  }
1061
+
1062
+ // 计数删除的字段数量
1063
+ let deletedCount = 0;
1064
+ const updatedValue = { ...item.value };
1065
+
1066
+ // 遍历要删除的字段
1067
+ fieldArray.forEach(field => {
1068
+ if (field && Object.prototype.hasOwnProperty.call(updatedValue, field)) {
1069
+ delete updatedValue[field];
1070
+ deletedCount++;
1071
+ }
1072
+ });
1073
+
1074
+ // 如果删除了字段,更新数据库
1075
+ if (deletedCount > 0) {
1076
+ // 使用正确的方式更新数据库
1077
+ await this.db.collection(this.table).updateOne(
1078
+ { key: key },
1079
+ { $set: { value: updatedValue, updated_at: new Date() } }
1080
+ );
1081
+ }
1082
+
1083
+ return deletedCount;
1084
+ } catch (error) {
1085
+ $.log.error('[MongoDB] hdel操作失败:', error);
1086
+ return 0;
589
1087
  }
1088
+ };
590
1089
 
591
- /**
592
- * @description Redis兼容API - 检查键是否存在(别名)
593
- * @param {String} key 键名
594
- * @return {Promise<Boolean>} 是否存在
595
- */
596
- async exists(key) {
597
- try {
598
- await this._ensureConnected();
599
- return this.has(key);
600
- } catch (error) {
601
- $.log.error('[MongoDB] 检查键是否存在失败:', error);
1090
+ /**
1091
+ * @description Redis兼容API - 检查键是否存在
1092
+ * @param {String} key 键名
1093
+ * @return {Promise<Boolean>} 是否存在
1094
+ */
1095
+ MongoDB.prototype.has = async function (key) {
1096
+ try {
1097
+ const result = await this.findOne({
1098
+ key: key
1099
+ });
1100
+ return result !== null;
1101
+ } catch (error) {
1102
+ $.log.error('[MongoDB] [has] 检查键是否存在失败:', error);
1103
+ throw error;
1104
+ }
1105
+ };
1106
+
1107
+ /**
1108
+ * @description Redis兼容API - 设置键过期时间(映射到ttl)
1109
+ * @param {String} key 键名
1110
+ * @param {Number} seconds 过期时间(秒)
1111
+ * @return {Promise<Boolean>} 是否成功
1112
+ */
1113
+ MongoDB.prototype.expire = async function (key, seconds) {
1114
+ try {
1115
+ // 参数验证
1116
+ seconds = parseInt(seconds) || 0;
1117
+
1118
+ // 触发前置事件,允许修改或阻止操作
1119
+ const beforeEventData = {
1120
+ key,
1121
+ seconds,
1122
+ cancel: false
1123
+ };
1124
+ this.emit('expire_before', beforeEventData);
1125
+
1126
+ // 如果事件处理器标记为取消,则返回默认值
1127
+ if (beforeEventData.cancel) {
1128
+ $.log.debug('[MongoDB] expire操作被事件处理器取消');
602
1129
  return false;
603
1130
  }
1131
+
1132
+ // 使用可能被修改的值
1133
+ const {
1134
+ key: finalKey,
1135
+ seconds: finalSeconds
1136
+ } = beforeEventData;
1137
+
1138
+ // 调用ttl方法设置过期时间
1139
+ const result = await this.ttl(finalKey, finalSeconds);
1140
+
1141
+ // 触发后置事件
1142
+ this.emit('expire_after', {
1143
+ key: finalKey,
1144
+ seconds: finalSeconds,
1145
+ result
1146
+ });
1147
+
1148
+ // 返回布尔值表示成功或失败
1149
+ return result === true || (typeof result === 'number' && result >= -2);
1150
+ } catch (error) {
1151
+ // 记录错误日志
1152
+ $.log.error('[MongoDB] expire方法执行错误', { error: error.message, key, seconds });
1153
+
1154
+ // 触发错误事件
1155
+ this.emit('expire_error', {
1156
+ key,
1157
+ seconds,
1158
+ error
1159
+ });
1160
+
1161
+ return false;
604
1162
  }
1163
+ };
605
1164
 
606
- /**
607
- * @description Redis兼容API - 设置键值
608
- * @param {String} key 键名
609
- * @param {*} value 值
610
- * @return {Promise<Boolean>} 是否成功
611
- */
612
- async _set(key, value) {
613
- try {
614
- await this._ensureConnected();
615
- // 直接使用已有的add方法,它已正确处理查询条件格式
616
- return await this._add(key, value);
617
- } catch (error) {
618
- $.log.error('[MongoDB] 设置键值失败:', error);
619
- return false;
1165
+ /**
1166
+ * @description Redis兼容API - 递增(通过addInt模拟)
1167
+ * @param {String} key 键名
1168
+ * @return {Promise<Number>} 递增后的值
1169
+ */
1170
+ MongoDB.prototype.incr = async function (key, seconds = 0) {
1171
+ try {
1172
+ // 触发前置事件,允许修改或阻止操作
1173
+ const beforeEventData = {
1174
+ key,
1175
+ seconds,
1176
+ cancel: false
1177
+ };
1178
+ this.emit('incr_before', beforeEventData);
1179
+
1180
+ // 如果事件处理器标记为取消,则返回默认值
1181
+ if (beforeEventData.cancel) {
1182
+ $.log.debug('[MongoDB] incr操作被事件处理器取消');
1183
+ return 0;
1184
+ }
1185
+
1186
+ // 使用可能被修改的值
1187
+ let {
1188
+ key: finalKey,
1189
+ seconds: finalSeconds
1190
+ } = beforeEventData;
1191
+
1192
+ // 参数验证
1193
+ if (!finalKey || typeof finalKey !== 'string') {
1194
+ $.log.warn('[MongoDB] incr: key必须是非空字符串');
1195
+ return 0;
1196
+ }
1197
+
1198
+ await this._ensureConnected();
1199
+ const result = await this.addInt(finalKey, 1, finalSeconds);
1200
+ const returnResult = result !== undefined ? result : 0;
1201
+
1202
+ // 触发后置事件
1203
+ this.emit('incr_after', {
1204
+ key: finalKey,
1205
+ seconds: finalSeconds,
1206
+ result: returnResult,
1207
+ success: true
1208
+ });
1209
+
1210
+ return returnResult;
1211
+ } catch (error) {
1212
+ // 触发错误事件
1213
+ this.emit('incr_error', {
1214
+ key,
1215
+ seconds,
1216
+ error
1217
+ });
1218
+ $.log.error('[MongoDB] 递增操作失败:', error);
1219
+ return 0;
1220
+ }
1221
+ };
1222
+
1223
+ /**
1224
+ * @description Redis兼容API - 递减(通过addInt模拟)
1225
+ * @param {String} key 键名
1226
+ * @return {Promise<Number>} 递减后的值
1227
+ */
1228
+ MongoDB.prototype.decr = async function (key, seconds = 0) {
1229
+ try {
1230
+ // 触发前置事件,允许修改或阻止操作
1231
+ const beforeEventData = {
1232
+ key,
1233
+ seconds,
1234
+ cancel: false
1235
+ };
1236
+ this.emit('decr_before', beforeEventData);
1237
+
1238
+ // 如果事件处理器标记为取消,则返回默认值
1239
+ if (beforeEventData.cancel) {
1240
+ $.log.debug('[MongoDB] decr操作被事件处理器取消');
1241
+ return 0;
1242
+ }
1243
+
1244
+ // 使用可能被修改的值
1245
+ let {
1246
+ key: finalKey,
1247
+ seconds: finalSeconds
1248
+ } = beforeEventData;
1249
+
1250
+ // 参数验证
1251
+ if (!finalKey || typeof finalKey !== 'string') {
1252
+ $.log.warn('[MongoDB] decr: key必须是非空字符串');
1253
+ return 0;
1254
+ }
1255
+
1256
+ await this._ensureConnected();
1257
+ const result = await this.addInt(finalKey, -1, finalSeconds);
1258
+ const returnResult = result !== undefined ? result : 0;
1259
+
1260
+ // 触发后置事件
1261
+ this.emit('decr_after', {
1262
+ key: finalKey,
1263
+ seconds: finalSeconds,
1264
+ result: returnResult,
1265
+ success: true
1266
+ });
1267
+
1268
+ return returnResult;
1269
+ } catch (error) {
1270
+ // 触发错误事件
1271
+ this.emit('decr_error', {
1272
+ key,
1273
+ seconds,
1274
+ error
1275
+ });
1276
+ $.log.error('[MongoDB] 递减操作失败:', error);
1277
+ return 0;
1278
+ }
1279
+ };
1280
+
1281
+ /**
1282
+ * @description Redis兼容API - 按指定值递增
1283
+ * @param {String} key 键名
1284
+ * @param {Number} value 增加值
1285
+ * @return {Promise<Number>} 递增后的值
1286
+ */
1287
+ MongoDB.prototype.incrby = async function (key, value = 1, seconds = 0) {
1288
+ try {
1289
+ // 触发前置事件,允许修改或阻止操作
1290
+ const beforeEventData = {
1291
+ key,
1292
+ value,
1293
+ seconds,
1294
+ cancel: false
1295
+ };
1296
+ this.emit('incrby_before', beforeEventData);
1297
+
1298
+ // 如果事件处理器标记为取消,则返回默认值
1299
+ if (beforeEventData.cancel) {
1300
+ $.log.debug('[MongoDB] incrby操作被事件处理器取消');
1301
+ return 0;
1302
+ }
1303
+
1304
+ // 使用可能被修改的值
1305
+ let {
1306
+ key: finalKey,
1307
+ value: finalValue,
1308
+ seconds: finalSeconds
1309
+ } = beforeEventData;
1310
+
1311
+ // 参数验证
1312
+ if (!finalKey || typeof finalKey !== 'string') {
1313
+ $.log.warn('[MongoDB] incrby: key必须是非空字符串');
1314
+ return 0;
1315
+ }
1316
+
1317
+ // 确保value是数字
1318
+ finalValue = Number(finalValue) || 0;
1319
+
1320
+ await this._ensureConnected();
1321
+ const result = await this.addInt(finalKey, finalValue, finalSeconds);
1322
+ const returnResult = result !== undefined ? result : 0;
1323
+
1324
+ // 触发后置事件
1325
+ this.emit('incrby_after', {
1326
+ key: finalKey,
1327
+ value: finalValue,
1328
+ seconds: finalSeconds,
1329
+ result: returnResult,
1330
+ success: true
1331
+ });
1332
+
1333
+ return returnResult;
1334
+ } catch (error) {
1335
+ // 触发错误事件
1336
+ this.emit('incrby_error', {
1337
+ key,
1338
+ value,
1339
+ seconds,
1340
+ error
1341
+ });
1342
+ $.log.error('[MongoDB] 递增操作失败:', error);
1343
+ return 0;
1344
+ }
1345
+ };
1346
+
1347
+ /**
1348
+ * @description Redis兼容API - 按指定值递减
1349
+ * @param {String} key 键名
1350
+ * @param {Number} value 减少值
1351
+ * @return {Promise<Number>} 递减后的值
1352
+ */
1353
+ MongoDB.prototype.decrby = async function (key, value = 1, seconds = 0) {
1354
+ try {
1355
+ // 触发前置事件,允许修改或阻止操作
1356
+ const beforeEventData = {
1357
+ key,
1358
+ value,
1359
+ seconds,
1360
+ cancel: false
1361
+ };
1362
+ this.emit('decrby_before', beforeEventData);
1363
+
1364
+ // 如果事件处理器标记为取消,则返回默认值
1365
+ if (beforeEventData.cancel) {
1366
+ $.log.debug('[MongoDB] decrby操作被事件处理器取消');
1367
+ return 0;
1368
+ }
1369
+
1370
+ // 使用可能被修改的值
1371
+ let {
1372
+ key: finalKey,
1373
+ value: finalValue,
1374
+ seconds: finalSeconds
1375
+ } = beforeEventData;
1376
+
1377
+ // 参数验证
1378
+ if (!finalKey || typeof finalKey !== 'string') {
1379
+ $.log.warn('[MongoDB] decrby: key必须是非空字符串');
1380
+ return 0;
1381
+ }
1382
+
1383
+ // 确保value是数字
1384
+ finalValue = Number(finalValue) || 0;
1385
+
1386
+ await this._ensureConnected();
1387
+ const result = await this.addInt(finalKey, -finalValue, finalSeconds);
1388
+ const returnResult = result !== undefined ? result : 0;
1389
+
1390
+ // 触发后置事件
1391
+ this.emit('decrby_after', {
1392
+ key: finalKey,
1393
+ value: finalValue,
1394
+ seconds: finalSeconds,
1395
+ result: returnResult,
1396
+ success: true
1397
+ });
1398
+
1399
+ return returnResult;
1400
+ } catch (error) {
1401
+ // 触发错误事件
1402
+ this.emit('decrby_error', {
1403
+ key,
1404
+ value,
1405
+ seconds,
1406
+ error
1407
+ });
1408
+ $.log.error('[MongoDB] 递减操作失败:', error);
1409
+ return 0;
1410
+ }
1411
+ };
1412
+
1413
+ /**
1414
+ * @description Redis兼容API - 按指定浮点值递增
1415
+ * @param {String} key 键名
1416
+ * @param {Number} value 浮点增加值
1417
+ * @return {Promise<Number>} 递增后的值
1418
+ */
1419
+ MongoDB.prototype.incrbyfloat = async function (key, value = 1.0, seconds = 0) {
1420
+ try {
1421
+ // 触发前置事件,允许修改或阻止操作
1422
+ const beforeEventData = {
1423
+ key,
1424
+ value,
1425
+ seconds,
1426
+ cancel: false
1427
+ };
1428
+ this.emit('incrbyfloat_before', beforeEventData);
1429
+
1430
+ // 如果事件处理器标记为取消,则返回默认值
1431
+ if (beforeEventData.cancel) {
1432
+ $.log.debug('[MongoDB] incrbyfloat操作被事件处理器取消');
1433
+ return 0.0;
1434
+ }
1435
+
1436
+ // 使用可能被修改的值
1437
+ let {
1438
+ key: finalKey,
1439
+ value: finalValue,
1440
+ seconds: finalSeconds
1441
+ } = beforeEventData;
1442
+
1443
+ // 参数验证
1444
+ if (!finalKey || typeof finalKey !== 'string') {
1445
+ $.log.warn('[MongoDB] incrbyfloat: key必须是非空字符串');
1446
+ return 0.0;
1447
+ }
1448
+
1449
+ // 确保value是数字
1450
+ finalValue = Number(finalValue) || 0.0;
1451
+
1452
+ await this._ensureConnected();
1453
+ const result = await this.addFloat(finalKey, finalValue, finalSeconds);
1454
+ const returnResult = result !== undefined ? result : 0.0;
1455
+
1456
+ // 触发后置事件
1457
+ this.emit('incrbyfloat_after', {
1458
+ key: finalKey,
1459
+ value: finalValue,
1460
+ seconds: finalSeconds,
1461
+ result: returnResult,
1462
+ success: true
1463
+ });
1464
+
1465
+ return returnResult;
1466
+ } catch (error) {
1467
+ // 触发错误事件
1468
+ this.emit('incrbyfloat_error', {
1469
+ key,
1470
+ value,
1471
+ seconds,
1472
+ error
1473
+ });
1474
+ $.log.error('[MongoDB] 浮点递增操作失败:', error);
1475
+ return 0.0;
1476
+ }
1477
+ };
1478
+
1479
+ /**
1480
+ * @description Redis兼容API - 检查键是否存在
1481
+ * @param {String|Array<String>} key 键名或键名数组
1482
+ * @return {Promise<number>} 返回存在的键数量
1483
+ */
1484
+ MongoDB.prototype.exists = async function (key) {
1485
+ try {
1486
+ await this._ensureConnected();
1487
+
1488
+ // 验证数组中的每个元素都是非空字符串
1489
+ if (Array.isArray(key)) {
1490
+ for (const k of key) {
1491
+ if (typeof k !== 'string' || !k) {
1492
+ throw new Error('all keys must be non-empty strings');
1493
+ }
1494
+ }
620
1495
  }
1496
+
1497
+ let existsCount = 0;
1498
+
1499
+ // 检查输入类型并处理
1500
+ if (Array.isArray(key)) {
1501
+ // 处理多键检查
1502
+ const results = await this.find({
1503
+ key: {
1504
+ $in: key
1505
+ }
1506
+ });
1507
+ existsCount = results.length;
1508
+ } else {
1509
+ // 处理单键检查
1510
+ const result = await this.findOne({
1511
+ key: key
1512
+ });
1513
+ existsCount = result !== null ? 1 : 0;
1514
+ }
1515
+
1516
+ return existsCount;
1517
+ } catch (error) {
1518
+ $.log.error('[MongoDB] 检查键是否存在失败:', error);
1519
+ throw error;
621
1520
  }
1521
+ };
1522
+
1523
+ /**
1524
+ * @description Redis兼容API - 设置键值
1525
+ * @param {String} key 键名
1526
+ * @param {*} value 值
1527
+ * @return {Promise<Boolean>} 是否成功
1528
+ */
1529
+ MongoDB.prototype._set = async function (key, value, expire) {
1530
+ try {
1531
+ await this._ensureConnected();
1532
+ // 准备更新操作
1533
+ const updateData = { $set: { value: value } };
1534
+
1535
+ // 如果提供了过期时间,添加expire_at字段
1536
+ if (expire && expire > 0) {
1537
+ updateData.$set.expire_at = new Date(Date.now() + expire * 1000);
1538
+ }
1539
+
1540
+ // 直接更新数据库,避免调用_add方法形成循环引用
1541
+ const result = await this.db.collection(this.table).updateOne(
1542
+ { key: key },
1543
+ updateData,
1544
+ { upsert: true }
1545
+ );
1546
+ return true;
1547
+ } catch (error) {
1548
+ $.log.error('[MongoDB] 设置键值失败:', error);
1549
+ return false; // 与CacheBase保持一致,返回false而不是抛出异常
1550
+ }
1551
+ };
1552
+
1553
+ /**
1554
+ * @description Redis兼容API - 设置键值
1555
+ * @param {String} key 键名
1556
+ * @param {*} value 值
1557
+ * @param {Number} seconds 过期时间(秒)
1558
+ * @return {Promise<Boolean>} 是否成功
1559
+ */
1560
+ MongoDB.prototype.set = async function (key, value, seconds = 0) {
1561
+ return this._set(key, value, seconds);
1562
+ };
1563
+
1564
+ /**
1565
+ * @description Redis兼容API - 哈希表批量设置
1566
+ * @param {String} key 键名
1567
+ * @param {Array} fieldValues 字段值对数组,格式: [field1, value1, field2, value2, ...] 或对象格式 {field1: value1, field2: value2}
1568
+ * @return {Promise<Number>} 设置的字段数量
1569
+ */
1570
+ MongoDB.prototype.hmset = async function (key, ...fieldValues) {
1571
+ try {
1572
+ // 参数验证
1573
+ if (!key || typeof key !== 'string') {
1574
+ $.log.warn('[MongoDB] hmset: 键名无效');
1575
+ return 0;
1576
+ }
1577
+
1578
+ // 处理可变参数或对象参数
1579
+ let hashData = {};
1580
+ if (fieldValues.length === 1 && typeof fieldValues[0] === 'object' && fieldValues[0] !== null && !Array
1581
+ .isArray(fieldValues[0])) {
1582
+ // 单个对象参数格式 {field1: value1, field2: value2}
1583
+ hashData = fieldValues[0];
1584
+ } else {
1585
+ // 可变参数格式 field1, value1, field2, value2...
1586
+ for (let i = 0; i < fieldValues.length; i += 2) {
1587
+ if (i + 1 < fieldValues.length) {
1588
+ hashData[fieldValues[i]] = fieldValues[i + 1];
1589
+ }
1590
+ }
1591
+ }
1592
+
1593
+ const fieldsCount = Object.keys(hashData).length;
1594
+
1595
+ // 确保连接
1596
+ await this._ensureConnected();
1597
+
1598
+ // 直接创建或更新哈希数据,不使用事件系统
1599
+ const data = {
1600
+ key: key,
1601
+ value: hashData
1602
+ };
1603
+
1604
+ const existing = await this.findOne({
1605
+ key: key
1606
+ });
1607
+ if (existing) {
1608
+ // 如果存在,合并现有数据
1609
+ if (typeof existing.value === 'object' && existing.value !== null && !Array.isArray(existing
1610
+ .value)) {
1611
+ data.value = {
1612
+ ...existing.value,
1613
+ ...hashData
1614
+ };
1615
+ }
1616
+ await this._set({
1617
+ key: key
1618
+ }, data);
1619
+ } else {
1620
+ await this.addObj(data);
1621
+ }
1622
+
1623
+ return fieldsCount;
1624
+ } catch (error) {
1625
+ $.log.error('[MongoDB] [hmset] 哈希表批量设置失败:', error);
1626
+ return 0;
1627
+ }
1628
+ };
1629
+
1630
+ /**
1631
+ * @description Redis兼容API - 哈希表批量获取
1632
+ * @param {String} key 键名
1633
+ * @param {Array} fields 字段名数组
1634
+ * @return {Promise<Array>} 与字段名顺序对应的数组值,不存在返回undefined
1635
+ */
1636
+ MongoDB.prototype.hmget = async function (key, fields) {
1637
+ try {
1638
+ // 参数验证
1639
+ if (!key || typeof key !== 'string') {
1640
+ return Array.isArray(fields) ? fields.map(() => null) : [null];
1641
+ }
622
1642
 
623
- /**
624
- * @description Redis兼容API - 设置键值
625
- * @param {String} key 键名
626
- * @param {*} value 值
627
- * @return {Promise<Boolean>} 是否成功
628
- */
629
- async set(key, value) {
630
- return this._set(key, value);
631
- }
1643
+ await this._ensureConnected();
1644
+ const item = await this.findOne({ key: key });
632
1645
 
633
- /**
634
- * @description Redis兼容API - 哈希表批量设置
635
- * @param {String} key 键名
636
- * @param {Object} fieldValues 字段值对象
637
- * @return {Promise<Boolean>} 是否成功
638
- */
639
- async hmset(key, fieldValues) {
640
- try {
641
- await this._ensureConnected();
642
- const item = await this.findOne({ key: key });
643
- let hash = item && typeof item.value === 'object' && item.value !== null ? item.value : {};
1646
+ // 确保fields是数组
1647
+ const fieldArray = Array.isArray(fields) ? fields : [fields];
644
1648
 
645
- // 合并字段值
646
- Object.assign(hash, fieldValues);
647
- await this._add(key, hash);
648
- return true;
649
- } catch (error) {
650
- $.log.error('[MongoDB] 哈希表批量设置失败:', error);
651
- return false;
1649
+ // 如果键不存在或不是哈希表格式,返回与字段数量相同的null数组
1650
+ if (!item || typeof item.value !== 'object' || item.value === null || Array.isArray(item.value)) {
1651
+ return fieldArray.map(() => null);
652
1652
  }
653
- }
654
1653
 
655
- /**
656
- * @description Redis兼容API - 哈希表批量获取
657
- * @param {String} key 键名
658
- * @param {Array} fields 字段名数组
659
- * @return {Promise<Object>} 字段值对象
660
- */
661
- async hmget(key, fields) {
662
- try {
663
- await this._ensureConnected();
664
- const item = await this.findOne({ key: key });
665
- if (!item || typeof item.value !== 'object' || item.value === null) {
666
- return {};
667
- }
1654
+ // 返回与字段名顺序对应的数组值,不存在的字段返回null
1655
+ const values = fieldArray.map(field => {
1656
+ // 严格检查字段是否存在于对象中
1657
+ return Object.prototype.hasOwnProperty.call(item.value, field) ? item.value[field] : null;
1658
+ });
668
1659
 
669
- const result = {};
670
- fields.forEach(field => {
671
- result[field] = item.value[field] !== undefined ? item.value[field] : null;
672
- });
673
- return result;
674
- } catch (error) {
675
- $.log.error('[MongoDB] 哈希表批量获取失败:', error);
676
- return {};
677
- }
1660
+ return values;
1661
+ } catch (error) {
1662
+ $.log.error('[MongoDB] hmget操作失败:', error);
1663
+ // 错误时返回与fields长度相同的null数组
1664
+ const fieldArray = Array.isArray(fields) ? fields : [fields];
1665
+ return fieldArray.map(() => null);
678
1666
  }
1667
+ };
679
1668
 
680
- /**
681
- * @description Redis兼容API - 哈希表批量删除字段
682
- * @param {String} key 键名
683
- * @param {Array} fields 字段名数组
684
- * @return {Promise<Boolean>} 是否成功
685
- */
686
- async hdelMulti(key, fields) {
687
- try {
688
- await this._ensureConnected();
689
- const item = await this.findOne({ key: key });
690
- if (!item || typeof item.value !== 'object' || item.value === null) {
691
- return false;
1669
+ /**
1670
+ * @description Redis兼容API - 哈希表批量删除字段
1671
+ * @param {String} key 键名
1672
+ * @param {Array} fields 字段名数组
1673
+ * @return {Promise<Boolean>} 是否成功
1674
+ */
1675
+ MongoDB.prototype.hdelMulti = async function (key, fields) {
1676
+ try {
1677
+ await this._ensureConnected();
1678
+ const item = await this.findOne({
1679
+ key: key
1680
+ });
1681
+ if (!item || typeof item.value !== 'object' || item.value === null) {
1682
+ return false;
1683
+ }
1684
+
1685
+ let deleted = false;
1686
+ fields.forEach(field => {
1687
+ if (item.value[field] !== undefined) {
1688
+ delete item.value[field];
1689
+ deleted = true;
692
1690
  }
1691
+ });
693
1692
 
694
- let deleted = false;
695
- fields.forEach(field => {
696
- if (item.value[field] !== undefined) {
697
- delete item.value[field];
698
- deleted = true;
699
- }
1693
+ if (deleted) {
1694
+ await this._set({
1695
+ key: key
1696
+ }, {
1697
+ value: item.value
700
1698
  });
701
-
702
- if (deleted) {
703
- await this._set({ key: key }, { value: item.value });
704
- }
705
- return deleted;
706
- } catch (error) {
707
- $.log.error('[MongoDB] 哈希表批量删除失败:', error);
708
- return false;
709
1699
  }
1700
+ return deleted;
1701
+ } catch (error) {
1702
+ $.log.error('[MongoDB] 哈希表批量删除失败:', error);
1703
+ return false;
710
1704
  }
1705
+ };
711
1706
 
712
- /**
713
- * @description Redis兼容API - 哈希表获取所有值
714
- * @param {String} key 键名
715
- * @return {Promise<Array>} 值数组
716
- */
717
- async hvals(key) {
718
- try {
719
- await this._ensureConnected();
720
- const item = await this.findOne({ key: key });
721
- if (!item || typeof item.value !== 'object' || item.value === null) {
722
- return [];
723
- }
724
- return Object.values(item.value);
725
- } catch (error) {
726
- $.log.error('[MongoDB] 哈希表获取所有值失败:', error);
1707
+ /**
1708
+ * @description Redis兼容API - 哈希表获取所有值
1709
+ * @param {String} key 键名
1710
+ * @return {Promise<Array>} 值数组
1711
+ */
1712
+ MongoDB.prototype.hvals = async function (key) {
1713
+ try {
1714
+ await this._ensureConnected();
1715
+ const item = await this.findOne({
1716
+ key: key
1717
+ });
1718
+ if (!item || typeof item.value !== 'object' || item.value === null) {
727
1719
  return [];
728
1720
  }
1721
+ return Object.values(item.value);
1722
+ } catch (error) {
1723
+ $.log.error('[MongoDB] 哈希表获取所有值失败:', error);
1724
+ return [];
729
1725
  }
1726
+ };
730
1727
 
731
- /**
732
- * @description Redis兼容API - 哈希表获取所有字段名
733
- * @param {String} key 键名
734
- * @return {Promise<Array>} 字段名数组
735
- */
736
- async hkeys(key) {
737
- try {
738
- await this._ensureConnected();
739
- const item = await this.findOne({ key: key });
740
- if (!item || typeof item.value !== 'object' || item.value === null) {
741
- return [];
742
- }
743
- return Object.keys(item.value);
744
- } catch (error) {
745
- $.log.error('[MongoDB] 哈希表获取所有字段名失败:', error);
1728
+ /**
1729
+ * @description Redis兼容API - 哈希表获取所有字段名
1730
+ * @param {String} key 键名
1731
+ * @return {Promise<Array>} 字段名数组
1732
+ */
1733
+ MongoDB.prototype.hkeys = async function (key) {
1734
+ try {
1735
+ await this._ensureConnected();
1736
+ const item = await this.findOne({
1737
+ key: key
1738
+ });
1739
+ if (!item || typeof item.value !== 'object' || item.value === null) {
746
1740
  return [];
747
1741
  }
1742
+ return Object.keys(item.value);
1743
+ } catch (error) {
1744
+ $.log.error('[MongoDB] 哈希表获取所有字段名失败:', error);
1745
+ return [];
748
1746
  }
1747
+ };
749
1748
 
750
- /**
751
- * @description Redis兼容API - 哈希表字段数量
752
- * @param {String} key 键名
753
- * @return {Promise<Number>} 字段数量
754
- */
755
- async hlen(key) {
756
- try {
757
- await this._ensureConnected();
758
- const item = await this.findOne({ key: key });
759
- if (!item || typeof item.value !== 'object' || item.value === null) {
760
- return 0;
761
- }
762
- return Object.keys(item.value).length;
763
- } catch (error) {
764
- $.log.error('[MongoDB] 哈希表字段数量失败:', error);
1749
+ /**
1750
+ * @description Redis兼容API - 哈希表字段数量
1751
+ * @param {String} key 键名
1752
+ * @return {Promise<Number>} 字段数量
1753
+ */
1754
+ MongoDB.prototype.hlen = async function (key) {
1755
+ try {
1756
+ // 参数验证
1757
+ if (!key || typeof key !== 'string') {
765
1758
  return 0;
766
1759
  }
767
- }
768
-
769
- /**
770
- * @description Redis兼容API - 哈希表字段是否存在
771
- * @param {String} key 键名
772
- * @param {String} field 字段名
773
- * @return {Promise<Boolean>} 是否存在
774
- */
775
- async hexists(key, field) {
776
- try {
777
- await this._ensureConnected();
778
- const item = await this.findOne({ key: key });
779
- if (!item || typeof item.value !== 'object' || item.value === null) {
780
- return false;
781
- }
782
- return field in item.value;
783
- } catch (error) {
784
- $.log.error('[MongoDB] 哈希表字段是否存在失败:', error);
785
- return false;
786
- }
787
- }
788
1760
 
789
- /**
790
- * @description Redis兼容API - 列表尾部添加元素
791
- * @param {String} key 键名
792
- * @param {*} value 值
793
- * @return {Promise<Number>} 添加后列表长度
794
- */
795
- async rpush(key, value) {
796
- try {
797
- await this._ensureConnected();
798
- const item = await this.findOne({ key: key });
799
- let list = item && Array.isArray(item.value) ? item.value : [];
1761
+ await this._ensureConnected();
1762
+ const item = await this.findOne({ key: key });
800
1763
 
801
- list.push(value);
802
- await this._add(key, list);
803
- return list.length;
804
- } catch (error) {
805
- $.log.error('[MongoDB] 列表尾部添加失败:', error);
1764
+ // 如果键不存在或不是哈希表格式,返回0
1765
+ if (!item || typeof item.value !== 'object' || item.value === null || Array.isArray(item.value)) {
806
1766
  return 0;
807
1767
  }
808
- }
809
1768
 
810
- /**
811
- * @description Redis兼容API - 列表头部添加元素
812
- * @param {String} key 键名
813
- * @param {*} value 值
814
- * @return {Promise<Number>} 添加后列表长度
815
- */
816
- async lpush(key, value) {
817
- try {
818
- await this._ensureConnected();
819
- const item = await this.findOne({ key: key });
820
- let list = item && Array.isArray(item.value) ? item.value : [];
1769
+ // 使用Object.keys获取自有属性数量,这比for-in循环更高效
1770
+ return Object.keys(item.value).length;
1771
+ } catch (error) {
1772
+ $.log.error('[MongoDB] hlen操作失败:', error);
1773
+ return 0;
1774
+ }
1775
+ };
821
1776
 
822
- list.unshift(value);
823
- await this._add(key, list);
824
- return list.length;
825
- } catch (error) {
826
- $.log.error('[MongoDB] 列表头部添加失败:', error);
827
- return 0;
1777
+ /**
1778
+ * @description Redis兼容API - 哈希表字段是否存在
1779
+ * @param {String} key 键名
1780
+ * @param {String} field 字段名
1781
+ * @return {Promise<Boolean>} 是否存在
1782
+ */
1783
+ MongoDB.prototype.hexists = async function (key, field) {
1784
+ try {
1785
+ await this._ensureConnected();
1786
+ const item = await this.findOne({
1787
+ key: key
1788
+ });
1789
+ if (!item || typeof item.value !== 'object' || item.value === null) {
1790
+ return false;
828
1791
  }
1792
+ return field in item.value;
1793
+ } catch (error) {
1794
+ $.log.error('[MongoDB] 哈希表字段是否存在失败:', error);
1795
+ return false;
829
1796
  }
1797
+ };
830
1798
 
831
- /**
832
- * @description Redis兼容API - 列表范围查询
833
- * @param {String} key 键名
834
- * @param {Number} start 起始索引
835
- * @param {Number} stop 结束索引
836
- * @return {Promise<Array>} 元素数组
837
- */
838
- async lrange(key, start, stop) {
839
- try {
840
- await this._ensureConnected();
841
- const item = await this.findOne({ key: key });
842
- if (!item || !Array.isArray(item.value)) {
843
- return [];
844
- }
845
-
846
- // 处理负数索引
847
- let startIndex = start < 0 ? item.value.length + start : start;
848
- let endIndex = stop < 0 ? item.value.length + stop : stop;
1799
+ /**
1800
+ * @description Redis兼容API - 列表尾部添加元素
1801
+ * @param {String} key 键名
1802
+ * @param {*} value 值
1803
+ * @return {Promise<Number>} 添加后列表长度
1804
+ */
1805
+ MongoDB.prototype.rpush = async function (key, value) {
1806
+ try {
1807
+ await this._ensureConnected();
1808
+ const item = await this.findOne({
1809
+ key: key
1810
+ });
1811
+ let list = item && Array.isArray(item.value) ? item.value : [];
1812
+
1813
+ // 将值转换为字符串,与Redis行为一致
1814
+ list.push(value.toString());
1815
+ await this._add(key, list);
1816
+ return list.length;
1817
+ } catch (error) {
1818
+ $.log.error('[MongoDB] 列表尾部添加失败:', error);
1819
+ return 0;
1820
+ }
1821
+ };
849
1822
 
850
- // 确保索引有效
851
- startIndex = Math.max(0, Math.min(startIndex, item.value.length - 1));
852
- endIndex = Math.max(startIndex, Math.min(endIndex, item.value.length - 1));
1823
+ /**
1824
+ * @description Redis兼容API - 列表头部添加元素
1825
+ * @param {String} key 键名
1826
+ * @param {*} value 值
1827
+ * @return {Promise<Number>} 添加后列表长度
1828
+ */
1829
+ MongoDB.prototype.lpush = async function (key, value) {
1830
+ try {
1831
+ await this._ensureConnected();
1832
+ const item = await this.findOne({
1833
+ key: key
1834
+ });
1835
+ let list = item && Array.isArray(item.value) ? item.value : [];
1836
+
1837
+ // 将值转换为字符串,与Redis行为一致
1838
+ list.unshift(value.toString());
1839
+ await this._add(key, list);
1840
+ return list.length;
1841
+ } catch (error) {
1842
+ $.log.error('[MongoDB] 列表头部添加失败:', error);
1843
+ return 0;
1844
+ }
1845
+ };
853
1846
 
854
- return item.value.slice(startIndex, endIndex + 1);
855
- } catch (error) {
856
- $.log.error('[MongoDB] 列表范围查询失败:', error);
1847
+ /**
1848
+ * @description Redis兼容API - 列表范围查询
1849
+ * @param {String} key 键名
1850
+ * @param {Number} start 起始索引
1851
+ * @param {Number} stop 结束索引
1852
+ * @return {Promise<Array>} 元素数组
1853
+ */
1854
+ MongoDB.prototype.lrange = async function (key, start, stop) {
1855
+ try {
1856
+ await this._ensureConnected();
1857
+ const item = await this.findOne({
1858
+ key: key
1859
+ });
1860
+ if (!item || !Array.isArray(item.value)) {
857
1861
  return [];
858
1862
  }
859
- }
860
1863
 
861
- /**
862
- * @description Redis兼容API - 获取列表指定索引元素
863
- * @param {String} key 键名
864
- * @param {Number} index 索引
865
- * @return {Promise<any>} 元素值
866
- */
867
- async lindex(key, index) {
868
- try {
869
- await this._ensureConnected();
870
- const item = await this.findOne({ key: key });
871
- if (!item || !Array.isArray(item.value)) {
872
- return null;
873
- }
1864
+ // 处理负数索引
1865
+ let startIndex = start < 0 ? item.value.length + start : start;
1866
+ let endIndex = stop < 0 ? item.value.length + stop : stop;
874
1867
 
875
- // 处理负数索引
876
- const realIndex = index < 0 ? item.value.length + index : index;
1868
+ // 确保索引有效
1869
+ startIndex = Math.max(0, Math.min(startIndex, item.value.length - 1));
1870
+ endIndex = Math.max(startIndex, Math.min(endIndex, item.value.length - 1));
877
1871
 
878
- if (realIndex < 0 || realIndex >= item.value.length) {
879
- return null;
880
- }
1872
+ return item.value.slice(startIndex, endIndex + 1);
1873
+ } catch (error) {
1874
+ $.log.error('[MongoDB] 列表范围查询失败:', error);
1875
+ return [];
1876
+ }
1877
+ };
881
1878
 
882
- return item.value[realIndex];
883
- } catch (error) {
884
- $.log.error('[MongoDB] 获取列表索引元素失败:', error);
1879
+ /**
1880
+ * @description Redis兼容API - 获取列表指定索引元素
1881
+ * @param {String} key 键名
1882
+ * @param {Number} index 索引
1883
+ * @return {Promise<any>} 元素值
1884
+ */
1885
+ MongoDB.prototype.lindex = async function (key, index) {
1886
+ try {
1887
+ await this._ensureConnected();
1888
+ const item = await this.findOne({
1889
+ key: key
1890
+ });
1891
+ if (!item || !Array.isArray(item.value)) {
885
1892
  return null;
886
1893
  }
887
- }
888
1894
 
889
- /**
890
- * @description Redis兼容API - 设置列表指定索引元素
891
- * @param {String} key 键名
892
- * @param {Number} index 索引
893
- * @param {*} value 值
894
- * @return {Promise<Boolean>} 是否成功
895
- */
896
- async lset(key, index, value) {
897
- try {
898
- await this._ensureConnected();
899
- const item = await this.findOne({ key: key });
900
- if (!item || !Array.isArray(item.value)) {
901
- return false;
902
- }
1895
+ // 处理负数索引
1896
+ const realIndex = index < 0 ? item.value.length + index : index;
903
1897
 
904
- // 处理负数索引
905
- const realIndex = index < 0 ? item.value.length + index : index;
1898
+ if (realIndex < 0 || realIndex >= item.value.length) {
1899
+ return null;
1900
+ }
906
1901
 
907
- if (realIndex < 0 || realIndex >= item.value.length) {
908
- return false;
909
- }
1902
+ return item.value[realIndex];
1903
+ } catch (error) {
1904
+ $.log.error('[MongoDB] 获取列表索引元素失败:', error);
1905
+ return null;
1906
+ }
1907
+ };
910
1908
 
911
- item.value[realIndex] = value;
912
- await this._set({ key: key }, { value: item.value });
913
- return true;
914
- } catch (error) {
915
- $.log.error('[MongoDB] 设置列表索引元素失败:', error);
1909
+ /**
1910
+ * @description Redis兼容API - 设置列表指定索引元素
1911
+ * @param {String} key 键名
1912
+ * @param {Number} index 索引
1913
+ * @param {*} value 值
1914
+ * @return {Promise<Boolean>} 是否成功
1915
+ */
1916
+ MongoDB.prototype.lset = async function (key, index, value) {
1917
+ try {
1918
+ await this._ensureConnected();
1919
+ const item = await this.findOne({
1920
+ key: key
1921
+ });
1922
+ if (!item || !Array.isArray(item.value)) {
1923
+ return false;
1924
+ }
1925
+
1926
+ // 处理负数索引
1927
+ const realIndex = index < 0 ? item.value.length + index : index;
1928
+
1929
+ if (realIndex < 0 || realIndex >= item.value.length) {
916
1930
  return false;
917
1931
  }
1932
+
1933
+ item.value[realIndex] = value;
1934
+ await this._set({
1935
+ key: key
1936
+ }, {
1937
+ value: item.value
1938
+ });
1939
+ return true;
1940
+ } catch (error) {
1941
+ $.log.error('[MongoDB] 设置列表索引元素失败:', error);
1942
+ return false;
918
1943
  }
1944
+ };
919
1945
 
920
- /**
921
- * @description Redis兼容API - 删除列表元素
922
- * @param {String} key 键名
923
- * @param {Number} count 删除数量
924
- * @param {*} value 要删除的值
925
- * @return {Promise<Number>} 删除的元素数量
926
- */
927
- async lrem(key, count, value) {
928
- try {
929
- await this._ensureConnected();
930
- const item = await this.findOne({ key: key });
931
- if (!item || !Array.isArray(item.value)) {
932
- return 0;
933
- }
1946
+ /**
1947
+ * @description Redis兼容API - 删除列表元素
1948
+ * @param {String} key 键名
1949
+ * @param {Number} count 删除数量
1950
+ * @param {*} value 要删除的值
1951
+ * @return {Promise<Number>} 删除的元素数量
1952
+ */
1953
+ MongoDB.prototype.lrem = async function (key, count, value) {
1954
+ try {
1955
+ await this._ensureConnected();
1956
+ const item = await this.findOne({
1957
+ key: key
1958
+ });
1959
+ if (!item || !Array.isArray(item.value)) {
1960
+ return 0;
1961
+ }
934
1962
 
935
- let list = [...item.value];
936
- let removedCount = 0;
1963
+ let list = [...item.value];
1964
+ let removedCount = 0;
937
1965
 
938
- if (count > 0) {
939
- // 从头部删除count个等于value的元素
940
- for (let i = 0; i < list.length && removedCount < count; i++) {
941
- if (JSON.stringify(list[i]) === JSON.stringify(value)) {
942
- list.splice(i, 1);
943
- removedCount++;
944
- i--; // 调整索引
945
- }
946
- }
947
- } else if (count < 0) {
948
- // 从尾部删除|count|个等于value的元素
949
- count = Math.abs(count);
950
- for (let i = list.length - 1; i >= 0 && removedCount < count; i--) {
951
- if (JSON.stringify(list[i]) === JSON.stringify(value)) {
952
- list.splice(i, 1);
953
- removedCount++;
954
- }
1966
+ if (count > 0) {
1967
+ // 从头部删除count个等于value的元素
1968
+ for (let i = 0; i < list.length && removedCount < count; i++) {
1969
+ if (JSON.stringify(list[i]) === JSON.stringify(value)) {
1970
+ list.splice(i, 1);
1971
+ removedCount++;
1972
+ i--; // 调整索引
955
1973
  }
956
- } else {
957
- // 删除所有等于value的元素
958
- list = list.filter(item => JSON.stringify(item) !== JSON.stringify(value));
959
- removedCount = item.value.length - list.length;
960
1974
  }
961
-
962
- if (removedCount > 0) {
963
- await this._set({ key: key }, { value: list });
1975
+ } else if (count < 0) {
1976
+ // 从尾部删除|count|个等于value的元素
1977
+ count = Math.abs(count);
1978
+ for (let i = list.length - 1; i >= 0 && removedCount < count; i--) {
1979
+ if (JSON.stringify(list[i]) === JSON.stringify(value)) {
1980
+ list.splice(i, 1);
1981
+ removedCount++;
1982
+ }
964
1983
  }
1984
+ } else {
1985
+ // 删除所有等于value的元素
1986
+ list = list.filter(item => JSON.stringify(item) !== JSON.stringify(value));
1987
+ removedCount = item.value.length - list.length;
1988
+ }
965
1989
 
966
- return removedCount;
967
- } catch (error) {
968
- $.log.error('[MongoDB] 删除列表元素失败:', error);
969
- return 0;
1990
+ if (removedCount > 0) {
1991
+ await this._set({
1992
+ key: key
1993
+ }, {
1994
+ value: list
1995
+ });
970
1996
  }
971
- }
972
1997
 
973
- /**
974
- * @description Redis兼容API - 裁剪列表
975
- * @param {String} key 键名
976
- * @param {Number} start 起始索引
977
- * @param {Number} stop 结束索引
978
- * @return {Promise<Boolean>} 是否成功
979
- */
980
- async ltrim(key, start, stop) {
981
- try {
982
- await this._ensureConnected();
983
- const item = await this.findOne({ key: key });
984
- if (!item || !Array.isArray(item.value)) {
985
- return false;
986
- }
1998
+ return removedCount;
1999
+ } catch (error) {
2000
+ $.log.error('[MongoDB] 删除列表元素失败:', error);
2001
+ return 0;
2002
+ }
2003
+ };
987
2004
 
988
- // 使用lrange的逻辑获取裁剪后的列表
989
- const trimmed = await this.lrange(key, start, stop);
990
- await this._set({ key: key }, { value: trimmed });
991
- return true;
992
- } catch (error) {
993
- $.log.error('[MongoDB] 裁剪列表失败:', error);
2005
+ /**
2006
+ * @description Redis兼容API - 裁剪列表
2007
+ * @param {String} key 键名
2008
+ * @param {Number} start 起始索引
2009
+ * @param {Number} stop 结束索引
2010
+ * @return {Promise<Boolean>} 是否成功
2011
+ */
2012
+ MongoDB.prototype.ltrim = async function (key, start, stop) {
2013
+ try {
2014
+ await this._ensureConnected();
2015
+ const item = await this.findOne({
2016
+ key: key
2017
+ });
2018
+ if (!item || !Array.isArray(item.value)) {
994
2019
  return false;
995
2020
  }
996
- }
997
2021
 
998
- /**
999
- * @description Redis兼容API - 列表长度
1000
- * @param {String} key 键名
1001
- * @return {Promise<Number>} 列表长度
1002
- */
1003
- async llen(key) {
1004
- try {
1005
- await this._ensureConnected();
1006
- const item = await this.findOne({ key: key });
1007
- if (!item || !Array.isArray(item.value)) {
1008
- return 0;
1009
- }
1010
- return item.value.length;
1011
- } catch (error) {
1012
- $.log.error('[MongoDB] 获取列表长度失败:', error);
1013
- return 0;
1014
- }
2022
+ // 使用lrange的逻辑获取裁剪后的列表
2023
+ const trimmed = await this.lrange(key, start, stop);
2024
+ await this._set({
2025
+ key: key
2026
+ }, {
2027
+ value: trimmed
2028
+ });
2029
+ return true;
2030
+ } catch (error) {
2031
+ $.log.error('[MongoDB] 裁剪列表失败:', error);
2032
+ return false;
1015
2033
  }
2034
+ };
1016
2035
 
1017
- /**
1018
- * @description Redis兼容API - 集合批量添加
1019
- * @param {String} key 键名
1020
- * @param {Array} members 成员数组
1021
- * @return {Promise<Number>} 添加的成员数量
1022
- */
1023
- async saddMulti(key, members) {
1024
- try {
1025
- await this._ensureConnected();
1026
- const item = await this.findOne({ key: key });
1027
- let set = item && Array.isArray(item.value) ? [...new Set(item.value)] : [];
1028
- let addedCount = 0;
1029
-
1030
- members.forEach(member => {
1031
- if (!set.some(existing => JSON.stringify(existing) === JSON.stringify(member))) {
1032
- set.push(member);
1033
- addedCount++;
1034
- }
1035
- });
1036
-
1037
- if (addedCount > 0) {
1038
- await this._add(key, set);
1039
- }
1040
-
1041
- return addedCount;
1042
- } catch (error) {
1043
- $.log.error('[MongoDB] 集合并发添加失败:', error);
2036
+ /**
2037
+ * @description Redis兼容API - 列表长度
2038
+ * @param {String} key 键名
2039
+ * @return {Promise<Number>} 列表长度
2040
+ */
2041
+ MongoDB.prototype.llen = async function (key) {
2042
+ try {
2043
+ await this._ensureConnected();
2044
+ const item = await this.findOne({
2045
+ key: key
2046
+ });
2047
+ if (!item || !Array.isArray(item.value)) {
1044
2048
  return 0;
1045
2049
  }
2050
+ return item.value.length;
2051
+ } catch (error) {
2052
+ $.log.error('[MongoDB] 获取列表长度失败:', error);
2053
+ return 0;
1046
2054
  }
2055
+ };
1047
2056
 
1048
- /**
1049
- * @description Redis兼容API - 集合批量删除
1050
- * @param {String} key 键名
1051
- * @param {Array} members 成员数组
1052
- * @return {Promise<Number>} 删除的成员数量
1053
- */
1054
- async sremMulti(key, members) {
1055
- try {
1056
- await this._ensureConnected();
1057
- const item = await this.findOne({ key: key });
1058
- if (!item || !Array.isArray(item.value)) {
1059
- return 0;
2057
+ /**
2058
+ * @description Redis兼容API - 集合批量添加
2059
+ * @param {String} key 键名
2060
+ * @param {Array} members 成员数组
2061
+ * @return {Promise<Number>} 添加的成员数量
2062
+ */
2063
+ MongoDB.prototype.saddMulti = async function (key, members) {
2064
+ try {
2065
+ await this._ensureConnected();
2066
+ const item = await this.findOne({
2067
+ key: key
2068
+ });
2069
+ let set = item && Array.isArray(item.value) ? [...new Set(item.value)] : [];
2070
+ let addedCount = 0;
2071
+
2072
+ members.forEach(member => {
2073
+ if (!set.some(existing => JSON.stringify(existing) === JSON.stringify(member))) {
2074
+ set.push(member);
2075
+ addedCount++;
1060
2076
  }
2077
+ });
1061
2078
 
1062
- let set = [...new Set(item.value)];
1063
- const originalLength = set.length;
1064
-
1065
- // 创建要删除的值的字符串表示集合用于快速查找
1066
- const membersToRemove = new Set(members.map(m => JSON.stringify(m)));
1067
- set = set.filter(member => !membersToRemove.has(JSON.stringify(member)));
2079
+ if (addedCount > 0) {
2080
+ await this._add(key, set);
2081
+ }
1068
2082
 
1069
- const removedCount = originalLength - set.length;
1070
- if (removedCount > 0) {
1071
- await this._add(key, set);
1072
- }
2083
+ return addedCount;
2084
+ } catch (error) {
2085
+ $.log.error('[MongoDB] 集合并发添加失败:', error);
2086
+ return 0;
2087
+ }
2088
+ };
1073
2089
 
1074
- return removedCount;
1075
- } catch (error) {
1076
- $.log.error('[MongoDB] 集合并发删除失败:', error);
2090
+ /**
2091
+ * @description Redis兼容API - 集合批量删除
2092
+ * @param {String} key 键名
2093
+ * @param {Array} members 成员数组
2094
+ * @return {Promise<Number>} 删除的成员数量
2095
+ */
2096
+ MongoDB.prototype.sremMulti = async function (key, members) {
2097
+ try {
2098
+ await this._ensureConnected();
2099
+ const item = await this.findOne({
2100
+ key: key
2101
+ });
2102
+ if (!item || !Array.isArray(item.value)) {
1077
2103
  return 0;
1078
2104
  }
1079
- }
1080
-
1081
- /**
1082
- * @description Redis兼容API - 集合差集
1083
- * @param {String} key1 第一个集合键
1084
- * @param {String} key2 第二个集合键
1085
- * @return {Promise<Array>} 差集数组
1086
- */
1087
- async sdiff(key1, key2) {
1088
- try {
1089
- await this._ensureConnected();
1090
- const item1 = await this.findOne({ key: key1 });
1091
- const item2 = await this.findOne({ key: key2 });
1092
2105
 
1093
- const set1 = item1 && Array.isArray(item1.value) ? [...new Set(item1.value)] : [];
1094
- const set2 = item2 && Array.isArray(item2.value) ? [...new Set(item2.value)] : [];
2106
+ let set = [...new Set(item.value)];
2107
+ const originalLength = set.length;
1095
2108
 
1096
- // 创建第二个集合值的字符串表示用于快速查找
1097
- const set2Str = new Set(set2.map(m => JSON.stringify(m)));
2109
+ // 创建要删除的值的字符串表示集合用于快速查找
2110
+ const membersToRemove = new Set(members.map(m => JSON.stringify(m)));
2111
+ set = set.filter(member => !membersToRemove.has(JSON.stringify(member)));
1098
2112
 
1099
- // 找出在set1中但不在set2中的元素
1100
- return set1.filter(member => !set2Str.has(JSON.stringify(member)));
1101
- } catch (error) {
1102
- $.log.error('[MongoDB] 集合差集失败:', error);
1103
- return [];
2113
+ const removedCount = originalLength - set.length;
2114
+ if (removedCount > 0) {
2115
+ await this._add(key, set);
1104
2116
  }
1105
- }
1106
2117
 
1107
- /**
1108
- * @description Redis兼容API - 集合交集
1109
- * @param {String} key1 第一个集合键
1110
- * @param {String} key2 第二个集合键
1111
- * @return {Promise<Array>} 交集数组
1112
- */
1113
- async sinter(key1, key2) {
1114
- try {
1115
- await this._ensureConnected();
1116
- const item1 = await this.findOne({ key: key1 });
1117
- const item2 = await this.findOne({ key: key2 });
2118
+ return removedCount;
2119
+ } catch (error) {
2120
+ $.log.error('[MongoDB] 集合并发删除失败:', error);
2121
+ return 0;
2122
+ }
2123
+ };
1118
2124
 
1119
- const set1 = item1 && Array.isArray(item1.value) ? [...new Set(item1.value)] : [];
1120
- const set2 = item2 && Array.isArray(item2.value) ? [...new Set(item2.value)] : [];
2125
+ /**
2126
+ * @description Redis兼容API - 集合差集
2127
+ * @param {String} key1 第一个集合键
2128
+ * @param {String} key2 第二个集合键
2129
+ * @return {Promise<Array>} 差集数组
2130
+ */
2131
+ MongoDB.prototype.sdiff = async function (key1, key2) {
2132
+ try {
2133
+ await this._ensureConnected();
2134
+ const item1 = await this.findOne({
2135
+ key: key1
2136
+ });
2137
+ const item2 = await this.findOne({
2138
+ key: key2
2139
+ });
2140
+
2141
+ const set1 = item1 && Array.isArray(item1.value) ? [...new Set(item1.value)] : [];
2142
+ const set2 = item2 && Array.isArray(item2.value) ? [...new Set(item2.value)] : [];
2143
+
2144
+ // 创建第二个集合值的字符串表示用于快速查找
2145
+ const set2Str = new Set(set2.map(m => JSON.stringify(m)));
2146
+
2147
+ // 找出在set1中但不在set2中的元素
2148
+ return set1.filter(member => !set2Str.has(JSON.stringify(member)));
2149
+ } catch (error) {
2150
+ $.log.error('[MongoDB] 集合差集失败:', error);
2151
+ return [];
2152
+ }
2153
+ };
1121
2154
 
1122
- // 创建较小集合的字符串表示用于快速查找,优化性能
1123
- let smallerSet, largerSet;
1124
- if (set1.length <= set2.length) {
1125
- smallerSet = set1;
1126
- largerSet = set2;
1127
- } else {
1128
- smallerSet = set2;
1129
- largerSet = set1;
1130
- }
2155
+ /**
2156
+ * @description Redis兼容API - 集合交集
2157
+ * @param {String} key1 第一个集合键
2158
+ * @param {String} key2 第二个集合键
2159
+ * @return {Promise<Array>} 交集数组
2160
+ */
2161
+ MongoDB.prototype.sinter = async function (key1, key2) {
2162
+ try {
2163
+ await this._ensureConnected();
2164
+ const item1 = await this.findOne({
2165
+ key: key1
2166
+ });
2167
+ const item2 = await this.findOne({
2168
+ key: key2
2169
+ });
2170
+
2171
+ const set1 = item1 && Array.isArray(item1.value) ? [...new Set(item1.value)] : [];
2172
+ const set2 = item2 && Array.isArray(item2.value) ? [...new Set(item2.value)] : [];
2173
+
2174
+ // 创建较小集合的字符串表示用于快速查找,优化性能
2175
+ let smallerSet, largerSet;
2176
+ if (set1.length <= set2.length) {
2177
+ smallerSet = set1;
2178
+ largerSet = set2;
2179
+ } else {
2180
+ smallerSet = set2;
2181
+ largerSet = set1;
2182
+ }
1131
2183
 
1132
- const largerSetStr = new Set(largerSet.map(m => JSON.stringify(m)));
2184
+ const largerSetStr = new Set(largerSet.map(m => JSON.stringify(m)));
1133
2185
 
1134
- // 找出同时存在于两个集合中的元素
1135
- return smallerSet.filter(member => largerSetStr.has(JSON.stringify(member)));
1136
- } catch (error) {
1137
- $.log.error('[MongoDB] 集合并集失败:', error);
1138
- return [];
1139
- }
2186
+ // 找出同时存在于两个集合中的元素
2187
+ return smallerSet.filter(member => largerSetStr.has(JSON.stringify(member)));
2188
+ } catch (error) {
2189
+ $.log.error('[MongoDB] 集合并集失败:', error);
2190
+ return [];
1140
2191
  }
2192
+ };
1141
2193
 
1142
- /**
1143
- * @description Redis兼容API - 集合并集
1144
- * @param {String} key1 第一个集合键
1145
- * @param {String} key2 第二个集合键
1146
- * @return {Promise<Array>} 并集数组
1147
- */
1148
- async sunion(key1, key2) {
1149
- try {
1150
- await this._ensureConnected();
1151
- const item1 = await this.findOne({ key: key1 });
1152
- const item2 = await this.findOne({ key: key2 });
2194
+ /**
2195
+ * @description Redis兼容API - 集合并集
2196
+ * @param {String} key1 第一个集合键
2197
+ * @param {String} key2 第二个集合键
2198
+ * @return {Promise<Array>} 并集数组
2199
+ */
2200
+ MongoDB.prototype.sunion = async function (key1, key2) {
2201
+ try {
2202
+ await this._ensureConnected();
2203
+ const item1 = await this.findOne({
2204
+ key: key1
2205
+ });
2206
+ const item2 = await this.findOne({
2207
+ key: key2
2208
+ });
2209
+
2210
+ const set1 = item1 && Array.isArray(item1.value) ? item1.value : [];
2211
+ const set2 = item2 && Array.isArray(item2.value) ? item2.value : [];
2212
+
2213
+ // 合并并去重
2214
+ const combined = [...set1, ...set2];
2215
+ // 使用Map进行对象去重,基于JSON字符串表示
2216
+ const uniqueMap = new Map();
2217
+
2218
+ combined.forEach(item => {
2219
+ const key = JSON.stringify(item);
2220
+ if (!uniqueMap.has(key)) {
2221
+ uniqueMap.set(key, item);
2222
+ }
2223
+ });
1153
2224
 
1154
- const set1 = item1 && Array.isArray(item1.value) ? item1.value : [];
1155
- const set2 = item2 && Array.isArray(item2.value) ? item2.value : [];
2225
+ return Array.from(uniqueMap.values());
2226
+ } catch (error) {
2227
+ $.log.error('[MongoDB] 集合并集失败:', error);
2228
+ return [];
2229
+ }
2230
+ };
1156
2231
 
1157
- // 合并并去重
1158
- const combined = [...set1, ...set2];
1159
- // 使用Map进行对象去重,基于JSON字符串表示
1160
- const uniqueMap = new Map();
2232
+ /**
2233
+ * @description Redis兼容API - 集合成员数量
2234
+ * @param {String} key 键名
2235
+ * @return {Promise<Number>} 成员数量
2236
+ */
2237
+ MongoDB.prototype.scard = async function (key) {
2238
+ try {
2239
+ await this._ensureConnected();
2240
+ const item = await this.findOne({
2241
+ key: key
2242
+ });
2243
+ if (!item || !Array.isArray(item.value)) {
2244
+ return 0;
2245
+ }
1161
2246
 
1162
- combined.forEach(item => {
1163
- const key = JSON.stringify(item);
1164
- if (!uniqueMap.has(key)) {
1165
- uniqueMap.set(key, item);
1166
- }
1167
- });
2247
+ // 使用Set去重并计算大小
2248
+ const uniqueItems = new Set(item.value.map(m => JSON.stringify(m)));
2249
+ return uniqueItems.size;
2250
+ } catch (error) {
2251
+ $.log.error('[MongoDB] 获取集合成员数量失败:', error);
2252
+ return 0;
2253
+ }
2254
+ };
1168
2255
 
1169
- return Array.from(uniqueMap.values());
1170
- } catch (error) {
1171
- $.log.error('[MongoDB] 集合并集失败:', error);
1172
- return [];
2256
+ /**
2257
+ * @description Redis兼容API - 检查是否是集合成员
2258
+ * @param {String} key 键名
2259
+ * @param {*} member 成员
2260
+ * @return {Promise<Boolean>} 是否是成员
2261
+ */
2262
+ MongoDB.prototype.sismember = async function (key, member) {
2263
+ try {
2264
+ this.emit('sismember_before', {
2265
+ key,
2266
+ member
2267
+ });
2268
+ await this._ensureConnected();
2269
+ const item = await this.findOne({
2270
+ key: key
2271
+ });
2272
+
2273
+ // 将成员转换为字符串,与Redis行为一致
2274
+ const memberStr = String(member);
2275
+
2276
+ let result = false;
2277
+ if (item && Array.isArray(item.value)) {
2278
+ // 检查是否存在相同的成员,使用字符串比较
2279
+ result = item.value.includes(memberStr);
1173
2280
  }
2281
+
2282
+ this.emit('sismember_after', {
2283
+ key,
2284
+ member,
2285
+ result
2286
+ });
2287
+ return result;
2288
+ } catch (error) {
2289
+ this.emit('sismember_error', {
2290
+ key,
2291
+ member,
2292
+ error
2293
+ });
2294
+ $.log.error('[MongoDB] 检查集合成员失败:', error);
2295
+ return false;
1174
2296
  }
2297
+ };
1175
2298
 
1176
- /**
1177
- * @description Redis兼容API - 集合成员数量
1178
- * @param {String} key 键名
1179
- * @return {Promise<Number>} 成员数量
1180
- */
1181
- async scard(key) {
1182
- try {
1183
- await this._ensureConnected();
1184
- const item = await this.findOne({ key: key });
1185
- if (!item || !Array.isArray(item.value)) {
1186
- return 0;
2299
+ /**
2300
+ * @description Redis兼容API - 清空所有过期的数据
2301
+ * @return {Promise<Boolean>} 是否成功
2302
+ */
2303
+ MongoDB.prototype.clearExpire = async function () {
2304
+ try {
2305
+ this.emit('clearExpire_before', {});
2306
+ await this._ensureConnected();
2307
+ // 删除所有已过期的数据
2308
+ const result = await this.delete({
2309
+ expire_at: {
2310
+ $exists: true,
2311
+ $lt: new Date()
1187
2312
  }
1188
-
1189
- // 使用Set去重并计算大小
1190
- const uniqueItems = new Set(item.value.map(m => JSON.stringify(m)));
1191
- return uniqueItems.size;
1192
- } catch (error) {
1193
- $.log.error('[MongoDB] 获取集合成员数量失败:', error);
1194
- return 0;
1195
- }
2313
+ });
2314
+ this.emit('clearExpire_after', {
2315
+ result: true
2316
+ });
2317
+ return true;
2318
+ } catch (error) {
2319
+ this.emit('clearExpire_error', {
2320
+ error
2321
+ });
2322
+ $.log.error('[MongoDB] 清空过期数据失败:', error);
2323
+ return false;
1196
2324
  }
1197
-
1198
- /**
1199
- * @description Redis兼容API - 检查是否是集合成员
1200
- * @param {String} key 键名
1201
- * @param {*} member 成员
1202
- * @return {Promise<Boolean>} 是否是成员
1203
- */
1204
- async sismember(key, member) {
1205
- try {
1206
- await this._ensureConnected();
1207
- const item = await this.findOne({ key: key });
1208
- if (!item || !Array.isArray(item.value)) {
1209
- return false;
2325
+ };
2326
+ /**
2327
+ * @param {...String} keys 键名列表
2328
+ * @return {Promise<Array>} 与键顺序对应的值数组,不存在返回undefined
2329
+ */
2330
+ MongoDB.prototype.mget = async function (...keys) {
2331
+ try {
2332
+ await this._ensureConnected();
2333
+ // 确保keys是数组
2334
+ const keyArray = Array.isArray(keys[0]) ? keys[0] : keys;
2335
+ const results = await this.find({
2336
+ key: {
2337
+ $in: keyArray
1210
2338
  }
1211
-
1212
- // 检查是否存在相同的成员
1213
- const memberStr = JSON.stringify(member);
1214
- return item.value.some(m => JSON.stringify(m) === memberStr);
1215
- } catch (error) {
1216
- $.log.error('[MongoDB] 检查集合成员失败:', error);
1217
- return false;
1218
- }
2339
+ });
2340
+ const resultMap = {};
2341
+ results.forEach(item => {
2342
+ resultMap[item.key] = item.value;
2343
+ });
2344
+
2345
+ // 返回与输入键顺序对应的数组,不存在的键对应位置返回undefined
2346
+ return keyArray.map(key => resultMap.hasOwnProperty(key) ? resultMap[key] : undefined);
2347
+ } catch (error) {
2348
+ $.log.error('[MongoDB] 批量获取失败:', error);
2349
+ throw error;
1219
2350
  }
2351
+ };
1220
2352
 
1221
- /**
1222
- * @description Redis兼容API - 批量获取
1223
- * @param {...String} keys 键名列表
1224
- * @return {Promise<Array>} 与键顺序对应的值数组,不存在返回null
1225
- */
1226
- async mget(...keys) {
1227
- try {
1228
- await this._ensureConnected();
1229
- // 确保keys是数组
1230
- const keyArray = Array.isArray(keys[0]) ? keys[0] : keys;
1231
- const results = await this.find({ key: { $in: keyArray } });
1232
- const resultMap = {};
1233
- results.forEach(item => {
1234
- resultMap[item.key] = item.value;
1235
- });
2353
+ /**
2354
+ * @description Redis兼容API - 批量设置
2355
+ * @param {Object} keyValues 键值对象
2356
+ * @return {Promise<string>} 返回'OK'
2357
+ */
2358
+ MongoDB.prototype.mset = async function (keyValues, seconds = 0) {
2359
+ try {
2360
+ // 触发前置事件,允许修改或阻止操作
2361
+ const beforeEventData = {
2362
+ keyValues,
2363
+ seconds,
2364
+ cancel: false
2365
+ };
2366
+ this.emit('mset_before', beforeEventData);
1236
2367
 
1237
- // 返回与输入键顺序对应的数组,不存在的键对应位置返回null
1238
- return keyArray.map(key => resultMap.hasOwnProperty(key) ? resultMap[key] : null);
1239
- } catch (error) {
1240
- $.log.error('[MongoDB] 批量获取失败:', error);
1241
- return [];
2368
+ // 如果事件处理器标记为取消,则返回false
2369
+ if (beforeEventData.cancel) {
2370
+ $.log.debug('[MongoDB] mset操作被事件处理器取消');
2371
+ return false;
1242
2372
  }
1243
- }
1244
2373
 
1245
- /**
1246
- * @description Redis兼容API - 批量设置
1247
- * @param {Object} keyValues 键值对象
1248
- * @return {Promise<Boolean>} 是否成功
1249
- */
1250
- async mset(keyValues) {
1251
- try {
1252
- await this._ensureConnected();
1253
- for (const [key, value] of Object.entries(keyValues)) {
1254
- await this._add(key, value);
1255
- }
1256
- return true;
1257
- } catch (error) {
1258
- $.log.error('[MongoDB] 批量设置失败:', error);
2374
+ // 使用可能被修改的值
2375
+ let {
2376
+ keyValues: finalData,
2377
+ seconds: finalSeconds
2378
+ } = beforeEventData;
2379
+
2380
+ // 参数验证,确保keyValues是对象且不为null
2381
+ if (typeof finalData !== 'object' || finalData === null || Array.isArray(finalData)) {
2382
+ $.log.warn('[MongoDB] mset: 参数必须是非空对象');
1259
2383
  return false;
1260
2384
  }
1261
- }
1262
-
1263
- /**
1264
- * @description Redis兼容API - 有序集合添加成员
1265
- * @param {String} key 键名
1266
- * @param {Number} score 分数
1267
- * @param {*} member 成员
1268
- * @return {Promise<Boolean>} 是否成功
1269
- */
1270
- async zadd(key, score, member) {
1271
- try {
1272
- await this._ensureConnected();
1273
- const item = await this.findOne({ key: key });
1274
- let sortedSet = item && Array.isArray(item.value) ? item.value : [];
1275
2385
 
1276
- // 检查成员是否已存在,如果存在则更新分数
1277
- const memberStr = JSON.stringify(member);
1278
- const existingIndex = sortedSet.findIndex(item => JSON.stringify(item.member) === memberStr);
2386
+ await this._ensureConnected();
2387
+ for (const [key, value] of Object.entries(finalData)) {
2388
+ await this._add(key, value, finalSeconds);
2389
+ }
1279
2390
 
1280
- if (existingIndex >= 0) {
1281
- sortedSet[existingIndex].score = score;
1282
- } else {
1283
- sortedSet.push({ score, member });
1284
- }
2391
+ // 触发后置事件
2392
+ this.emit('mset_after', {
2393
+ keyValues: finalData,
2394
+ seconds: finalSeconds,
2395
+ result: true
2396
+ });
2397
+ return true;
2398
+ } catch (error) {
2399
+ // 触发错误事件
2400
+ this.emit('mset_error', {
2401
+ keyValues,
2402
+ seconds,
2403
+ error
2404
+ });
2405
+ $.log.error('[MongoDB] 批量设置失败:', error);
2406
+ return false;
2407
+ }
2408
+ };
1285
2409
 
1286
- // 按分数排序
1287
- sortedSet.sort((a, b) => a.score - b.score);
1288
- await this._add(key, sortedSet);
1289
- return true;
1290
- } catch (error) {
1291
- $.log.error('[MongoDB] 有序集合添加成员失败:', error);
1292
- return false;
2410
+ /**
2411
+ * @description Redis兼容API - 有序集合添加成员
2412
+ * @param {String} key 键名
2413
+ * @param {Number} score 分数
2414
+ * @param {*} member 成员
2415
+ * @return {Promise<Boolean>} 是否成功
2416
+ */
2417
+ MongoDB.prototype.zadd = async function (key, score, member) {
2418
+ try {
2419
+ await this._ensureConnected();
2420
+ const item = await this.findOne({
2421
+ key: key
2422
+ });
2423
+ let sortedSet = item && Array.isArray(item.value) ? item.value : [];
2424
+
2425
+ // 检查成员是否已存在,如果存在则更新分数
2426
+ const memberStr = JSON.stringify(member);
2427
+ const existingIndex = sortedSet.findIndex(item => JSON.stringify(item.member) === memberStr);
2428
+
2429
+ if (existingIndex >= 0) {
2430
+ sortedSet[existingIndex].score = score;
2431
+ } else {
2432
+ sortedSet.push({
2433
+ score,
2434
+ member
2435
+ });
1293
2436
  }
1294
- }
1295
2437
 
1296
- /**
1297
- * @description Redis兼容API - 有序集合范围查询(按分数)
1298
- * @param {String} key 键名
1299
- * @param {Number} min 最小分数
1300
- * @param {Number} max 最大分数
1301
- * @return {Promise<Array>} 成员数组
1302
- */
1303
- async zrangebyscore(key, min, max) {
1304
- try {
1305
- await this._ensureConnected();
1306
- const item = await this.findOne({ key: key });
1307
- if (!item || !Array.isArray(item.value)) {
1308
- return [];
1309
- }
2438
+ // 按分数排序
2439
+ sortedSet.sort((a, b) => a.score - b.score);
2440
+ await this._add(key, sortedSet);
2441
+ return true;
2442
+ } catch (error) {
2443
+ $.log.error('[MongoDB] 有序集合添加成员失败:', error);
2444
+ return false;
2445
+ }
2446
+ };
1310
2447
 
1311
- // 过滤分数范围内的成员
1312
- return item.value
1313
- .filter(item => item.score >= min && item.score <= max)
1314
- .map(item => item.member);
1315
- } catch (error) {
1316
- $.log.error('[MongoDB] 有序集合范围查询失败:', error);
2448
+ /**
2449
+ * @description Redis兼容API - 有序集合范围查询(按分数)
2450
+ * @param {String} key 键名
2451
+ * @param {Number} min 最小分数
2452
+ * @param {Number} max 最大分数
2453
+ * @return {Promise<Array>} 成员数组
2454
+ */
2455
+ MongoDB.prototype.zrangebyscore = async function (key, min, max) {
2456
+ try {
2457
+ await this._ensureConnected();
2458
+ const item = await this.findOne({
2459
+ key: key
2460
+ });
2461
+ if (!item || !Array.isArray(item.value)) {
1317
2462
  return [];
1318
2463
  }
2464
+
2465
+ // 过滤分数范围内的成员
2466
+ return item.value
2467
+ .filter(item => item.score >= min && item.score <= max)
2468
+ .map(item => item.member);
2469
+ } catch (error) {
2470
+ $.log.error('[MongoDB] 有序集合范围查询失败:', error);
2471
+ return [];
1319
2472
  }
2473
+ };
1320
2474
 
1321
- /**
1322
- * @description Redis兼容API - 有序集合成员数量
1323
- * @param {String} key 键名
1324
- * @return {Promise<Number>} 成员数量
1325
- */
1326
- async zcard(key) {
1327
- try {
1328
- await this._ensureConnected();
1329
- const item = await this.findOne({ key: key });
1330
- if (!item || !Array.isArray(item.value)) {
1331
- return 0;
1332
- }
1333
- return item.value.length;
1334
- } catch (error) {
1335
- $.log.error('[MongoDB] 有序集合成员数量失败:', error);
2475
+ /**
2476
+ * @description Redis兼容API - 有序集合成员数量
2477
+ * @param {String} key 键名
2478
+ * @return {Promise<Number>} 成员数量
2479
+ */
2480
+ MongoDB.prototype.zcard = async function (key) {
2481
+ try {
2482
+ await this._ensureConnected();
2483
+ const item = await this.findOne({
2484
+ key: key
2485
+ });
2486
+ if (!item || !Array.isArray(item.value)) {
1336
2487
  return 0;
1337
2488
  }
2489
+ return item.value.length;
2490
+ } catch (error) {
2491
+ $.log.error('[MongoDB] 有序集合成员数量失败:', error);
2492
+ return 0;
1338
2493
  }
2494
+ };
1339
2495
 
1340
- /**
1341
- * @description Redis兼容API - 有序集合删除成员
1342
- * @param {String} key 键名
1343
- * @param {*} member 成员
1344
- * @return {Promise<Number>} 删除的成员数量
1345
- */
1346
- async zrem(key, member) {
1347
- try {
1348
- await this._ensureConnected();
1349
- const item = await this.findOne({ key: key });
1350
- if (!item || !Array.isArray(item.value)) {
1351
- return 0;
1352
- }
1353
-
1354
- const memberStr = JSON.stringify(member);
1355
- const filtered = item.value.filter(item => JSON.stringify(item.member) !== memberStr);
1356
- const removedCount = item.value.length - filtered.length;
2496
+ /**
2497
+ * @description Redis兼容API - 有序集合删除成员
2498
+ * @param {String} key 键名
2499
+ * @param {*} member 成员
2500
+ * @return {Promise<Number>} 删除的成员数量
2501
+ */
2502
+ MongoDB.prototype.zrem = async function (key, member) {
2503
+ try {
2504
+ await this._ensureConnected();
2505
+ const item = await this.findOne({
2506
+ key: key
2507
+ });
2508
+ if (!item || !Array.isArray(item.value)) {
2509
+ return 0;
2510
+ }
1357
2511
 
1358
- if (removedCount > 0) {
1359
- await this._add(key, filtered);
1360
- }
2512
+ const memberStr = JSON.stringify(member);
2513
+ const filtered = item.value.filter(item => JSON.stringify(item.member) !== memberStr);
2514
+ const removedCount = item.value.length - filtered.length;
1361
2515
 
1362
- return removedCount;
1363
- } catch (error) {
1364
- $.log.error('[MongoDB] 有序集合删除成员失败:', error);
1365
- return 0;
2516
+ if (removedCount > 0) {
2517
+ await this._add(key, filtered);
1366
2518
  }
1367
- }
1368
2519
 
1369
- /**
1370
- * @description Redis兼容API - 获取成员分数
1371
- * @param {String} key 键名
1372
- * @param {*} member 成员
1373
- * @return {Promise<Number|null>} 分数
1374
- */
1375
- async zscore(key, member) {
1376
- try {
1377
- await this._ensureConnected();
1378
- const item = await this.findOne({ key: key });
1379
- if (!item || !Array.isArray(item.value)) {
1380
- return null;
1381
- }
2520
+ return removedCount;
2521
+ } catch (error) {
2522
+ $.log.error('[MongoDB] 有序集合删除成员失败:', error);
2523
+ return 0;
2524
+ }
2525
+ };
1382
2526
 
1383
- const memberStr = JSON.stringify(member);
1384
- const found = item.value.find(item => JSON.stringify(item.member) === memberStr);
1385
- return found ? found.score : null;
1386
- } catch (error) {
1387
- $.log.error('[MongoDB] 获取成员分数失败:', error);
2527
+ /**
2528
+ * @description Redis兼容API - 获取成员分数
2529
+ * @param {String} key 键名
2530
+ * @param {*} member 成员
2531
+ * @return {Promise<Number|null>} 分数
2532
+ */
2533
+ MongoDB.prototype.zscore = async function (key, member) {
2534
+ try {
2535
+ await this._ensureConnected();
2536
+ const item = await this.findOne({
2537
+ key: key
2538
+ });
2539
+ if (!item || !Array.isArray(item.value)) {
1388
2540
  return null;
1389
2541
  }
1390
- }
1391
2542
 
1392
- /**
1393
- * @description Redis兼容API - 列表弹出头部元素
1394
- * @param {String} key 键名
1395
- * @return {Promise<any|null>} 弹出的元素
1396
- */
1397
- async lpop(key) {
1398
- try {
1399
- await this._ensureConnected();
1400
- const item = await this.findOne({ key: key });
1401
- if (!item || !Array.isArray(item.value) || item.value.length === 0) {
1402
- return null;
1403
- }
2543
+ const memberStr = JSON.stringify(member);
2544
+ const found = item.value.find(item => JSON.stringify(item.member) === memberStr);
2545
+ return found ? found.score : null;
2546
+ } catch (error) {
2547
+ $.log.error('[MongoDB] 获取成员分数失败:', error);
2548
+ return null;
2549
+ }
2550
+ };
1404
2551
 
1405
- const popped = item.value.shift();
1406
- await this._set({ key: key }, { value: item.value });
1407
- return popped;
1408
- } catch (error) {
1409
- $.log.error('[MongoDB] 列表弹出头部元素失败:', error);
2552
+ /**
2553
+ * @description Redis兼容API - 列表弹出头部元素
2554
+ * @param {String} key 键名
2555
+ * @return {Promise<any|null>} 弹出的元素
2556
+ */
2557
+ MongoDB.prototype.lpop = async function (key) {
2558
+ try {
2559
+ await this._ensureConnected();
2560
+ const item = await this.findOne({
2561
+ key: key
2562
+ });
2563
+ if (!item || !Array.isArray(item.value) || item.value.length === 0) {
1410
2564
  return null;
1411
2565
  }
1412
- }
1413
2566
 
1414
- /**
1415
- * @description Redis兼容API - 列表弹出尾部元素
1416
- * @param {String} key 键名
1417
- * @return {Promise<any|null>} 弹出的元素
1418
- */
1419
- async rpop(key) {
1420
- try {
1421
- await this._ensureConnected();
1422
- const item = await this.findOne({ key: key });
1423
- if (!item || !Array.isArray(item.value) || item.value.length === 0) {
1424
- return null;
1425
- }
2567
+ const popped = item.value.shift();
2568
+ await this._set({
2569
+ key: key
2570
+ }, {
2571
+ value: item.value
2572
+ });
2573
+ return popped;
2574
+ } catch (error) {
2575
+ $.log.error('[MongoDB] 列表弹出头部元素失败:', error);
2576
+ return null;
2577
+ }
2578
+ };
1426
2579
 
1427
- const popped = item.value.pop();
1428
- await this._set({ key: key }, { value: item.value });
1429
- return popped;
1430
- } catch (error) {
1431
- $.log.error('[MongoDB] 列表弹出尾部元素失败:', error);
2580
+ /**
2581
+ * @description Redis兼容API - 列表弹出尾部元素
2582
+ * @param {String} key 键名
2583
+ * @return {Promise<any|null>} 弹出的元素
2584
+ */
2585
+ MongoDB.prototype.rpop = async function (key) {
2586
+ try {
2587
+ await this._ensureConnected();
2588
+ const item = await this.findOne({
2589
+ key: key
2590
+ });
2591
+ if (!item || !Array.isArray(item.value) || item.value.length === 0) {
1432
2592
  return null;
1433
2593
  }
1434
- }
1435
2594
 
1436
- /**
1437
- * @description Redis兼容API - 获取集合所有成员
1438
- * @param {String} key 键名
1439
- * @return {Promise<Array>} 成员数组
1440
- */
1441
- async smembers(key) {
1442
- try {
1443
- await this._ensureConnected();
1444
- const item = await this.findOne({ key: key });
1445
- if (!item || !Array.isArray(item.value)) {
1446
- return [];
1447
- }
2595
+ const popped = item.value.pop();
2596
+ await this._set({
2597
+ key: key
2598
+ }, {
2599
+ value: item.value
2600
+ });
2601
+ return popped;
2602
+ } catch (error) {
2603
+ $.log.error('[MongoDB] 列表弹出尾部元素失败:', error);
2604
+ return null;
2605
+ }
2606
+ };
1448
2607
 
1449
- // 去重并返回
1450
- const uniqueMap = new Map();
1451
- item.value.forEach(member => {
1452
- const keyStr = JSON.stringify(member);
1453
- if (!uniqueMap.has(keyStr)) {
1454
- uniqueMap.set(keyStr, member);
1455
- }
1456
- });
1457
- return Array.from(uniqueMap.values());
1458
- } catch (error) {
1459
- $.log.error('[MongoDB] 获取集合所有成员失败:', error);
2608
+ /**
2609
+ * @description Redis兼容API - 获取集合所有成员
2610
+ * @param {String} key 键名
2611
+ * @return {Promise<Array>} 成员数组
2612
+ */
2613
+ MongoDB.prototype.smembers = async function (key) {
2614
+ try {
2615
+ await this._ensureConnected();
2616
+ const item = await this.findOne({
2617
+ key: key
2618
+ });
2619
+ if (!item || !Array.isArray(item.value)) {
1460
2620
  return [];
1461
2621
  }
2622
+
2623
+ // 去重并返回
2624
+ const uniqueMap = new Map();
2625
+ item.value.forEach(member => {
2626
+ const keyStr = JSON.stringify(member);
2627
+ if (!uniqueMap.has(keyStr)) {
2628
+ uniqueMap.set(keyStr, member);
2629
+ }
2630
+ });
2631
+ return Array.from(uniqueMap.values());
2632
+ } catch (error) {
2633
+ $.log.error('[MongoDB] 获取集合所有成员失败:', error);
2634
+ return [];
1462
2635
  }
1463
- }
2636
+ };
1464
2637
 
1465
2638
  /**
1466
- * @description 设置配置
1467
- * @param {Object|String} cg 配置对象或配置路径
2639
+ * @description 查询单条数据
2640
+ * @param {Object} query 查询条件对象
2641
+ * @param {Object} options 查询选项
2642
+ * @return {Promise<Object|null>} 查询结果
1468
2643
  */
1469
- MongoDB.prototype.setConfig = function (cg) {
1470
- var obj;
1471
- if (typeof (cg) === "string") {
1472
- obj = cg.loadJson();
1473
- } else if (cg) {
1474
- obj = cg;
1475
- }
1476
- else {
1477
- return;
1478
- }
1479
- if (obj) {
1480
- $.push(this.config, obj);
1481
- // 设置新配置后重新验证
1482
- this._validateConfig();
2644
+ MongoDB.prototype.findOne = async function (query, options = {}) {
2645
+ await this._ensureConnected();
2646
+ try {
2647
+ // 确保query是对象
2648
+ if (typeof query !== 'object' || query === null) {
2649
+ return null;
2650
+ }
2651
+ // 不需要额外的sanitize,MongoDB驱动会处理
2652
+ return await this.db.collection(this.table).findOne(query, options);
2653
+ } catch (error) {
2654
+ $.log.error('[MongoDB] 查询单条数据失败:', error);
2655
+ return null;
1483
2656
  }
1484
2657
  };
1485
2658
 
@@ -1521,7 +2694,9 @@ MongoDB.prototype.open = async function () {
1521
2694
  this.connected = false;
1522
2695
  });
1523
2696
 
1524
- $.log.info(`[MongoDB] 成功连接到 ${this.config.host}:${this.config.port}/${this.config.database}`);
2697
+ $.log.info(
2698
+ `[MongoDB] 成功连接到 ${this.config.host}:${this.config.port}/${this.config.database}`
2699
+ );
1525
2700
  return true;
1526
2701
  } catch (error) {
1527
2702
  $.log.error('[MongoDB] MongoDB连接失败:', error);
@@ -1546,10 +2721,10 @@ MongoDB.prototype.close = async function () {
1546
2721
  };
1547
2722
 
1548
2723
  /**
1549
- * @description 创建数据表
1550
- * @param {Object} table 表名
1551
- * @return {Promise|Object} 执行结果
1552
- */
2724
+ * @description 创建数据表
2725
+ * @param {Object} table 表名
2726
+ * @return {Promise|Object} 执行结果
2727
+ */
1553
2728
  MongoDB.prototype.addTable = async function (table) {
1554
2729
  await this._ensureConnected();
1555
2730
  if (table) {
@@ -1573,7 +2748,9 @@ MongoDB.prototype.createIndex = async function (keys, options = {}) {
1573
2748
  throw new Error('数据库未连接或未指定表名');
1574
2749
  }
1575
2750
 
1576
- $.log.info(`[MongoDB] 创建索引: ${this.table}, keys: ${JSON.stringify(keys)}, options: ${JSON.stringify(options)}`);
2751
+ $.log.info(
2752
+ `[MongoDB] 创建索引: ${this.table}, keys: ${JSON.stringify(keys)}, options: ${JSON.stringify(options)}`
2753
+ );
1577
2754
 
1578
2755
  return await this.db.collection(this.table).createIndex(keys, options);
1579
2756
  } catch (error) {
@@ -1600,7 +2777,8 @@ MongoDB.prototype.createTTLIndex = async function (keys, expireAfterSeconds) {
1600
2777
  expireAfterSeconds: expireAfterSeconds
1601
2778
  };
1602
2779
 
1603
- $.log.info(`[MongoDB] 创建TTL索引: ${this.table}, keys: ${JSON.stringify(keys)}, 过期时间: ${expireAfterSeconds}秒`);
2780
+ $.log.info(
2781
+ `[MongoDB] 创建TTL索引: ${this.table}, keys: ${JSON.stringify(keys)}, 过期时间: ${expireAfterSeconds}秒`);
1604
2782
 
1605
2783
  return await this.db.collection(this.table).createIndex(keys, options);
1606
2784
  } catch (error) {
@@ -1610,9 +2788,9 @@ MongoDB.prototype.createTTLIndex = async function (keys, expireAfterSeconds) {
1610
2788
  };
1611
2789
 
1612
2790
  /**
1613
- * @description 获取集合的所有索引
1614
- * @returns {Promise<Array>} 索引信息数组
1615
- */
2791
+ * @description 获取集合的所有索引
2792
+ * @returns {Promise<Array>} 索引信息数组
2793
+ */
1616
2794
  MongoDB.prototype.getIndexes = async function () {
1617
2795
  await this._ensureConnected();
1618
2796
  try {
@@ -1623,7 +2801,7 @@ MongoDB.prototype.getIndexes = async function () {
1623
2801
  $.log.info(`[MongoDB] 获取索引: ${this.table}`);
1624
2802
  return await this.db.collection(this.table).indexes();
1625
2803
  } catch (error) {
1626
- $.log.error(`[MongoDB] 获取索引失败: ${error.message}`);
2804
+ $.log.error(`[MongoDB] [getIndexes] 获取索引失败: ${error.message}`);
1627
2805
  return [];
1628
2806
  }
1629
2807
  };
@@ -1642,11 +2820,11 @@ MongoDB.prototype.dropIndex = async function (indexName) {
1642
2820
  throw new Error('索引名称不能为空');
1643
2821
  }
1644
2822
  try {
1645
- $.log.info(`[MongoDB] 删除索引: ${this.table}, 索引名: ${indexName}`);
2823
+ $.log.info(`[MongoDB] [dropIndex] 删除索引: ${this.table}, 索引名: ${indexName}`);
1646
2824
  await this.db.collection(this.table).dropIndex(indexName);
1647
2825
  return true;
1648
2826
  } catch (error) {
1649
- $.log.error(`[MongoDB] 删除索引失败: ${error.message}`);
2827
+ $.log.error(`[MongoDB] [dropIndex] 删除索引失败: ${error.message}`);
1650
2828
  throw error;
1651
2829
  }
1652
2830
  };
@@ -1661,11 +2839,11 @@ MongoDB.prototype.dropTable = async function () {
1661
2839
  throw new Error('未指定表名');
1662
2840
  }
1663
2841
  try {
1664
- $.log.info(`[MongoDB] 删除集合: ${this.table}`);
2842
+ $.log.info(`[MongoDB] [dropTable] 删除集合: ${this.table}`);
1665
2843
  await this.db.collection(this.table).drop();
1666
2844
  return true;
1667
2845
  } catch (error) {
1668
- $.log.error(`[MongoDB] 删除集合失败: ${error.message}`);
2846
+ $.log.error(`[MongoDB] [dropTable] 删除集合失败: ${error.message}`);
1669
2847
  throw error;
1670
2848
  }
1671
2849
  };
@@ -1679,31 +2857,49 @@ MongoDB.prototype.addObj = async function (obj) {
1679
2857
  await this._ensureConnected();
1680
2858
  try {
1681
2859
  // 触发前置事件
1682
- this.emit('addObj_before', { table: this.table, data: obj });
2860
+ this.emit('addObj_before', {
2861
+ table: this.table,
2862
+ data: obj
2863
+ });
1683
2864
 
1684
2865
  // 执行插入操作
1685
2866
  const result = await this.db.collection(this.table).insertOne(obj);
1686
2867
 
1687
2868
  // 触发后置事件
1688
- this.emit('addObj_after', { table: this.table, data: obj, result });
2869
+ this.emit('addObj_after', {
2870
+ table: this.table,
2871
+ data: obj,
2872
+ result
2873
+ });
1689
2874
 
1690
2875
  return result;
1691
2876
  } catch (error) {
1692
2877
  // 触发错误事件
1693
- this.emit('addObj_error', { table: this.table, data: obj, error });
2878
+ this.emit('addObj_error', {
2879
+ table: this.table,
2880
+ data: obj,
2881
+ error
2882
+ });
1694
2883
  throw error;
1695
2884
  }
1696
2885
  };
1697
2886
 
1698
2887
  /**
1699
- * @description Redis兼容API - 根据键获取值
1700
- * @param {String} key 键名
1701
- * @return {Promise<any|null>} 存储的值,如果不存在则返回null
1702
- */
2888
+ * @description Redis兼容API - 根据键获取值
2889
+ * @param {String} key 键名
2890
+ * @return {Promise<any|null>} 存储的值,如果不存在则返回null
2891
+ */
1703
2892
  MongoDB.prototype.get = async function (key) {
1704
2893
  await this._ensureConnected();
1705
2894
  try {
1706
- const result = await this.db.collection(this.table).findOne({ key: key });
2895
+ // 参数验证
2896
+ if (!key || typeof key !== 'string') {
2897
+ return null;
2898
+ }
2899
+
2900
+ const result = await this.db.collection(this.table).findOne({
2901
+ key: key
2902
+ });
1707
2903
  return result ? result.value : null;
1708
2904
  } catch (error) {
1709
2905
  $.log.error(`[MongoDB] 获取键值失败: ${error.message}`);
@@ -1712,26 +2908,380 @@ MongoDB.prototype.get = async function (key) {
1712
2908
  };
1713
2909
 
1714
2910
  /**
1715
- * @description Redis兼容API - 删除键
1716
- * @param {String} key 键名
1717
- * @return {Promise<Boolean>} 是否删除成功
1718
- */
2911
+ * @description 设置列表
2912
+ * @param {String} key 键名
2913
+ * @param {Array} array 数组值
2914
+ * @param {Number} seconds 过期时间(秒),0表示永不过期
2915
+ * @return {Promise<Number>} 数组长度
2916
+ */
2917
+ MongoDB.prototype.setForList = async function (key, array, seconds = 0) {
2918
+ try {
2919
+ // 参数验证
2920
+ if (!key || typeof key !== 'string') {
2921
+ return 0;
2922
+ }
2923
+
2924
+ // 确保array是数组类型
2925
+ if (!Array.isArray(array)) {
2926
+ throw new Error('value必须是数组类型');
2927
+ }
2928
+
2929
+ // 触发前置事件,允许修改或阻止操作
2930
+ const beforeEventData = {
2931
+ key,
2932
+ value: array,
2933
+ ttl: seconds,
2934
+ cancel: false
2935
+ };
2936
+ this.emit('setForList_before', beforeEventData);
2937
+
2938
+ if (beforeEventData.cancel) {
2939
+ $.log.debug(`[MongoDB] [setForList] setForList操作被事件处理器取消 [${key}]`);
2940
+ return 0;
2941
+ }
2942
+
2943
+ const {
2944
+ key: finalKey,
2945
+ value: finalValue,
2946
+ ttl: finalSeconds
2947
+ } = beforeEventData;
2948
+
2949
+ // 确保连接
2950
+ await this._ensureConnected();
2951
+
2952
+ // 在MongoDB中存储数组
2953
+ const success = await this._add(finalKey, finalValue, finalSeconds);
2954
+ const length = finalValue.length;
2955
+
2956
+ // 触发后置事件
2957
+ this.emit('setForList_after', {
2958
+ key: finalKey,
2959
+ value: finalValue,
2960
+ ttl: finalSeconds,
2961
+ result: length,
2962
+ success
2963
+ });
2964
+ return length;
2965
+ } catch (error) {
2966
+ this.emit('setForList_error', {
2967
+ key,
2968
+ value: array,
2969
+ error
2970
+ });
2971
+ $.log.error(`[MongoDB] [setForList] setForList操作失败: ${error.message}`);
2972
+ return 0;
2973
+ }
2974
+ };
2975
+
2976
+ /**
2977
+ * @description 向列表中添加元素
2978
+ * @param {String} key 键名
2979
+ * @param {*} value 要添加的元素
2980
+ * @param {Number} seconds 过期时间(秒),0表示永不过期
2981
+ * @return {Promise<Number>} 添加后列表的长度
2982
+ */
2983
+ MongoDB.prototype.addForList = async function (key, value, seconds = 0) {
2984
+ try {
2985
+ // 参数验证
2986
+ if (!key || typeof key !== 'string') {
2987
+ return 0;
2988
+ }
2989
+
2990
+ // 触发前置事件,允许修改或阻止操作
2991
+ const beforeEventData = {
2992
+ key,
2993
+ value,
2994
+ ttl: seconds,
2995
+ cancel: false
2996
+ };
2997
+ this.emit('addForList_before', beforeEventData);
2998
+
2999
+ if (beforeEventData.cancel) {
3000
+ $.log.debug(`[MongoDB] addForList操作被事件处理器取消 [${key}]`);
3001
+ return 0;
3002
+ }
3003
+
3004
+ const {
3005
+ key: finalKey,
3006
+ value: finalValue,
3007
+ ttl: finalSeconds
3008
+ } = beforeEventData;
3009
+
3010
+ // 确保连接
3011
+ await this._ensureConnected();
3012
+
3013
+ // 先获取当前数组
3014
+ let currentArray = await this.get(finalKey);
3015
+ if (!Array.isArray(currentArray)) {
3016
+ currentArray = [];
3017
+ }
3018
+
3019
+ // 添加新元素
3020
+ currentArray.push(finalValue);
3021
+
3022
+ // 保存更新后的数组
3023
+ const success = await this.set(finalKey, currentArray, finalSeconds);
3024
+ const length = currentArray.length;
3025
+
3026
+ // 触发后置事件
3027
+ this.emit('addForList_after', {
3028
+ key: finalKey,
3029
+ value: finalValue,
3030
+ ttl: finalSeconds,
3031
+ result: length,
3032
+ success
3033
+ });
3034
+ return length;
3035
+ } catch (error) {
3036
+ this.emit('addForList_error', {
3037
+ key,
3038
+ value,
3039
+ error
3040
+ });
3041
+ $.log.error(`[MongoDB] addForList操作失败: ${error.message}`);
3042
+ return 0;
3043
+ }
3044
+ };
3045
+
3046
+ /**
3047
+ * @description Redis兼容API - 检查列表中是否存在指定元素
3048
+ * @param {String} key 键名
3049
+ * @param {any} value 要检查的元素值
3050
+ * @return {Promise<Boolean>} 存在返回true,不存在返回false
3051
+ */
3052
+ MongoDB.prototype.hasForList = async function (key, value) {
3053
+ try {
3054
+ // 参数验证
3055
+ if (!key || typeof key !== 'string') {
3056
+ return false;
3057
+ }
3058
+
3059
+ // 确保连接
3060
+ await this._ensureConnected();
3061
+
3062
+ // 获取当前数组
3063
+ const array = await this.get(key);
3064
+
3065
+ // 检查是否为数组
3066
+ if (!Array.isArray(array)) {
3067
+ return false;
3068
+ }
3069
+
3070
+ // 使用JSON.stringify比较,确保对象类型也能正确比较
3071
+ const valueStr = JSON.stringify(value);
3072
+ return array.some(item => JSON.stringify(item) === valueStr);
3073
+ } catch (error) {
3074
+ $.log.error(`[MongoDB] hasForList操作失败: ${error.message}`);
3075
+ return false;
3076
+ }
3077
+ };
3078
+
3079
+ /**
3080
+ * @description 获取列表指定范围的元素
3081
+ * @param {String} key 键名
3082
+ * @param {Number} start 起始索引,默认0
3083
+ * @param {Number} stop 结束索引,默认-1(表示到末尾)
3084
+ * @return {Promise<Array>} 元素数组
3085
+ */
3086
+ MongoDB.prototype.getForList = async function (key, start = 0, stop = -1) {
3087
+ try {
3088
+ // 参数验证
3089
+ if (!key || typeof key !== 'string') {
3090
+ return [];
3091
+ }
3092
+
3093
+ // 确保连接
3094
+ await this._ensureConnected();
3095
+
3096
+ // 获取整个数组
3097
+ const array = await this.get(key);
3098
+ const list = Array.isArray(array) ? array : [];
3099
+
3100
+ // 处理Redis风格的索引范围
3101
+ let result = [];
3102
+ if (start >= 0 && stop >= -1) {
3103
+ if (stop === -1) {
3104
+ result = list.slice(start);
3105
+ } else {
3106
+ result = list.slice(start, stop + 1);
3107
+ }
3108
+ }
3109
+
3110
+ return result;
3111
+ } catch (error) {
3112
+ $.log.error(`[MongoDB] getForList操作失败: ${error.message}`);
3113
+ return [];
3114
+ }
3115
+ };
3116
+
3117
+ /**
3118
+ * @description 清空列表,保留键和过期时间
3119
+ * @param {String} key 键名
3120
+ * @return {Promise<Boolean>} 是否成功
3121
+ */
3122
+ MongoDB.prototype.clearForList = async function (key) {
3123
+ try {
3124
+ // 参数验证
3125
+ if (!key || typeof key !== 'string') {
3126
+ return false;
3127
+ }
3128
+
3129
+ // 确保连接
3130
+ await this._ensureConnected();
3131
+
3132
+ // 使用set方法直接设置为空数组,与其他模块行为一致
3133
+ return await this.set(key, []);
3134
+ } catch (error) {
3135
+ $.log.error(`[MongoDB] clearForList操作失败: ${error.message}`);
3136
+ return false;
3137
+ }
3138
+ };
3139
+
3140
+ MongoDB.prototype.has = async function (key) {
3141
+ await this._ensureConnected();
3142
+ try {
3143
+ // 调用exists方法并转换结果为布尔值
3144
+ const exists = await this.exists(key);
3145
+ return exists > 0;
3146
+ } catch (error) {
3147
+ $.log.error(`[MongoDB] 检查键是否存在失败: ${error.message}`);
3148
+ throw error;
3149
+ }
3150
+ };
3151
+
3152
+ MongoDB.prototype.exists = async function (key) {
3153
+ await this._ensureConnected();
3154
+ try {
3155
+ // 检查单个键的情况
3156
+ if (typeof key === 'string') {
3157
+ const result = await this.db.collection(this.table).findOne({
3158
+ key: key
3159
+ }, {
3160
+ projection: {
3161
+ _id: 1
3162
+ }
3163
+ });
3164
+ return result ? 1 : 0;
3165
+ }
3166
+ // 检查数组键的情况
3167
+ else if (Array.isArray(key)) {
3168
+ // 验证数组中的每个元素是否都是非空字符串
3169
+ for (const k of key) {
3170
+ if (!k || typeof k !== 'string') {
3171
+ throw new Error('key must be a non-empty string or array of non-empty strings');
3172
+ }
3173
+ }
3174
+
3175
+ const filter = {
3176
+ key: {
3177
+ $in: key
3178
+ }
3179
+ };
3180
+ const count = await this.db.collection(this.table).countDocuments(filter);
3181
+ return count;
3182
+ } else {
3183
+ throw new Error('key must be a non-empty string or array of non-empty strings');
3184
+ }
3185
+ } catch (error) {
3186
+ $.log.error(`[MongoDB] 检查键是否存在失败: ${error.message}`);
3187
+ throw error;
3188
+ }
3189
+ };
3190
+
3191
+ /**
3192
+ * @description Redis兼容API - 删除键
3193
+ * @param {String|Array<String>} key 键名或键名数组
3194
+ * @return {Promise<Boolean>} 是否删除成功
3195
+ */
1719
3196
  MongoDB.prototype.del = async function (key) {
1720
3197
  await this._ensureConnected();
1721
3198
  try {
1722
- // 触发前置事件
1723
- this.emit('del_before', { table: this.table, key: key });
3199
+ // 键名必须是非空字符串或非空字符串数组
3200
+ if (!key) {
3201
+ $.log.error('[MongoDB] 参数错误: key必须是非空字符串或非空字符串数组');
3202
+ return false;
3203
+ }
3204
+
3205
+ // 如果是单个键,验证是否为非空字符串
3206
+ if (typeof key === 'string') {
3207
+ if (!key) {
3208
+ $.log.error('[MongoDB] 参数错误: key必须是非空字符串');
3209
+ return false;
3210
+ }
3211
+ }
3212
+ // 如果是数组,验证每个元素是否为非空字符串
3213
+ else if (Array.isArray(key)) {
3214
+ for (const k of key) {
3215
+ if (!k || typeof k !== 'string') {
3216
+ $.log.error('[MongoDB] 参数错误: key必须是非空字符串或非空字符串数组');
3217
+ return false;
3218
+ }
3219
+ }
3220
+ } else {
3221
+ $.log.error('[MongoDB] 参数错误: key必须是非空字符串或非空字符串数组');
3222
+ return false;
3223
+ }
3224
+
3225
+ // 触发前置事件,允许修改或阻止操作
3226
+ const beforeEventData = {
3227
+ key,
3228
+ cancel: false
3229
+ };
3230
+ this.emit('del_before', beforeEventData);
3231
+
3232
+ // 如果事件处理器标记为取消,则返回false
3233
+ if (beforeEventData.cancel) {
3234
+ $.log.debug(`[MongoDB] del操作被事件处理器取消 [${key}]`);
3235
+ return false;
3236
+ }
1724
3237
 
1725
- const result = await this.db.collection(this.table).deleteOne({ key: key });
3238
+ // 使用可能被修改的值
3239
+ let {
3240
+ key: finalKey
3241
+ } = beforeEventData;
3242
+
3243
+ let success = false;
3244
+ let deleted = [];
3245
+
3246
+ if (Array.isArray(finalKey)) {
3247
+ // 批量删除
3248
+ success = true;
3249
+ for (const k of finalKey) {
3250
+ const result = await this.db.collection(this.table).deleteOne({
3251
+ key: k
3252
+ });
3253
+ if (result.deletedCount === 1) {
3254
+ deleted.push(k);
3255
+ } else {
3256
+ success = false;
3257
+ }
3258
+ }
3259
+ } else {
3260
+ // 单个删除
3261
+ const result = await this.db.collection(this.table).deleteOne({
3262
+ key: finalKey
3263
+ });
3264
+ success = result.deletedCount === 1;
3265
+ if (success) {
3266
+ deleted.push(finalKey);
3267
+ }
3268
+ }
1726
3269
 
1727
3270
  // 触发后置事件
1728
- this.emit('del_after', { table: this.table, key: key, result });
3271
+ this.emit('del_after', {
3272
+ key: finalKey,
3273
+ success,
3274
+ deleted
3275
+ });
1729
3276
 
1730
- return result.deletedCount > 0;
3277
+ return success;
1731
3278
  } catch (error) {
1732
3279
  // 触发错误事件
1733
- this.emit('del_error', { table: this.table, key: key, error });
1734
- $.log.error(`[MongoDB] 删除键失败: ${error.message}`);
3280
+ this.emit('del_error', {
3281
+ key,
3282
+ error: error
3283
+ });
3284
+ $.log.error('[MongoDB] 删除缓存失败:', error);
1735
3285
  return false;
1736
3286
  }
1737
3287
  };
@@ -1740,34 +3290,81 @@ MongoDB.prototype.del = async function (key) {
1740
3290
  * @description Redis兼容API - 添加键值对
1741
3291
  * @param {String} key 键名
1742
3292
  * @param {any} value 值
3293
+ * @param {Number} seconds 过期时间(秒)
1743
3294
  * @return {Promise<Boolean>} 是否添加成功(如果键已存在则返回false)
1744
3295
  */
1745
- MongoDB.prototype.add = async function (key, value) {
3296
+ MongoDB.prototype.add = async function (key, value, seconds) {
1746
3297
  await this._ensureConnected();
1747
3298
  try {
1748
- // 触发前置事件 - 无论键是否存在都触发
1749
- this.emit('add_before', { table: this.table, key: key, value: value });
3299
+ // 触发前置事件,允许修改或阻止操作
3300
+ const beforeEventData = {
3301
+ key,
3302
+ value,
3303
+ ttl: seconds,
3304
+ cancel: false
3305
+ };
3306
+ this.emit('add_before', beforeEventData);
3307
+
3308
+ // 如果事件处理器标记为取消,则返回false
3309
+ if (beforeEventData.cancel) {
3310
+ $.log.debug(`[MongoDB] add操作被事件处理器取消 [${key}]`);
3311
+ return false;
3312
+ }
3313
+
3314
+ // 使用可能被修改的值
3315
+ let {
3316
+ key: finalKey,
3317
+ value: finalValue,
3318
+ ttl: finalSeconds
3319
+ } = beforeEventData;
1750
3320
 
1751
3321
  // 检查键是否已存在
1752
- const exists = await this.db.collection(this.table).findOne({ key: key });
3322
+ const exists = await this.db.collection(this.table).findOne({
3323
+ key: finalKey
3324
+ });
1753
3325
  if (exists) {
1754
3326
  // 键已存在时触发后置事件,但返回false
1755
- this.emit('add_after', { table: this.table, key: key, value: value, result: null, exists: true });
3327
+ this.emit('add_after', {
3328
+ key: finalKey,
3329
+ value: finalValue,
3330
+ ttl: finalSeconds,
3331
+ success: false
3332
+ });
1756
3333
  return false;
1757
3334
  }
1758
3335
 
1759
- const data = { key: key, value: value };
3336
+ const data = {
3337
+ key: finalKey,
3338
+ value: finalValue
3339
+ };
3340
+
3341
+ // 如果设置了过期时间
3342
+ if (finalSeconds) {
3343
+ const now = new Date();
3344
+ data.expire_at = new Date(now.getTime() + finalSeconds * 1000);
3345
+ }
1760
3346
 
1761
3347
  // 添加键值对
1762
3348
  const result = await this.db.collection(this.table).insertOne(data);
3349
+ const success = result.acknowledged;
1763
3350
 
1764
3351
  // 触发后置事件
1765
- this.emit('add_after', { table: this.table, key: key, value: value, result, exists: false });
1766
-
1767
- return result.acknowledged;
3352
+ this.emit('add_after', {
3353
+ key: finalKey,
3354
+ value: finalValue,
3355
+ ttl: finalSeconds,
3356
+ success
3357
+ });
3358
+
3359
+ return success;
1768
3360
  } catch (error) {
1769
3361
  // 触发错误事件
1770
- this.emit('add_error', { table: this.table, key: key, value: value, error });
3362
+ this.emit('add_error', {
3363
+ key,
3364
+ value,
3365
+ ttl: seconds,
3366
+ error
3367
+ });
1771
3368
  $.log.error(`[MongoDB] 添加键值失败: ${error.message}`);
1772
3369
  return false;
1773
3370
  }
@@ -1779,26 +3376,81 @@ MongoDB.prototype.add = async function (key, value) {
1779
3376
  * @param {any} value 值
1780
3377
  * @return {Promise<Boolean>} 是否设置成功
1781
3378
  */
1782
- MongoDB.prototype.set = async function (key, value) {
3379
+ MongoDB.prototype.set = async function (key, value, seconds) {
1783
3380
  await this._ensureConnected();
1784
3381
  try {
1785
- // 触发前置事件
1786
- this.emit('set_before', { table: this.table, key: key, value: value });
3382
+ // 键名必须是非空字符串
3383
+ if (!key || typeof key !== 'string') {
3384
+ $.log.error('[MongoDB] 参数错误: key必须是非空字符串');
3385
+ return false;
3386
+ }
3387
+
3388
+ // 触发前置事件,允许修改或阻止操作
3389
+ const beforeEventData = {
3390
+ key,
3391
+ value,
3392
+ ttl: seconds,
3393
+ cancel: false
3394
+ };
3395
+ this.emit('set_before', beforeEventData);
3396
+
3397
+ // 如果事件处理器标记为取消,则返回false
3398
+ if (beforeEventData.cancel) {
3399
+ $.log.debug(`[MongoDB] set操作被事件处理器取消 [${key}]`);
3400
+ return false;
3401
+ }
3402
+
3403
+ // 使用可能被修改的值
3404
+ let {
3405
+ key: finalKey,
3406
+ value: finalValue,
3407
+ ttl: finalSeconds
3408
+ } = beforeEventData;
3409
+
3410
+ // 准备更新数据,包括值和可能的过期时间
3411
+ const updateData = {
3412
+ $set: {
3413
+ value: finalValue
3414
+ }
3415
+ };
3416
+
3417
+ // 如果指定了过期时间,设置过期时间
3418
+ if (finalSeconds && typeof finalSeconds === 'number' && finalSeconds > 0) {
3419
+ const expireTime = new Date(Date.now() + finalSeconds * 1000);
3420
+ updateData.$set.expire_at = expireTime;
3421
+ } else {
3422
+ // 如果没有指定过期时间,移除可能存在的过期字段
3423
+ updateData.$unset = {
3424
+ expire_at: ""
3425
+ };
3426
+ }
1787
3427
 
1788
3428
  // 更新或插入键值对
1789
- const result = await this.db.collection(this.table).updateOne(
1790
- { key: key },
1791
- { $set: { value: value } },
1792
- { upsert: true }
3429
+ const result = await this.db.collection(this.table).updateOne({
3430
+ key: finalKey
3431
+ },
3432
+ updateData, {
3433
+ upsert: true
3434
+ }
1793
3435
  );
1794
3436
 
1795
3437
  // 触发后置事件
1796
- this.emit('set_after', { table: this.table, key: key, value: value, result });
3438
+ this.emit('set_after', {
3439
+ key: finalKey,
3440
+ value: finalValue,
3441
+ ttl: finalSeconds,
3442
+ success: result.acknowledged
3443
+ });
1797
3444
 
1798
3445
  return result.acknowledged;
1799
3446
  } catch (error) {
1800
3447
  // 触发错误事件
1801
- this.emit('set_error', { table: this.table, key: key, value: value, error });
3448
+ this.emit('set_error', {
3449
+ key,
3450
+ value,
3451
+ ttl: seconds,
3452
+ error
3453
+ });
1802
3454
  $.log.error(`[MongoDB] 设置键值失败: ${error.message}`);
1803
3455
  return false;
1804
3456
  }
@@ -1813,18 +3465,29 @@ MongoDB.prototype.addList = async function (list) {
1813
3465
  await this._ensureConnected();
1814
3466
  try {
1815
3467
  // 触发前置事件
1816
- this.emit('addList_before', { table: this.table, data: list });
3468
+ this.emit('addList_before', {
3469
+ table: this.table,
3470
+ data: list
3471
+ });
1817
3472
 
1818
3473
  // 执行插入操作
1819
3474
  const result = await this.db.collection(this.table).insertMany(list);
1820
3475
 
1821
3476
  // 触发后置事件
1822
- this.emit('addList_after', { table: this.table, data: list, result });
3477
+ this.emit('addList_after', {
3478
+ table: this.table,
3479
+ data: list,
3480
+ result
3481
+ });
1823
3482
 
1824
3483
  return result;
1825
3484
  } catch (error) {
1826
3485
  // 触发错误事件
1827
- this.emit('addList_error', { table: this.table, data: list, error });
3486
+ this.emit('addList_error', {
3487
+ table: this.table,
3488
+ data: list,
3489
+ error
3490
+ });
1828
3491
  throw error;
1829
3492
  }
1830
3493
  };
@@ -1838,21 +3501,34 @@ MongoDB.prototype.delete = async function (query) {
1838
3501
  await this._ensureConnected();
1839
3502
  try {
1840
3503
  // 如果query是字符串,则作为键名处理
1841
- const safeQuery = typeof query === 'string' ? { key: query } : query;
3504
+ const safeQuery = typeof query === 'string' ? {
3505
+ key: query
3506
+ } : query;
1842
3507
 
1843
3508
  // 触发前置事件
1844
- this.emit('delete_before', { table: this.table, query: safeQuery });
3509
+ this.emit('delete_before', {
3510
+ table: this.table,
3511
+ query: safeQuery
3512
+ });
1845
3513
 
1846
3514
  // 执行删除操作
1847
3515
  const result = await this.db.collection(this.table).deleteMany(safeQuery);
1848
3516
 
1849
3517
  // 触发后置事件
1850
- this.emit('delete_after', { table: this.table, query: safeQuery, result });
3518
+ this.emit('delete_after', {
3519
+ table: this.table,
3520
+ query: safeQuery,
3521
+ result
3522
+ });
1851
3523
 
1852
3524
  return result;
1853
3525
  } catch (error) {
1854
3526
  // 触发错误事件
1855
- this.emit('delete_error', { table: this.table, query, error });
3527
+ this.emit('delete_error', {
3528
+ table: this.table,
3529
+ query,
3530
+ error
3531
+ });
1856
3532
  throw error;
1857
3533
  }
1858
3534
  };
@@ -1868,36 +3544,63 @@ MongoDB.prototype.setting = async function (query, body, options = {}) {
1868
3544
  await this._ensureConnected();
1869
3545
  try {
1870
3546
  // 处理Redis兼容模式:如果query是字符串,则将query作为键名
1871
- const safeQuery = typeof query === 'string' ? { key: query } : query;
3547
+ const safeQuery = typeof query === 'string' ? {
3548
+ key: query
3549
+ } : query;
1872
3550
 
1873
3551
  // 默认使用$set操作符进行部分更新
1874
3552
  let updateBody;
1875
3553
  if (typeof query === 'string') {
1876
3554
  // Redis兼容模式:如果是字符串键名,直接设置value
1877
- updateBody = { $set: { value: body } };
3555
+ updateBody = {
3556
+ $set: {
3557
+ value: body
3558
+ }
3559
+ };
1878
3560
  } else if (!body.$set && !body.$inc && !body.$push) {
1879
- updateBody = { $set: body };
3561
+ updateBody = {
3562
+ $set: body
3563
+ };
1880
3564
  } else {
1881
3565
  updateBody = body;
1882
3566
  }
1883
3567
 
1884
3568
  // 默认更新多条
1885
- const updateOptions = Object.assign({ multi: true }, options);
3569
+ const updateOptions = Object.assign({
3570
+ multi: true
3571
+ }, options);
1886
3572
 
1887
3573
  // 触发前置事件
1888
- this.emit('setting_before', { table: this.table, query: safeQuery, update: updateBody, options: updateOptions });
3574
+ this.emit('setting_before', {
3575
+ table: this.table,
3576
+ query: safeQuery,
3577
+ update: updateBody,
3578
+ options: updateOptions
3579
+ });
1889
3580
 
1890
3581
  // 执行更新操作
1891
3582
  const result = await this.db.collection(this.table).updateMany(safeQuery, updateBody, updateOptions);
1892
3583
 
1893
3584
  // 触发后置事件
1894
- this.emit('setting_after', { table: this.table, query: safeQuery, update: updateBody, options: updateOptions, result });
3585
+ this.emit('setting_after', {
3586
+ table: this.table,
3587
+ query: safeQuery,
3588
+ update: updateBody,
3589
+ options: updateOptions,
3590
+ result
3591
+ });
1895
3592
 
1896
3593
  // Redis兼容模式返回布尔值,否则返回完整结果
1897
3594
  return typeof query === 'string' ? result.modifiedCount > 0 : result;
1898
3595
  } catch (error) {
1899
3596
  // 触发错误事件
1900
- this.emit('setting_error', { table: this.table, query, update: body, options, error });
3597
+ this.emit('setting_error', {
3598
+ table: this.table,
3599
+ query,
3600
+ update: body,
3601
+ options,
3602
+ error
3603
+ });
1901
3604
 
1902
3605
  // Redis兼容模式返回布尔值
1903
3606
  return typeof query === 'string' ? false : null;
@@ -1923,22 +3626,6 @@ MongoDB.prototype.find = async function (query, options = {}) {
1923
3626
  }
1924
3627
  };
1925
3628
 
1926
- /**
1927
- * @description 查询单条数据
1928
- * @param {Object} query 查询条件对象
1929
- * @param {Object} options 查询选项
1930
- * @return {Promise<Object|null>} 查询结果
1931
- */
1932
- MongoDB.prototype.findOne = async function (query, options = {}) {
1933
- await this._ensureConnected();
1934
- try {
1935
- const safeQuery = this._sanitizeQuery(query);
1936
- return await this.db.collection(this.table).findOne(safeQuery, options);
1937
- } catch (error) {
1938
- $.log.error('[MongoDB] 查询单条数据失败:', error);
1939
- return null;
1940
- }
1941
- };
1942
3629
 
1943
3630
  /**
1944
3631
  * @description 计算文档数量
@@ -1968,7 +3655,8 @@ MongoDB.prototype._sanitizeQuery = function (query) {
1968
3655
 
1969
3656
  for (const [key, value] of Object.entries(query)) {
1970
3657
  // 防止NoSQL注入,如 {$ne: null} 等操作符注入
1971
- if (key.startsWith('$') && typeof key === 'string' && !Array.isArray(value) && typeof value === 'object' && value !== null) {
3658
+ if (key.startsWith('$') && typeof key === 'string' && !Array.isArray(value) && typeof value === 'object' &&
3659
+ value !== null) {
1972
3660
  $.log.warn('[MongoDB] 检测到可能的操作符注入尝试:', key);
1973
3661
  continue;
1974
3662
  }
@@ -1989,7 +3677,7 @@ MongoDB.prototype.startTransaction = async function () {
1989
3677
  session.startTransaction();
1990
3678
  return session;
1991
3679
  } catch (error) {
1992
- $.log.error('[MongoDB] 启动事务失败:', error);
3680
+ $.log.error('[MongoDB] [startTransaction] 启动事务失败:', error);
1993
3681
  throw error;
1994
3682
  }
1995
3683
  };
@@ -2004,7 +3692,7 @@ MongoDB.prototype.commitTransaction = async function (session) {
2004
3692
  await session.commitTransaction();
2005
3693
  await session.endSession();
2006
3694
  } catch (error) {
2007
- $.log.error('[MongoDB] 提交事务失败:', error);
3695
+ $.log.error('[MongoDB] [commitTransaction] 提交事务失败:', error);
2008
3696
  throw error;
2009
3697
  }
2010
3698
  };
@@ -2019,7 +3707,7 @@ MongoDB.prototype.rollbackTransaction = async function (session) {
2019
3707
  await session.abortTransaction();
2020
3708
  await session.endSession();
2021
3709
  } catch (error) {
2022
- $.log.error('[MongoDB] 回滚事务失败:', error);
3710
+ $.log.error('[MongoDB] [rollbackTransaction] 回滚事务失败:', error);
2023
3711
  throw error;
2024
3712
  }
2025
3713
  };
@@ -2051,7 +3739,14 @@ function mongodb_admin(scope, config) {
2051
3739
  }
2052
3740
  var obj = $.pool.mongoDB[scope];
2053
3741
  if (!obj) {
2054
- $.pool.mongoDB[scope] = new MongoDB(scope, config);
3742
+ // 确保配置对象存在并有必要的字段
3743
+ const dbConfig = {
3744
+ host: 'localhost',
3745
+ port: 27017,
3746
+ database: scope,
3747
+ ...(typeof config === 'object' && config ? config : {})
3748
+ };
3749
+ $.pool.mongoDB[scope] = new MongoDB(dbConfig);
2055
3750
  obj = $.pool.mongoDB[scope];
2056
3751
  }
2057
3752
  return obj;
@@ -2060,4 +3755,4 @@ function mongodb_admin(scope, config) {
2060
3755
  /**
2061
3756
  * @module 导出MongoDB管理器
2062
3757
  */
2063
- exports.mongodb_admin = mongodb_admin;
3758
+ exports.mongodb_admin = mongodb_admin;