mm_mongodb 1.4.7 → 1.4.9

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,38 +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
- * @param {String} dir 当前路径
17
+ * @param {Object} config 配置参数
25
18
  * @constructor
26
19
  */
27
- constructor(scope, dir, config) {
20
+ constructor(config) {
28
21
  super();
29
- // 作用域
30
- this.scope = scope || ($.val && $.val.scope ? String($.val.scope) : 'default');
31
- // 当前目录
32
- this.dir = dir || __dirname;
33
22
 
34
- // 从环境变量加载配置
35
- const envConfig = this._loadEnvConfig();
36
-
37
- this.config = Object.assign({
23
+ // 合并配置,确保使用对象扩展运算符正确合并
24
+ this.config = {
38
25
  // 服务器地址
39
26
  host: "localhost",
40
27
  // 端口号
@@ -47,16 +34,10 @@ class MongoDB extends eventEmitter {
47
34
  password: "",
48
35
  // 连接选项
49
36
  options: {
50
- // 移除已废弃的选项
51
- // 使用MongoDB驱动推荐的配置
52
- // 连接池配置
53
37
  minPoolSize: 5,
54
38
  maxPoolSize: 20,
55
- // 连接超时时间
56
39
  connectTimeoutMS: 10000,
57
- // 服务器选择超时
58
40
  serverSelectionTimeoutMS: 5000,
59
- // 重试策略
60
41
  retryWrites: true
61
42
  },
62
43
  // 重试配置
@@ -64,10 +45,7 @@ class MongoDB extends eventEmitter {
64
45
  maxAttempts: 3,
65
46
  delayMS: 1000
66
47
  }
67
- }, envConfig, config);
68
-
69
- // 验证配置
70
- this._validateConfig();
48
+ };
71
49
 
72
50
  // 连接地址
73
51
  this.url = null;
@@ -92,1392 +70,2589 @@ class MongoDB extends eventEmitter {
92
70
  // 连接重试次数
93
71
  this.connectAttempts = 0;
94
72
  this.maxConnectAttempts = 3;
95
- }
96
-
97
- /**
98
- * @private
99
- * @description 从环境变量加载配置
100
- * @return {Object} 配置对象
101
- */
102
- _loadEnvConfig() {
103
- const envConfig = {};
104
-
105
- if (process.env.MONGODB_HOST) envConfig.host = process.env.MONGODB_HOST;
106
- if (process.env.MONGODB_PORT) envConfig.port = parseInt(process.env.MONGODB_PORT, 10);
107
- if (process.env.MONGODB_DATABASE) envConfig.database = process.env.MONGODB_DATABASE;
108
- if (process.env.MONGODB_USER) envConfig.user = process.env.MONGODB_USER;
109
- if (process.env.MONGODB_PASSWORD) envConfig.password = process.env.MONGODB_PASSWORD;
110
73
 
111
- return envConfig;
74
+ this.setConfig(config);
75
+ // 验证配置
76
+ this._validateConfig();
112
77
  }
78
+ }
113
79
 
114
- /**
115
- * @private
116
- * @description 验证配置有效性
117
- * @throws {Error} 配置无效时抛出错误
118
- */
119
- _validateConfig() {
120
- if (!this.config.host || typeof this.config.host !== 'string') {
121
- throw new Error('MongoDB配置无效:host必须是有效的字符串');
122
- }
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
+ }
123
89
 
124
- if (!Number.isInteger(this.config.port) || this.config.port < 1 || this.config.port > 65535) {
125
- throw new Error('MongoDB配置无效:port必须是1-65535之间的整数');
126
- }
90
+ if (!Number.isInteger(this.config.port) || this.config.port < 1 || this.config.port > 65535) {
91
+ throw new Error('MongoDB配置无效:port必须是1-65535之间的整数');
92
+ }
127
93
 
128
- if (!this.config.database || typeof this.config.database !== 'string') {
129
- throw new Error('MongoDB配置无效:database必须是有效的字符串');
130
- }
94
+ if (!this.config.database || typeof this.config.database !== 'string') {
95
+ throw new Error('MongoDB配置无效:database必须是有效的字符串');
131
96
  }
97
+ };
132
98
 
133
- /**
134
- * @private
135
- * @description 执行带重试的操作
136
- * @param {Function} operation 要执行的操作
137
- * @param {String} operationName 操作名称
138
- * @return {Promise} 操作结果
139
- */
140
- async _withRetry(operation, operationName) {
141
- const maxAttempts = this.config.retry?.maxAttempts || 3;
142
- const delayMS = this.config.retry?.delayMS || 1000;
143
- 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;
144
110
 
145
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
146
- try {
147
- return await operation();
148
- } catch (error) {
149
- lastError = error;
150
- $.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);
151
117
 
152
- // 如果不是最后一次尝试,则等待后重试
153
- if (attempt < maxAttempts) {
154
- await new Promise(resolve => setTimeout(resolve, delayMS));
155
- }
118
+ // 如果不是最后一次尝试,则等待后重试
119
+ if (attempt < maxAttempts) {
120
+ await new Promise(resolve => setTimeout(resolve, delayMS));
156
121
  }
157
122
  }
158
-
159
- // 所有重试都失败,抛出最后一个错误
160
- throw lastError;
161
123
  }
162
124
 
163
- /**
164
- * @description 确保数据库连接已建立
165
- * @private
166
- * @return {Promise<void>}
167
- */
168
- async _ensureConnected() {
169
- // 如果已有连接Promise,等待其完成
170
- if (this.connectPromise) {
171
- return this.connectPromise;
172
- }
173
-
174
- // 创建新的连接Promise
175
- this.connectPromise = (async () => {
176
- try {
177
- // 检查连接状态
178
- if (!this.db || !this.connected) {
179
- // 记录连接尝试
180
- this.connectAttempts++;
181
-
182
- // 尝试连接
183
- await this.open();
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
+ }
184
138
 
185
- // 检查连接是否成功
186
- if (this.db && this.db.collection) {
187
- this.connected = true;
188
- 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
+ }
189
164
 
190
- // 确保索引已创建
165
+ // 确保索引已创建
166
+ if (this.collection) {
191
167
  await this._ensureIndexes();
192
-
193
- $.log.info(`[MongoDB] 成功连接到数据库,表名: ${this.table}`);
194
- } else {
195
- throw new Error('数据库连接失败,db对象为空');
196
168
  }
197
- }
198
- } catch (error) {
199
- $.log.error(`[MongoDB] 连接失败 (尝试 ${this.connectAttempts}/${this.maxConnectAttempts}):`, error.message);
200
-
201
- // 重置连接Promise,允许下次重试
202
- this.connectPromise = null;
203
169
 
204
- // 如果尝试次数未超过限制,抛出错误让上层处理
205
- if (this.connectAttempts < this.maxConnectAttempts) {
206
- throw error;
170
+ $.log.info(`[MongoDB] 成功连接到数据库,表名: ${this.table}`);
207
171
  } else {
208
- // 超过重试次数,记录严重错误
209
- $.log.error('[MongoDB] 达到最大连接重试次数,连接失败');
210
- throw new Error('数据库连接失败,已达到最大重试次数');
172
+ $.log.error(`[MongoDB] [_ensureConnected] 连接失败,db对象为空或不具有collection方法`);
173
+ throw new Error('数据库连接失败,db对象为空');
211
174
  }
212
175
  }
213
- })();
176
+ } catch (error) {
177
+ $.log.error(`[MongoDB] 连接失败 (尝试 ${this.connectAttempts}/${this.maxConnectAttempts}):`,
178
+ error.message);
214
179
 
215
- return this.connectPromise;
216
- }
180
+ // 重置连接Promise,允许下次重试
181
+ this.connectPromise = null;
217
182
 
218
- /**
219
- * @description 确保索引已创建
220
- * @private
221
- * @return {Promise<void>}
222
- */
223
- async _ensureIndexes() {
224
- try {
225
- // 确保db对象有效
226
- if (!this.db || !this.db.collection) {
227
- throw new Error('数据库连接无效,无法创建索引');
183
+ // 如果尝试次数未超过限制,抛出错误让上层处理
184
+ if (this.connectAttempts < this.maxConnectAttempts) {
185
+ throw error;
186
+ } else {
187
+ // 超过重试次数,记录严重错误
188
+ $.log.error('[MongoDB] 达到最大连接重试次数,连接失败');
189
+ throw new Error('数据库连接失败,已达到最大重试次数');
228
190
  }
191
+ }
192
+ })();
229
193
 
230
- // 创建TTL索引用于自动过期
231
- await this.db.collection(this.table).createIndex(
232
- { expireAt: 1 },
233
- { expireAfterSeconds: 0 }
234
- );
235
- // 创建_id索引(通常自动创建,但显式指定更清晰)
236
- await this.db.collection(this.table).createIndex({ _id: 1 });
237
- } catch (error) {
238
- // 忽略索引已存在的错误
239
- if (error.code !== 85) {
240
- $.log.error('[MongoDB] 创建索引失败:', error);
241
- }
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('数据库连接无效,无法创建索引');
242
207
  }
243
- }
244
208
 
245
- /**
246
- * @description Redis兼容API - 获取缓存值
247
- * @param {String} key 键名
248
- * @return {Promise<any>} 缓存值
249
- */
250
- async get(key) {
251
- try {
252
- await this._ensureConnected();
253
- const result = await this.findOne({ key: key });
254
- return result ? result.value : null;
255
- } catch (error) {
256
- $.log.error('[MongoDB] 获取缓存失败:', error);
257
- return null;
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);
258
223
  }
259
224
  }
225
+ };
260
226
 
261
- /**
262
- * @description Redis兼容API - 清空所有缓存
263
- * @return {Promise<Boolean>} 是否成功
264
- */
265
- async clear() {
266
- try {
267
- await this._ensureConnected();
268
- await this.del({});
269
- return true;
270
- } catch (error) {
271
- $.log.error('[MongoDB] 清空缓存失败:', error);
272
- return false;
273
- }
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;
274
242
  }
243
+ };
275
244
 
276
- /**
277
- * @description Redis兼容API - 获取所有键
278
- * @param {String} pattern 匹配模式
279
- * @return {Promise<Array>} 键名数组
280
- */
281
- async keys(pattern = '*') {
282
- try {
283
- await this._ensureConnected();
284
- let query = {};
285
- if (pattern !== '*') {
286
- // 简单的通配符支持
287
- const regexPattern = pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
288
- query.key = { $regex: new RegExp(`^${regexPattern}$`) };
289
- }
290
- const results = await this.find(query, { projection: { key: 1 } });
291
- return results.map(item => item.key);
292
- } catch (error) {
293
- $.log.error('[MongoDB] 获取键列表失败:', error);
294
- return [];
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;
295
279
  }
280
+ } catch (error) {
281
+ this.emit('clear_error', {
282
+ error,
283
+ pattern
284
+ });
285
+ $.log.error('[MongoDB] 清空缓存失败:', error);
286
+ return false;
296
287
  }
