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