mm_mongodb 1.4.3 → 1.4.5
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 +565 -231
- package/cache.js +2680 -173
- package/index.js +483 -64
- package/package.json +23 -16
- package/test.js +6 -8
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
|
-
|
|
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
|
|
54
66
|
}
|
|
55
|
-
}, config);
|
|
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,74 @@ 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;
|
|
72
156
|
}
|
|
73
157
|
}
|
|
74
158
|
|
|
@@ -76,49 +160,79 @@ class MongoDB {
|
|
|
76
160
|
* @description 设置配置
|
|
77
161
|
* @param {Object|String} cg 配置对象或配置路径
|
|
78
162
|
*/
|
|
79
|
-
MongoDB.prototype.setConfig = function(cg) {
|
|
163
|
+
MongoDB.prototype.setConfig = function (cg) {
|
|
80
164
|
var obj;
|
|
81
|
-
if (typeof(cg) === "string") {
|
|
82
|
-
obj = cg.loadJson(this.dir);
|
|
83
|
-
} else {
|
|
165
|
+
if (typeof (cg) === "string") {
|
|
166
|
+
obj = cg.loadJson ? cg.loadJson(this.dir) : null;
|
|
167
|
+
} else if (cg) {
|
|
84
168
|
obj = cg;
|
|
85
169
|
}
|
|
86
|
-
|
|
170
|
+
if (obj) {
|
|
171
|
+
$.push(this.config, obj);
|
|
172
|
+
// 设置新配置后重新验证
|
|
173
|
+
this._validateConfig();
|
|
174
|
+
}
|
|
87
175
|
};
|
|
88
176
|
|
|
89
177
|
/**
|
|
90
178
|
* @description 设置Url
|
|
91
179
|
*/
|
|
92
|
-
MongoDB.prototype.setUrl = function() {
|
|
180
|
+
MongoDB.prototype.setUrl = function () {
|
|
93
181
|
var url = "mongodb://";
|
|
94
182
|
if (this.config.password) {
|
|
95
|
-
url += this.config.user + ':' + this.config.password + '@';
|
|
183
|
+
url += encodeURIComponent(this.config.user) + ':' + encodeURIComponent(this.config.password) + '@';
|
|
96
184
|
}
|
|
97
185
|
url += this.config.host + ":" + this.config.port + "/";
|
|
98
186
|
this.url = url;
|
|
99
187
|
};
|
|
100
188
|
|
|
101
189
|
/**
|
|
102
|
-
* @description 连接
|
|
103
|
-
* @return {Boolean} 连接成功返回true,
|
|
190
|
+
* @description 连接MongoDB数据库
|
|
191
|
+
* @return {Promise<Boolean>} 连接成功返回true, 失败抛出错误
|
|
104
192
|
*/
|
|
105
|
-
MongoDB.prototype.open = async function() {
|
|
193
|
+
MongoDB.prototype.open = async function () {
|
|
106
194
|
if (!this.url) {
|
|
107
195
|
this.setUrl();
|
|
108
196
|
}
|
|
109
197
|
if (!this.conn) {
|
|
110
|
-
|
|
111
|
-
|
|
198
|
+
return await this._withRetry(async () => {
|
|
199
|
+
try {
|
|
200
|
+
this.conn = await MongoClient.connect(this.url, this.config.options);
|
|
201
|
+
this.db = this.conn.db(this.config.database);
|
|
202
|
+
this.connected = true;
|
|
203
|
+
|
|
204
|
+
// 设置连接事件监听
|
|
205
|
+
this.conn.on('error', (err) => {
|
|
206
|
+
$.log.error('[MongoDB] 连接错误:', err);
|
|
207
|
+
this.connected = false;
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
this.conn.on('close', () => {
|
|
211
|
+
$.log.warn('[MongoDB] 连接已关闭');
|
|
212
|
+
this.connected = false;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
$.log.info(`[MongoDB] 成功连接到 ${this.config.host}:${this.config.port}/${this.config.database}`);
|
|
216
|
+
return true;
|
|
217
|
+
} catch (error) {
|
|
218
|
+
$.log.error('[MongoDB] MongoDB连接失败:', error);
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
}, '数据库连接');
|
|
112
222
|
}
|
|
113
|
-
return
|
|
223
|
+
return true;
|
|
114
224
|
};
|
|
115
225
|
|
|
116
226
|
/**
|
|
117
227
|
* 关闭数据库连接
|
|
118
228
|
*/
|
|
119
|
-
MongoDB.prototype.close = function() {
|
|
229
|
+
MongoDB.prototype.close = async function () {
|
|
120
230
|
if (this.conn) {
|
|
121
|
-
|
|
231
|
+
$.log.info(`[MongoDB] 关闭连接`);
|
|
232
|
+
await this.conn.close().catch(err => $.log.error('[MongoDB] 关闭MongoDB连接时出错:', err));
|
|
233
|
+
this.conn = null;
|
|
234
|
+
this.db = null;
|
|
235
|
+
this.connected = false;
|
|
122
236
|
}
|
|
123
237
|
};
|
|
124
238
|
|
|
@@ -127,7 +241,7 @@ MongoDB.prototype.close = function() {
|
|
|
127
241
|
* @param {Object} table 表名
|
|
128
242
|
* @return {Promise|Object} 执行结果
|
|
129
243
|
*/
|
|
130
|
-
MongoDB.prototype.addTable = function(table) {
|
|
244
|
+
MongoDB.prototype.addTable = function (table) {
|
|
131
245
|
if (table) {
|
|
132
246
|
this.table = table;
|
|
133
247
|
} else {
|
|
@@ -136,13 +250,161 @@ MongoDB.prototype.addTable = function(table) {
|
|
|
136
250
|
return this.db.createCollection(table);
|
|
137
251
|
};
|
|
138
252
|
|
|
253
|
+
/**
|
|
254
|
+
* @description 创建TTL索引
|
|
255
|
+
* @param {String} table 表名
|
|
256
|
+
* @param {String} field 过期字段名
|
|
257
|
+
* @param {Number} expireAfterSeconds 过期时间(秒)
|
|
258
|
+
* @return {Promise<Object>} 执行结果
|
|
259
|
+
*/
|
|
260
|
+
MongoDB.prototype.createTTLIndex = async function (table, field, expireAfterSeconds) {
|
|
261
|
+
if (!table) table = this.table;
|
|
262
|
+
return await this.db.collection(table).createIndex(
|
|
263
|
+
{ [field]: 1 },
|
|
264
|
+
{ expireAfterSeconds: expireAfterSeconds || 0 }
|
|
265
|
+
);
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* @description 创建普通索引
|
|
270
|
+
* @param {String} table 表名
|
|
271
|
+
* @param {Object} keys 索引字段对象
|
|
272
|
+
* @param {Object} options 索引选项
|
|
273
|
+
* @return {Promise<Object>} 执行结果
|
|
274
|
+
*/
|
|
275
|
+
/**
|
|
276
|
+
* @description 创建索引
|
|
277
|
+
* @param {Object} keys 索引键配置 {field: 1或-1}
|
|
278
|
+
* @param {Object} options 索引选项
|
|
279
|
+
* @returns {Promise<Object>} 创建索引的结果
|
|
280
|
+
*/
|
|
281
|
+
MongoDB.prototype.createIndex = async function (keys, options = {}) {
|
|
282
|
+
try {
|
|
283
|
+
if (!this.db || !this.table) {
|
|
284
|
+
throw new Error('数据库未连接或未指定表名');
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
$.log.info(`[MongoDB] 创建索引: ${this.table}, keys: ${JSON.stringify(keys)}, options: ${JSON.stringify(options)}`);
|
|
288
|
+
|
|
289
|
+
return await this.db.collection(this.table).createIndex(keys, options);
|
|
290
|
+
} catch (error) {
|
|
291
|
+
$.log.error(`[MongoDB] 创建索引失败: ${error.message}`);
|
|
292
|
+
throw error;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* @description 创建TTL索引(数据自动过期)
|
|
298
|
+
* @param {Object} keys 索引键配置 {field: 1}
|
|
299
|
+
* @param {Number} expireAfterSeconds 过期时间(秒)
|
|
300
|
+
* @returns {Promise<Object>} 创建索引的结果
|
|
301
|
+
*/
|
|
302
|
+
MongoDB.prototype.createTTLIndex = async function (keys, expireAfterSeconds) {
|
|
303
|
+
try {
|
|
304
|
+
if (!this.db || !this.table) {
|
|
305
|
+
throw new Error('数据库未连接或未指定表名');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const options = {
|
|
309
|
+
background: true,
|
|
310
|
+
expireAfterSeconds: expireAfterSeconds
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
$.log.info(`[MongoDB] 创建TTL索引: ${this.table}, keys: ${JSON.stringify(keys)}, 过期时间: ${expireAfterSeconds}秒`);
|
|
314
|
+
|
|
315
|
+
return await this.db.collection(this.table).createIndex(keys, options);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
$.log.error(`[MongoDB] 创建TTL索引失败: ${error.message}`);
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* @description 获取集合的所有索引
|
|
324
|
+
* @returns {Promise<Array>} 索引信息数组
|
|
325
|
+
*/
|
|
326
|
+
MongoDB.prototype.getIndexes = async function () {
|
|
327
|
+
try {
|
|
328
|
+
if (!this.db || !this.table) {
|
|
329
|
+
throw new Error('数据库未连接或未指定表名');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
$.log.info(`[MongoDB] 获取索引: ${this.table}`);
|
|
333
|
+
return await this.db.collection(this.table).indexes();
|
|
334
|
+
} catch (error) {
|
|
335
|
+
$.log.error(`[MongoDB] 获取索引失败: ${error.message}`);
|
|
336
|
+
return [];
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* @description 删除指定索引
|
|
342
|
+
* @param {String} indexName 索引名称
|
|
343
|
+
* @returns {Promise<Boolean>} 是否删除成功
|
|
344
|
+
*/
|
|
345
|
+
MongoDB.prototype.dropIndex = async function (indexName) {
|
|
346
|
+
if (!this.conn || !this.db) {
|
|
347
|
+
throw new Error('数据库未连接');
|
|
348
|
+
}
|
|
349
|
+
if (!this.table) {
|
|
350
|
+
throw new Error('未指定表名');
|
|
351
|
+
}
|
|
352
|
+
if (!indexName) {
|
|
353
|
+
throw new Error('索引名称不能为空');
|
|
354
|
+
}
|
|
355
|
+
try {
|
|
356
|
+
$.log.info(`[MongoDB] 删除索引: ${this.table}, 索引名: ${indexName}`);
|
|
357
|
+
await this.db.collection(this.table).dropIndex(indexName);
|
|
358
|
+
return true;
|
|
359
|
+
} catch (error) {
|
|
360
|
+
$.log.error(`[MongoDB] 删除索引失败: ${error.message}`);
|
|
361
|
+
throw error;
|
|
362
|
+
}
|
|
363
|
+
};
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* @description 删除集合(表)
|
|
367
|
+
* @returns {Promise<Boolean>} 是否删除成功
|
|
368
|
+
*/
|
|
369
|
+
MongoDB.prototype.dropTable = async function () {
|
|
370
|
+
if (!this.conn || !this.db) {
|
|
371
|
+
throw new Error('数据库未连接');
|
|
372
|
+
}
|
|
373
|
+
if (!this.table) {
|
|
374
|
+
throw new Error('未指定表名');
|
|
375
|
+
}
|
|
376
|
+
try {
|
|
377
|
+
$.log.info(`[MongoDB] 删除集合: ${this.table}`);
|
|
378
|
+
await this.db.collection(this.table).drop();
|
|
379
|
+
return true;
|
|
380
|
+
} catch (error) {
|
|
381
|
+
$.log.error(`[MongoDB] 删除集合失败: ${error.message}`);
|
|
382
|
+
throw error;
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
|
|
139
386
|
/**
|
|
140
387
|
* @description 增加一条缓存
|
|
141
388
|
* @param {Object} obj 对象
|
|
142
389
|
* @return {Promise|Object} 执行结果
|
|
143
390
|
*/
|
|
144
|
-
MongoDB.prototype.addObj = function(obj) {
|
|
145
|
-
|
|
391
|
+
MongoDB.prototype.addObj = async function(obj) {
|
|
392
|
+
try {
|
|
393
|
+
// 触发前置事件
|
|
394
|
+
this.emit('beforeInsert', { table: this.table, data: obj });
|
|
395
|
+
|
|
396
|
+
// 执行插入操作
|
|
397
|
+
const result = await this.db.collection(this.table).insertOne(obj);
|
|
398
|
+
|
|
399
|
+
// 触发后置事件
|
|
400
|
+
this.emit('afterInsert', { table: this.table, data: obj, result });
|
|
401
|
+
|
|
402
|
+
return result;
|
|
403
|
+
} catch (error) {
|
|
404
|
+
// 触发错误事件
|
|
405
|
+
this.emit('insertError', { table: this.table, data: obj, error });
|
|
406
|
+
throw error;
|
|
407
|
+
}
|
|
146
408
|
};
|
|
147
409
|
|
|
148
410
|
/**
|
|
@@ -150,8 +412,23 @@ MongoDB.prototype.addObj = function(obj) {
|
|
|
150
412
|
* @param {Array} list 对象数组
|
|
151
413
|
* @return {Promise|Object} 执行结果
|
|
152
414
|
*/
|
|
153
|
-
MongoDB.prototype.addList = function(list) {
|
|
154
|
-
|
|
415
|
+
MongoDB.prototype.addList = async function(list) {
|
|
416
|
+
try {
|
|
417
|
+
// 触发前置事件
|
|
418
|
+
this.emit('beforeInsertMany', { table: this.table, data: list });
|
|
419
|
+
|
|
420
|
+
// 执行插入操作
|
|
421
|
+
const result = await this.db.collection(this.table).insertMany(list);
|
|
422
|
+
|
|
423
|
+
// 触发后置事件
|
|
424
|
+
this.emit('afterInsertMany', { table: this.table, data: list, result });
|
|
425
|
+
|
|
426
|
+
return result;
|
|
427
|
+
} catch (error) {
|
|
428
|
+
// 触发错误事件
|
|
429
|
+
this.emit('insertManyError', { table: this.table, data: list, error });
|
|
430
|
+
throw error;
|
|
431
|
+
}
|
|
155
432
|
};
|
|
156
433
|
|
|
157
434
|
/**
|
|
@@ -159,43 +436,185 @@ MongoDB.prototype.addList = function(list) {
|
|
|
159
436
|
* @param {Object} query 查询条件对象
|
|
160
437
|
* @return {Promise|Object} 执行结果
|
|
161
438
|
*/
|
|
162
|
-
MongoDB.prototype.del = function(query) {
|
|
163
|
-
|
|
439
|
+
MongoDB.prototype.del = async function(query) {
|
|
440
|
+
try {
|
|
441
|
+
// 触发前置事件
|
|
442
|
+
this.emit('beforeDelete', { table: this.table, query });
|
|
443
|
+
|
|
444
|
+
// 执行删除操作
|
|
445
|
+
const result = await this.db.collection(this.table).deleteMany(query);
|
|
446
|
+
|
|
447
|
+
// 触发后置事件
|
|
448
|
+
this.emit('afterDelete', { table: this.table, query, result });
|
|
449
|
+
|
|
450
|
+
return result;
|
|
451
|
+
} catch (error) {
|
|
452
|
+
// 触发错误事件
|
|
453
|
+
this.emit('deleteError', { table: this.table, query, error });
|
|
454
|
+
throw error;
|
|
455
|
+
}
|
|
164
456
|
};
|
|
165
457
|
|
|
166
458
|
/**
|
|
167
459
|
* @description 修改缓存
|
|
168
460
|
* @param {Object} query 查询条件对象
|
|
169
461
|
* @param {Object} body 修改对象
|
|
462
|
+
* @param {Object} options 附加选项
|
|
170
463
|
* @return {Promise|Object} 执行结果
|
|
171
464
|
*/
|
|
172
|
-
MongoDB.prototype.set = function(query, body) {
|
|
173
|
-
|
|
465
|
+
MongoDB.prototype.set = async function(query, body, options = {}) {
|
|
466
|
+
try {
|
|
467
|
+
// 默认使用$set操作符进行部分更新
|
|
468
|
+
if (!body.$set && !body.$inc && !body.$push) {
|
|
469
|
+
body = { $set: body };
|
|
470
|
+
}
|
|
471
|
+
// 默认更新多条
|
|
472
|
+
const updateOptions = Object.assign({ multi: true }, options);
|
|
473
|
+
|
|
474
|
+
// 触发前置事件
|
|
475
|
+
this.emit('beforeUpdate', { table: this.table, query, update: body, options: updateOptions });
|
|
476
|
+
|
|
477
|
+
// 执行更新操作
|
|
478
|
+
const result = await this.db.collection(this.table).updateMany(query, body, updateOptions);
|
|
479
|
+
|
|
480
|
+
// 触发后置事件
|
|
481
|
+
this.emit('afterUpdate', { table: this.table, query, update: body, options: updateOptions, result });
|
|
482
|
+
|
|
483
|
+
return result;
|
|
484
|
+
} catch (error) {
|
|
485
|
+
// 触发错误事件
|
|
486
|
+
this.emit('updateError', { table: this.table, query, update: body, options, error });
|
|
487
|
+
throw error;
|
|
488
|
+
}
|
|
174
489
|
};
|
|
175
490
|
|
|
176
491
|
/**
|
|
177
492
|
* @description 查询缓存
|
|
178
493
|
* @param {Object} query 查询条件对象
|
|
179
|
-
* @
|
|
494
|
+
* @param {Object} options 查询选项
|
|
495
|
+
* @return {Promise|Array} 查询结果数组
|
|
180
496
|
*/
|
|
181
|
-
MongoDB.prototype.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return
|
|
497
|
+
MongoDB.prototype.find = async function (query, options = {}) {
|
|
498
|
+
try {
|
|
499
|
+
// 安全处理查询条件,避免注入
|
|
500
|
+
const safeQuery = this._sanitizeQuery(query);
|
|
501
|
+
const cursor = this.db.collection(this.table).find(safeQuery, options);
|
|
502
|
+
return await cursor.toArray();
|
|
503
|
+
} catch (error) {
|
|
504
|
+
$.log.error('[MongoDB] 查询失败:', error);
|
|
505
|
+
return [];
|
|
187
506
|
}
|
|
188
507
|
};
|
|
189
508
|
|
|
190
509
|
/**
|
|
191
|
-
* @
|
|
510
|
+
* @description 查询单条数据
|
|
511
|
+
* @param {Object} query 查询条件对象
|
|
512
|
+
* @param {Object} options 查询选项
|
|
513
|
+
* @return {Promise<Object|null>} 查询结果
|
|
192
514
|
*/
|
|
193
|
-
|
|
515
|
+
MongoDB.prototype.findOne = async function (query, options = {}) {
|
|
516
|
+
try {
|
|
517
|
+
const safeQuery = this._sanitizeQuery(query);
|
|
518
|
+
return await this.db.collection(this.table).findOne(safeQuery, options);
|
|
519
|
+
} catch (error) {
|
|
520
|
+
$.log.error('[MongoDB] 查询单条数据失败:', error);
|
|
521
|
+
return null;
|
|
522
|
+
}
|
|
523
|
+
};
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* @description 计算文档数量
|
|
527
|
+
* @param {Object} query 查询条件对象
|
|
528
|
+
* @return {Promise<Number>} 文档数量
|
|
529
|
+
*/
|
|
530
|
+
MongoDB.prototype.count = async function (query = {}) {
|
|
531
|
+
try {
|
|
532
|
+
const safeQuery = this._sanitizeQuery(query);
|
|
533
|
+
return await this.db.collection(this.table).countDocuments(safeQuery);
|
|
534
|
+
} catch (error) {
|
|
535
|
+
$.log.error('[MongoDB] 计算文档数量失败:', error);
|
|
536
|
+
return 0;
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* @private
|
|
542
|
+
* @description 清理查询条件,防止注入攻击
|
|
543
|
+
* @param {Object} query 查询条件
|
|
544
|
+
* @return {Object} 安全的查询条件
|
|
545
|
+
*/
|
|
546
|
+
MongoDB.prototype._sanitizeQuery = function (query) {
|
|
547
|
+
// 简单实现,可根据需要扩展更复杂的安全检查
|
|
548
|
+
const sanitized = {};
|
|
549
|
+
|
|
550
|
+
for (const [key, value] of Object.entries(query)) {
|
|
551
|
+
// 防止NoSQL注入,如 {$ne: null} 等操作符注入
|
|
552
|
+
if (key.startsWith('$') && typeof key === 'string' && !Array.isArray(value) && typeof value === 'object' && value !== null) {
|
|
553
|
+
$.log.warn('[MongoDB] 检测到可能的操作符注入尝试:', key);
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
sanitized[key] = value;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
return sanitized;
|
|
560
|
+
};
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* @description 启动事务
|
|
564
|
+
* @return {Promise<Object>} 会话对象
|
|
565
|
+
*/
|
|
566
|
+
MongoDB.prototype.startTransaction = async function () {
|
|
567
|
+
try {
|
|
568
|
+
const session = this.conn.startSession();
|
|
569
|
+
session.startTransaction();
|
|
570
|
+
return session;
|
|
571
|
+
} catch (error) {
|
|
572
|
+
$.log.error('[MongoDB] 启动事务失败:', error);
|
|
573
|
+
throw error;
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* @description 提交事务
|
|
579
|
+
* @param {Object} session 会话对象
|
|
580
|
+
* @return {Promise<void>}
|
|
581
|
+
*/
|
|
582
|
+
MongoDB.prototype.commitTransaction = async function (session) {
|
|
583
|
+
try {
|
|
584
|
+
await session.commitTransaction();
|
|
585
|
+
await session.endSession();
|
|
586
|
+
} catch (error) {
|
|
587
|
+
$.log.error('[MongoDB] 提交事务失败:', error);
|
|
588
|
+
throw error;
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* @description 回滚事务
|
|
594
|
+
* @param {Object} session 会话对象
|
|
595
|
+
* @return {Promise<void>}
|
|
596
|
+
*/
|
|
597
|
+
MongoDB.prototype.rollbackTransaction = async function (session) {
|
|
598
|
+
try {
|
|
599
|
+
await session.abortTransaction();
|
|
600
|
+
await session.endSession();
|
|
601
|
+
} catch (error) {
|
|
602
|
+
$.log.error('[MongoDB] 回滚事务失败:', error);
|
|
603
|
+
throw error;
|
|
604
|
+
}
|
|
605
|
+
};
|
|
194
606
|
|
|
607
|
+
/**
|
|
608
|
+
* 导出MongoDB类
|
|
609
|
+
*/
|
|
610
|
+
exports.MongoDB = MongoDB;
|
|
195
611
|
|
|
196
612
|
/**
|
|
197
|
-
*
|
|
613
|
+
* 确保连接池对象存在
|
|
198
614
|
*/
|
|
615
|
+
if (!$.pool) {
|
|
616
|
+
$.pool = {};
|
|
617
|
+
}
|
|
199
618
|
if (!$.pool.mongoDB) {
|
|
200
619
|
$.pool.mongoDB = {};
|
|
201
620
|
}
|
|
@@ -204,11 +623,11 @@ if (!$.pool.mongoDB) {
|
|
|
204
623
|
* @description MongoDB管理器,用于创建缓存
|
|
205
624
|
* @param {String} scope 作用域
|
|
206
625
|
* @param {String} dir 当前路径
|
|
207
|
-
* @return {Object}
|
|
626
|
+
* @return {Object} 返回一个MongoDB类实例
|
|
208
627
|
*/
|
|
209
628
|
function mongoDB_admin(scope, dir) {
|
|
210
629
|
if (!scope) {
|
|
211
|
-
scope = $.val.scope
|
|
630
|
+
scope = ($.val && $.val.scope) || 'default';
|
|
212
631
|
}
|
|
213
632
|
var obj = $.pool.mongoDB[scope];
|
|
214
633
|
if (!obj) {
|
|
@@ -221,4 +640,4 @@ function mongoDB_admin(scope, dir) {
|
|
|
221
640
|
/**
|
|
222
641
|
* @module 导出MongoDB管理器
|
|
223
642
|
*/
|
|
224
|
-
exports.mongoDB_admin = mongoDB_admin;
|
|
643
|
+
exports.mongoDB_admin = mongoDB_admin;
|