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