mm_mongodb 1.4.4 → 1.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +483 -231
- package/cache.js +2680 -173
- package/compatibility_table.md +85 -0
- package/index.js +1613 -67
- package/package.json +20 -13
- package/test.js +6 -8
- package/test_redis_compatibility.js +123 -0
package/index.js
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileOverview
|
|
2
|
+
* @fileOverview MongoDB帮助类函数
|
|
3
3
|
* @author <a href="http://qww.elins.cn">邱文武</a>
|
|
4
4
|
* @version 1.2.1
|
|
5
5
|
*/
|
|
6
|
-
require('
|
|
6
|
+
const eventEmitter = require('events');
|
|
7
|
+
require('mm_logs');
|
|
7
8
|
const MongoClient = require('mongodb').MongoClient;
|
|
8
9
|
|
|
10
|
+
/**
|
|
11
|
+
* 确保全局$对象存在
|
|
12
|
+
*/
|
|
13
|
+
if (!global.$) {
|
|
14
|
+
global.$ = {};
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
/**
|
|
10
18
|
* @description MongoDB帮助类
|
|
11
19
|
*/
|
|
12
|
-
class MongoDB {
|
|
20
|
+
class MongoDB extends eventEmitter {
|
|
13
21
|
/**
|
|
14
22
|
* @description 创建MongoDB帮助类函数 (构造函数)
|
|
15
23
|
* @param {String} scope 作用域
|
|
@@ -17,18 +25,15 @@ class MongoDB {
|
|
|
17
25
|
* @constructor
|
|
18
26
|
*/
|
|
19
27
|
constructor(scope, dir, config) {
|
|
28
|
+
super();
|
|
20
29
|
// 作用域
|
|
21
|
-
this.scope;
|
|
22
|
-
if (scope) {
|
|
23
|
-
this.scope = scope;
|
|
24
|
-
} else {
|
|
25
|
-
this.scope = $.val.scope + '';
|
|
26
|
-
}
|
|
30
|
+
this.scope = scope || ($.val && $.val.scope ? String($.val.scope) : 'default');
|
|
27
31
|
// 当前目录
|
|
28
|
-
this.dir = __dirname;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
this.dir = dir || __dirname;
|
|
33
|
+
|
|
34
|
+
// 从环境变量加载配置
|
|
35
|
+
const envConfig = this._loadEnvConfig();
|
|
36
|
+
|
|
32
37
|
this.config = Object.assign({
|
|
33
38
|
// 服务器地址
|
|
34
39
|
host: "localhost",
|
|
@@ -40,25 +45,36 @@ class MongoDB {
|
|
|
40
45
|
user: "admin",
|
|
41
46
|
// 密码
|
|
42
47
|
password: "",
|
|
43
|
-
//
|
|
48
|
+
// 连接选项
|
|
44
49
|
options: {
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
//
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
// 移除已废弃的选项
|
|
51
|
+
// 使用MongoDB驱动推荐的配置
|
|
52
|
+
// 连接池配置
|
|
53
|
+
minPoolSize: 5,
|
|
54
|
+
maxPoolSize: 20,
|
|
55
|
+
// 连接超时时间
|
|
56
|
+
connectTimeoutMS: 10000,
|
|
57
|
+
// 服务器选择超时
|
|
58
|
+
serverSelectionTimeoutMS: 5000,
|
|
59
|
+
// 重试策略
|
|
60
|
+
retryWrites: true
|
|
61
|
+
},
|
|
62
|
+
// 重试配置
|
|
63
|
+
retry: {
|
|
64
|
+
maxAttempts: 3,
|
|
65
|
+
delayMS: 1000
|
|
66
|
+
}
|
|
67
|
+
}, envConfig, config);
|
|
68
|
+
|
|
69
|
+
// 验证配置
|
|
70
|
+
this._validateConfig();
|
|
71
|
+
|
|
56
72
|
// 连接地址
|
|
57
|
-
this.url;
|
|
73
|
+
this.url = null;
|
|
58
74
|
// 数据库连接器
|
|
59
|
-
this.conn;
|
|
75
|
+
this.conn = null;
|
|
60
76
|
// 操作的数据库
|
|
61
|
-
this.db;
|
|
77
|
+
this.db = null;
|
|
62
78
|
// 表名
|
|
63
79
|
this.table = "cache";
|
|
64
80
|
/**
|
|
@@ -69,6 +85,1207 @@ class MongoDB {
|
|
|
69
85
|
* 每页显示条数
|
|
70
86
|
*/
|
|
71
87
|
this.size = 0;
|
|
88
|
+
// 连接状态
|
|
89
|
+
this.connected = false;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @private
|
|
94
|
+
* @description 从环境变量加载配置
|
|
95
|
+
* @return {Object} 配置对象
|
|
96
|
+
*/
|
|
97
|
+
_loadEnvConfig() {
|
|
98
|
+
const envConfig = {};
|
|
99
|
+
|
|
100
|
+
if (process.env.MONGODB_HOST) envConfig.host = process.env.MONGODB_HOST;
|
|
101
|
+
if (process.env.MONGODB_PORT) envConfig.port = parseInt(process.env.MONGODB_PORT, 10);
|
|
102
|
+
if (process.env.MONGODB_DATABASE) envConfig.database = process.env.MONGODB_DATABASE;
|
|
103
|
+
if (process.env.MONGODB_USER) envConfig.user = process.env.MONGODB_USER;
|
|
104
|
+
if (process.env.MONGODB_PASSWORD) envConfig.password = process.env.MONGODB_PASSWORD;
|
|
105
|
+
|
|
106
|
+
return envConfig;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @private
|
|
111
|
+
* @description 验证配置有效性
|
|
112
|
+
* @throws {Error} 配置无效时抛出错误
|
|
113
|
+
*/
|
|
114
|
+
_validateConfig() {
|
|
115
|
+
if (!this.config.host || typeof this.config.host !== 'string') {
|
|
116
|
+
throw new Error('MongoDB配置无效:host必须是有效的字符串');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!Number.isInteger(this.config.port) || this.config.port < 1 || this.config.port > 65535) {
|
|
120
|
+
throw new Error('MongoDB配置无效:port必须是1-65535之间的整数');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!this.config.database || typeof this.config.database !== 'string') {
|
|
124
|
+
throw new Error('MongoDB配置无效:database必须是有效的字符串');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @private
|
|
130
|
+
* @description 执行带重试的操作
|
|
131
|
+
* @param {Function} operation 要执行的操作
|
|
132
|
+
* @param {String} operationName 操作名称
|
|
133
|
+
* @return {Promise} 操作结果
|
|
134
|
+
*/
|
|
135
|
+
async _withRetry(operation, operationName) {
|
|
136
|
+
const maxAttempts = this.config.retry?.maxAttempts || 3;
|
|
137
|
+
const delayMS = this.config.retry?.delayMS || 1000;
|
|
138
|
+
let lastError;
|
|
139
|
+
|
|
140
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
141
|
+
try {
|
|
142
|
+
return await operation();
|
|
143
|
+
} catch (error) {
|
|
144
|
+
lastError = error;
|
|
145
|
+
$.log.error(`[MongoDB] ${operationName} 第${attempt}次尝试失败:`, error.message);
|
|
146
|
+
|
|
147
|
+
// 如果不是最后一次尝试,则等待后重试
|
|
148
|
+
if (attempt < maxAttempts) {
|
|
149
|
+
await new Promise(resolve => setTimeout(resolve, delayMS));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 所有重试都失败,抛出最后一个错误
|
|
155
|
+
throw lastError;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* @description Redis兼容API - 获取缓存值
|
|
160
|
+
* @param {String} key 键名
|
|
161
|
+
* @return {Promise<any>} 缓存值
|
|
162
|
+
*/
|
|
163
|
+
async get(key) {
|
|
164
|
+
try {
|
|
165
|
+
const result = await this.findOne({ key: key });
|
|
166
|
+
return result ? result.value : null;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
$.log.error('[MongoDB] 获取缓存失败:', error);
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @description Redis兼容API - 清空所有缓存
|
|
175
|
+
* @return {Promise<Boolean>} 是否成功
|
|
176
|
+
*/
|
|
177
|
+
async clear() {
|
|
178
|
+
try {
|
|
179
|
+
await this.del({});
|
|
180
|
+
return true;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
$.log.error('[MongoDB] 清空缓存失败:', error);
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* @description Redis兼容API - 获取所有键
|
|
189
|
+
* @param {String} pattern 匹配模式
|
|
190
|
+
* @return {Promise<Array>} 键名数组
|
|
191
|
+
*/
|
|
192
|
+
async keys(pattern = '*') {
|
|
193
|
+
try {
|
|
194
|
+
let query = {};
|
|
195
|
+
if (pattern !== '*') {
|
|
196
|
+
// 简单的通配符支持
|
|
197
|
+
const regexPattern = pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
|
|
198
|
+
query.key = { $regex: new RegExp(`^${regexPattern}$`) };
|
|
199
|
+
}
|
|
200
|
+
const results = await this.find(query, { projection: { key: 1 } });
|
|
201
|
+
return results.map(item => item.key);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
$.log.error('[MongoDB] 获取键列表失败:', error);
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* @description Redis兼容API - 设置过期时间
|
|
210
|
+
* @param {String} key 键名
|
|
211
|
+
* @param {Number} seconds 过期时间(秒)
|
|
212
|
+
* @return {Promise<Boolean>} 是否成功
|
|
213
|
+
*/
|
|
214
|
+
async ttl(key, seconds) {
|
|
215
|
+
try {
|
|
216
|
+
if (!seconds && seconds !== 0) {
|
|
217
|
+
// 获取剩余过期时间
|
|
218
|
+
const item = await this.findOne({ key: key });
|
|
219
|
+
if (!item || !item.expireAt) return -1;
|
|
220
|
+
const ttl = Math.floor((item.expireAt.getTime() - Date.now()) / 1000);
|
|
221
|
+
return ttl > 0 ? ttl : -2; // -2 表示已过期
|
|
222
|
+
} else {
|
|
223
|
+
// 设置过期时间
|
|
224
|
+
const expireAt = seconds === 0 ? null : new Date(Date.now() + seconds * 1000);
|
|
225
|
+
const result = await this.set({ key: key }, { expireAt: expireAt });
|
|
226
|
+
return result.modifiedCount > 0;
|
|
227
|
+
}
|
|
228
|
+
} catch (error) {
|
|
229
|
+
$.log.error('[MongoDB] 设置TTL失败:', error);
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @description Redis兼容API - 添加缓存
|
|
236
|
+
* @param {String} key 键名
|
|
237
|
+
* @param {*} value 值
|
|
238
|
+
* @param {Number} expire 过期时间(秒)
|
|
239
|
+
* @return {Promise<Boolean>} 是否成功
|
|
240
|
+
*/
|
|
241
|
+
async add(key, value, expire) {
|
|
242
|
+
try {
|
|
243
|
+
const data = {
|
|
244
|
+
key: key,
|
|
245
|
+
value: value
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
if (expire) {
|
|
249
|
+
data.expireAt = new Date(Date.now() + expire * 1000);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// 检查是否已存在
|
|
253
|
+
const existing = await this.findOne({ key: key });
|
|
254
|
+
if (existing) {
|
|
255
|
+
await this.set({ key: key }, data);
|
|
256
|
+
} else {
|
|
257
|
+
await this.addObj(data);
|
|
258
|
+
}
|
|
259
|
+
return true;
|
|
260
|
+
} catch (error) {
|
|
261
|
+
$.log.error('[MongoDB] 添加缓存失败:', error);
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* @description Redis兼容API - 增加整数值
|
|
268
|
+
* @param {String} key 键名
|
|
269
|
+
* @param {Number} value 增加值
|
|
270
|
+
* @return {Promise<Number>} 增加后的值
|
|
271
|
+
*/
|
|
272
|
+
async addInt(key, value = 1) {
|
|
273
|
+
try {
|
|
274
|
+
const item = await this.findOne({ key: key });
|
|
275
|
+
let currentValue = 0;
|
|
276
|
+
|
|
277
|
+
if (item && typeof item.value === 'number') {
|
|
278
|
+
currentValue = item.value;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const newValue = currentValue + value;
|
|
282
|
+
await this.add(key, newValue);
|
|
283
|
+
return newValue;
|
|
284
|
+
} catch (error) {
|
|
285
|
+
$.log.error('[MongoDB] 增加整数值失败:', error);
|
|
286
|
+
return undefined;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @description Redis兼容API - 增加浮点数值
|
|
292
|
+
* @param {String} key 键名
|
|
293
|
+
* @param {Number} value 增加值
|
|
294
|
+
* @return {Promise<Number>} 增加后的值
|
|
295
|
+
*/
|
|
296
|
+
async addFloat(key, value = 1.0) {
|
|
297
|
+
return this.addInt(key, value); // 复用addInt方法
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @description Redis兼容API - 添加字符串
|
|
302
|
+
* @param {String} key 键名
|
|
303
|
+
* @param {String} value 字符串值
|
|
304
|
+
* @return {Promise<Boolean>} 是否成功
|
|
305
|
+
*/
|
|
306
|
+
async addStr(key, value) {
|
|
307
|
+
return this.add(key, String(value));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* @description Redis兼容API - 排序
|
|
312
|
+
* @param {String} key 键名
|
|
313
|
+
* @param {Object} options 排序选项
|
|
314
|
+
* @return {Promise<Array>} 排序结果
|
|
315
|
+
*/
|
|
316
|
+
async sort(key, options = {}) {
|
|
317
|
+
try {
|
|
318
|
+
// 假设value是数组
|
|
319
|
+
const item = await this.findOne({ key: key });
|
|
320
|
+
if (!item || !Array.isArray(item.value)) return [];
|
|
321
|
+
|
|
322
|
+
const sorted = [...item.value];
|
|
323
|
+
|
|
324
|
+
// 处理排序选项
|
|
325
|
+
if (options.desc) {
|
|
326
|
+
sorted.sort((a, b) => b - a);
|
|
327
|
+
} else {
|
|
328
|
+
sorted.sort((a, b) => a - b);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// 处理限制
|
|
332
|
+
if (options.limit) {
|
|
333
|
+
const offset = options.offset || 0;
|
|
334
|
+
return sorted.slice(offset, offset + options.limit);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
return sorted;
|
|
338
|
+
} catch (error) {
|
|
339
|
+
$.log.error('[MongoDB] 排序失败:', error);
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* @description Redis兼容API - 哈希表设置字段
|
|
346
|
+
* @param {String} key 键名
|
|
347
|
+
* @param {String} field 字段名
|
|
348
|
+
* @param {*} value 值
|
|
349
|
+
* @return {Promise<Boolean>} 是否成功
|
|
350
|
+
*/
|
|
351
|
+
async hset(key, field, value) {
|
|
352
|
+
try {
|
|
353
|
+
const item = await this.findOne({ key: key });
|
|
354
|
+
let hash = item && typeof item.value === 'object' && item.value !== null ? item.value : {};
|
|
355
|
+
|
|
356
|
+
hash[field] = value;
|
|
357
|
+
await this.add(key, hash);
|
|
358
|
+
return true;
|
|
359
|
+
} catch (error) {
|
|
360
|
+
$.log.error('[MongoDB] 哈希表设置失败:', error);
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* @description Redis兼容API - 哈希表获取字段
|
|
367
|
+
* @param {String} key 键名
|
|
368
|
+
* @param {String} field 字段名
|
|
369
|
+
* @return {Promise<any>} 字段值
|
|
370
|
+
*/
|
|
371
|
+
async hget(key, field) {
|
|
372
|
+
try {
|
|
373
|
+
const item = await this.findOne({ key: key });
|
|
374
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
375
|
+
return null;
|
|
376
|
+
}
|
|
377
|
+
return item.value[field] !== undefined ? item.value[field] : null;
|
|
378
|
+
} catch (error) {
|
|
379
|
+
$.log.error('[MongoDB] 哈希表获取失败:', error);
|
|
380
|
+
return null;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* @description Redis兼容API - 哈希表获取所有字段和值
|
|
386
|
+
* @param {String} key 键名
|
|
387
|
+
* @return {Promise<Object>} 哈希表对象
|
|
388
|
+
*/
|
|
389
|
+
async hgetall(key) {
|
|
390
|
+
try {
|
|
391
|
+
const item = await this.findOne({ key: key });
|
|
392
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
393
|
+
return {};
|
|
394
|
+
}
|
|
395
|
+
return item.value;
|
|
396
|
+
} catch (error) {
|
|
397
|
+
$.log.error('[MongoDB] 哈希表获取全部失败:', error);
|
|
398
|
+
return {};
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* @description Redis兼容API - 哈希表删除字段
|
|
404
|
+
* @param {String} key 键名
|
|
405
|
+
* @param {String} field 字段名
|
|
406
|
+
* @return {Promise<Boolean>} 是否成功
|
|
407
|
+
*/
|
|
408
|
+
async hdel(key, field) {
|
|
409
|
+
try {
|
|
410
|
+
const item = await this.findOne({ key: key });
|
|
411
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
412
|
+
return false;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (item.value[field] === undefined) {
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
delete item.value[field];
|
|
420
|
+
await this.set({ key: key }, { value: item.value });
|
|
421
|
+
return true;
|
|
422
|
+
} catch (error) {
|
|
423
|
+
$.log.error('[MongoDB] 哈希表删除失败:', error);
|
|
424
|
+
return false;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* @description Redis兼容API - 检查键是否存在
|
|
430
|
+
* @param {String} key 键名
|
|
431
|
+
* @return {Promise<Boolean>} 是否存在
|
|
432
|
+
*/
|
|
433
|
+
async has(key) {
|
|
434
|
+
try {
|
|
435
|
+
const result = await this.findOne({ key: key });
|
|
436
|
+
return result !== null;
|
|
437
|
+
} catch (error) {
|
|
438
|
+
$.log.error('[MongoDB] 检查键是否存在失败:', error);
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* @description Redis兼容API - 设置键过期时间(映射到ttl)
|
|
445
|
+
* @param {String} key 键名
|
|
446
|
+
* @param {Number} seconds 过期时间(秒)
|
|
447
|
+
* @return {Promise<Boolean>} 是否成功
|
|
448
|
+
*/
|
|
449
|
+
async expire(key, seconds) {
|
|
450
|
+
return this.ttl(key, seconds);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* @description Redis兼容API - 递增(通过addInt模拟)
|
|
455
|
+
* @param {String} key 键名
|
|
456
|
+
* @return {Promise<Number>} 递增后的值
|
|
457
|
+
*/
|
|
458
|
+
async incr(key) {
|
|
459
|
+
return this.addInt(key, 1);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* @description Redis兼容API - 递减(通过addInt模拟)
|
|
464
|
+
* @param {String} key 键名
|
|
465
|
+
* @return {Promise<Number>} 递减后的值
|
|
466
|
+
*/
|
|
467
|
+
async decr(key) {
|
|
468
|
+
return this.addInt(key, -1);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* @description Redis兼容API - 检查键是否存在(别名)
|
|
473
|
+
* @param {String} key 键名
|
|
474
|
+
* @return {Promise<Boolean>} 是否存在
|
|
475
|
+
*/
|
|
476
|
+
async exists(key) {
|
|
477
|
+
return this.has(key);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* @description Redis兼容API - 设置键值
|
|
482
|
+
* @param {String} key 键名
|
|
483
|
+
* @param {*} value 值
|
|
484
|
+
* @return {Promise<Boolean>} 是否成功
|
|
485
|
+
*/
|
|
486
|
+
async set(key, value) {
|
|
487
|
+
try {
|
|
488
|
+
// 直接使用已有的add方法,它已正确处理查询条件格式
|
|
489
|
+
return await this.add(key, value);
|
|
490
|
+
} catch (error) {
|
|
491
|
+
$.log.error('[MongoDB] 设置键值失败:', error);
|
|
492
|
+
return false;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* @description Redis兼容API - 哈希表批量设置
|
|
498
|
+
* @param {String} key 键名
|
|
499
|
+
* @param {Object} fieldValues 字段值对象
|
|
500
|
+
* @return {Promise<Boolean>} 是否成功
|
|
501
|
+
*/
|
|
502
|
+
async hmset(key, fieldValues) {
|
|
503
|
+
try {
|
|
504
|
+
const item = await this.findOne({ key: key });
|
|
505
|
+
let hash = item && typeof item.value === 'object' && item.value !== null ? item.value : {};
|
|
506
|
+
|
|
507
|
+
// 合并字段值
|
|
508
|
+
Object.assign(hash, fieldValues);
|
|
509
|
+
await this.add(key, hash);
|
|
510
|
+
return true;
|
|
511
|
+
} catch (error) {
|
|
512
|
+
$.log.error('[MongoDB] 哈希表批量设置失败:', error);
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* @description Redis兼容API - 哈希表批量获取
|
|
519
|
+
* @param {String} key 键名
|
|
520
|
+
* @param {Array} fields 字段名数组
|
|
521
|
+
* @return {Promise<Object>} 字段值对象
|
|
522
|
+
*/
|
|
523
|
+
async hmget(key, fields) {
|
|
524
|
+
try {
|
|
525
|
+
const item = await this.findOne({ key: key });
|
|
526
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
527
|
+
return {};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const result = {};
|
|
531
|
+
fields.forEach(field => {
|
|
532
|
+
result[field] = item.value[field] !== undefined ? item.value[field] : null;
|
|
533
|
+
});
|
|
534
|
+
return result;
|
|
535
|
+
} catch (error) {
|
|
536
|
+
$.log.error('[MongoDB] 哈希表批量获取失败:', error);
|
|
537
|
+
return {};
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* @description Redis兼容API - 哈希表批量删除字段
|
|
543
|
+
* @param {String} key 键名
|
|
544
|
+
* @param {Array} fields 字段名数组
|
|
545
|
+
* @return {Promise<Boolean>} 是否成功
|
|
546
|
+
*/
|
|
547
|
+
async hdelMulti(key, fields) {
|
|
548
|
+
try {
|
|
549
|
+
const item = await this.findOne({ key: key });
|
|
550
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
let deleted = false;
|
|
555
|
+
fields.forEach(field => {
|
|
556
|
+
if (item.value[field] !== undefined) {
|
|
557
|
+
delete item.value[field];
|
|
558
|
+
deleted = true;
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
if (deleted) {
|
|
563
|
+
await this.set({ key: key }, { value: item.value });
|
|
564
|
+
}
|
|
565
|
+
return deleted;
|
|
566
|
+
} catch (error) {
|
|
567
|
+
$.log.error('[MongoDB] 哈希表批量删除失败:', error);
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* @description Redis兼容API - 哈希表获取所有值
|
|
574
|
+
* @param {String} key 键名
|
|
575
|
+
* @return {Promise<Array>} 值数组
|
|
576
|
+
*/
|
|
577
|
+
async hvals(key) {
|
|
578
|
+
try {
|
|
579
|
+
const item = await this.findOne({ key: key });
|
|
580
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
581
|
+
return [];
|
|
582
|
+
}
|
|
583
|
+
return Object.values(item.value);
|
|
584
|
+
} catch (error) {
|
|
585
|
+
$.log.error('[MongoDB] 哈希表获取所有值失败:', error);
|
|
586
|
+
return [];
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* @description Redis兼容API - 哈希表获取所有字段名
|
|
592
|
+
* @param {String} key 键名
|
|
593
|
+
* @return {Promise<Array>} 字段名数组
|
|
594
|
+
*/
|
|
595
|
+
async hkeys(key) {
|
|
596
|
+
try {
|
|
597
|
+
const item = await this.findOne({ key: key });
|
|
598
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
599
|
+
return [];
|
|
600
|
+
}
|
|
601
|
+
return Object.keys(item.value);
|
|
602
|
+
} catch (error) {
|
|
603
|
+
$.log.error('[MongoDB] 哈希表获取所有字段名失败:', error);
|
|
604
|
+
return [];
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* @description Redis兼容API - 哈希表字段数量
|
|
610
|
+
* @param {String} key 键名
|
|
611
|
+
* @return {Promise<Number>} 字段数量
|
|
612
|
+
*/
|
|
613
|
+
async hlen(key) {
|
|
614
|
+
try {
|
|
615
|
+
const item = await this.findOne({ key: key });
|
|
616
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
617
|
+
return 0;
|
|
618
|
+
}
|
|
619
|
+
return Object.keys(item.value).length;
|
|
620
|
+
} catch (error) {
|
|
621
|
+
$.log.error('[MongoDB] 哈希表字段数量失败:', error);
|
|
622
|
+
return 0;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* @description Redis兼容API - 哈希表字段是否存在
|
|
628
|
+
* @param {String} key 键名
|
|
629
|
+
* @param {String} field 字段名
|
|
630
|
+
* @return {Promise<Boolean>} 是否存在
|
|
631
|
+
*/
|
|
632
|
+
async hexists(key, field) {
|
|
633
|
+
try {
|
|
634
|
+
const item = await this.findOne({ key: key });
|
|
635
|
+
if (!item || typeof item.value !== 'object' || item.value === null) {
|
|
636
|
+
return false;
|
|
637
|
+
}
|
|
638
|
+
return field in item.value;
|
|
639
|
+
} catch (error) {
|
|
640
|
+
$.log.error('[MongoDB] 哈希表字段是否存在失败:', error);
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* @description Redis兼容API - 列表尾部添加元素
|
|
647
|
+
* @param {String} key 键名
|
|
648
|
+
* @param {*} value 值
|
|
649
|
+
* @return {Promise<Number>} 添加后列表长度
|
|
650
|
+
*/
|
|
651
|
+
async rpush(key, value) {
|
|
652
|
+
try {
|
|
653
|
+
const item = await this.findOne({ key: key });
|
|
654
|
+
let list = item && Array.isArray(item.value) ? item.value : [];
|
|
655
|
+
|
|
656
|
+
list.push(value);
|
|
657
|
+
await this.add(key, list);
|
|
658
|
+
return list.length;
|
|
659
|
+
} catch (error) {
|
|
660
|
+
$.log.error('[MongoDB] 列表尾部添加失败:', error);
|
|
661
|
+
return 0;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* @description Redis兼容API - 列表头部添加元素
|
|
667
|
+
* @param {String} key 键名
|
|
668
|
+
* @param {*} value 值
|
|
669
|
+
* @return {Promise<Number>} 添加后列表长度
|
|
670
|
+
*/
|
|
671
|
+
async lpush(key, value) {
|
|
672
|
+
try {
|
|
673
|
+
const item = await this.findOne({ key: key });
|
|
674
|
+
let list = item && Array.isArray(item.value) ? item.value : [];
|
|
675
|
+
|
|
676
|
+
list.unshift(value);
|
|
677
|
+
await this.add(key, list);
|
|
678
|
+
return list.length;
|
|
679
|
+
} catch (error) {
|
|
680
|
+
$.log.error('[MongoDB] 列表头部添加失败:', error);
|
|
681
|
+
return 0;
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/**
|
|
686
|
+
* @description Redis兼容API - 列表范围查询
|
|
687
|
+
* @param {String} key 键名
|
|
688
|
+
* @param {Number} start 起始索引
|
|
689
|
+
* @param {Number} stop 结束索引
|
|
690
|
+
* @return {Promise<Array>} 元素数组
|
|
691
|
+
*/
|
|
692
|
+
async lrange(key, start, stop) {
|
|
693
|
+
try {
|
|
694
|
+
const item = await this.findOne({ key: key });
|
|
695
|
+
if (!item || !Array.isArray(item.value)) {
|
|
696
|
+
return [];
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// 处理负数索引
|
|
700
|
+
let startIndex = start < 0 ? item.value.length + start : start;
|
|
701
|
+
let endIndex = stop < 0 ? item.value.length + stop : stop;
|
|
702
|
+
|
|
703
|
+
// 确保索引有效
|
|
704
|
+
startIndex = Math.max(0, Math.min(startIndex, item.value.length - 1));
|
|
705
|
+
endIndex = Math.max(startIndex, Math.min(endIndex, item.value.length - 1));
|
|
706
|
+
|
|
707
|
+
return item.value.slice(startIndex, endIndex + 1);
|
|
708
|
+
} catch (error) {
|
|
709
|
+
$.log.error('[MongoDB] 列表范围查询失败:', error);
|
|
710
|
+
return [];
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
/**
|
|
715
|
+
* @description Redis兼容API - 获取列表指定索引元素
|
|
716
|
+
* @param {String} key 键名
|
|
717
|
+
* @param {Number} index 索引
|
|
718
|
+
* @return {Promise<any>} 元素值
|
|
719
|
+
*/
|
|
720
|
+
async lindex(key, index) {
|
|
721
|
+
try {
|
|
722
|
+
const item = await this.findOne({ key: key });
|
|
723
|
+
if (!item || !Array.isArray(item.value)) {
|
|
724
|
+
return null;
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// 处理负数索引
|
|
728
|
+
const realIndex = index < 0 ? item.value.length + index : index;
|
|
729
|
+
|
|
730
|
+
if (realIndex < 0 || realIndex >= item.value.length) {
|
|
731
|
+
return null;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
return item.value[realIndex];
|
|
735
|
+
} catch (error) {
|
|
736
|
+
$.log.error('[MongoDB] 获取列表索引元素失败:', error);
|
|
737
|
+
return null;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* @description Redis兼容API - 设置列表指定索引元素
|
|
743
|
+
* @param {String} key 键名
|
|
744
|
+
* @param {Number} index 索引
|
|
745
|
+
* @param {*} value 值
|
|
746
|
+
* @return {Promise<Boolean>} 是否成功
|
|
747
|
+
*/
|
|
748
|
+
async lset(key, index, value) {
|
|
749
|
+
try {
|
|
750
|
+
const item = await this.findOne({ key: key });
|
|
751
|
+
if (!item || !Array.isArray(item.value)) {
|
|
752
|
+
return false;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// 处理负数索引
|
|
756
|
+
const realIndex = index < 0 ? item.value.length + index : index;
|
|
757
|
+
|
|
758
|
+
if (realIndex < 0 || realIndex >= item.value.length) {
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
item.value[realIndex] = value;
|
|
763
|
+
await this.set({ key: key }, { value: item.value });
|
|
764
|
+
return true;
|
|
765
|
+
} catch (error) {
|
|
766
|
+
$.log.error('[MongoDB] 设置列表索引元素失败:', error);
|
|
767
|
+
return false;
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
/**
|
|
772
|
+
* @description Redis兼容API - 删除列表元素
|
|
773
|
+
* @param {String} key 键名
|
|
774
|
+
* @param {Number} count 删除数量
|
|
775
|
+
* @param {*} value 要删除的值
|
|
776
|
+
* @return {Promise<Number>} 删除的元素数量
|
|
777
|
+
*/
|
|
778
|
+
async lrem(key, count, value) {
|
|
779
|
+
try {
|
|
780
|
+
const item = await this.findOne({ key: key });
|
|
781
|
+
if (!item || !Array.isArray(item.value)) {
|
|
782
|
+
return 0;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
let list = [...item.value];
|
|
786
|
+
let removedCount = 0;
|
|
787
|
+
|
|
788
|
+
if (count > 0) {
|
|
789
|
+
// 从头部删除count个等于value的元素
|
|
790
|
+
for (let i = 0; i < list.length && removedCount < count; i++) {
|
|
791
|
+
if (JSON.stringify(list[i]) === JSON.stringify(value)) {
|
|
792
|
+
list.splice(i, 1);
|
|
793
|
+
removedCount++;
|
|
794
|
+
i--; // 调整索引
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
} else if (count < 0) {
|
|
798
|
+
// 从尾部删除|count|个等于value的元素
|
|
799
|
+
count = Math.abs(count);
|
|
800
|
+
for (let i = list.length - 1; i >= 0 && removedCount < count; i--) {
|
|
801
|
+
if (JSON.stringify(list[i]) === JSON.stringify(value)) {
|
|
802
|
+
list.splice(i, 1);
|
|
803
|
+
removedCount++;
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
// 删除所有等于value的元素
|
|
808
|
+
list = list.filter(item => JSON.stringify(item) !== JSON.stringify(value));
|
|
809
|
+
removedCount = item.value.length - list.length;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (removedCount > 0) {
|
|
813
|
+
await this.set({ key: key }, { value: list });
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
return removedCount;
|
|
817
|
+
} catch (error) {
|
|
818
|
+
$.log.error('[MongoDB] 删除列表元素失败:', error);
|
|
819
|
+
return 0;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* @description Redis兼容API - 裁剪列表
|
|
825
|
+
* @param {String} key 键名
|
|
826
|
+
* @param {Number} start 起始索引
|
|
827
|
+
* @param {Number} stop 结束索引
|
|
828
|
+
* @return {Promise<Boolean>} 是否成功
|
|
829
|
+
*/
|
|
830
|
+
async ltrim(key, start, stop) {
|
|
831
|
+
try {
|
|
832
|
+
const item = await this.findOne({ key: key });
|
|
833
|
+
if (!item || !Array.isArray(item.value)) {
|
|
834
|
+
return false;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// 使用lrange的逻辑获取裁剪后的列表
|
|
838
|
+
const trimmed = await this.lrange(key, start, stop);
|
|
839
|
+
await this.set({ key: key }, { value: trimmed });
|
|
840
|
+
return true;
|
|
841
|
+
} catch (error) {
|
|
842
|
+
$.log.error('[MongoDB] 裁剪列表失败:', error);
|
|
843
|
+
return false;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* @description Redis兼容API - 列表长度
|
|
849
|
+
* @param {String} key 键名
|
|
850
|
+
* @return {Promise<Number>} 列表长度
|
|
851
|
+
*/
|
|
852
|
+
async llen(key) {
|
|
853
|
+
try {
|
|
854
|
+
const item = await this.findOne({ key: key });
|
|
855
|
+
if (!item || !Array.isArray(item.value)) {
|
|
856
|
+
return 0;
|
|
857
|
+
}
|
|
858
|
+
return item.value.length;
|
|
859
|
+
} catch (error) {
|
|
860
|
+
$.log.error('[MongoDB] 获取列表长度失败:', error);
|
|
861
|
+
return 0;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* @description Redis兼容API - 集合批量添加
|
|
867
|
+
* @param {String} key 键名
|
|
868
|
+
* @param {Array} members 成员数组
|
|
869
|
+
* @return {Promise<Number>} 添加的成员数量
|
|
870
|
+
*/
|
|
871
|
+
async saddMulti(key, members) {
|
|
872
|
+
try {
|
|
873
|
+
const item = await this.findOne({ key: key });
|
|
874
|
+
let set = item && Array.isArray(item.value) ? [...new Set(item.value)] : [];
|
|
875
|
+
let addedCount = 0;
|
|
876
|
+
|
|
877
|
+
members.forEach(member => {
|
|
878
|
+
if (!set.some(existing => JSON.stringify(existing) === JSON.stringify(member))) {
|
|
879
|
+
set.push(member);
|
|
880
|
+
addedCount++;
|
|
881
|
+
}
|
|
882
|
+
});
|
|
883
|
+
|
|
884
|
+
if (addedCount > 0) {
|
|
885
|
+
await this.add(key, set);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
return addedCount;
|
|
889
|
+
} catch (error) {
|
|
890
|
+
$.log.error('[MongoDB] 集合并发添加失败:', error);
|
|
891
|
+
return 0;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* @description Redis兼容API - 集合批量删除
|
|
897
|
+
* @param {String} key 键名
|
|
898
|
+
* @param {Array} members 成员数组
|
|
899
|
+
* @return {Promise<Number>} 删除的成员数量
|
|
900
|
+
*/
|
|
901
|
+
async sremMulti(key, members) {
|
|
902
|
+
try {
|
|
903
|
+
const item = await this.findOne({ key: key });
|
|
904
|
+
if (!item || !Array.isArray(item.value)) {
|
|
905
|
+
return 0;
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
let set = [...new Set(item.value)];
|
|
909
|
+
const originalLength = set.length;
|
|
910
|
+
|
|
911
|
+
// 创建要删除的值的字符串表示集合用于快速查找
|
|
912
|
+
const membersToRemove = new Set(members.map(m => JSON.stringify(m)));
|
|
913
|
+
set = set.filter(member => !membersToRemove.has(JSON.stringify(member)));
|
|
914
|
+
|
|
915
|
+
const removedCount = originalLength - set.length;
|
|
916
|
+
if (removedCount > 0) {
|
|
917
|
+
await this.add(key, set);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
return removedCount;
|
|
921
|
+
} catch (error) {
|
|
922
|
+
$.log.error('[MongoDB] 集合并发删除失败:', error);
|
|
923
|
+
return 0;
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
/**
|
|
928
|
+
* @description Redis兼容API - 集合差集
|
|
929
|
+
* @param {String} key1 第一个集合键
|
|
930
|
+
* @param {String} key2 第二个集合键
|
|
931
|
+
* @return {Promise<Array>} 差集数组
|
|
932
|
+
*/
|
|
933
|
+
async sdiff(key1, key2) {
|
|
934
|
+
try {
|
|
935
|
+
const item1 = await this.findOne({ key: key1 });
|
|
936
|
+
const item2 = await this.findOne({ key: key2 });
|
|
937
|
+
|
|
938
|
+
const set1 = item1 && Array.isArray(item1.value) ? [...new Set(item1.value)] : [];
|
|
939
|
+
const set2 = item2 && Array.isArray(item2.value) ? [...new Set(item2.value)] : [];
|
|
940
|
+
|
|
941
|
+
// 创建第二个集合值的字符串表示用于快速查找
|
|
942
|
+
const set2Str = new Set(set2.map(m => JSON.stringify(m)));
|
|
943
|
+
|
|
944
|
+
// 找出在set1中但不在set2中的元素
|
|
945
|
+
return set1.filter(member => !set2Str.has(JSON.stringify(member)));
|
|
946
|
+
} catch (error) {
|
|
947
|
+
$.log.error('[MongoDB] 集合差集失败:', error);
|
|
948
|
+
return [];
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/**
|
|
953
|
+
* @description Redis兼容API - 集合交集
|
|
954
|
+
* @param {String} key1 第一个集合键
|
|
955
|
+
* @param {String} key2 第二个集合键
|
|
956
|
+
* @return {Promise<Array>} 交集数组
|
|
957
|
+
*/
|
|
958
|
+
async sinter(key1, key2) {
|
|
959
|
+
try {
|
|
960
|
+
const item1 = await this.findOne({ key: key1 });
|
|
961
|
+
const item2 = await this.findOne({ key: key2 });
|
|
962
|
+
|
|
963
|
+
const set1 = item1 && Array.isArray(item1.value) ? [...new Set(item1.value)] : [];
|
|
964
|
+
const set2 = item2 && Array.isArray(item2.value) ? [...new Set(item2.value)] : [];
|
|
965
|
+
|
|
966
|
+
// 创建较小集合的字符串表示用于快速查找,优化性能
|
|
967
|
+
let smallerSet, largerSet;
|
|
968
|
+
if (set1.length <= set2.length) {
|
|
969
|
+
smallerSet = set1;
|
|
970
|
+
largerSet = set2;
|
|
971
|
+
} else {
|
|
972
|
+
smallerSet = set2;
|
|
973
|
+
largerSet = set1;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
const largerSetStr = new Set(largerSet.map(m => JSON.stringify(m)));
|
|
977
|
+
|
|
978
|
+
// 找出同时存在于两个集合中的元素
|
|
979
|
+
return smallerSet.filter(member => largerSetStr.has(JSON.stringify(member)));
|
|
980
|
+
} catch (error) {
|
|
981
|
+
$.log.error('[MongoDB] 集合并集失败:', error);
|
|
982
|
+
return [];
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* @description Redis兼容API - 集合并集
|
|
988
|
+
* @param {String} key1 第一个集合键
|
|
989
|
+
* @param {String} key2 第二个集合键
|
|
990
|
+
* @return {Promise<Array>} 并集数组
|
|
991
|
+
*/
|
|
992
|
+
async sunion(key1, key2) {
|
|
993
|
+
try {
|
|
994
|
+
const item1 = await this.findOne({ key: key1 });
|
|
995
|
+
const item2 = await this.findOne({ key: key2 });
|
|
996
|
+
|
|
997
|
+
const set1 = item1 && Array.isArray(item1.value) ? item1.value : [];
|
|
998
|
+
const set2 = item2 && Array.isArray(item2.value) ? item2.value : [];
|
|
999
|
+
|
|
1000
|
+
// 合并并去重
|
|
1001
|
+
const combined = [...set1, ...set2];
|
|
1002
|
+
// 使用Map进行对象去重,基于JSON字符串表示
|
|
1003
|
+
const uniqueMap = new Map();
|
|
1004
|
+
|
|
1005
|
+
combined.forEach(item => {
|
|
1006
|
+
const key = JSON.stringify(item);
|
|
1007
|
+
if (!uniqueMap.has(key)) {
|
|
1008
|
+
uniqueMap.set(key, item);
|
|
1009
|
+
}
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
return Array.from(uniqueMap.values());
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
$.log.error('[MongoDB] 集合并集失败:', error);
|
|
1015
|
+
return [];
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/**
|
|
1020
|
+
* @description Redis兼容API - 集合成员数量
|
|
1021
|
+
* @param {String} key 键名
|
|
1022
|
+
* @return {Promise<Number>} 成员数量
|
|
1023
|
+
*/
|
|
1024
|
+
async scard(key) {
|
|
1025
|
+
try {
|
|
1026
|
+
const item = await this.findOne({ key: key });
|
|
1027
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1028
|
+
return 0;
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
// 使用Set去重并计算大小
|
|
1032
|
+
const uniqueItems = new Set(item.value.map(m => JSON.stringify(m)));
|
|
1033
|
+
return uniqueItems.size;
|
|
1034
|
+
} catch (error) {
|
|
1035
|
+
$.log.error('[MongoDB] 获取集合成员数量失败:', error);
|
|
1036
|
+
return 0;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
/**
|
|
1041
|
+
* @description Redis兼容API - 检查是否是集合成员
|
|
1042
|
+
* @param {String} key 键名
|
|
1043
|
+
* @param {*} member 成员
|
|
1044
|
+
* @return {Promise<Boolean>} 是否是成员
|
|
1045
|
+
*/
|
|
1046
|
+
async sismember(key, member) {
|
|
1047
|
+
try {
|
|
1048
|
+
const item = await this.findOne({ key: key });
|
|
1049
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1050
|
+
return false;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// 检查是否存在相同的成员
|
|
1054
|
+
const memberStr = JSON.stringify(member);
|
|
1055
|
+
return item.value.some(m => JSON.stringify(m) === memberStr);
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
$.log.error('[MongoDB] 检查集合成员失败:', error);
|
|
1058
|
+
return false;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* @description Redis兼容API - 批量获取
|
|
1064
|
+
* @param {Array} keys 键名数组
|
|
1065
|
+
* @return {Promise<Object>} 键值对象
|
|
1066
|
+
*/
|
|
1067
|
+
async mget(keys) {
|
|
1068
|
+
try {
|
|
1069
|
+
const results = await this.find({ key: { $in: keys } });
|
|
1070
|
+
const resultMap = {};
|
|
1071
|
+
results.forEach(item => {
|
|
1072
|
+
resultMap[item.key] = item.value;
|
|
1073
|
+
});
|
|
1074
|
+
return resultMap;
|
|
1075
|
+
} catch (error) {
|
|
1076
|
+
$.log.error('[MongoDB] 批量获取失败:', error);
|
|
1077
|
+
return {};
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
/**
|
|
1082
|
+
* @description Redis兼容API - 批量设置
|
|
1083
|
+
* @param {Object} keyValues 键值对象
|
|
1084
|
+
* @return {Promise<Boolean>} 是否成功
|
|
1085
|
+
*/
|
|
1086
|
+
async mset(keyValues) {
|
|
1087
|
+
try {
|
|
1088
|
+
for (const [key, value] of Object.entries(keyValues)) {
|
|
1089
|
+
await this.add(key, value);
|
|
1090
|
+
}
|
|
1091
|
+
return true;
|
|
1092
|
+
} catch (error) {
|
|
1093
|
+
$.log.error('[MongoDB] 批量设置失败:', error);
|
|
1094
|
+
return false;
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/**
|
|
1099
|
+
* @description Redis兼容API - 有序集合添加成员
|
|
1100
|
+
* @param {String} key 键名
|
|
1101
|
+
* @param {Number} score 分数
|
|
1102
|
+
* @param {*} member 成员
|
|
1103
|
+
* @return {Promise<Boolean>} 是否成功
|
|
1104
|
+
*/
|
|
1105
|
+
async zadd(key, score, member) {
|
|
1106
|
+
try {
|
|
1107
|
+
const item = await this.findOne({ key: key });
|
|
1108
|
+
let sortedSet = item && Array.isArray(item.value) ? item.value : [];
|
|
1109
|
+
|
|
1110
|
+
// 检查成员是否已存在,如果存在则更新分数
|
|
1111
|
+
const memberStr = JSON.stringify(member);
|
|
1112
|
+
const existingIndex = sortedSet.findIndex(item => JSON.stringify(item.member) === memberStr);
|
|
1113
|
+
|
|
1114
|
+
if (existingIndex >= 0) {
|
|
1115
|
+
sortedSet[existingIndex].score = score;
|
|
1116
|
+
} else {
|
|
1117
|
+
sortedSet.push({ score, member });
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
// 按分数排序
|
|
1121
|
+
sortedSet.sort((a, b) => a.score - b.score);
|
|
1122
|
+
await this.add(key, sortedSet);
|
|
1123
|
+
return true;
|
|
1124
|
+
} catch (error) {
|
|
1125
|
+
$.log.error('[MongoDB] 有序集合添加成员失败:', error);
|
|
1126
|
+
return false;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
/**
|
|
1131
|
+
* @description Redis兼容API - 有序集合范围查询(按分数)
|
|
1132
|
+
* @param {String} key 键名
|
|
1133
|
+
* @param {Number} min 最小分数
|
|
1134
|
+
* @param {Number} max 最大分数
|
|
1135
|
+
* @return {Promise<Array>} 成员数组
|
|
1136
|
+
*/
|
|
1137
|
+
async zrangebyscore(key, min, max) {
|
|
1138
|
+
try {
|
|
1139
|
+
const item = await this.findOne({ key: key });
|
|
1140
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1141
|
+
return [];
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// 过滤分数范围内的成员
|
|
1145
|
+
return item.value
|
|
1146
|
+
.filter(item => item.score >= min && item.score <= max)
|
|
1147
|
+
.map(item => item.member);
|
|
1148
|
+
} catch (error) {
|
|
1149
|
+
$.log.error('[MongoDB] 有序集合范围查询失败:', error);
|
|
1150
|
+
return [];
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
/**
|
|
1155
|
+
* @description Redis兼容API - 有序集合成员数量
|
|
1156
|
+
* @param {String} key 键名
|
|
1157
|
+
* @return {Promise<Number>} 成员数量
|
|
1158
|
+
*/
|
|
1159
|
+
async zcard(key) {
|
|
1160
|
+
try {
|
|
1161
|
+
const item = await this.findOne({ key: key });
|
|
1162
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1163
|
+
return 0;
|
|
1164
|
+
}
|
|
1165
|
+
return item.value.length;
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
$.log.error('[MongoDB] 有序集合成员数量失败:', error);
|
|
1168
|
+
return 0;
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
/**
|
|
1173
|
+
* @description Redis兼容API - 有序集合删除成员
|
|
1174
|
+
* @param {String} key 键名
|
|
1175
|
+
* @param {*} member 成员
|
|
1176
|
+
* @return {Promise<Number>} 删除的成员数量
|
|
1177
|
+
*/
|
|
1178
|
+
async zrem(key, member) {
|
|
1179
|
+
try {
|
|
1180
|
+
const item = await this.findOne({ key: key });
|
|
1181
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1182
|
+
return 0;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
const memberStr = JSON.stringify(member);
|
|
1186
|
+
const filtered = item.value.filter(item => JSON.stringify(item.member) !== memberStr);
|
|
1187
|
+
const removedCount = item.value.length - filtered.length;
|
|
1188
|
+
|
|
1189
|
+
if (removedCount > 0) {
|
|
1190
|
+
await this.add(key, filtered);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
return removedCount;
|
|
1194
|
+
} catch (error) {
|
|
1195
|
+
$.log.error('[MongoDB] 有序集合删除成员失败:', error);
|
|
1196
|
+
return 0;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
/**
|
|
1201
|
+
* @description Redis兼容API - 获取成员分数
|
|
1202
|
+
* @param {String} key 键名
|
|
1203
|
+
* @param {*} member 成员
|
|
1204
|
+
* @return {Promise<Number|null>} 分数
|
|
1205
|
+
*/
|
|
1206
|
+
async zscore(key, member) {
|
|
1207
|
+
try {
|
|
1208
|
+
const item = await this.findOne({ key: key });
|
|
1209
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1210
|
+
return null;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
const memberStr = JSON.stringify(member);
|
|
1214
|
+
const found = item.value.find(item => JSON.stringify(item.member) === memberStr);
|
|
1215
|
+
return found ? found.score : null;
|
|
1216
|
+
} catch (error) {
|
|
1217
|
+
$.log.error('[MongoDB] 获取成员分数失败:', error);
|
|
1218
|
+
return null;
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
/**
|
|
1223
|
+
* @description Redis兼容API - 列表弹出头部元素
|
|
1224
|
+
* @param {String} key 键名
|
|
1225
|
+
* @return {Promise<any|null>} 弹出的元素
|
|
1226
|
+
*/
|
|
1227
|
+
async lpop(key) {
|
|
1228
|
+
try {
|
|
1229
|
+
const item = await this.findOne({ key: key });
|
|
1230
|
+
if (!item || !Array.isArray(item.value) || item.value.length === 0) {
|
|
1231
|
+
return null;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
const popped = item.value.shift();
|
|
1235
|
+
await this.set({ key: key }, { value: item.value });
|
|
1236
|
+
return popped;
|
|
1237
|
+
} catch (error) {
|
|
1238
|
+
$.log.error('[MongoDB] 列表弹出头部元素失败:', error);
|
|
1239
|
+
return null;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/**
|
|
1244
|
+
* @description Redis兼容API - 列表弹出尾部元素
|
|
1245
|
+
* @param {String} key 键名
|
|
1246
|
+
* @return {Promise<any|null>} 弹出的元素
|
|
1247
|
+
*/
|
|
1248
|
+
async rpop(key) {
|
|
1249
|
+
try {
|
|
1250
|
+
const item = await this.findOne({ key: key });
|
|
1251
|
+
if (!item || !Array.isArray(item.value) || item.value.length === 0) {
|
|
1252
|
+
return null;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
const popped = item.value.pop();
|
|
1256
|
+
await this.set({ key: key }, { value: item.value });
|
|
1257
|
+
return popped;
|
|
1258
|
+
} catch (error) {
|
|
1259
|
+
$.log.error('[MongoDB] 列表弹出尾部元素失败:', error);
|
|
1260
|
+
return null;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* @description Redis兼容API - 获取集合所有成员
|
|
1266
|
+
* @param {String} key 键名
|
|
1267
|
+
* @return {Promise<Array>} 成员数组
|
|
1268
|
+
*/
|
|
1269
|
+
async smembers(key) {
|
|
1270
|
+
try {
|
|
1271
|
+
const item = await this.findOne({ key: key });
|
|
1272
|
+
if (!item || !Array.isArray(item.value)) {
|
|
1273
|
+
return [];
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// 去重并返回
|
|
1277
|
+
const uniqueMap = new Map();
|
|
1278
|
+
item.value.forEach(member => {
|
|
1279
|
+
const keyStr = JSON.stringify(member);
|
|
1280
|
+
if (!uniqueMap.has(keyStr)) {
|
|
1281
|
+
uniqueMap.set(keyStr, member);
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1284
|
+
return Array.from(uniqueMap.values());
|
|
1285
|
+
} catch (error) {
|
|
1286
|
+
$.log.error('[MongoDB] 获取集合所有成员失败:', error);
|
|
1287
|
+
return [];
|
|
1288
|
+
}
|
|
72
1289
|
}
|
|
73
1290
|
}
|
|
74
1291
|
|
|
@@ -76,49 +1293,79 @@ class MongoDB {
|
|
|
76
1293
|
* @description 设置配置
|
|
77
1294
|
* @param {Object|String} cg 配置对象或配置路径
|
|
78
1295
|
*/
|
|
79
|
-
MongoDB.prototype.setConfig = function(cg) {
|
|
1296
|
+
MongoDB.prototype.setConfig = function (cg) {
|
|
80
1297
|
var obj;
|
|
81
|
-
if (typeof(cg) === "string") {
|
|
82
|
-
obj = cg.loadJson(this.dir);
|
|
83
|
-
} else {
|
|
1298
|
+
if (typeof (cg) === "string") {
|
|
1299
|
+
obj = cg.loadJson ? cg.loadJson(this.dir) : null;
|
|
1300
|
+
} else if (cg) {
|
|
84
1301
|
obj = cg;
|
|
85
1302
|
}
|
|
86
|
-
|
|
1303
|
+
if (obj) {
|
|
1304
|
+
$.push(this.config, obj);
|
|
1305
|
+
// 设置新配置后重新验证
|
|
1306
|
+
this._validateConfig();
|
|
1307
|
+
}
|
|
87
1308
|
};
|
|
88
1309
|
|
|
89
1310
|
/**
|
|
90
1311
|
* @description 设置Url
|
|
91
1312
|
*/
|
|
92
|
-
MongoDB.prototype.setUrl = function() {
|
|
1313
|
+
MongoDB.prototype.setUrl = function () {
|
|
93
1314
|
var url = "mongodb://";
|
|
94
1315
|
if (this.config.password) {
|
|
95
|
-
url += this.config.user + ':' + this.config.password + '@';
|
|
1316
|
+
url += encodeURIComponent(this.config.user) + ':' + encodeURIComponent(this.config.password) + '@';
|
|
96
1317
|
}
|
|
97
1318
|
url += this.config.host + ":" + this.config.port + "/";
|
|
98
1319
|
this.url = url;
|
|
99
1320
|
};
|
|
100
1321
|
|
|
101
1322
|
/**
|
|
102
|
-
* @description 连接
|
|
103
|
-
* @return {Boolean} 连接成功返回true,
|
|
1323
|
+
* @description 连接MongoDB数据库
|
|
1324
|
+
* @return {Promise<Boolean>} 连接成功返回true, 失败抛出错误
|
|
104
1325
|
*/
|
|
105
|
-
MongoDB.prototype.open = async function() {
|
|
1326
|
+
MongoDB.prototype.open = async function () {
|
|
106
1327
|
if (!this.url) {
|
|
107
1328
|
this.setUrl();
|
|
108
1329
|
}
|
|
109
1330
|
if (!this.conn) {
|
|
110
|
-
|
|
111
|
-
|
|
1331
|
+
return await this._withRetry(async () => {
|
|
1332
|
+
try {
|
|
1333
|
+
this.conn = await MongoClient.connect(this.url, this.config.options);
|
|
1334
|
+
this.db = this.conn.db(this.config.database);
|
|
1335
|
+
this.connected = true;
|
|
1336
|
+
|
|
1337
|
+
// 设置连接事件监听
|
|
1338
|
+
this.conn.on('error', (err) => {
|
|
1339
|
+
$.log.error('[MongoDB] 连接错误:', err);
|
|
1340
|
+
this.connected = false;
|
|
1341
|
+
});
|
|
1342
|
+
|
|
1343
|
+
this.conn.on('close', () => {
|
|
1344
|
+
$.log.warn('[MongoDB] 连接已关闭');
|
|
1345
|
+
this.connected = false;
|
|
1346
|
+
});
|
|
1347
|
+
|
|
1348
|
+
$.log.info(`[MongoDB] 成功连接到 ${this.config.host}:${this.config.port}/${this.config.database}`);
|
|
1349
|
+
return true;
|
|
1350
|
+
} catch (error) {
|
|
1351
|
+
$.log.error('[MongoDB] MongoDB连接失败:', error);
|
|
1352
|
+
throw error;
|
|
1353
|
+
}
|
|
1354
|
+
}, '数据库连接');
|
|
112
1355
|
}
|
|
113
|
-
return
|
|
1356
|
+
return true;
|
|
114
1357
|
};
|
|
115
1358
|
|
|
116
1359
|
/**
|
|
117
1360
|
* 关闭数据库连接
|
|
118
1361
|
*/
|
|
119
|
-
MongoDB.prototype.close = function() {
|
|
1362
|
+
MongoDB.prototype.close = async function () {
|
|
120
1363
|
if (this.conn) {
|
|
121
|
-
|
|
1364
|
+
$.log.info(`[MongoDB] 关闭连接`);
|
|
1365
|
+
await this.conn.close().catch(err => $.log.error('[MongoDB] 关闭MongoDB连接时出错:', err));
|
|
1366
|
+
this.conn = null;
|
|
1367
|
+
this.db = null;
|
|
1368
|
+
this.connected = false;
|
|
122
1369
|
}
|
|
123
1370
|
};
|
|
124
1371
|
|
|
@@ -127,7 +1374,7 @@ MongoDB.prototype.close = function() {
|
|
|
127
1374
|
* @param {Object} table 表名
|
|
128
1375
|
* @return {Promise|Object} 执行结果
|
|
129
1376
|
*/
|
|
130
|
-
MongoDB.prototype.addTable = function(table) {
|
|
1377
|
+
MongoDB.prototype.addTable = function (table) {
|
|
131
1378
|
if (table) {
|
|
132
1379
|
this.table = table;
|
|
133
1380
|
} else {
|
|
@@ -136,13 +1383,139 @@ MongoDB.prototype.addTable = function(table) {
|
|
|
136
1383
|
return this.db.createCollection(table);
|
|
137
1384
|
};
|
|
138
1385
|
|
|
1386
|
+
/**
|
|
1387
|
+
* @description 创建索引
|
|
1388
|
+
* @param {Object} keys 索引键配置 {field: 1或-1}
|
|
1389
|
+
* @param {Object} options 索引选项
|
|
1390
|
+
* @returns {Promise<Object>} 创建索引的结果
|
|
1391
|
+
*/
|
|
1392
|
+
MongoDB.prototype.createIndex = async function (keys, options = {}) {
|
|
1393
|
+
try {
|
|
1394
|
+
if (!this.db || !this.table) {
|
|
1395
|
+
throw new Error('数据库未连接或未指定表名');
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
$.log.info(`[MongoDB] 创建索引: ${this.table}, keys: ${JSON.stringify(keys)}, options: ${JSON.stringify(options)}`);
|
|
1399
|
+
|
|
1400
|
+
return await this.db.collection(this.table).createIndex(keys, options);
|
|
1401
|
+
} catch (error) {
|
|
1402
|
+
$.log.error(`[MongoDB] 创建索引失败: ${error.message}`);
|
|
1403
|
+
throw error;
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
|
|
1407
|
+
/**
|
|
1408
|
+
* @description 创建TTL索引(数据自动过期)
|
|
1409
|
+
* @param {Object} keys 索引键配置 {field: 1}
|
|
1410
|
+
* @param {Number} expireAfterSeconds 过期时间(秒)
|
|
1411
|
+
* @returns {Promise<Object>} 创建索引的结果
|
|
1412
|
+
*/
|
|
1413
|
+
MongoDB.prototype.createTTLIndex = async function (keys, expireAfterSeconds) {
|
|
1414
|
+
try {
|
|
1415
|
+
if (!this.db || !this.table) {
|
|
1416
|
+
throw new Error('数据库未连接或未指定表名');
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
const options = {
|
|
1420
|
+
background: true,
|
|
1421
|
+
expireAfterSeconds: expireAfterSeconds
|
|
1422
|
+
};
|
|
1423
|
+
|
|
1424
|
+
$.log.info(`[MongoDB] 创建TTL索引: ${this.table}, keys: ${JSON.stringify(keys)}, 过期时间: ${expireAfterSeconds}秒`);
|
|
1425
|
+
|
|
1426
|
+
return await this.db.collection(this.table).createIndex(keys, options);
|
|
1427
|
+
} catch (error) {
|
|
1428
|
+
$.log.error(`[MongoDB] 创建TTL索引失败: ${error.message}`);
|
|
1429
|
+
throw error;
|
|
1430
|
+
}
|
|
1431
|
+
};
|
|
1432
|
+
|
|
1433
|
+
/**
|
|
1434
|
+
* @description 获取集合的所有索引
|
|
1435
|
+
* @returns {Promise<Array>} 索引信息数组
|
|
1436
|
+
*/
|
|
1437
|
+
MongoDB.prototype.getIndexes = async function () {
|
|
1438
|
+
try {
|
|
1439
|
+
if (!this.db || !this.table) {
|
|
1440
|
+
throw new Error('数据库未连接或未指定表名');
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
$.log.info(`[MongoDB] 获取索引: ${this.table}`);
|
|
1444
|
+
return await this.db.collection(this.table).indexes();
|
|
1445
|
+
} catch (error) {
|
|
1446
|
+
$.log.error(`[MongoDB] 获取索引失败: ${error.message}`);
|
|
1447
|
+
return [];
|
|
1448
|
+
}
|
|
1449
|
+
};
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* @description 删除指定索引
|
|
1453
|
+
* @param {String} indexName 索引名称
|
|
1454
|
+
* @returns {Promise<Boolean>} 是否删除成功
|
|
1455
|
+
*/
|
|
1456
|
+
MongoDB.prototype.dropIndex = async function (indexName) {
|
|
1457
|
+
if (!this.conn || !this.db) {
|
|
1458
|
+
throw new Error('数据库未连接');
|
|
1459
|
+
}
|
|
1460
|
+
if (!this.table) {
|
|
1461
|
+
throw new Error('未指定表名');
|
|
1462
|
+
}
|
|
1463
|
+
if (!indexName) {
|
|
1464
|
+
throw new Error('索引名称不能为空');
|
|
1465
|
+
}
|
|
1466
|
+
try {
|
|
1467
|
+
$.log.info(`[MongoDB] 删除索引: ${this.table}, 索引名: ${indexName}`);
|
|
1468
|
+
await this.db.collection(this.table).dropIndex(indexName);
|
|
1469
|
+
return true;
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
$.log.error(`[MongoDB] 删除索引失败: ${error.message}`);
|
|
1472
|
+
throw error;
|
|
1473
|
+
}
|
|
1474
|
+
};
|
|
1475
|
+
|
|
1476
|
+
/**
|
|
1477
|
+
* @description 删除集合(表)
|
|
1478
|
+
* @returns {Promise<Boolean>} 是否删除成功
|
|
1479
|
+
*/
|
|
1480
|
+
MongoDB.prototype.dropTable = async function () {
|
|
1481
|
+
if (!this.conn || !this.db) {
|
|
1482
|
+
throw new Error('数据库未连接');
|
|
1483
|
+
}
|
|
1484
|
+
if (!this.table) {
|
|
1485
|
+
throw new Error('未指定表名');
|
|
1486
|
+
}
|
|
1487
|
+
try {
|
|
1488
|
+
$.log.info(`[MongoDB] 删除集合: ${this.table}`);
|
|
1489
|
+
await this.db.collection(this.table).drop();
|
|
1490
|
+
return true;
|
|
1491
|
+
} catch (error) {
|
|
1492
|
+
$.log.error(`[MongoDB] 删除集合失败: ${error.message}`);
|
|
1493
|
+
throw error;
|
|
1494
|
+
}
|
|
1495
|
+
};
|
|
1496
|
+
|
|
139
1497
|
/**
|
|
140
1498
|
* @description 增加一条缓存
|
|
141
1499
|
* @param {Object} obj 对象
|
|
142
1500
|
* @return {Promise|Object} 执行结果
|
|
143
1501
|
*/
|
|
144
|
-
MongoDB.prototype.addObj = function(obj) {
|
|
145
|
-
|
|
1502
|
+
MongoDB.prototype.addObj = async function(obj) {
|
|
1503
|
+
try {
|
|
1504
|
+
// 触发前置事件
|
|
1505
|
+
this.emit('beforeInsert', { table: this.table, data: obj });
|
|
1506
|
+
|
|
1507
|
+
// 执行插入操作
|
|
1508
|
+
const result = await this.db.collection(this.table).insertOne(obj);
|
|
1509
|
+
|
|
1510
|
+
// 触发后置事件
|
|
1511
|
+
this.emit('afterInsert', { table: this.table, data: obj, result });
|
|
1512
|
+
|
|
1513
|
+
return result;
|
|
1514
|
+
} catch (error) {
|
|
1515
|
+
// 触发错误事件
|
|
1516
|
+
this.emit('insertError', { table: this.table, data: obj, error });
|
|
1517
|
+
throw error;
|
|
1518
|
+
}
|
|
146
1519
|
};
|
|
147
1520
|
|
|
148
1521
|
/**
|
|
@@ -150,52 +1523,225 @@ MongoDB.prototype.addObj = function(obj) {
|
|
|
150
1523
|
* @param {Array} list 对象数组
|
|
151
1524
|
* @return {Promise|Object} 执行结果
|
|
152
1525
|
*/
|
|
153
|
-
MongoDB.prototype.addList = function(list) {
|
|
154
|
-
|
|
1526
|
+
MongoDB.prototype.addList = async function(list) {
|
|
1527
|
+
try {
|
|
1528
|
+
// 触发前置事件
|
|
1529
|
+
this.emit('beforeInsertMany', { table: this.table, data: list });
|
|
1530
|
+
|
|
1531
|
+
// 执行插入操作
|
|
1532
|
+
const result = await this.db.collection(this.table).insertMany(list);
|
|
1533
|
+
|
|
1534
|
+
// 触发后置事件
|
|
1535
|
+
this.emit('afterInsertMany', { table: this.table, data: list, result });
|
|
1536
|
+
|
|
1537
|
+
return result;
|
|
1538
|
+
} catch (error) {
|
|
1539
|
+
// 触发错误事件
|
|
1540
|
+
this.emit('insertManyError', { table: this.table, data: list, error });
|
|
1541
|
+
throw error;
|
|
1542
|
+
}
|
|
155
1543
|
};
|
|
156
1544
|
|
|
157
1545
|
/**
|
|
158
1546
|
* @description 删除缓存
|
|
159
|
-
* @param {Object} query
|
|
1547
|
+
* @param {Object|String} query 查询条件对象或键名
|
|
160
1548
|
* @return {Promise|Object} 执行结果
|
|
161
1549
|
*/
|
|
162
|
-
MongoDB.prototype.del = function(query) {
|
|
163
|
-
|
|
1550
|
+
MongoDB.prototype.del = async function(query) {
|
|
1551
|
+
try {
|
|
1552
|
+
// 如果query是字符串,则作为键名处理
|
|
1553
|
+
const safeQuery = typeof query === 'string' ? { key: query } : query;
|
|
1554
|
+
|
|
1555
|
+
// 触发前置事件
|
|
1556
|
+
this.emit('beforeDelete', { table: this.table, query: safeQuery });
|
|
1557
|
+
|
|
1558
|
+
// 执行删除操作
|
|
1559
|
+
const result = await this.db.collection(this.table).deleteMany(safeQuery);
|
|
1560
|
+
|
|
1561
|
+
// 触发后置事件
|
|
1562
|
+
this.emit('afterDelete', { table: this.table, query: safeQuery, result });
|
|
1563
|
+
|
|
1564
|
+
return result;
|
|
1565
|
+
} catch (error) {
|
|
1566
|
+
// 触发错误事件
|
|
1567
|
+
this.emit('deleteError', { table: this.table, query, error });
|
|
1568
|
+
throw error;
|
|
1569
|
+
}
|
|
164
1570
|
};
|
|
165
1571
|
|
|
166
1572
|
/**
|
|
167
1573
|
* @description 修改缓存
|
|
168
|
-
* @param {Object} query
|
|
1574
|
+
* @param {Object|String} query 查询条件对象或键名
|
|
169
1575
|
* @param {Object} body 修改对象
|
|
1576
|
+
* @param {Object} options 附加选项
|
|
170
1577
|
* @return {Promise|Object} 执行结果
|
|
171
1578
|
*/
|
|
172
|
-
MongoDB.prototype.set = function(query, body) {
|
|
173
|
-
|
|
1579
|
+
MongoDB.prototype.set = async function(query, body, options = {}) {
|
|
1580
|
+
try {
|
|
1581
|
+
// 处理Redis兼容模式:如果query是字符串,则将query作为键名
|
|
1582
|
+
const safeQuery = typeof query === 'string' ? { key: query } : query;
|
|
1583
|
+
|
|
1584
|
+
// 默认使用$set操作符进行部分更新
|
|
1585
|
+
let updateBody;
|
|
1586
|
+
if (typeof query === 'string') {
|
|
1587
|
+
// Redis兼容模式:如果是字符串键名,直接设置value
|
|
1588
|
+
updateBody = { $set: { value: body } };
|
|
1589
|
+
} else if (!body.$set && !body.$inc && !body.$push) {
|
|
1590
|
+
updateBody = { $set: body };
|
|
1591
|
+
} else {
|
|
1592
|
+
updateBody = body;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
// 默认更新多条
|
|
1596
|
+
const updateOptions = Object.assign({ multi: true }, options);
|
|
1597
|
+
|
|
1598
|
+
// 触发前置事件
|
|
1599
|
+
this.emit('beforeUpdate', { table: this.table, query: safeQuery, update: updateBody, options: updateOptions });
|
|
1600
|
+
|
|
1601
|
+
// 执行更新操作
|
|
1602
|
+
const result = await this.db.collection(this.table).updateMany(safeQuery, updateBody, updateOptions);
|
|
1603
|
+
|
|
1604
|
+
// 触发后置事件
|
|
1605
|
+
this.emit('afterUpdate', { table: this.table, query: safeQuery, update: updateBody, options: updateOptions, result });
|
|
1606
|
+
|
|
1607
|
+
// Redis兼容模式返回布尔值,否则返回完整结果
|
|
1608
|
+
return typeof query === 'string' ? result.modifiedCount > 0 : result;
|
|
1609
|
+
} catch (error) {
|
|
1610
|
+
// 触发错误事件
|
|
1611
|
+
this.emit('updateError', { table: this.table, query, update: body, options, error });
|
|
1612
|
+
|
|
1613
|
+
// Redis兼容模式返回布尔值
|
|
1614
|
+
return typeof query === 'string' ? false : null;
|
|
1615
|
+
}
|
|
174
1616
|
};
|
|
175
1617
|
|
|
176
1618
|
/**
|
|
177
1619
|
* @description 查询缓存
|
|
178
1620
|
* @param {Object} query 查询条件对象
|
|
179
|
-
* @
|
|
1621
|
+
* @param {Object} options 查询选项
|
|
1622
|
+
* @return {Promise|Array} 查询结果数组
|
|
180
1623
|
*/
|
|
181
|
-
MongoDB.prototype.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return
|
|
1624
|
+
MongoDB.prototype.find = async function (query, options = {}) {
|
|
1625
|
+
try {
|
|
1626
|
+
// 安全处理查询条件,避免注入
|
|
1627
|
+
const safeQuery = this._sanitizeQuery(query);
|
|
1628
|
+
const cursor = this.db.collection(this.table).find(safeQuery, options);
|
|
1629
|
+
return await cursor.toArray();
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
$.log.error('[MongoDB] 查询失败:', error);
|
|
1632
|
+
return [];
|
|
187
1633
|
}
|
|
188
1634
|
};
|
|
189
1635
|
|
|
190
1636
|
/**
|
|
191
|
-
* @
|
|
1637
|
+
* @description 查询单条数据
|
|
1638
|
+
* @param {Object} query 查询条件对象
|
|
1639
|
+
* @param {Object} options 查询选项
|
|
1640
|
+
* @return {Promise<Object|null>} 查询结果
|
|
192
1641
|
*/
|
|
193
|
-
|
|
1642
|
+
MongoDB.prototype.findOne = async function (query, options = {}) {
|
|
1643
|
+
try {
|
|
1644
|
+
const safeQuery = this._sanitizeQuery(query);
|
|
1645
|
+
return await this.db.collection(this.table).findOne(safeQuery, options);
|
|
1646
|
+
} catch (error) {
|
|
1647
|
+
$.log.error('[MongoDB] 查询单条数据失败:', error);
|
|
1648
|
+
return null;
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
|
|
1652
|
+
/**
|
|
1653
|
+
* @description 计算文档数量
|
|
1654
|
+
* @param {Object} query 查询条件对象
|
|
1655
|
+
* @return {Promise<Number>} 文档数量
|
|
1656
|
+
*/
|
|
1657
|
+
MongoDB.prototype.count = async function (query = {}) {
|
|
1658
|
+
try {
|
|
1659
|
+
const safeQuery = this._sanitizeQuery(query);
|
|
1660
|
+
return await this.db.collection(this.table).countDocuments(safeQuery);
|
|
1661
|
+
} catch (error) {
|
|
1662
|
+
$.log.error('[MongoDB] 计算文档数量失败:', error);
|
|
1663
|
+
return 0;
|
|
1664
|
+
}
|
|
1665
|
+
};
|
|
1666
|
+
|
|
1667
|
+
/**
|
|
1668
|
+
* @private
|
|
1669
|
+
* @description 清理查询条件,防止注入攻击
|
|
1670
|
+
* @param {Object} query 查询条件
|
|
1671
|
+
* @return {Object} 安全的查询条件
|
|
1672
|
+
*/
|
|
1673
|
+
MongoDB.prototype._sanitizeQuery = function (query) {
|
|
1674
|
+
// 简单实现,可根据需要扩展更复杂的安全检查
|
|
1675
|
+
const sanitized = {};
|
|
1676
|
+
|
|
1677
|
+
for (const [key, value] of Object.entries(query)) {
|
|
1678
|
+
// 防止NoSQL注入,如 {$ne: null} 等操作符注入
|
|
1679
|
+
if (key.startsWith('$') && typeof key === 'string' && !Array.isArray(value) && typeof value === 'object' && value !== null) {
|
|
1680
|
+
$.log.warn('[MongoDB] 检测到可能的操作符注入尝试:', key);
|
|
1681
|
+
continue;
|
|
1682
|
+
}
|
|
1683
|
+
sanitized[key] = value;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
return sanitized;
|
|
1687
|
+
};
|
|
194
1688
|
|
|
1689
|
+
/**
|
|
1690
|
+
* @description 启动事务
|
|
1691
|
+
* @return {Promise<Object>} 会话对象
|
|
1692
|
+
*/
|
|
1693
|
+
MongoDB.prototype.startTransaction = async function () {
|
|
1694
|
+
try {
|
|
1695
|
+
const session = this.conn.startSession();
|
|
1696
|
+
session.startTransaction();
|
|
1697
|
+
return session;
|
|
1698
|
+
} catch (error) {
|
|
1699
|
+
$.log.error('[MongoDB] 启动事务失败:', error);
|
|
1700
|
+
throw error;
|
|
1701
|
+
}
|
|
1702
|
+
};
|
|
195
1703
|
|
|
196
1704
|
/**
|
|
197
|
-
* @description
|
|
1705
|
+
* @description 提交事务
|
|
1706
|
+
* @param {Object} session 会话对象
|
|
1707
|
+
* @return {Promise<void>}
|
|
198
1708
|
*/
|
|
1709
|
+
MongoDB.prototype.commitTransaction = async function (session) {
|
|
1710
|
+
try {
|
|
1711
|
+
await session.commitTransaction();
|
|
1712
|
+
await session.endSession();
|
|
1713
|
+
} catch (error) {
|
|
1714
|
+
$.log.error('[MongoDB] 提交事务失败:', error);
|
|
1715
|
+
throw error;
|
|
1716
|
+
}
|
|
1717
|
+
};
|
|
1718
|
+
|
|
1719
|
+
/**
|
|
1720
|
+
* @description 回滚事务
|
|
1721
|
+
* @param {Object} session 会话对象
|
|
1722
|
+
* @return {Promise<void>}
|
|
1723
|
+
*/
|
|
1724
|
+
MongoDB.prototype.rollbackTransaction = async function (session) {
|
|
1725
|
+
try {
|
|
1726
|
+
await session.abortTransaction();
|
|
1727
|
+
await session.endSession();
|
|
1728
|
+
} catch (error) {
|
|
1729
|
+
$.log.error('[MongoDB] 回滚事务失败:', error);
|
|
1730
|
+
throw error;
|
|
1731
|
+
}
|
|
1732
|
+
};
|
|
1733
|
+
|
|
1734
|
+
/**
|
|
1735
|
+
* 导出MongoDB类
|
|
1736
|
+
*/
|
|
1737
|
+
exports.MongoDB = MongoDB;
|
|
1738
|
+
|
|
1739
|
+
/**
|
|
1740
|
+
* 确保连接池对象存在
|
|
1741
|
+
*/
|
|
1742
|
+
if (!$.pool) {
|
|
1743
|
+
$.pool = {};
|
|
1744
|
+
}
|
|
199
1745
|
if (!$.pool.mongoDB) {
|
|
200
1746
|
$.pool.mongoDB = {};
|
|
201
1747
|
}
|
|
@@ -204,11 +1750,11 @@ if (!$.pool.mongoDB) {
|
|
|
204
1750
|
* @description MongoDB管理器,用于创建缓存
|
|
205
1751
|
* @param {String} scope 作用域
|
|
206
1752
|
* @param {String} dir 当前路径
|
|
207
|
-
* @return {Object}
|
|
1753
|
+
* @return {Object} 返回一个MongoDB类实例
|
|
208
1754
|
*/
|
|
209
1755
|
function mongoDB_admin(scope, dir) {
|
|
210
1756
|
if (!scope) {
|
|
211
|
-
scope = $.val.scope
|
|
1757
|
+
scope = ($.val && $.val.scope) || 'default';
|
|
212
1758
|
}
|
|
213
1759
|
var obj = $.pool.mongoDB[scope];
|
|
214
1760
|
if (!obj) {
|
|
@@ -221,4 +1767,4 @@ function mongoDB_admin(scope, dir) {
|
|
|
221
1767
|
/**
|
|
222
1768
|
* @module 导出MongoDB管理器
|
|
223
1769
|
*/
|
|
224
|
-
exports.mongoDB_admin = mongoDB_admin;
|
|
1770
|
+
exports.mongoDB_admin = mongoDB_admin;
|