288
+ };
297
289
 
298
- /**
299
- * @description Redis兼容API - 设置过期时间
300
- * @param {String} key 键名
301
- * @param {Number} seconds 过期时间(秒)
302
- * @return {Promise<Boolean>} 是否成功
303
- */
304
- async ttl(key, seconds) {
305
- try {
306
- await this._ensureConnected();
307
- if (!seconds && seconds !== 0) {
308
- // 获取剩余过期时间
309
- const item = await this.findOne({ key: key });
310
- if (!item || !item.expireAt) return -1;
311
- const ttl = Math.floor((item.expireAt.getTime() - Date.now()) / 1000);
312
- return ttl > 0 ? ttl : -2; // -2 表示已过期
313
- } else {
314
- // 设置过期时间
315
- const expireAt = seconds === 0 ? null : new Date(Date.now() + seconds * 1000);
316
- const result = await this._set({ key: key }, { expireAt: expireAt });
317
- return result.modifiedCount > 0;
318
- }
319
- } catch (error) {
320
- $.log.error('[MongoDB] 设置TTL失败:', error);
321
- return false;
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
+ };
322
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 [];
323
315
  }
316
+ };
324
317
 
325
- /**
326
- * @description Redis兼容API - 添加缓存
327
- * @param {String} key 键名
328
- * @param {*} value 值
329
- * @param {Number} expire 过期时间(秒)
330
- * @return {Promise<Boolean>} 是否成功
331
- */
332
- async _add(key, value, expire) {
333
- try {
334
- await this._ensureConnected();
335
- const data = {
336
- key: key,
337
- value: value
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
338
338
  };
339
+ this.emit('ttl_before', beforeEventData);
339
340
 
340
- if (expire) {
341
- data.expireAt = new Date(Date.now() + expire * 1000);
341
+ if (beforeEventData.cancel) {
342
+ return false;
342
343
  }
343
344
 
344
- // 检查是否已存在
345
- const existing = await this.findOne({ key: key });
346
- if (existing) {
347
- await this._set({ key: key }, data);
348
- } else {
349
- await this.addObj(data);
345
+ let { key: finalKey, seconds: finalSeconds } = beforeEventData;
346
+
347
+ await this._ensureConnected();
348
+
349
+ // 确保连接和collection存在
350
+ if (!this.db) {
351
+ await this.open();
350
352
  }
351
- return true;
352
- } catch (error) {
353
- $.log.error('[MongoDB] 添加缓存失败:', error);
354
- return false;
355
- }
356
- }
357
353
 
358
- /**
359
- * @description Redis兼容API - 添加缓存
360
- * @param {String} key 键名
361
- * @param {*} value 值
362
- * @param {Number} expire 过期时间(秒)
363
- * @return {Promise<Boolean>} 是否成功
364
- */
365
- async add(key, value, expire) {
366
- return this._add(key, value, expire);
367
- }
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
+ }
368
365
 
369
- /**
370
- * @description Redis兼容API - 增加整数值
371
- * @param {String} key 键名
372
- * @param {Number} value 增加值
373
- * @return {Promise<Number>} 增加后的值
374
- */
375
- async addInt(key, value = 1) {
376
- try {
377
- await this._ensureConnected();
378
- const item = await this.findOne({ key: key });
379
- let currentValue = 0;
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
+ }
380
375
 
381
- if (item && typeof item.value === 'number') {
382
- 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;
383
385
  }
384
386
 
385
- const newValue = currentValue + value;
386
- await this._add(key, newValue);
387
- return newValue;
388
- } catch (error) {
389
- $.log.error('[MongoDB] 增加整数值失败:', error);
390
- return undefined;
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;
391
412
  }
392
- }
413
+ // 获取模式:检查键的过期时间(不需要事件)
414
+ else {
415
+ await this._ensureConnected();
393
416
 
394
- /**
395
- * @description Redis兼容API - 增加浮点数值
396
- * @param {String} key 键名
397
- * @param {Number} value 增加值
398
- * @return {Promise<Number>} 增加后的值
399
- */
400
- async addFloat(key, value = 1.0) {
401
- return this.addInt(key, value); // 复用addInt方法
402
- }
417
+ // 确保连接和collection存在
418
+ if (!this.db) {
419
+ await this.open();
420
+ }
403
421
 
404
- /**
405
- * @description Redis兼容API - 添加字符串
406
- * @param {String} key 键名
407
- * @param {String} value 字符串值
408
- * @return {Promise<Boolean>} 是否成功
409
- */
410
- async addStr(key, value) {
411
- return this._add(key, String(value));
412
- }
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
+ }
413
433
 
414
- /**
415
- * @description Redis兼容API - 排序
416
- * @param {String} key 键名
417
- * @param {Object} options 排序选项
418
- * @return {Promise<Array>} 排序结果
419
- */
420
- async sort(key, options = {}) {
421
- try {
422
- await this._ensureConnected();
423
- // 假设value是数组
424
- const item = await this.findOne({ key: key });
425
- if (!item || !Array.isArray(item.value)) return [];
434
+ // 再次检查collection
435
+ if (!this.collection) {
436
+ return -2;
437
+ }
426
438
 
427
- const sorted = [...item.value];
439
+ // 查询键是否存在及过期时间
440
+ const item = await this.collection.findOne({ key });
441
+ $.log.debug(`[MongoDB] [ttl] 查询键结果: item=${JSON.stringify(item)}`);
428
442
 
429
- // 处理排序选项
430
- if (options.desc) {
431
- sorted.sort((a, b) => b - a);
432
- } else {
433
- sorted.sort((a, b) => a - b);
443
+ if (!item) {
444
+ // 键不存在返回-2
445
+ $.log.debug(`[MongoDB] [ttl] 键 ${key} 不存在,返回-2`);
446
+ return -2;
434
447
  }
435
448
 
436
- // 处理限制
437
- if (options.limit) {
438
- const offset = options.offset || 0;
439
- return sorted.slice(offset, offset + options.limit);
449
+ if (!item.expire_at) {
450
+ // 没有过期时间返回-1
451
+ $.log.debug(`[MongoDB] [ttl] 键 ${key} 没有过期时间,返回-1`);
452
+ return -1;
440
453
  }
441
454
 
442
- return sorted;
443
- } catch (error) {
444
- $.log.error('[MongoDB] 排序失败:', error);
445
- return [];
455
+ // 计算剩余过期时间
456
+ const expireTime = new Date(item.expire_at).getTime();
457
+ const currentTime = Date.now();
458
+ const remainingSeconds = Math.ceil((expireTime - currentTime) / 1000);
459
+
460
+ if (remainingSeconds <= 0) {
461
+ // 已过期返回-2
462
+ return -2;
463
+ }
464
+
465
+ // 返回剩余过期时间
466
+ return remainingSeconds;
446
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;
447
472
  }
473
+ };
448
474
 
449
- /**
450
- * @description Redis兼容API - 哈希表设置字段
451
- * @param {String} key 键名
452
- * @param {String} field 字段名
453
- * @param {*} value 值
454
- * @return {Promise<Boolean>} 是否成功
455
- */
456
- async hset(key, field, value) {
457
- try {
458
- await this._ensureConnected();
459
- const item = await this.findOne({ key: key });
460
- let hash = item && typeof item.value === 'object' && item.value !== null ? item.value : {};
461
-
462
- hash[field] = value;
463
- await this._add(key, hash);
464
- return true;
465
- } catch (error) {
466
- $.log.error('[MongoDB] 哈希表设置失败:', error);
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') {
467
484
  return false;
468
485
  }
469
- }
470
486
 
471
- /**
472
- * @description Redis兼容API - 哈希表获取字段
473
- * @param {String} key 键名
474
- * @param {String} field 字段名
475
- * @return {Promise<any>} 字段值
476
- */
477
- async hget(key, field) {
478
- try {
479
- await this._ensureConnected();
480
- const item = await this.findOne({ key: key });
481
- if (!item || typeof item.value !== 'object' || item.value === null) {
482
- return null;
483
- }
484
- return item.value[field] !== undefined ? item.value[field] : null;
485
- } catch (error) {
486
- $.log.error('[MongoDB] 哈希表获取失败:', error);
487
- return null;
487
+ // 触发前置事件
488
+ const beforeEventData = {
489
+ key,
490
+ cancel: false
491
+ };
492
+ this.emit('persist_before', beforeEventData);
493
+
494
+ if (beforeEventData.cancel) {
495
+ return false;
488
496
  }
489
- }
490
497
 
491
- /**
492
- * @description Redis兼容API - 哈希表获取所有字段和值
493
- * @param {String} key 键名
494
- * @return {Promise<Object>} 哈希表对象
495
- */
496
- async hgetall(key) {
497
- try {
498
- const item = await this.findOne({ key: key });
499
- if (!item || typeof item.value !== 'object' || item.value === null) {
500
- return {};
501
- }
502
- return item.value;
503
- } catch (error) {
504
- $.log.error('[MongoDB] 哈希表获取全部失败:', error);
505
- return {};
498
+ const { key: finalKey } = beforeEventData;
499
+
500
+ // 确保连接和collection存在
501
+ if (!this.db) {
502
+ await this.open();
506
503
  }
507
- }
508
504
 
509
- /**
510
- * @description Redis兼容API - 哈希表删除字段
511
- * @param {String} key 键名
512
- * @param {String} field 字段名
513
- * @return {Promise<Boolean>} 是否成功
514
- */
515
- async hdel(key, field) {
516
- try {
517
- const item = await this.findOne({ key: key });
518
- if (!item || typeof item.value !== 'object' || item.value === null) {
519
- return false;
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;
520
514
  }
515
+ }
521
516
 
522
- if (item.value[field] === undefined) {
523
- return false;
524
- }
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
+ });
524
+ return false;
525
+ }
525
526
 
526
- delete item.value[field];
527
- await this._set({ key: key }, { value: item.value });
528
- return true;
529
- } catch (error) {
530
- $.log.error('[MongoDB] 哈希表删除失败:', error);
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
+ });
531
535
  return false;
532
536
  }
533
- }
534
537
 
