monsqlize 1.0.0 → 1.0.2
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/CHANGELOG.md +92 -2419
- package/README.md +630 -1070
- package/index.d.ts +252 -15
- package/lib/cache.js +8 -8
- package/lib/common/validation.js +64 -1
- package/lib/connect.js +3 -3
- package/lib/errors.js +10 -0
- package/lib/index.js +118 -9
- package/lib/infrastructure/ssh-tunnel-ssh2.js +211 -0
- package/lib/infrastructure/ssh-tunnel.js +40 -0
- package/lib/infrastructure/uri-parser.js +35 -0
- package/lib/lock/Lock.js +66 -0
- package/lib/lock/errors.js +27 -0
- package/lib/lock/index.js +12 -0
- package/lib/logger.js +1 -1
- package/lib/model/examples/test.js +4 -4
- package/lib/mongodb/common/accessor-helpers.js +17 -3
- package/lib/mongodb/connect.js +68 -13
- package/lib/mongodb/index.js +140 -7
- package/lib/mongodb/management/collection-ops.js +4 -4
- package/lib/mongodb/management/index-ops.js +18 -18
- package/lib/mongodb/management/validation-ops.js +3 -3
- package/lib/mongodb/queries/aggregate.js +14 -5
- package/lib/mongodb/queries/chain.js +52 -45
- package/lib/mongodb/queries/count.js +16 -6
- package/lib/mongodb/queries/distinct.js +15 -6
- package/lib/mongodb/queries/find-and-count.js +22 -13
- package/lib/mongodb/queries/find-by-ids.js +5 -5
- package/lib/mongodb/queries/find-one-by-id.js +1 -1
- package/lib/mongodb/queries/find-one.js +12 -3
- package/lib/mongodb/queries/find-page.js +12 -0
- package/lib/mongodb/queries/find.js +15 -6
- package/lib/mongodb/queries/index.js +1 -0
- package/lib/mongodb/queries/watch.js +537 -0
- package/lib/mongodb/writes/delete-many.js +20 -11
- package/lib/mongodb/writes/delete-one.js +18 -9
- package/lib/mongodb/writes/find-one-and-delete.js +19 -10
- package/lib/mongodb/writes/find-one-and-replace.js +36 -20
- package/lib/mongodb/writes/find-one-and-update.js +36 -20
- package/lib/mongodb/writes/increment-one.js +16 -7
- package/lib/mongodb/writes/index.js +13 -13
- package/lib/mongodb/writes/insert-batch.js +46 -37
- package/lib/mongodb/writes/insert-many.js +22 -13
- package/lib/mongodb/writes/insert-one.js +18 -9
- package/lib/mongodb/writes/replace-one.js +33 -17
- package/lib/mongodb/writes/result-handler.js +14 -14
- package/lib/mongodb/writes/update-many.js +34 -18
- package/lib/mongodb/writes/update-one.js +33 -17
- package/lib/mongodb/writes/upsert-one.js +25 -9
- package/lib/operators.js +1 -1
- package/lib/redis-cache-adapter.js +3 -3
- package/lib/slow-query-log/base-storage.js +69 -0
- package/lib/slow-query-log/batch-queue.js +96 -0
- package/lib/slow-query-log/config-manager.js +195 -0
- package/lib/slow-query-log/index.js +237 -0
- package/lib/slow-query-log/mongodb-storage.js +323 -0
- package/lib/slow-query-log/query-hash.js +38 -0
- package/lib/transaction/DistributedCacheLockManager.js +240 -5
- package/lib/transaction/Transaction.js +1 -1
- package/lib/utils/objectid-converter.js +566 -0
- package/package.json +11 -5
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
* 更新单个匹配的文档
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
const { createError, ErrorCodes } = require(
|
|
7
|
-
const CacheFactory = require(
|
|
8
|
-
const { isInTransaction, getTransactionFromSession } = require(
|
|
6
|
+
const { createError, ErrorCodes } = require('../../errors');
|
|
7
|
+
const CacheFactory = require('../../cache');
|
|
8
|
+
const { isInTransaction, getTransactionFromSession } = require('../common/transaction-aware');
|
|
9
|
+
const { convertObjectIdStrings, convertUpdateDocument } = require('../../utils/objectid-converter');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* 创建 updateOne 操作
|
|
@@ -60,39 +61,54 @@ function createUpdateOneOps(context) {
|
|
|
60
61
|
const startTime = Date.now();
|
|
61
62
|
|
|
62
63
|
// 1. 参数验证
|
|
63
|
-
if (!filter || typeof filter !==
|
|
64
|
+
if (!filter || typeof filter !== 'object' || Array.isArray(filter)) {
|
|
64
65
|
throw createError(
|
|
65
66
|
ErrorCodes.INVALID_ARGUMENT,
|
|
66
|
-
|
|
67
|
-
[{ field:
|
|
67
|
+
'filter 必须是对象类型',
|
|
68
|
+
[{ field: 'filter', type: 'object.required', message: 'filter 是必需参数且必须是对象' }]
|
|
68
69
|
);
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
if (!update || typeof update !==
|
|
72
|
+
if (!update || typeof update !== 'object' || Array.isArray(update)) {
|
|
72
73
|
throw createError(
|
|
73
74
|
ErrorCodes.INVALID_ARGUMENT,
|
|
74
|
-
|
|
75
|
-
[{ field:
|
|
75
|
+
'update 必须是对象类型',
|
|
76
|
+
[{ field: 'update', type: 'object.required', message: 'update 是必需参数且必须是对象' }]
|
|
76
77
|
);
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
// 验证 update 包含更新操作符(防止整体替换)
|
|
80
81
|
const updateKeys = Object.keys(update);
|
|
81
|
-
if (updateKeys.length > 0 && !updateKeys.some(key => key.startsWith(
|
|
82
|
+
if (updateKeys.length > 0 && !updateKeys.some(key => key.startsWith('$'))) {
|
|
82
83
|
throw createError(
|
|
83
84
|
ErrorCodes.INVALID_ARGUMENT,
|
|
84
|
-
|
|
85
|
-
[{ field:
|
|
85
|
+
'update 必须使用更新操作符(如 $set, $inc 等)',
|
|
86
|
+
[{ field: 'update', type: 'object.invalid', message: '请使用 $set, $inc, $push 等更新操作符,或使用 replaceOne 进行整体替换' }]
|
|
86
87
|
);
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
// ✅ v1.3.0: 自动转换 ObjectId 字符串
|
|
91
|
+
const convertedFilter = convertObjectIdStrings(filter, 'filter', 0, new WeakSet(), {
|
|
92
|
+
logger: context.logger,
|
|
93
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
94
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
95
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const convertedUpdate = convertUpdateDocument(update, {
|
|
99
|
+
logger: context.logger,
|
|
100
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
101
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
102
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
103
|
+
});
|
|
104
|
+
|
|
89
105
|
// 2. 构建操作上下文
|
|
90
|
-
const operation =
|
|
106
|
+
const operation = 'updateOne';
|
|
91
107
|
const ns = `${databaseName}.${collectionName}`;
|
|
92
108
|
|
|
93
109
|
try {
|
|
94
110
|
// 3. 执行更新操作
|
|
95
|
-
const result = await nativeCollection.updateOne(
|
|
111
|
+
const result = await nativeCollection.updateOne(convertedFilter, convertedUpdate, options);
|
|
96
112
|
|
|
97
113
|
// 4. 自动失效缓存
|
|
98
114
|
if (cache && result.modifiedCount > 0) {
|
|
@@ -100,7 +116,7 @@ function createUpdateOneOps(context) {
|
|
|
100
116
|
// 使用标准命名空间模式删除该集合的所有缓存
|
|
101
117
|
const ns = {
|
|
102
118
|
iid: instanceId,
|
|
103
|
-
type:
|
|
119
|
+
type: 'mongodb',
|
|
104
120
|
db: databaseName,
|
|
105
121
|
collection: collectionName
|
|
106
122
|
};
|
|
@@ -184,8 +200,8 @@ function createUpdateOneOps(context) {
|
|
|
184
200
|
// MongoDB 重复键错误(可能在 upsert 时发生)
|
|
185
201
|
throw createError(
|
|
186
202
|
ErrorCodes.DUPLICATE_KEY,
|
|
187
|
-
|
|
188
|
-
[{ field:
|
|
203
|
+
'更新失败:违反唯一性约束',
|
|
204
|
+
[{ field: '_id', message: error.message }],
|
|
189
205
|
error
|
|
190
206
|
);
|
|
191
207
|
}
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
const { createError, ErrorCodes } = require('../../errors');
|
|
7
|
-
const { isInTransaction, getTransactionFromSession } = require(
|
|
7
|
+
const { isInTransaction, getTransactionFromSession } = require('../common/transaction-aware');
|
|
8
|
+
const { convertObjectIdStrings } = require('../../utils/objectid-converter');
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* 创建 upsertOne 操作
|
|
@@ -68,11 +69,26 @@ function createUpsertOneOps(context) {
|
|
|
68
69
|
);
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
// ✅ v1.3.0: 自动转换 ObjectId 字符串
|
|
73
|
+
const convertedFilter = convertObjectIdStrings(filter, 'filter', 0, new WeakSet(), {
|
|
74
|
+
logger: context.logger,
|
|
75
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
76
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
77
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const convertedUpdate = convertObjectIdStrings(update, 'document', 0, new WeakSet(), {
|
|
81
|
+
logger: context.logger,
|
|
82
|
+
excludeFields: context.autoConvertConfig?.excludeFields,
|
|
83
|
+
customFieldPatterns: context.autoConvertConfig?.customFieldPatterns,
|
|
84
|
+
maxDepth: context.autoConvertConfig?.maxDepth
|
|
85
|
+
});
|
|
86
|
+
|
|
71
87
|
// 2. 检查 update 是否包含更新操作符
|
|
72
|
-
const hasOperator = Object.keys(
|
|
88
|
+
const hasOperator = Object.keys(convertedUpdate).some(key => key.startsWith('$'));
|
|
73
89
|
|
|
74
90
|
// 如果没有操作符,自动包装为 $set
|
|
75
|
-
const updateDoc = hasOperator ?
|
|
91
|
+
const updateDoc = hasOperator ? convertedUpdate : { $set: convertedUpdate };
|
|
76
92
|
|
|
77
93
|
// 3. 构建选项
|
|
78
94
|
const maxTimeMS = options.maxTimeMS !== undefined ? options.maxTimeMS : defaults.maxTimeMS;
|
|
@@ -84,7 +100,7 @@ function createUpsertOneOps(context) {
|
|
|
84
100
|
// 4. 执行 updateOne 操作
|
|
85
101
|
let result;
|
|
86
102
|
try {
|
|
87
|
-
result = await collection.updateOne(
|
|
103
|
+
result = await collection.updateOne(convertedFilter, updateDoc, driverOpts);
|
|
88
104
|
} catch (error) {
|
|
89
105
|
// 统一错误处理
|
|
90
106
|
if (error.code === 11000) {
|
|
@@ -146,7 +162,7 @@ function createUpsertOneOps(context) {
|
|
|
146
162
|
operation: 'upsertOne',
|
|
147
163
|
durationMs: duration,
|
|
148
164
|
iid: instanceId,
|
|
149
|
-
type
|
|
165
|
+
type,
|
|
150
166
|
db: effectiveDbName,
|
|
151
167
|
collection: collection.collectionName,
|
|
152
168
|
matchedCount: result.matchedCount,
|
|
@@ -154,7 +170,7 @@ function createUpsertOneOps(context) {
|
|
|
154
170
|
upsertedId: result.upsertedId,
|
|
155
171
|
filter: mongoSlowLogShaper?.sanitize ? mongoSlowLogShaper.sanitize(filter) : filter,
|
|
156
172
|
update: mongoSlowLogShaper?.sanitize ? mongoSlowLogShaper.sanitize(updateDoc) : updateDoc,
|
|
157
|
-
comment
|
|
173
|
+
comment
|
|
158
174
|
};
|
|
159
175
|
logger?.warn?.('🐌 Slow query: upsertOne', meta);
|
|
160
176
|
emit?.('slow-query', meta);
|
|
@@ -166,11 +182,11 @@ function createUpsertOneOps(context) {
|
|
|
166
182
|
// 7. 日志记录
|
|
167
183
|
logger?.debug?.('[upsertOne] 操作完成', {
|
|
168
184
|
ns: `${effectiveDbName}.${collection.collectionName}`,
|
|
169
|
-
duration
|
|
185
|
+
duration,
|
|
170
186
|
matchedCount: result.matchedCount,
|
|
171
187
|
modifiedCount: result.modifiedCount,
|
|
172
188
|
upsertedId: result.upsertedId === null ? undefined : result.upsertedId,
|
|
173
|
-
upsertedCount
|
|
189
|
+
upsertedCount
|
|
174
190
|
});
|
|
175
191
|
|
|
176
192
|
// 8. 返回结果
|
|
@@ -179,7 +195,7 @@ function createUpsertOneOps(context) {
|
|
|
179
195
|
matchedCount: result.matchedCount,
|
|
180
196
|
modifiedCount: result.modifiedCount,
|
|
181
197
|
upsertedId: result.upsertedId === null ? undefined : result.upsertedId,
|
|
182
|
-
upsertedCount
|
|
198
|
+
upsertedCount
|
|
183
199
|
};
|
|
184
200
|
};
|
|
185
201
|
|
package/lib/operators.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Redis 缓存适配器:将 Redis 封装为 CacheLike 接口
|
|
3
3
|
* 支持直接传入 Redis URL 字符串或 ioredis 实例
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* @example
|
|
6
6
|
* const { createRedisCacheAdapter } = require('monsqlize/lib/redis-cache-adapter');
|
|
7
|
-
*
|
|
7
|
+
*
|
|
8
8
|
* // 方式 1:传入 URL 字符串(推荐)
|
|
9
9
|
* const cache = createRedisCacheAdapter('redis://localhost:6379/0');
|
|
10
|
-
*
|
|
10
|
+
*
|
|
11
11
|
* // 方式 2:传入 ioredis 实例
|
|
12
12
|
* const Redis = require('ioredis');
|
|
13
13
|
* const redis = new Redis('redis://localhost:6379/0');
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 慢查询日志存储接口
|
|
3
|
+
* 所有存储适配器必须实现此接口
|
|
4
|
+
*
|
|
5
|
+
* @version 1.3.0
|
|
6
|
+
* @since 2025-12-22
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
class ISlowQueryLogStorage {
|
|
10
|
+
/**
|
|
11
|
+
* 初始化存储(创建集合/表、索引等)
|
|
12
|
+
* @returns {Promise<void>}
|
|
13
|
+
*/
|
|
14
|
+
async initialize() {
|
|
15
|
+
throw new Error('ISlowQueryLogStorage.initialize() must be implemented');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 保存单条慢查询日志
|
|
20
|
+
* @param {Object} log - 慢查询日志对象
|
|
21
|
+
* @param {string} log.db - 数据库名
|
|
22
|
+
* @param {string} log.collection - 集合名
|
|
23
|
+
* @param {string} log.operation - 操作类型
|
|
24
|
+
* @param {Object} log.queryShape - 查询模式(已脱敏)
|
|
25
|
+
* @param {number} log.executionTimeMs - 执行时间(毫秒)
|
|
26
|
+
* @param {Date} log.timestamp - 时间戳
|
|
27
|
+
* @param {Object} [log.metadata] - 扩展元数据
|
|
28
|
+
* @returns {Promise<void>}
|
|
29
|
+
*/
|
|
30
|
+
async save(log) {
|
|
31
|
+
throw new Error('ISlowQueryLogStorage.save() must be implemented');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 批量保存慢查询日志
|
|
36
|
+
* @param {Object[]} logs - 慢查询日志数组
|
|
37
|
+
* @returns {Promise<void>}
|
|
38
|
+
*/
|
|
39
|
+
async saveBatch(logs) {
|
|
40
|
+
throw new Error('ISlowQueryLogStorage.saveBatch() must be implemented');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 查询慢查询日志
|
|
45
|
+
* @param {Object} filter - 查询条件
|
|
46
|
+
* @param {string} [filter.db] - 数据库名
|
|
47
|
+
* @param {string} [filter.collection] - 集合名
|
|
48
|
+
* @param {string} [filter.operation] - 操作类型
|
|
49
|
+
* @param {Object} options - 查询选项
|
|
50
|
+
* @param {Object} [options.sort] - 排序规则
|
|
51
|
+
* @param {number} [options.limit] - 限制数量
|
|
52
|
+
* @param {number} [options.skip] - 跳过数量
|
|
53
|
+
* @returns {Promise<Object[]>}
|
|
54
|
+
*/
|
|
55
|
+
async query(filter, options) {
|
|
56
|
+
throw new Error('ISlowQueryLogStorage.query() must be implemented');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 关闭连接
|
|
61
|
+
* @returns {Promise<void>}
|
|
62
|
+
*/
|
|
63
|
+
async close() {
|
|
64
|
+
throw new Error('ISlowQueryLogStorage.close() must be implemented');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { ISlowQueryLogStorage };
|
|
69
|
+
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 批量队列管理器
|
|
3
|
+
* 用于批量写入慢查询日志,优化性能
|
|
4
|
+
*
|
|
5
|
+
* @version 1.3.0
|
|
6
|
+
* @since 2025-12-22
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
class BatchQueue {
|
|
10
|
+
/**
|
|
11
|
+
* 创建批量队列
|
|
12
|
+
* @param {Object} storage - 存储适配器实例
|
|
13
|
+
* @param {Object} options - 队列配置
|
|
14
|
+
* @param {number} [options.batchSize=10] - 批量大小
|
|
15
|
+
* @param {number} [options.flushInterval=5000] - 刷新间隔(毫秒)
|
|
16
|
+
* @param {number} [options.maxBufferSize=100] - 最大缓冲区大小
|
|
17
|
+
* @param {Object} [logger] - 日志记录器
|
|
18
|
+
*/
|
|
19
|
+
constructor(storage, options = {}, logger) {
|
|
20
|
+
this.storage = storage;
|
|
21
|
+
this.buffer = [];
|
|
22
|
+
this.batchSize = options.batchSize || 10;
|
|
23
|
+
this.flushInterval = options.flushInterval || 5000;
|
|
24
|
+
this.maxBufferSize = options.maxBufferSize || 100;
|
|
25
|
+
this.timer = null;
|
|
26
|
+
this.flushing = false;
|
|
27
|
+
this.logger = logger || console;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 添加日志到队列
|
|
32
|
+
* @param {Object} log - 慢查询日志对象
|
|
33
|
+
* @returns {Promise<void>}
|
|
34
|
+
*/
|
|
35
|
+
async add(log) {
|
|
36
|
+
this.buffer.push(log);
|
|
37
|
+
|
|
38
|
+
// 防止内存溢出:达到最大缓冲区,立即刷新
|
|
39
|
+
if (this.buffer.length >= this.maxBufferSize) {
|
|
40
|
+
await this.flush();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 达到批量大小,立即刷新
|
|
45
|
+
if (this.buffer.length >= this.batchSize) {
|
|
46
|
+
await this.flush();
|
|
47
|
+
} else if (!this.timer) {
|
|
48
|
+
// 启动定时器(防止数据积压)
|
|
49
|
+
this.timer = setTimeout(() => this.flush(), this.flushInterval);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 刷新队列(批量写入)
|
|
55
|
+
* @returns {Promise<void>}
|
|
56
|
+
*/
|
|
57
|
+
async flush() {
|
|
58
|
+
// 防止并发刷新
|
|
59
|
+
if (this.flushing || this.buffer.length === 0) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
this.flushing = true;
|
|
64
|
+
const logs = this.buffer.splice(0); // 清空缓冲区
|
|
65
|
+
clearTimeout(this.timer);
|
|
66
|
+
this.timer = null;
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
// 批量写入
|
|
70
|
+
await this.storage.saveBatch(logs);
|
|
71
|
+
if (this.logger.debug) {
|
|
72
|
+
this.logger.debug(`[SlowQueryLog] Batch flushed: ${logs.length} logs`);
|
|
73
|
+
}
|
|
74
|
+
} catch (err) {
|
|
75
|
+
// ⚠️ 失败不阻塞主流程,仅记录错误
|
|
76
|
+
if (this.logger.error) {
|
|
77
|
+
this.logger.error('[SlowQueryLog] Failed to save slow query logs batch:', err);
|
|
78
|
+
}
|
|
79
|
+
} finally {
|
|
80
|
+
this.flushing = false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 关闭队列(确保数据不丢失)
|
|
86
|
+
* @returns {Promise<void>}
|
|
87
|
+
*/
|
|
88
|
+
async close() {
|
|
89
|
+
clearTimeout(this.timer);
|
|
90
|
+
this.timer = null;
|
|
91
|
+
await this.flush(); // 最后刷新一次
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = { BatchQueue };
|
|
96
|
+
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 慢查询日志配置管理器
|
|
3
|
+
* 负责配置合并、验证和默认值处理
|
|
4
|
+
*
|
|
5
|
+
* @version 1.3.0
|
|
6
|
+
* @since 2025-12-22
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// 默认配置
|
|
10
|
+
const DEFAULT_CONFIG = {
|
|
11
|
+
enabled: false,
|
|
12
|
+
|
|
13
|
+
storage: {
|
|
14
|
+
type: null, // null = 自动推断
|
|
15
|
+
useBusinessConnection: true,
|
|
16
|
+
uri: null,
|
|
17
|
+
|
|
18
|
+
mongodb: {
|
|
19
|
+
database: 'admin',
|
|
20
|
+
collection: 'slow_query_logs',
|
|
21
|
+
ttl: 7 * 24 * 3600, // 7天
|
|
22
|
+
ttlField: 'lastSeen'
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
deduplication: {
|
|
27
|
+
enabled: true,
|
|
28
|
+
strategy: 'aggregate',
|
|
29
|
+
keepRecentExecutions: 0
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
batch: {
|
|
33
|
+
enabled: true,
|
|
34
|
+
size: 10,
|
|
35
|
+
interval: 5000,
|
|
36
|
+
maxBufferSize: 100
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
filter: {
|
|
40
|
+
excludeDatabases: [],
|
|
41
|
+
excludeCollections: [],
|
|
42
|
+
excludeOperations: [],
|
|
43
|
+
minExecutionTimeMs: 0
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
advanced: {
|
|
47
|
+
autoCreateIndexes: true,
|
|
48
|
+
validateConnection: true,
|
|
49
|
+
errorHandling: 'log', // log | throw | silent
|
|
50
|
+
debug: false
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
class SlowQueryLogConfigManager {
|
|
55
|
+
/**
|
|
56
|
+
* 合并用户配置与默认配置
|
|
57
|
+
* @param {*} userConfig - 用户配置(可以是boolean或object)
|
|
58
|
+
* @param {string} businessType - 业务库类型
|
|
59
|
+
* @returns {Object} 合并后的完整配置
|
|
60
|
+
*/
|
|
61
|
+
static mergeConfig(userConfig, businessType) {
|
|
62
|
+
// 场景1:未配置(默认禁用)
|
|
63
|
+
if (userConfig === undefined || userConfig === null) {
|
|
64
|
+
return { ...deepClone(DEFAULT_CONFIG), enabled: false };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 场景2:boolean快捷配置
|
|
68
|
+
if (typeof userConfig === 'boolean') {
|
|
69
|
+
const config = deepClone(DEFAULT_CONFIG);
|
|
70
|
+
config.enabled = userConfig;
|
|
71
|
+
|
|
72
|
+
// 自动推断storage.type
|
|
73
|
+
if (userConfig && businessType) {
|
|
74
|
+
config.storage.type = businessType;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return config;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 场景3:对象配置(深度合并)
|
|
81
|
+
if (typeof userConfig === 'object') {
|
|
82
|
+
const merged = deepMerge(deepClone(DEFAULT_CONFIG), userConfig);
|
|
83
|
+
|
|
84
|
+
// 智能推断:如果提供了storage配置,自动启用
|
|
85
|
+
if (userConfig.storage && merged.enabled === false) {
|
|
86
|
+
merged.enabled = true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// 自动推断storage.type
|
|
90
|
+
if (merged.storage.type === null && businessType) {
|
|
91
|
+
if (merged.storage.useBusinessConnection) {
|
|
92
|
+
merged.storage.type = businessType;
|
|
93
|
+
} else {
|
|
94
|
+
merged.storage.type = 'mongodb'; // 独立连接默认MongoDB
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return merged;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
throw new Error('Invalid slowQueryLog config type. Expected boolean or object');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 验证配置合法性
|
|
106
|
+
* @param {Object} config - 配置对象
|
|
107
|
+
* @param {string} businessType - 业务库类型
|
|
108
|
+
* @throws {Error} 配置错误时抛出异常
|
|
109
|
+
*/
|
|
110
|
+
static validate(config, businessType) {
|
|
111
|
+
const { storage } = config;
|
|
112
|
+
|
|
113
|
+
// 验证storage.type
|
|
114
|
+
const validTypes = ['mongodb', 'postgresql', 'mysql', 'file', 'custom'];
|
|
115
|
+
if (storage.type && !validTypes.includes(storage.type)) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Invalid storage.type: ${storage.type}. ` +
|
|
118
|
+
`Valid types are: ${validTypes.join(', ')}`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 验证复用连接的类型一致性
|
|
123
|
+
if (storage.useBusinessConnection === true) {
|
|
124
|
+
if (storage.type && storage.type !== businessType) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Cannot use business connection when storage type (${storage.type}) ` +
|
|
127
|
+
`differs from business type (${businessType}). ` +
|
|
128
|
+
`Set useBusinessConnection=false and provide storage.uri`
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
} else {
|
|
132
|
+
// 验证独立连接的uri
|
|
133
|
+
if (!storage.uri) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
'storage.uri is required when useBusinessConnection=false'
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 验证deduplication.strategy
|
|
141
|
+
const validStrategies = ['aggregate', 'none'];
|
|
142
|
+
if (!validStrategies.includes(config.deduplication.strategy)) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Invalid deduplication.strategy: ${config.deduplication.strategy}. ` +
|
|
145
|
+
`Valid strategies are: ${validStrategies.join(', ')}`
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 验证TTL
|
|
150
|
+
if (storage.mongodb.ttl < 0) {
|
|
151
|
+
throw new Error('storage.mongodb.ttl must be positive');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// 验证batch配置
|
|
155
|
+
if (config.batch.size < 1 || config.batch.size > 1000) {
|
|
156
|
+
throw new Error('batch.size must be between 1 and 1000');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (config.batch.interval < 100) {
|
|
160
|
+
throw new Error('batch.interval must be >= 100ms');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 深度克隆对象
|
|
169
|
+
* @param {Object} obj - 源对象
|
|
170
|
+
* @returns {Object} 克隆后的对象
|
|
171
|
+
*/
|
|
172
|
+
function deepClone(obj) {
|
|
173
|
+
return JSON.parse(JSON.stringify(obj));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* 深度合并对象
|
|
178
|
+
* @param {Object} target - 目标对象
|
|
179
|
+
* @param {Object} source - 源对象
|
|
180
|
+
* @returns {Object} 合并后的对象
|
|
181
|
+
*/
|
|
182
|
+
function deepMerge(target, source) {
|
|
183
|
+
for (const key in source) {
|
|
184
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
185
|
+
target[key] = target[key] || {};
|
|
186
|
+
deepMerge(target[key], source[key]);
|
|
187
|
+
} else {
|
|
188
|
+
target[key] = source[key];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return target;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = { SlowQueryLogConfigManager, DEFAULT_CONFIG };
|
|
195
|
+
|