535
- /**
536
- * @description Redis兼容API - 检查键是否存在
537
- * @param {String} key 键名
538
- * @return {Promise<Boolean>} 是否存在
539
- */
540
- async has(key) {
541
- try {
542
- const result = await this.findOne({ key: key });
543
- return result !== null;
544
- } catch (error) {
545
- $.log.error('[MongoDB] 检查键是否存在失败:', error);
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;
570
+ }
571
+ };
572
+
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;
605
+ }
606
+ };
607
+
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}]`);
546
629
  return false;
547
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;
548
686
  }
687
+ };
549
688
 
550
- /**
551
- * @description Redis兼容API - 设置键过期时间(映射到ttl)
552
- * @param {String} key 键名
553
- * @param {Number} seconds 过期时间(秒)
554
- * @return {Promise<Boolean>} 是否成功
555
- */
556
- async expire(key, seconds) {
557
- return this.ttl(key, seconds);
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;
558
714
  }
715
+ };
559
716
 
560
- /**
561
- * @description Redis兼容API - 递增(通过addInt模拟)
562
- * @param {String} key 键名
563
- * @return {Promise<Number>} 递增后的值
564
- */
565
- async incr(key) {
566
- try {
567
- await this._ensureConnected();
568
- const result = await this.addInt(key, 1);
569
- return result !== undefined ? result : 0;
570
- } catch (error) {
571
- $.log.error('[MongoDB] 递增操作失败:', error);
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必须是非空字符串');
572
756
  return 0;
573
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;
574
796
  }
797
+ };
575
798
 
576
- /**
577
- * @description Redis兼容API - 递减(通过addInt模拟)
578
- * @param {String} key 键名
579
- * @return {Promise<Number>} 递减后的值
580
- */
581
- async decr(key) {
582
- try {
583
- await this._ensureConnected();
584
- const result = await this.addInt(key, -1);
585
- return result !== undefined ? result : 0;
586
- } catch (error) {
587
- $.log.error('[MongoDB] 递减操作失败:', error);
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)) {
588
1059
  return 0;
589
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;
590
1087
  }
1088
+ };
591
1089
 
592
- /**
593
- * @description Redis兼容API - 检查键是否存在(别名)
594
- * @param {String} key 键名
595
- * @return {Promise<Boolean>} 是否存在
596
- */
597
- async exists(key) {
598
- try {
599
- await this._ensureConnected();
600
- return this.has(key);
601
- } catch (error) {
602
- $.log.error('[MongoDB] 检查键是否存在失败:', error);
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操作被事件处理器取消');
603
1129
  return false;
604
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;
605
1162
  }
1163
+ };
606
1164
 
607
- /**
608
- * @description Redis兼容API - 设置键值
609
- * @param {String} key 键名
610
- * @param {*} value 值
611
- * @return {Promise<Boolean>} 是否成功
612
- */
613
- async _set(key, value) {
614
- try {
615
- await this._ensureConnected();
616
- // 直接使用已有的add方法,它已正确处理查询条件格式
617
- return await this._add(key, value);
618
- } catch (error) {
619
- $.log.error('[MongoDB] 设置键值失败:', error);
620
- return false;
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;
621
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;
622
1220
  }
1221
+ };
623
1222
 
624
- /**
625
- * @description Redis兼容API - 设置键值
626
- * @param {String} key 键名
627
- * @param {*} value 值
628
- * @return {Promise<Boolean>} 是否成功
629
- */
630
- async set(key, value) {
631
- return this._set(key, value);
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;
632
1278
  }
1279
+ };
633
1280
 
634
- /**
635
- * @description Redis兼容API - 哈希表批量设置
636
- * @param {String} key 键名
637
- * @param {Object} fieldValues 字段值对象
638
- * @return {Promise<Boolean>} 是否成功
639
- */
640
- async hmset(key, fieldValues) {
641
- try {
642
- await this._ensureConnected();
643
- const item = await this.findOne({ key: key });
644
- let hash = item && typeof item.value === 'object' && item.value !== null ? item.value : {};
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);
645
1297
 
646
- // 合并字段值
647
- Object.assign(hash, fieldValues);
648
- await this._add(key, hash);
649
- return true;
650
- } catch (error) {
651
- $.log.error('[MongoDB] 哈希表批量设置失败:', error);
652
- return false;
1298
+ // 如果事件处理器标记为取消,则返回默认值
1299
+ if (beforeEventData.cancel) {
1300
+ $.log.debug('[MongoDB] incrby操作被事件处理器取消');
1301
+ return 0;
653
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;
654
1344
  }
1345
+ };
655
1346
 
656
- /**
657
- * @description Redis兼容API - 哈希表批量获取
658
- * @param {String} key 键名
659
- * @param {Array} fields 字段名数组
660
- * @return {Promise<Object>} 字段值对象
661
- */
662
- async hmget(key, fields) {
663
- try {
664
- await this._ensureConnected();
665
- const item = await this.findOne({ key: key });
666
- if (!item || typeof item.value !== 'object' || item.value === null) {
667
- return {};
668
- }
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);
669
1363
 
670
- const result = {};
671
- fields.forEach(field => {
672
- result[field] = item.value[field] !== undefined ? item.value[field] : null;
673
- });
674
- return result;
675
- } catch (error) {
676
- $.log.error('[MongoDB] 哈希表批量获取失败:', error);
677
- return {};
1364
+ // 如果事件处理器标记为取消,则返回默认值
1365
+ if (beforeEventData.cancel) {
1366
+ $.log.debug('[MongoDB] decrby操作被事件处理器取消');
1367
+ return 0;
678
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;
679
1410
  }
1411
+ };
680
1412
 
681
- /**
682
- * @description Redis兼容API - 哈希表批量删除字段
683
- * @param {String} key 键名
684
- * @param {Array} fields 字段名数组
685
- * @return {Promise<Boolean>} 是否成功
686
- */
687
- async hdelMulti(key, fields) {
688
- try {
689
- await this._ensureConnected();
690
- const item = await this.findOne({ key: key });
691
- if (!item || typeof item.value !== 'object' || item.value === null) {
692
- return false;
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
+ }
693
1494
  }
1495
+ }
1496
+
1497
+ let existsCount = 0;
694
1498
 
695
- let deleted = false;
696
- fields.forEach(field => {
697
- if (item.value[field] !== undefined) {
698
- delete item.value[field];
699
- deleted = true;
1499
+ // 检查输入类型并处理
1500
+ if (Array.isArray(key)) {
1501
+ // 处理多键检查
1502
+ const results = await this.find({
1503
+ key: {
1504
+ $in: key
700
1505
  }
701
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;
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
+ }
702
1577
 
703
- if (deleted) {
704
- await this._set({ key: key }, { value: item.value });
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
+ }
705
1590
  }
706
- return deleted;
707
- } catch (error) {
708
- $.log.error('[MongoDB] 哈希表批量删除失败:', error);
709
- return false;
710
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;
711
1627
  }
1628
+ };
712
1629
 
713
- /**
714
- * @description Redis兼容API - 哈希表获取所有值
715
- * @param {String} key 键名
716
- * @return {Promise<Array>} 值数组
717
- */
718
- async hvals(key) {
719
- try {
720
- await this._ensureConnected();
721
- const item = await this.findOne({ key: key });
722
- if (!item || typeof item.value !== 'object' || item.value === null) {
723
- return [];
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
+ }
1642
+
1643
+ await this._ensureConnected();
1644
+ const item = await this.findOne({ key: key });
1645
+
1646
+ // 确保fields是数组
1647
+ const fieldArray = Array.isArray(fields) ? fields : [fields];
1648
+
1649
+ // 如果键不存在或不是哈希表格式,返回与字段数量相同的null数组
1650
+ if (!item || typeof item.value !== 'object' || item.value === null || Array.isArray(item.value)) {
1651
+ return fieldArray.map(() => null);
1652
+ }
1653
+
1654
+ // 返回与字段名顺序对应的数组值,不存在的字段返回null
1655
+ const values = fieldArray.map(field => {
1656
+ // 严格检查字段是否存在于对象中
1657
+ return Object.prototype.hasOwnProperty.call(item.value, field) ? item.value[field] : null;
1658
+ });
1659
+
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);
1666
+ }
1667
+ };
1668
+
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;
724
1690
  }
725
- return Object.values(item.value);
726
- } catch (error) {
727
- $.log.error('[MongoDB] 哈希表获取所有值失败:', error);
1691
+ });
1692
+
1693
+ if (deleted) {
1694
+ await this._set({
1695
+ key: key
1696
+ }, {
1697
+ value: item.value
1698
+ });
1699
+ }
1700
+ return deleted;
1701
+ } catch (error) {
1702
+ $.log.error('[MongoDB] 哈希表批量删除失败:', error);
1703
+ return false;
1704
+ }
1705
+ };
1706
+
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) {
728
1719
  return [];
729
1720
  }
1721
+ return Object.values(item.value);
1722
+ } catch (error) {
1723
+ $.log.error('[MongoDB] 哈希表获取所有值失败:', error);
1724
+ return [];
730
1725
  }
1726
+ };
731
1727
 
732
- /**
733
- * @description Redis兼容API - 哈希表获取所有字段名
734
- * @param {String} key 键名
735
- * @return {Promise<Array>} 字段名数组
736
- */
737
- async hkeys(key) {
738
- try {
739
- await this._ensureConnected();
740
- const item = await this.findOne({ key: key });
741
- if (!item || typeof item.value !== 'object' || item.value === null) {
742
- return [];
743
- }
744
- return Object.keys(item.value);
745
- } catch (error) {
746
- $.log.error('[MongoDB] 哈希表获取所有字段名失败:', error);
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) {
747
1740
  return [];
748
1741
  }
1742
+ return Object.keys(item.value);
1743
+ } catch (error) {
1744
+ $.log.error('[MongoDB] 哈希表获取所有字段名失败:', error);
1745
+ return [];
749
1746
  }
1747
+ };
750
1748
 
751
- /**
752
- * @description Redis兼容API - 哈希表字段数量
753
- * @param {String} key 键名
754
- * @return {Promise<Number>} 字段数量
755
- */
756
- async hlen(key) {
757
- try {
758
- await this._ensureConnected();
759
- const item = await this.findOne({ key: key });
760
- if (!item || typeof item.value !== 'object' || item.value === null) {
761
- return 0;
762
- }
763
- return Object.keys(item.value).length;
764
- } catch (error) {
765
- $.log.error('[MongoDB] 哈希表字段数量失败:', error);
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') {
766
1758
  return 0;
767
1759
  }
768
- }
769
-
770
- /**
771
- * @description Redis兼容API - 哈希表字段是否存在
772
- * @param {String} key 键名
773
- * @param {String} field 字段名
774
- * @return {Promise<Boolean>} 是否存在
775
- */
776
- async hexists(key, field) {
777
- try {
778
- await this._ensureConnected();
779
- const item = await this.findOne({ key: key });
780
- if (!item || typeof item.value !== 'object' || item.value === null) {
781
- return false;
782
- }
783
- return field in item.value;
784
- } catch (error) {
785
- $.log.error('[MongoDB] 哈希表字段是否存在失败:', error);
786
- return false;
787
- }
788
- }
789
1760
 
790
- /**
791
- * @description Redis兼容API - 列表尾部添加元素
792
- * @param {String} key 键名
793
- * @param {*} value 值
794
- * @return {Promise<Number>} 添加后列表长度
795
- */
796
- async rpush(key, value) {
797
- try {
798
- await this._ensureConnected();
799
- const item = await this.findOne({ key: key });
800
- let list = item && Array.isArray(item.value) ? item.value : [];
1761
+ await this._ensureConnected();
1762
+ const item = await this.findOne({ key: key });
801
1763
 
802
- list.push(value);
803
- await this._add(key, list);
804
- return list.length;
805
- } catch (error) {
806
- $.log.error('[MongoDB] 列表尾部添加失败:', error);
1764
+ // 如果键不存在或不是哈希表格式,返回0
1765
+ if (!item || typeof item.value !== 'object' || item.value === null || Array.isArray(item.value)) {
807
1766
  return 0;
808
1767
  }
809
- }
810
1768
 
811
- /**
812
- * @description Redis兼容API - 列表头部添加元素
813
- * @param {String} key 键名
814
- * @param {*} value 值
815
- * @return {Promise<Number>} 添加后列表长度
816
- */
817
- async lpush(key, value) {
818
- try {
819
- await this._ensureConnected();
820
- const item = await this.findOne({ key: key });
821
- let list = item && Array.isArray(item.value) ? item.value : [];
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
+ };
822
1776
 
823
- list.unshift(value);
824
- await this._add(key, list);
825
- return list.length;
826
- } catch (error) {
827
- $.log.error('[MongoDB] 列表头部添加失败:', error);
828
- return 0;
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;
829
1791
  }
1792
+ return field in item.value;
1793
+ } catch (error) {
1794
+ $.log.error('[MongoDB] 哈希表字段是否存在失败:', error);
1795
+ return false;
830
1796
  }
1797
+ };
831
1798
 
832
- /**
833
- * @description Redis兼容API - 列表范围查询
834
- * @param {String} key 键名
835
- * @param {Number} start 起始索引
836
- * @param {Number} stop 结束索引
837
- * @return {Promise<Array>} 元素数组
838
- */
839
- async lrange(key, start, stop) {
840
- try {
841
- await this._ensureConnected();
842
- const item = await this.findOne({ key: key });
843
- if (!item || !Array.isArray(item.value)) {
844
- return [];
845
- }
846
-
847
- // 处理负数索引
848
- let startIndex = start < 0 ? item.value.length + start : start;
849
- let endIndex = stop < 0 ? item.value.length + stop : stop;
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
+ };
850
1822
 
851
- // 确保索引有效
852
- startIndex = Math.max(0, Math.min(startIndex, item.value.length - 1));
853
- endIndex = Math.max(startIndex, Math.min(endIndex, item.value.length - 1));
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
+ };
854
1846
 
855
- return item.value.slice(startIndex, endIndex + 1);
856
- } catch (error) {
857
- $.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)) {
858
1861
  return [];
859
1862
  }
860
- }
861
1863
 
862
- /**
863
- * @description Redis兼容API - 获取列表指定索引元素
864
- * @param {String} key 键名
865
- * @param {Number} index 索引
866
- * @return {Promise<any>} 元素值
867
- */
868
- async lindex(key, index) {
869
- try {
870
- await this._ensureConnected();
871
- const item = await this.findOne({ key: key });
872
- if (!item || !Array.isArray(item.value)) {
873
- return null;
874
- }
1864
+ // 处理负数索引
1865
+ let startIndex = start < 0 ? item.value.length + start : start;
1866
+ let endIndex = stop < 0 ? item.value.length + stop : stop;
875
1867
 
876
- // 处理负数索引
877
- 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));
878
1871
 
879
- if (realIndex < 0 || realIndex >= item.value.length) {
880
- return null;
881
- }
1872
+ return item.value.slice(startIndex, endIndex + 1);
1873
+ } catch (error) {
1874
+ $.log.error('[MongoDB] 列表范围查询失败:', error);
1875
+ return [];
1876
+ }
1877
+ };
882
1878
 
883
- return item.value[realIndex];
884
- } catch (error) {
885
- $.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)) {
886
1892
  return null;
887
1893
  }
888
- }
889
1894
 
890
- /**
891
- * @description Redis兼容API - 设置列表指定索引元素
892
- * @param {String} key 键名
893
- * @param {Number} index 索引
894
- * @param {*} value 值
895
- * @return {Promise<Boolean>} 是否成功
896
- */
897
- async lset(key, index, value) {
898
- try {
899
- await this._ensureConnected();
900
- const item = await this.findOne({ key: key });
901
- if (!item || !Array.isArray(item.value)) {
902
- return false;
903
- }
1895
+ // 处理负数索引
1896
+ const realIndex = index < 0 ? item.value.length + index : index;
904
1897
 
905
- // 处理负数索引
906
- const realIndex = index < 0 ? item.value.length + index : index;
1898
+ if (realIndex < 0 || realIndex >= item.value.length) {
1899
+ return null;
1900
+ }
907
1901
 
908
- if (realIndex < 0 || realIndex >= item.value.length) {
909
- return false;
910
- }
1902
+ return item.value[realIndex];
1903
+ } catch (error) {
1904
+ $.log.error('[MongoDB] 获取列表索引元素失败:', error);
1905
+ return null;
1906
+ }
1907
+ };
911
1908
 
912
- item.value[realIndex] = value;
913
- await this._set({ key: key }, { value: item.value });
914
- return true;
915
- } catch (error) {
916
- $.log.error('[MongoDB] 设置列表索引元素失败:', error);
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) {
917
1930
  return false;
918
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;
919
1943
  }
1944
+ };
920
1945
 
921
- /**
922
- * @description Redis兼容API - 删除列表元素
923
- * @param {String} key 键名
924
- * @param {Number} count 删除数量
925
- * @param {*} value 要删除的值
926
- * @return {Promise<Number>} 删除的元素数量
927
- */
928
- async lrem(key, count, value) {
929
- try {
930
- await this._ensureConnected();
931
- const item = await this.findOne({ key: key });
932
- if (!item || !Array.isArray(item.value)) {
933
- return 0;
934
- }
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
+ }
935
1962
 
936
- let list = [...item.value];
937
- let removedCount = 0;
1963
+ let list = [...item.value];
1964
+ let removedCount = 0;
938
1965
 
939
- if (count > 0) {
940
- // 从头部删除count个等于value的元素
941
- for (let i = 0; i < list.length && removedCount < count; i++) {
942
- if (JSON.stringify(list[i]) === JSON.stringify(value)) {
943
- list.splice(i, 1);
944
- removedCount++;
945
- i--; // 调整索引
946
- }
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--; // 调整索引
947
1973
  }
948
- } else if (count < 0) {
949
- // 从尾部删除|count|个等于value的元素
950
- count = Math.abs(count);
951
- for (let i = list.length - 1; i >= 0 && removedCount < count; i--) {
952
- if (JSON.stringify(list[i]) === JSON.stringify(value)) {
953
- list.splice(i, 1);
954
- removedCount++;
955
- }
956
- }
957
- } else {
958
- // 删除所有等于value的元素
959
- list = list.filter(item => JSON.stringify(item) !== JSON.stringify(value));
960
- removedCount = item.value.length - list.length;
961
1974
  }
962
-
963
- if (removedCount > 0) {
964
- 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
+ }
965
1983
  }
1984
+ } else {
1985
+ // 删除所有等于value的元素
1986
+ list = list.filter(item => JSON.stringify(item) !== JSON.stringify(value));
1987
+ removedCount = item.value.length - list.length;
1988
+ }
966
1989
 
967
- return removedCount;
968
- } catch (error) {
969
- $.log.error('[MongoDB] 删除列表元素失败:', error);
970
- return 0;
1990
+ if (removedCount > 0) {
1991
+ await this._set({
1992
+ key: key
1993
+ }, {
1994
+ value: list
1995
+ });
971
1996
  }
972
- }
973
1997
 
974
- /**
975
- * @description Redis兼容API - 裁剪列表
976
- * @param {String} key 键名
977
- * @param {Number} start 起始索引
978
- * @param {Number} stop 结束索引
979
- * @return {Promise<Boolean>} 是否成功
980
- */
981
- async ltrim(key, start, stop) {
982
- try {
983
- await this._ensureConnected();
984
- const item = await this.findOne({ key: key });
985
- if (!item || !Array.isArray(item.value)) {
986
- return false;
987
- }
1998
+ return removedCount;
1999
+ } catch (error) {
2000
+ $.log.error('[MongoDB] 删除列表元素失败:', error);
2001
+ return 0;
2002
+ }
2003
+ };
988
2004
 
989
- // 使用lrange的逻辑获取裁剪后的列表
990
- const trimmed = await this.lrange(key, start, stop);
991
- await this._set({ key: key }, { value: trimmed });
992
- return true;
993
- } catch (error) {
994
- $.log.error('[MongoDB] 裁剪列表失败:', error);
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)) {
995
2019
  return false;
996
2020
  }
2021
+
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;
997
2033
  }
2034
+ };
998
2035
 
999
- /**
1000
- * @description Redis兼容API - 列表长度
1001
- * @param {String} key 键名
1002
- * @return {Promise<Number>} 列表长度
1003
- */
1004
- async llen(key) {
1005
- try {
1006
- await this._ensureConnected();
1007
- const item = await this.findOne({ key: key });
1008
- if (!item || !Array.isArray(item.value)) {
1009
- return 0;
1010
- }
1011
- return item.value.length;
1012
- } catch (error) {
1013
- $.log.error('[MongoDB] 获取列表长度失败:', error);
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)) {
1014
2048
  return 0;
1015
2049
  }
2050
+ return item.value.length;
2051
+ } catch (error) {
2052
+ $.log.error('[MongoDB] 获取列表长度失败:', error);
2053
+ return 0;
1016
2054
  }
2055
+ };
1017
2056
 
1018
- /**
1019
- * @description Redis兼容API - 集合批量添加
1020
- * @param {String} key 键名
1021
- * @param {Array} members 成员数组
1022
- * @return {Promise<Number>} 添加的成员数量
1023
- */
1024
- async saddMulti(key, members) {
1025
- try {
1026
- await this._ensureConnected();
1027
- const item = await this.findOne({ key: key });
1028
- let set = item && Array.isArray(item.value) ? [...new Set(item.value)] : [];
1029
- let addedCount = 0;
1030
-
1031
- members.forEach(member => {
1032
- if (!set.some(existing => JSON.stringify(existing) === JSON.stringify(member))) {
1033
- set.push(member);
1034
- addedCount++;
1035
- }
1036
- });
1037
-
1038
- if (addedCount > 0) {
1039
- await this._add(key, set);
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++;
1040
2076
  }
2077
+ });
1041
2078
 
1042
- return addedCount;
1043
- } catch (error) {
1044
- $.log.error('[MongoDB] 集合并发添加失败:', error);
1045
- return 0;
2079
+ if (addedCount > 0) {
2080
+ await this._add(key, set);
1046
2081
  }
1047
- }
1048
2082
 
1049
- /**
1050
- * @description Redis兼容API - 集合批量删除
1051
- * @param {String} key 键名
1052
- * @param {Array} members 成员数组
1053
- * @return {Promise<Number>} 删除的成员数量
1054
- */
1055
- async sremMulti(key, members) {
1056
- try {
1057
- await this._ensureConnected();
1058
- const item = await this.findOne({ key: key });
1059
- if (!item || !Array.isArray(item.value)) {
1060
- return 0;
1061
- }
2083
+ return addedCount;
2084
+ } catch (error) {
2085
+ $.log.error('[MongoDB] 集合并发添加失败:', error);
2086
+ return 0;
2087
+ }
2088
+ };
1062
2089
 
1063
- let set = [...new Set(item.value)];
1064
- const originalLength = set.length;
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)) {
2103
+ return 0;
2104
+ }
1065
2105
 
1066
- // 创建要删除的值的字符串表示集合用于快速查找
1067
- const membersToRemove = new Set(members.map(m => JSON.stringify(m)));
1068
- set = set.filter(member => !membersToRemove.has(JSON.stringify(member)));
2106
+ let set = [...new Set(item.value)];
2107
+ const originalLength = set.length;
1069
2108
 
1070
- const removedCount = originalLength - set.length;
1071
- if (removedCount > 0) {
1072
- await this._add(key, set);
1073
- }
2109
+ // 创建要删除的值的字符串表示集合用于快速查找
2110
+ const membersToRemove = new Set(members.map(m => JSON.stringify(m)));
2111
+ set = set.filter(member => !membersToRemove.has(JSON.stringify(member)));
1074
2112
 
1075
- return removedCount;
1076
- } catch (error) {
1077
- $.log.error('[MongoDB] 集合并发删除失败:', error);
1078
- return 0;
2113
+ const removedCount = originalLength - set.length;
2114
+ if (removedCount > 0) {
2115
+ await this._add(key, set);
1079
2116
  }
2117
+
2118
+ return removedCount;
2119
+ } catch (error) {
2120
+ $.log.error('[MongoDB] 集合并发删除失败:', error);
2121
+ return 0;
1080
2122
  }
2123
+ };
1081
2124
 
1082
- /**
1083
- * @description Redis兼容API - 集合差集
1084
- * @param {String} key1 第一个集合键
1085
- * @param {String} key2 第二个集合键
1086
- * @return {Promise<Array>} 差集数组
1087
- */
1088
- async sdiff(key1, key2) {
1089
- try {
1090
- await this._ensureConnected();
1091
- const item1 = await this.findOne({ key: key1 });
1092
- const item2 = await this.findOne({ key: key2 });
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)] : [];
1093
2143
 
1094
- const set1 = item1 && Array.isArray(item1.value) ? [...new Set(item1.value)] : [];
1095
- const set2 = item2 && Array.isArray(item2.value) ? [...new Set(item2.value)] : [];
2144
+ // 创建第二个集合值的字符串表示用于快速查找
2145
+ const set2Str = new Set(set2.map(m => JSON.stringify(m)));
1096
2146
 
1097
- // 创建第二个集合值的字符串表示用于快速查找
1098
- const set2Str = new Set(set2.map(m => JSON.stringify(m)));
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
+ };
1099
2154
 
1100
- // 找出在set1中但不在set2中的元素
1101
- return set1.filter(member => !set2Str.has(JSON.stringify(member)));
1102
- } catch (error) {
1103
- $.log.error('[MongoDB] 集合差集失败:', error);
1104
- return [];
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;
1105
2182
  }
1106
- }
1107
2183
 
1108
- /**
1109
- * @description Redis兼容API - 集合交集
1110
- * @param {String} key1 第一个集合键
1111
- * @param {String} key2 第二个集合键
1112
- * @return {Promise<Array>} 交集数组
1113
- */
1114
- async sinter(key1, key2) {
1115
- try {
1116
- await this._ensureConnected();
1117
- const item1 = await this.findOne({ key: key1 });
1118
- const item2 = await this.findOne({ key: key2 });
2184
+ const largerSetStr = new Set(largerSet.map(m => JSON.stringify(m)));
1119
2185
 
1120
- const set1 = item1 && Array.isArray(item1.value) ? [...new Set(item1.value)] : [];
1121
- const set2 = item2 && Array.isArray(item2.value) ? [...new Set(item2.value)] : [];
2186
+ // 找出同时存在于两个集合中的元素
2187
+ return smallerSet.filter(member => largerSetStr.has(JSON.stringify(member)));
2188
+ } catch (error) {
2189
+ $.log.error('[MongoDB] 集合并集失败:', error);
2190
+ return [];
2191
+ }
2192
+ };
1122
2193
 
1123
- // 创建较小集合的字符串表示用于快速查找,优化性能
1124
- let smallerSet, largerSet;
1125
- if (set1.length <= set2.length) {
1126
- smallerSet = set1;
1127
- largerSet = set2;
1128
- } else {
1129
- smallerSet = set2;
1130
- largerSet = set1;
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);
1131
2222
  }
2223
+ });
1132
2224
 
1133
- const largerSetStr = new Set(largerSet.map(m => JSON.stringify(m)));
1134
-
1135
- // 找出同时存在于两个集合中的元素
1136
- return smallerSet.filter(member => largerSetStr.has(JSON.stringify(member)));
1137
- } catch (error) {
1138
- $.log.error('[MongoDB] 集合并集失败:', error);
1139
- return [];
1140
- }
2225
+ return Array.from(uniqueMap.values());
2226
+ } catch (error) {
2227
+ $.log.error('[MongoDB] 集合并集失败:', error);
2228
+ return [];
1141
2229
  }
2230
+ };
1142
2231
 
1143
- /**
1144
- * @description Redis兼容API - 集合并集
1145
- * @param {String} key1 第一个集合键
1146
- * @param {String} key2 第二个集合键
1147
- * @return {Promise<Array>} 并集数组
1148
- */
1149
- async sunion(key1, key2) {
1150
- try {
1151
- await this._ensureConnected();
1152
- const item1 = await this.findOne({ key: key1 });
1153
- const item2 = await this.findOne({ key: key2 });
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
+ }
1154
2246
 
1155
- const set1 = item1 && Array.isArray(item1.value) ? item1.value : [];
1156
- const set2 = item2 && Array.isArray(item2.value) ? item2.value : [];
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
+ };
1157
2255
 
1158
- // 合并并去重
1159
- const combined = [...set1, ...set2];
1160
- // 使用Map进行对象去重,基于JSON字符串表示
1161
- const uniqueMap = new Map();
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
+ });
1162
2272
 
1163
- combined.forEach(item => {
1164
- const key = JSON.stringify(item);
1165
- if (!uniqueMap.has(key)) {
1166
- uniqueMap.set(key, item);
1167
- }
1168
- });
2273
+ // 将成员转换为字符串,与Redis行为一致
2274
+ const memberStr = String(member);
1169
2275
 
1170
- return Array.from(uniqueMap.values());
1171
- } catch (error) {
1172
- $.log.error('[MongoDB] 集合并集失败:', error);
1173
- return [];
2276
+ let result = false;
2277
+ if (item && Array.isArray(item.value)) {
2278
+ // 检查是否存在相同的成员,使用字符串比较
2279
+ result = item.value.includes(memberStr);
1174
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;
1175
2296
  }
2297
+ };
1176
2298
 
1177
- /**
1178
- * @description Redis兼容API - 集合成员数量
1179
- * @param {String} key 键名
1180
- * @return {Promise<Number>} 成员数量
1181
- */
1182
- async scard(key) {
1183
- try {
1184
- await this._ensureConnected();
1185
- const item = await this.findOne({ key: key });
1186
- if (!item || !Array.isArray(item.value)) {
1187
- return 0;
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()
1188
2312
  }
1189
-
1190
- // 使用Set去重并计算大小
1191
- const uniqueItems = new Set(item.value.map(m => JSON.stringify(m)));
1192
- return uniqueItems.size;
1193
- } catch (error) {
1194
- $.log.error('[MongoDB] 获取集合成员数量失败:', error);
1195
- return 0;
1196
- }
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;
1197
2324
  }
1198
-
1199
- /**
1200
- * @description Redis兼容API - 检查是否是集合成员
1201
- * @param {String} key 键名
1202
- * @param {*} member 成员
1203
- * @return {Promise<Boolean>} 是否是成员
1204
- */
1205
- async sismember(key, member) {
1206
- try {
1207
- await this._ensureConnected();
1208
- const item = await this.findOne({ key: key });
1209
- if (!item || !Array.isArray(item.value)) {
1210
- return false;
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
1211
2338
  }
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;
2350
+ }
2351
+ };
1212
2352
 
1213
- // 检查是否存在相同的成员
1214
- const memberStr = JSON.stringify(member);
1215
- return item.value.some(m => JSON.stringify(m) === memberStr);
1216
- } catch (error) {
1217
- $.log.error('[MongoDB] 检查集合成员失败:', error);
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);
2367
+
2368
+ // 如果事件处理器标记为取消,则返回false
2369
+ if (beforeEventData.cancel) {
2370
+ $.log.debug('[MongoDB] mset操作被事件处理器取消');
1218
2371
  return false;
1219
2372
  }
1220
- }
1221
2373
 
1222
- /**
1223
- * @description Redis兼容API - 批量获取
1224
- * @param {...String} keys 键名列表
1225
- * @return {Promise<Array>} 与键顺序对应的值数组,不存在返回null
1226
- */
1227
- async mget(...keys) {
1228
- try {
1229
- await this._ensureConnected();
1230
- // 确保keys是数组
1231
- const keyArray = Array.isArray(keys[0]) ? keys[0] : keys;
1232
- const results = await this.find({ key: { $in: keyArray } });
1233
- const resultMap = {};
1234
- results.forEach(item => {
1235
- resultMap[item.key] = item.value;
1236
- });
2374
+ // 使用可能被修改的值
2375
+ let {
2376
+ keyValues: finalData,
2377
+ seconds: finalSeconds
2378
+ } = beforeEventData;
1237
2379
 
1238
- // 返回与输入键顺序对应的数组,不存在的键对应位置返回null
1239
- return keyArray.map(key => resultMap.hasOwnProperty(key) ? resultMap[key] : null);
1240
- } catch (error) {
1241
- $.log.error('[MongoDB] 批量获取失败:', error);
1242
- return [];
2380
+ // 参数验证,确保keyValues是对象且不为null
2381
+ if (typeof finalData !== 'object' || finalData === null || Array.isArray(finalData)) {
2382
+ $.log.warn('[MongoDB] mset: 参数必须是非空对象');
2383
+ return false;
1243
2384
  }
1244
- }
1245
2385
 
1246
- /**
1247
- * @description Redis兼容API - 批量设置
1248
- * @param {Object} keyValues 键值对象
1249
- * @return {Promise<Boolean>} 是否成功
1250
- */
1251
- async mset(keyValues) {
1252
- try {
1253
- await this._ensureConnected();
1254
- for (const [key, value] of Object.entries(keyValues)) {
1255
- await this._add(key, value);
1256
- }
1257
- return true;
1258
- } catch (error) {
1259
- $.log.error('[MongoDB] 批量设置失败:', error);
1260
- return false;
2386
+ await this._ensureConnected();
2387
+ for (const [key, value] of Object.entries(finalData)) {
2388
+ await this._add(key, value, finalSeconds);
1261
2389
  }
1262
- }
1263
2390
 
1264
- /**
1265
- * @description Redis兼容API - 有序集合添加成员
1266
- * @param {String} key 键名
1267
- * @param {Number} score 分数
1268
- * @param {*} member 成员
1269
- * @return {Promise<Boolean>} 是否成功
1270
- */
1271
- async zadd(key, score, member) {
1272
- try {
1273
- await this._ensureConnected();
1274
- const item = await this.findOne({ key: key });
1275
- let sortedSet = item && Array.isArray(item.value) ? item.value : [];
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
+ };
1276
2409
 
1277
- // 检查成员是否已存在,如果存在则更新分数
1278
- const memberStr = JSON.stringify(member);
1279
- const existingIndex = sortedSet.findIndex(item => JSON.stringify(item.member) === memberStr);
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 : [];
1280
2424
 
1281
- if (existingIndex >= 0) {
1282
- sortedSet[existingIndex].score = score;
1283
- } else {
1284
- sortedSet.push({ score, member });
1285
- }
2425
+ // 检查成员是否已存在,如果存在则更新分数
2426
+ const memberStr = JSON.stringify(member);
2427
+ const existingIndex = sortedSet.findIndex(item => JSON.stringify(item.member) === memberStr);
1286
2428
 
1287
- // 按分数排序
1288
- sortedSet.sort((a, b) => a.score - b.score);
1289
- await this._add(key, sortedSet);
1290
- return true;
1291
- } catch (error) {
1292
- $.log.error('[MongoDB] 有序集合添加成员失败:', error);
1293
- return false;
2429
+ if (existingIndex >= 0) {
2430
+ sortedSet[existingIndex].score = score;
2431
+ } else {
2432
+ sortedSet.push({
2433
+ score,
2434
+ member
2435
+ });
1294
2436
  }
1295
- }
1296
2437
 
1297
- /**
1298
- * @description Redis兼容API - 有序集合范围查询(按分数)
1299
- * @param {String} key 键名
1300
- * @param {Number} min 最小分数
1301
- * @param {Number} max 最大分数
1302
- * @return {Promise<Array>} 成员数组
1303
- */
1304
- async zrangebyscore(key, min, max) {
1305
- try {
1306
- await this._ensureConnected();
1307
- const item = await this.findOne({ key: key });
1308
- if (!item || !Array.isArray(item.value)) {
1309
- return [];
1310
- }
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
+ };
1311
2447
 
1312
- // 过滤分数范围内的成员
1313
- return item.value
1314
- .filter(item => item.score >= min && item.score <= max)
1315
- .map(item => item.member);
1316
- } catch (error) {
1317
- $.log.error('[MongoDB] 有序集合范围查询失败:', error);
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)) {
1318
2462
  return [];
1319
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 [];
1320
2472
  }
2473
+ };
1321
2474
 
1322
- /**
1323
- * @description Redis兼容API - 有序集合成员数量
1324
- * @param {String} key 键名
1325
- * @return {Promise<Number>} 成员数量
1326
- */
1327
- async zcard(key) {
1328
- try {
1329
- await this._ensureConnected();
1330
- const item = await this.findOne({ key: key });
1331
- if (!item || !Array.isArray(item.value)) {
1332
- return 0;
1333
- }
1334
- return item.value.length;
1335
- } catch (error) {
1336
- $.log.error('[MongoDB] 有序集合成员数量失败:', error);
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)) {
1337
2487
  return 0;
1338
2488
  }
2489
+ return item.value.length;
2490
+ } catch (error) {
2491
+ $.log.error('[MongoDB] 有序集合成员数量失败:', error);
2492
+ return 0;
1339
2493
  }
2494
+ };
1340
2495
 
1341
- /**
1342
- * @description Redis兼容API - 有序集合删除成员
1343
- * @param {String} key 键名
1344
- * @param {*} member 成员
1345
- * @return {Promise<Number>} 删除的成员数量
1346
- */
1347
- async zrem(key, member) {
1348
- try {
1349
- await this._ensureConnected();
1350
- const item = await this.findOne({ key: key });
1351
- if (!item || !Array.isArray(item.value)) {
1352
- return 0;
1353
- }
1354
-
1355
- const memberStr = JSON.stringify(member);
1356
- const filtered = item.value.filter(item => JSON.stringify(item.member) !== memberStr);
1357
- const removedCount = item.value.length - filtered.length;
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
+ }
1358
2511
 
1359
- if (removedCount > 0) {
1360
- await this._add(key, filtered);
1361
- }
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;
1362
2515
 
1363
- return removedCount;
1364
- } catch (error) {
1365
- $.log.error('[MongoDB] 有序集合删除成员失败:', error);
1366
- return 0;
2516
+ if (removedCount > 0) {
2517
+ await this._add(key, filtered);
1367
2518
  }
1368
- }
1369
2519
 
1370
- /**
1371
- * @description Redis兼容API - 获取成员分数
1372
- * @param {String} key 键名
1373
- * @param {*} member 成员
1374
- * @return {Promise<Number|null>} 分数
1375
- */
1376
- async zscore(key, member) {
1377
- try {
1378
- await this._ensureConnected();
1379
- const item = await this.findOne({ key: key });
1380
- if (!item || !Array.isArray(item.value)) {
1381
- return null;
1382
- }
2520
+ return removedCount;
2521
+ } catch (error) {
2522
+ $.log.error('[MongoDB] 有序集合删除成员失败:', error);
2523
+ return 0;
2524
+ }
2525
+ };
1383
2526
 
1384
- const memberStr = JSON.stringify(member);
1385
- const found = item.value.find(item => JSON.stringify(item.member) === memberStr);
1386
- return found ? found.score : null;
1387
- } catch (error) {
1388
- $.log.error('[MongoDB] 获取成员分数失败:', error);
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)) {
1389
2540
  return null;
1390
2541
  }
1391
- }
1392
2542
 
1393
- /**
1394
- * @description Redis兼容API - 列表弹出头部元素
1395
- * @param {String} key 键名
1396
- * @return {Promise<any|null>} 弹出的元素
1397
- */
1398
- async lpop(key) {
1399
- try {
1400
- await this._ensureConnected();
1401
- const item = await this.findOne({ key: key });
1402
- if (!item || !Array.isArray(item.value) || item.value.length === 0) {
1403
- return null;
1404
- }
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
+ };
1405
2551
 
1406
- const popped = item.value.shift();
1407
- await this._set({ key: key }, { value: item.value });
1408
- return popped;
1409
- } catch (error) {
1410
- $.log.error('[MongoDB] 列表弹出头部元素失败:', error);
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) {
1411
2564
  return null;
1412
2565
  }
1413
- }
1414
2566
 
1415
- /**
1416
- * @description Redis兼容API - 列表弹出尾部元素
1417
- * @param {String} key 键名
1418
- * @return {Promise<any|null>} 弹出的元素
1419
- */
1420
- async rpop(key) {
1421
- try {
1422
- await this._ensureConnected();
1423
- const item = await this.findOne({ key: key });
1424
- if (!item || !Array.isArray(item.value) || item.value.length === 0) {
1425
- return null;
1426
- }
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
+ };
1427
2579
 
1428
- const popped = item.value.pop();
1429
- await this._set({ key: key }, { value: item.value });
1430
- return popped;
1431
- } catch (error) {
1432
- $.log.error('[MongoDB] 列表弹出尾部元素失败:', error);
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) {
1433
2592
  return null;
1434
2593
  }
1435
- }
1436
2594
 
1437
- /**
1438
- * @description Redis兼容API - 获取集合所有成员
1439
- * @param {String} key 键名
1440
- * @return {Promise<Array>} 成员数组
1441
- */
1442
- async smembers(key) {
1443
- try {
1444
- await this._ensureConnected();
1445
- const item = await this.findOne({ key: key });
1446
- if (!item || !Array.isArray(item.value)) {
1447
- return [];
1448
- }
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
+ };
1449
2607
 
1450
- // 去重并返回
1451
- const uniqueMap = new Map();
1452
- item.value.forEach(member => {
1453
- const keyStr = JSON.stringify(member);
1454
- if (!uniqueMap.has(keyStr)) {
1455
- uniqueMap.set(keyStr, member);
1456
- }
1457
- });
1458
- return Array.from(uniqueMap.values());
1459
- } catch (error) {
1460
- $.log.error('[MongoDB] 获取集合所有成员失败:', error);
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)) {
1461
2620
  return [];
1462
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 [];
1463
2635
  }
1464
- }
2636
+ };
1465
2637
 
1466
2638
  /**
1467
- * @description 设置配置
1468
- * @param {Object|String} cg 配置对象或配置路径
2639
+ * @description 查询单条数据
2640
+ * @param {Object} query 查询条件对象
2641
+ * @param {Object} options 查询选项
2642
+ * @return {Promise<Object|null>} 查询结果
1469
2643
  */
1470
- MongoDB.prototype.setConfig = function (cg) {
1471
- var obj;
1472
- if (typeof (cg) === "string") {
1473
- obj = cg.loadJson ? cg.loadJson(this.dir) : null;
1474
- } else if (cg) {
1475
- obj = cg;
1476
- }
1477
- if (obj) {
1478
- $.push(this.config, obj);
1479
- // 设置新配置后重新验证
1480
- 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;
1481
2656
  }
1482
2657
  };
1483
2658
 
@@ -1519,7 +2694,9 @@ MongoDB.prototype.open = async function () {
1519
2694
  this.connected = false;
1520
2695
  });
1521
2696
 
1522
- $.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
+ );
1523
2700
  return true;
1524
2701
  } catch (error) {
1525
2702
  $.log.error('[MongoDB] MongoDB连接失败:', error);
@@ -1544,10 +2721,10 @@ MongoDB.prototype.close = async function () {
1544
2721
  };
1545
2722
 
1546
2723
  /**
1547
- * @description 创建数据表
1548
- * @param {Object} table 表名
1549
- * @return {Promise|Object} 执行结果
1550
- */
2724
+ * @description 创建数据表
2725
+ * @param {Object} table 表名
2726
+ * @return {Promise|Object} 执行结果
2727
+ */
1551
2728
  MongoDB.prototype.addTable = async function (table) {
1552
2729
  await this._ensureConnected();
1553
2730
  if (table) {
@@ -1571,7 +2748,9 @@ MongoDB.prototype.createIndex = async function (keys, options = {}) {
1571
2748
  throw new Error('数据库未连接或未指定表名');
1572
2749
  }
1573
2750
 
1574
- $.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
+ );
1575
2754
 
1576
2755
  return await this.db.collection(this.table).createIndex(keys, options);
1577
2756
  } catch (error) {
@@ -1598,7 +2777,8 @@ MongoDB.prototype.createTTLIndex = async function (keys, expireAfterSeconds) {
1598
2777
  expireAfterSeconds: expireAfterSeconds
1599
2778
  };
1600
2779
 
1601
- $.log.info(`[MongoDB] 创建TTL索引: ${this.table}, keys: ${JSON.stringify(keys)}, 过期时间: ${expireAfterSeconds}秒`);
2780
+ $.log.info(
2781
+ `[MongoDB] 创建TTL索引: ${this.table}, keys: ${JSON.stringify(keys)}, 过期时间: ${expireAfterSeconds}秒`);
1602
2782
 
1603
2783
  return await this.db.collection(this.table).createIndex(keys, options);
1604
2784
  } catch (error) {
@@ -1608,9 +2788,9 @@ MongoDB.prototype.createTTLIndex = async function (keys, expireAfterSeconds) {
1608
2788
  };
1609
2789
 
1610
2790
  /**
1611
- * @description 获取集合的所有索引
1612
- * @returns {Promise<Array>} 索引信息数组
1613
- */
2791
+ * @description 获取集合的所有索引
2792
+ * @returns {Promise<Array>} 索引信息数组
2793
+ */
1614
2794
  MongoDB.prototype.getIndexes = async function () {
1615
2795
  await this._ensureConnected();
1616
2796
  try {
@@ -1621,7 +2801,7 @@ MongoDB.prototype.getIndexes = async function () {
1621
2801
  $.log.info(`[MongoDB] 获取索引: ${this.table}`);
1622
2802
  return await this.db.collection(this.table).indexes();
1623
2803
  } catch (error) {
1624
- $.log.error(`[MongoDB] 获取索引失败: ${error.message}`);
2804
+ $.log.error(`[MongoDB] [getIndexes] 获取索引失败: ${error.message}`);
1625
2805
  return [];
1626
2806
  }
1627
2807
  };
@@ -1640,11 +2820,11 @@ MongoDB.prototype.dropIndex = async function (indexName) {
1640
2820
  throw new Error('索引名称不能为空');
1641
2821
  }
1642
2822
  try {
1643
- $.log.info(`[MongoDB] 删除索引: ${this.table}, 索引名: ${indexName}`);
2823
+ $.log.info(`[MongoDB] [dropIndex] 删除索引: ${this.table}, 索引名: ${indexName}`);
1644
2824
  await this.db.collection(this.table).dropIndex(indexName);
1645
2825
  return true;
1646
2826
  } catch (error) {
1647
- $.log.error(`[MongoDB] 删除索引失败: ${error.message}`);
2827
+ $.log.error(`[MongoDB] [dropIndex] 删除索引失败: ${error.message}`);
1648
2828
  throw error;
1649
2829
  }
1650
2830
  };
@@ -1659,11 +2839,11 @@ MongoDB.prototype.dropTable = async function () {
1659
2839
  throw new Error('未指定表名');
1660
2840
  }
1661
2841
  try {
1662
- $.log.info(`[MongoDB] 删除集合: ${this.table}`);
2842
+ $.log.info(`[MongoDB] [dropTable] 删除集合: ${this.table}`);
1663
2843
  await this.db.collection(this.table).drop();
1664
2844
  return true;
1665
2845
  } catch (error) {
1666
- $.log.error(`[MongoDB] 删除集合失败: ${error.message}`);
2846
+ $.log.error(`[MongoDB] [dropTable] 删除集合失败: ${error.message}`);
1667
2847
  throw error;
1668
2848
  }
1669
2849
  };
@@ -1677,59 +2857,431 @@ MongoDB.prototype.addObj = async function (obj) {
1677
2857
  await this._ensureConnected();
1678
2858
  try {
1679
2859
  // 触发前置事件
1680
- this.emit('addObj_before', { table: this.table, data: obj });
2860
+ this.emit('addObj_before', {
2861
+ table: this.table,
2862
+ data: obj
2863
+ });
1681
2864
 
1682
2865
  // 执行插入操作
1683
2866
  const result = await this.db.collection(this.table).insertOne(obj);
1684
2867
 
1685
2868
  // 触发后置事件
1686
- 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
+ });
1687
2874
 
1688
2875
  return result;
1689
2876
  } catch (error) {
1690
2877
  // 触发错误事件
1691
- 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
+ });
1692
2883
  throw error;
1693
2884
  }
1694
2885
  };
1695
2886
 
1696
2887
  /**
1697
- * @description Redis兼容API - 根据键获取值
1698
- * @param {String} key 键名
1699
- * @return {Promise<any|null>} 存储的值,如果不存在则返回null
1700
- */
1701
- MongoDB.prototype.get = async function (key) {
1702
- await this._ensureConnected();
1703
- try {
1704
- const result = await this.db.collection(this.table).findOne({ key: key });
1705
- return result ? result.value : null;
1706
- } catch (error) {
1707
- $.log.error(`[MongoDB] 获取键值失败: ${error.message}`);
2888
+ * @description Redis兼容API - 根据键获取值
2889
+ * @param {String} key 键名
2890
+ * @return {Promise<any|null>} 存储的值,如果不存在则返回null
2891
+ */
2892
+ MongoDB.prototype.get = async function (key) {
2893
+ await this._ensureConnected();
2894
+ try {
2895
+ // 参数验证
2896
+ if (!key || typeof key !== 'string') {
1708
2897
  return null;
1709
2898
  }
1710
- };
1711
2899
 
1712
- /**
1713
- * @description Redis兼容API - 删除键
2900
+ const result = await this.db.collection(this.table).findOne({
2901
+ key: key
2902
+ });
2903
+ return result ? result.value : null;
2904
+ } catch (error) {
2905
+ $.log.error(`[MongoDB] 获取键值失败: ${error.message}`);
2906
+ return null;
2907
+ }
2908
+ };
2909
+
2910
+ /**
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 清空列表,保留键和过期时间
1714
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 键名或键名数组
1715
3194
  * @return {Promise<Boolean>} 是否删除成功
1716
3195
  */
1717
3196
  MongoDB.prototype.del = async function (key) {
1718
3197
  await this._ensureConnected();
1719
3198
  try {
1720
- // 触发前置事件
1721
- this.emit('del_before', { table: this.table, key: key });
1722
-
1723
- const result = await this.db.collection(this.table).deleteOne({ key: key });
1724
-
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
+ }
3237
+
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
+ }
3269
+
1725
3270
  // 触发后置事件
1726
- this.emit('del_after', { table: this.table, key: key, result });
1727
-
1728
- return result.deletedCount > 0;
3271
+ this.emit('del_after', {
3272
+ key: finalKey,
3273
+ success,
3274
+ deleted
3275
+ });
3276
+
3277
+ return success;
1729
3278
  } catch (error) {
1730
3279
  // 触发错误事件
1731
- this.emit('del_error', { table: this.table, key: key, error });
1732
- $.log.error(`[MongoDB] 删除键失败: ${error.message}`);
3280
+ this.emit('del_error', {
3281
+ key,
3282
+ error: error
3283
+ });
3284
+ $.log.error('[MongoDB] 删除缓存失败:', error);
1733
3285
  return false;
1734
3286
  }
1735
3287
  };
@@ -1738,32 +3290,81 @@ MongoDB.prototype.del = async function (key) {
1738
3290
  * @description Redis兼容API - 添加键值对
1739
3291
  * @param {String} key 键名
1740
3292
  * @param {any} value 值
3293
+ * @param {Number} seconds 过期时间(秒)
1741
3294
  * @return {Promise<Boolean>} 是否添加成功(如果键已存在则返回false)
1742
3295
  */
1743
- MongoDB.prototype.add = async function (key, value) {
3296
+ MongoDB.prototype.add = async function (key, value, seconds) {
1744
3297
  await this._ensureConnected();
1745
3298
  try {
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;
3320
+
1746
3321
  // 检查键是否已存在
1747
- 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
+ });
1748
3325
  if (exists) {
3326
+ // 键已存在时触发后置事件,但返回false
3327
+ this.emit('add_after', {
3328
+ key: finalKey,
3329
+ value: finalValue,
3330
+ ttl: finalSeconds,
3331
+ success: false
3332
+ });
1749
3333
  return false;
1750
3334
  }
1751
-
1752
- const data = { key: key, value: value };
1753
-
1754
- // 触发前置事件
1755
- this.emit('add_before', { table: this.table, key: key, value: value });
1756
-
3335
+
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
+ }
3346
+
1757
3347
  // 添加键值对
1758
3348
  const result = await this.db.collection(this.table).insertOne(data);
1759
-
3349
+ const success = result.acknowledged;
3350
+
1760
3351
  // 触发后置事件
1761
- this.emit('add_after', { table: this.table, key: key, value: value, result });
1762
-
1763
- return result.acknowledged;
3352
+ this.emit('add_after', {
3353
+ key: finalKey,
3354
+ value: finalValue,
3355
+ ttl: finalSeconds,
3356
+ success
3357
+ });
3358
+
3359
+ return success;
1764
3360
  } catch (error) {
1765
3361
  // 触发错误事件
1766
- 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
+ });
1767
3368
  $.log.error(`[MongoDB] 添加键值失败: ${error.message}`);
1768
3369
  return false;
1769
3370
  }
@@ -1775,26 +3376,81 @@ MongoDB.prototype.add = async function (key, value) {
1775
3376
  * @param {any} value 值
1776
3377
  * @return {Promise<Boolean>} 是否设置成功
1777
3378
  */
1778
- MongoDB.prototype.set = async function (key, value) {
3379
+ MongoDB.prototype.set = async function (key, value, seconds) {
1779
3380
  await this._ensureConnected();
1780
3381
  try {
1781
- // 触发前置事件
1782
- this.emit('set_before', { table: this.table, key: key, value: value });
1783
-
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
+ }
3427
+
1784
3428
  // 更新或插入键值对
1785
- const result = await this.db.collection(this.table).updateOne(
1786
- { key: key },
1787
- { $set: { value: value } },
1788
- { upsert: true }
3429
+ const result = await this.db.collection(this.table).updateOne({
3430
+ key: finalKey
3431
+ },
3432
+ updateData, {
3433
+ upsert: true
3434
+ }
1789
3435
  );
1790
-
3436
+
1791
3437
  // 触发后置事件
1792
- this.emit('set_after', { table: this.table, key: key, value: value, result });
1793
-
3438
+ this.emit('set_after', {
3439
+ key: finalKey,
3440
+ value: finalValue,
3441
+ ttl: finalSeconds,
3442
+ success: result.acknowledged
3443
+ });
3444
+
1794
3445
  return result.acknowledged;
1795
3446
  } catch (error) {
1796
3447
  // 触发错误事件
1797
- 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
+ });
1798
3454
  $.log.error(`[MongoDB] 设置键值失败: ${error.message}`);
1799
3455
  return false;
1800
3456
  }
@@ -1809,18 +3465,29 @@ MongoDB.prototype.addList = async function (list) {
1809
3465
  await this._ensureConnected();
1810
3466
  try {
1811
3467
  // 触发前置事件
1812
- this.emit('addList_before', { table: this.table, data: list });
3468
+ this.emit('addList_before', {
3469
+ table: this.table,
3470
+ data: list
3471
+ });
1813
3472
 
1814
3473
  // 执行插入操作
1815
3474
  const result = await this.db.collection(this.table).insertMany(list);
1816
3475
 
1817
3476
  // 触发后置事件
1818
- 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
+ });
1819
3482
 
1820
3483
  return result;
1821
3484
  } catch (error) {
1822
3485
  // 触发错误事件
1823
- 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
+ });
1824
3491
  throw error;
1825
3492
  }
1826
3493
  };
@@ -1834,21 +3501,34 @@ MongoDB.prototype.delete = async function (query) {
1834
3501
  await this._ensureConnected();
1835
3502
  try {
1836
3503
  // 如果query是字符串,则作为键名处理
1837
- const safeQuery = typeof query === 'string' ? { key: query } : query;
3504
+ const safeQuery = typeof query === 'string' ? {
3505
+ key: query
3506
+ } : query;
1838
3507
 
1839
3508
  // 触发前置事件
1840
- this.emit('delete_before', { table: this.table, query: safeQuery });
3509
+ this.emit('delete_before', {
3510
+ table: this.table,
3511
+ query: safeQuery
3512
+ });
1841
3513
 
1842
3514
  // 执行删除操作
1843
3515
  const result = await this.db.collection(this.table).deleteMany(safeQuery);
1844
3516
 
1845
3517
  // 触发后置事件
1846
- 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
+ });
1847
3523
 
1848
3524
  return result;
1849
3525
  } catch (error) {
1850
3526
  // 触发错误事件
1851
- this.emit('delete_error', { table: this.table, query, error });
3527
+ this.emit('delete_error', {
3528
+ table: this.table,
3529
+ query,
3530
+ error
3531
+ });
1852
3532
  throw error;
1853
3533
  }
1854
3534
  };
@@ -1864,36 +3544,63 @@ MongoDB.prototype.setting = async function (query, body, options = {}) {
1864
3544
  await this._ensureConnected();
1865
3545
  try {
1866
3546
  // 处理Redis兼容模式:如果query是字符串,则将query作为键名
1867
- const safeQuery = typeof query === 'string' ? { key: query } : query;
3547
+ const safeQuery = typeof query === 'string' ? {
3548
+ key: query
3549
+ } : query;
1868
3550
 
1869
3551
  // 默认使用$set操作符进行部分更新
1870
3552
  let updateBody;
1871
3553
  if (typeof query === 'string') {
1872
3554
  // Redis兼容模式:如果是字符串键名,直接设置value
1873
- updateBody = { $set: { value: body } };
3555
+ updateBody = {
3556
+ $set: {
3557
+ value: body
3558
+ }
3559
+ };
1874
3560
  } else if (!body.$set && !body.$inc && !body.$push) {
1875
- updateBody = { $set: body };
3561
+ updateBody = {
3562
+ $set: body
3563
+ };
1876
3564
  } else {
1877
3565
  updateBody = body;
1878
3566
  }
1879
3567
 
1880
3568
  // 默认更新多条
1881
- const updateOptions = Object.assign({ multi: true }, options);
3569
+ const updateOptions = Object.assign({
3570
+ multi: true
3571
+ }, options);
1882
3572
 
1883
3573
  // 触发前置事件
1884
- 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
+ });
1885
3580
 
1886
3581
  // 执行更新操作
1887
3582
  const result = await this.db.collection(this.table).updateMany(safeQuery, updateBody, updateOptions);
1888
3583
 
1889
3584
  // 触发后置事件
1890
- 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
+ });
1891
3592
 
1892
3593
  // Redis兼容模式返回布尔值,否则返回完整结果
1893
3594
  return typeof query === 'string' ? result.modifiedCount > 0 : result;
1894
3595
  } catch (error) {
1895
3596
  // 触发错误事件
1896
- 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
+ });
1897
3604
 
1898
3605
  // Redis兼容模式返回布尔值
1899
3606
  return typeof query === 'string' ? false : null;
@@ -1919,22 +3626,6 @@ MongoDB.prototype.find = async function (query, options = {}) {
1919
3626
  }
1920
3627
  };
1921
3628
 
1922
- /**
1923
- * @description 查询单条数据
1924
- * @param {Object} query 查询条件对象
1925
- * @param {Object} options 查询选项
1926
- * @return {Promise<Object|null>} 查询结果
1927
- */
1928
- MongoDB.prototype.findOne = async function (query, options = {}) {
1929
- await this._ensureConnected();
1930
- try {
1931
- const safeQuery = this._sanitizeQuery(query);
1932
- return await this.db.collection(this.table).findOne(safeQuery, options);
1933
- } catch (error) {
1934
- $.log.error('[MongoDB] 查询单条数据失败:', error);
1935
- return null;
1936
- }
1937
- };
1938
3629
 
1939
3630
  /**
1940
3631
  * @description 计算文档数量
@@ -1964,7 +3655,8 @@ MongoDB.prototype._sanitizeQuery = function (query) {
1964
3655
 
1965
3656
  for (const [key, value] of Object.entries(query)) {
1966
3657
  // 防止NoSQL注入,如 {$ne: null} 等操作符注入
1967
- 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) {
1968
3660
  $.log.warn('[MongoDB] 检测到可能的操作符注入尝试:', key);
1969
3661
  continue;
1970
3662
  }
@@ -1985,7 +3677,7 @@ MongoDB.prototype.startTransaction = async function () {
1985
3677
  session.startTransaction();
1986
3678
  return session;
1987
3679
  } catch (error) {
1988
- $.log.error('[MongoDB] 启动事务失败:', error);
3680
+ $.log.error('[MongoDB] [startTransaction] 启动事务失败:', error);
1989
3681
  throw error;
1990
3682
  }
1991
3683
  };
@@ -2000,7 +3692,7 @@ MongoDB.prototype.commitTransaction = async function (session) {
2000
3692
  await session.commitTransaction();
2001
3693
  await session.endSession();
2002
3694
  } catch (error) {
2003
- $.log.error('[MongoDB] 提交事务失败:', error);
3695
+ $.log.error('[MongoDB] [commitTransaction] 提交事务失败:', error);
2004
3696
  throw error;
2005
3697
  }
2006
3698
  };
@@ -2015,7 +3707,7 @@ MongoDB.prototype.rollbackTransaction = async function (session) {
2015
3707
  await session.abortTransaction();
2016
3708
  await session.endSession();
2017
3709
  } catch (error) {
2018
- $.log.error('[MongoDB] 回滚事务失败:', error);
3710
+ $.log.error('[MongoDB] [rollbackTransaction] 回滚事务失败:', error);
2019
3711
  throw error;
2020
3712
  }
2021
3713
  };
@@ -2038,25 +3730,29 @@ if (!$.pool.mongoDB) {
2038
3730
  /**
2039
3731
  * @description MongoDB管理器,用于创建缓存
2040
3732
  * @param {String} scope 作用域
2041
- * @param {String} dir 当前路径
3733
+ * @param {Object} config 配置参数
2042
3734
  * @return {Object} 返回一个MongoDB类实例
2043
3735
  */
2044
- function mongoDB_admin(scope, dir) {
3736
+ function mongodb_admin(scope, config) {
2045
3737
  if (!scope) {
2046
- scope = ($.val && $.val.scope) || 'default';
3738
+ scope = 'sys';
2047
3739
  }
2048
3740
  var obj = $.pool.mongoDB[scope];
2049
3741
  if (!obj) {
2050
- $.pool.mongoDB[scope] = new MongoDB(scope, dir);
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);
2051
3750
  obj = $.pool.mongoDB[scope];
2052
3751
  }
2053
3752
  return obj;
2054
3753
  }
2055
3754
 
2056
-
2057
-
2058
-
2059
3755
  /**
2060
3756
  * @module 导出MongoDB管理器
2061
3757
  */
2062
- exports.mongoDB_admin = mongoDB_admin;
3758
+ exports.mongodb_admin = mongodb_admin;