monsqlize 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -60
- package/LICENSE +201 -21
- package/README.md +537 -1828
- package/changelogs/README.md +160 -0
- package/changelogs/v2.0.0.md +222 -0
- package/dist/cjs/index.cjs +10600 -0
- package/dist/cjs/mongodb/common/transaction-aware.cjs +10 -0
- package/dist/cjs/transaction/CacheLockManager.cjs +100 -0
- package/dist/cjs/transaction/Transaction.cjs +158 -0
- package/dist/cjs/transaction/TransactionManager.cjs +298 -0
- package/dist/esm/index.mjs +10650 -0
- package/dist/types/base.d.ts +81 -0
- package/dist/types/collection.d.ts +1031 -0
- package/dist/types/expression.d.ts +115 -0
- package/dist/types/index.d.ts +23 -0
- package/dist/types/lock.d.ts +74 -0
- package/dist/types/model.d.ts +526 -0
- package/dist/types/mongodb.d.ts +49 -0
- package/dist/types/monsqlize.d.ts +491 -0
- package/dist/types/pool.d.ts +84 -0
- package/dist/types/runtime.d.ts +362 -0
- package/dist/types/saga.d.ts +143 -0
- package/dist/types/slow-query-log.d.ts +126 -0
- package/dist/types/sync.d.ts +103 -0
- package/dist/types/transaction.d.ts +132 -0
- package/package.json +67 -69
- package/index.d.ts +0 -206
- package/index.mjs +0 -52
- package/lib/cache-invalidation.js +0 -279
- package/lib/cache.js +0 -530
- package/lib/common/cursor.js +0 -59
- package/lib/common/docs-urls.js +0 -73
- package/lib/common/index-options.js +0 -223
- package/lib/common/log.js +0 -61
- package/lib/common/namespace.js +0 -22
- package/lib/common/normalize.js +0 -34
- package/lib/common/page-result.js +0 -43
- package/lib/common/runner.js +0 -57
- package/lib/common/server-features.js +0 -232
- package/lib/common/shape-builders.js +0 -27
- package/lib/common/validation.js +0 -113
- package/lib/connect.js +0 -99
- package/lib/constants.js +0 -55
- package/lib/count-queue.js +0 -188
- package/lib/distributed-cache-invalidator.js +0 -260
- package/lib/errors.js +0 -168
- package/lib/expression/cache/ExpressionCache.js +0 -114
- package/lib/expression/compiler/ExpressionCompiler.js +0 -1090
- package/lib/expression/compiler/ExpressionCompilerExtensions.js +0 -531
- package/lib/expression/detector.js +0 -84
- package/lib/expression/factory.js +0 -29
- package/lib/expression/index.js +0 -19
- package/lib/function-cache.js +0 -533
- package/lib/index.js +0 -1251
- package/lib/infrastructure/ConnectionPoolManager.js +0 -464
- package/lib/infrastructure/HealthChecker.js +0 -281
- package/lib/infrastructure/PoolConfig.js +0 -199
- package/lib/infrastructure/PoolSelector.js +0 -225
- package/lib/infrastructure/PoolStats.js +0 -244
- package/lib/infrastructure/ssh-tunnel-ssh2.js +0 -212
- package/lib/infrastructure/ssh-tunnel.js +0 -41
- package/lib/infrastructure/uri-parser.js +0 -36
- package/lib/lock/Lock.js +0 -67
- package/lib/lock/errors.js +0 -28
- package/lib/lock/index.js +0 -13
- package/lib/logger.js +0 -225
- package/lib/model/examples/test.js +0 -311
- package/lib/model/features/defaults.js +0 -161
- package/lib/model/features/populate.js +0 -568
- package/lib/model/features/relations.js +0 -120
- package/lib/model/features/soft-delete.js +0 -349
- package/lib/model/features/version.js +0 -157
- package/lib/model/features/virtuals.js +0 -219
- package/lib/model/index.js +0 -1265
- package/lib/mongodb/common/accessor-helpers.js +0 -59
- package/lib/mongodb/common/agg-pipeline.js +0 -36
- package/lib/mongodb/common/aggregation-validator.js +0 -127
- package/lib/mongodb/common/iid.js +0 -28
- package/lib/mongodb/common/lexicographic-expr.js +0 -53
- package/lib/mongodb/common/shape.js +0 -32
- package/lib/mongodb/common/sort.js +0 -39
- package/lib/mongodb/common/transaction-aware.js +0 -25
- package/lib/mongodb/connect.js +0 -234
- package/lib/mongodb/index.js +0 -639
- package/lib/mongodb/management/admin-ops.js +0 -200
- package/lib/mongodb/management/bookmark-ops.js +0 -167
- package/lib/mongodb/management/cache-ops.js +0 -50
- package/lib/mongodb/management/collection-ops.js +0 -387
- package/lib/mongodb/management/database-ops.js +0 -202
- package/lib/mongodb/management/index-ops.js +0 -475
- package/lib/mongodb/management/index.js +0 -17
- package/lib/mongodb/management/namespace.js +0 -31
- package/lib/mongodb/management/validation-ops.js +0 -268
- package/lib/mongodb/queries/aggregate.js +0 -172
- package/lib/mongodb/queries/chain.js +0 -631
- package/lib/mongodb/queries/count.js +0 -99
- package/lib/mongodb/queries/distinct.js +0 -78
- package/lib/mongodb/queries/find-and-count.js +0 -193
- package/lib/mongodb/queries/find-by-ids.js +0 -236
- package/lib/mongodb/queries/find-one-by-id.js +0 -171
- package/lib/mongodb/queries/find-one.js +0 -71
- package/lib/mongodb/queries/find-page.js +0 -618
- package/lib/mongodb/queries/find.js +0 -171
- package/lib/mongodb/queries/index.js +0 -51
- package/lib/mongodb/queries/watch.js +0 -538
- package/lib/mongodb/writes/common/batch-retry.js +0 -65
- package/lib/mongodb/writes/delete-batch.js +0 -323
- package/lib/mongodb/writes/delete-many.js +0 -181
- package/lib/mongodb/writes/delete-one.js +0 -173
- package/lib/mongodb/writes/find-one-and-delete.js +0 -203
- package/lib/mongodb/writes/find-one-and-replace.js +0 -239
- package/lib/mongodb/writes/find-one-and-update.js +0 -240
- package/lib/mongodb/writes/increment-one.js +0 -259
- package/lib/mongodb/writes/index.js +0 -46
- package/lib/mongodb/writes/insert-batch.js +0 -508
- package/lib/mongodb/writes/insert-many.js +0 -223
- package/lib/mongodb/writes/insert-one.js +0 -169
- package/lib/mongodb/writes/replace-one.js +0 -226
- package/lib/mongodb/writes/result-handler.js +0 -237
- package/lib/mongodb/writes/update-batch.js +0 -416
- package/lib/mongodb/writes/update-many.js +0 -275
- package/lib/mongodb/writes/update-one.js +0 -273
- package/lib/mongodb/writes/upsert-one.js +0 -203
- package/lib/multi-level-cache.js +0 -244
- package/lib/operators.js +0 -330
- package/lib/redis-cache-adapter.js +0 -267
- package/lib/saga/SagaContext.js +0 -67
- package/lib/saga/SagaDefinition.js +0 -32
- package/lib/saga/SagaExecutor.js +0 -201
- package/lib/saga/SagaOrchestrator.js +0 -186
- package/lib/saga/index.js +0 -11
- package/lib/slow-query-log/base-storage.js +0 -70
- package/lib/slow-query-log/batch-queue.js +0 -97
- package/lib/slow-query-log/config-manager.js +0 -196
- package/lib/slow-query-log/index.js +0 -238
- package/lib/slow-query-log/mongodb-storage.js +0 -324
- package/lib/slow-query-log/query-hash.js +0 -39
- package/lib/sync/ChangeStreamSyncManager.js +0 -405
- package/lib/sync/ResumeTokenStore.js +0 -192
- package/lib/sync/SyncConfig.js +0 -127
- package/lib/sync/SyncTarget.js +0 -240
- package/lib/sync/index.js +0 -20
- package/lib/transaction/CacheLockManager.js +0 -162
- package/lib/transaction/DistributedCacheLockManager.js +0 -475
- package/lib/transaction/Transaction.js +0 -315
- package/lib/transaction/TransactionManager.js +0 -267
- package/lib/transaction/index.js +0 -11
- package/lib/utils/objectid-converter.js +0 -632
- package/types/README.md +0 -122
- package/types/base.ts +0 -94
- package/types/batch.ts +0 -187
- package/types/cache.ts +0 -71
- package/types/chain.ts +0 -254
- package/types/collection.ts +0 -357
- package/types/expression.ts +0 -109
- package/types/function-cache.d.ts +0 -135
- package/types/lock.ts +0 -95
- package/types/model/definition.ts +0 -152
- package/types/model/index.ts +0 -10
- package/types/model/instance.ts +0 -121
- package/types/model/relations.ts +0 -121
- package/types/model/virtuals.ts +0 -32
- package/types/monsqlize.ts +0 -245
- package/types/options.ts +0 -192
- package/types/pagination.ts +0 -154
- package/types/pool.ts +0 -125
- package/types/query.ts +0 -71
- package/types/saga.ts +0 -125
- package/types/stream.ts +0 -64
- package/types/sync.ts +0 -79
- package/types/transaction.ts +0 -79
- package/types/write.ts +0 -77
|
@@ -1,405 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ChangeStreamSyncManager - Change Stream 同步管理器
|
|
3
|
-
*
|
|
4
|
-
* 负责管理 MongoDB Change Stream,实时同步数据到备份库
|
|
5
|
-
*
|
|
6
|
-
* 核心功能:
|
|
7
|
-
* - 启动/停止 Change Stream
|
|
8
|
-
* - 事件过滤和转换
|
|
9
|
-
* - Resume Token 管理
|
|
10
|
-
* - 错误处理和自动重连
|
|
11
|
-
*
|
|
12
|
-
* @module lib/sync/ChangeStreamSyncManager
|
|
13
|
-
* @since v1.0.8
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const SyncTarget = require('./SyncTarget');
|
|
17
|
-
const ResumeTokenStore = require('./ResumeTokenStore');
|
|
18
|
-
const { validateSyncConfig } = require('./SyncConfig');
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Change Stream 同步管理器
|
|
22
|
-
*/
|
|
23
|
-
class ChangeStreamSyncManager {
|
|
24
|
-
/**
|
|
25
|
-
* 构造函数
|
|
26
|
-
*
|
|
27
|
-
* @param {Object} options - 配置选项
|
|
28
|
-
* @param {Object} options.db - MongoDB 数据库实例
|
|
29
|
-
* @param {Object} options.poolManager - ConnectionPoolManager 实例
|
|
30
|
-
* @param {Object} options.config - 同步配置
|
|
31
|
-
* @param {Object} [options.logger] - 日志记录器
|
|
32
|
-
*/
|
|
33
|
-
constructor(options) {
|
|
34
|
-
this.db = options.db;
|
|
35
|
-
this.poolManager = options.poolManager;
|
|
36
|
-
this.config = options.config;
|
|
37
|
-
this.logger = options.logger || console;
|
|
38
|
-
|
|
39
|
-
// 验证配置
|
|
40
|
-
validateSyncConfig(this.config);
|
|
41
|
-
|
|
42
|
-
// Resume Token 管理器
|
|
43
|
-
this.tokenStore = new ResumeTokenStore({
|
|
44
|
-
storage: this.config.resumeToken?.storage || 'file',
|
|
45
|
-
path: this.config.resumeToken?.path || './.sync-resume-token',
|
|
46
|
-
redis: this.config.resumeToken?.redis,
|
|
47
|
-
logger: this.logger
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
// 备份目标列表
|
|
51
|
-
this.targets = [];
|
|
52
|
-
|
|
53
|
-
// Change Stream 实例
|
|
54
|
-
this.changeStream = null;
|
|
55
|
-
this.isRunning = false;
|
|
56
|
-
this.isReconnecting = false;
|
|
57
|
-
|
|
58
|
-
// 统计信息
|
|
59
|
-
this.stats = {
|
|
60
|
-
eventCount: 0,
|
|
61
|
-
syncedCount: 0,
|
|
62
|
-
errorCount: 0,
|
|
63
|
-
startTime: null,
|
|
64
|
-
lastEventTime: null
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* 启动同步
|
|
70
|
-
*
|
|
71
|
-
* @returns {Promise<void>}
|
|
72
|
-
*/
|
|
73
|
-
async start() {
|
|
74
|
-
if (this.isRunning) {
|
|
75
|
-
this.logger.warn('[ChangeStreamSync] 同步已在运行中');
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
try {
|
|
80
|
-
// 1. 验证环境(检查 Change Stream 支持)
|
|
81
|
-
await this._validateEnvironment();
|
|
82
|
-
|
|
83
|
-
// 2. 初始化备份目标
|
|
84
|
-
await this._initTargets();
|
|
85
|
-
|
|
86
|
-
// 3. 加载 Resume Token
|
|
87
|
-
const resumeAfter = await this.tokenStore.load();
|
|
88
|
-
|
|
89
|
-
if (resumeAfter) {
|
|
90
|
-
this.logger.info('[ChangeStreamSync] 从 Resume Token 恢复');
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// 4. 构建过滤管道
|
|
94
|
-
const pipeline = this._buildPipeline();
|
|
95
|
-
|
|
96
|
-
// 5. 启动 Change Stream
|
|
97
|
-
const options = {
|
|
98
|
-
fullDocument: 'updateLookup', // 获取完整文档
|
|
99
|
-
resumeAfter: resumeAfter || undefined
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
this.changeStream = this.db.watch(pipeline, options);
|
|
103
|
-
|
|
104
|
-
// 6. 注册事件监听
|
|
105
|
-
this.changeStream.on('change', (event) => this._handleChange(event));
|
|
106
|
-
this.changeStream.on('error', (error) => this._handleError(error));
|
|
107
|
-
this.changeStream.on('close', () => this._handleClose());
|
|
108
|
-
|
|
109
|
-
this.isRunning = true;
|
|
110
|
-
this.stats.startTime = new Date();
|
|
111
|
-
|
|
112
|
-
this.logger.info('[ChangeStreamSync] 同步已启动', {
|
|
113
|
-
targets: this.targets.length,
|
|
114
|
-
targetNames: this.targets.map(t => t.name),
|
|
115
|
-
resumeToken: !!resumeAfter,
|
|
116
|
-
collections: this.config.collections || ['*']
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
} catch (error) {
|
|
120
|
-
this.logger.error('[ChangeStreamSync] 启动失败', {
|
|
121
|
-
error: error.message,
|
|
122
|
-
stack: error.stack
|
|
123
|
-
});
|
|
124
|
-
throw error;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* 停止同步
|
|
130
|
-
*
|
|
131
|
-
* @returns {Promise<void>}
|
|
132
|
-
*/
|
|
133
|
-
async stop() {
|
|
134
|
-
if (!this.isRunning) {
|
|
135
|
-
this.logger.warn('[ChangeStreamSync] 同步未运行');
|
|
136
|
-
return;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
// 关闭 Change Stream
|
|
141
|
-
if (this.changeStream) {
|
|
142
|
-
await this.changeStream.close();
|
|
143
|
-
this.changeStream = null;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// 关闭所有目标连接
|
|
147
|
-
await Promise.all(this.targets.map(target => target.close()));
|
|
148
|
-
|
|
149
|
-
this.isRunning = false;
|
|
150
|
-
|
|
151
|
-
this.logger.info('[ChangeStreamSync] 同步已停止', {
|
|
152
|
-
stats: this.getStats()
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
} catch (error) {
|
|
156
|
-
this.logger.error('[ChangeStreamSync] 停止失败', {
|
|
157
|
-
error: error.message
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* 验证环境(检查 Change Stream 支持)
|
|
164
|
-
*
|
|
165
|
-
* @private
|
|
166
|
-
* @returns {Promise<void>}
|
|
167
|
-
*/
|
|
168
|
-
async _validateEnvironment() {
|
|
169
|
-
try {
|
|
170
|
-
// 创建临时 Change Stream 测试支持
|
|
171
|
-
const testCollection = this.db.collection('_sync_test');
|
|
172
|
-
const testStream = testCollection.watch();
|
|
173
|
-
await testStream.close();
|
|
174
|
-
|
|
175
|
-
this.logger.debug('[ChangeStreamSync] Change Stream 支持检查通过');
|
|
176
|
-
|
|
177
|
-
} catch (error) {
|
|
178
|
-
if (error.code === 40573 || error.message.includes('changeStream')) {
|
|
179
|
-
throw new Error(
|
|
180
|
-
'Change Stream 不可用。请确保:\n' +
|
|
181
|
-
'1. MongoDB 是 Replica Set 或 Sharded Cluster\n' +
|
|
182
|
-
'2. MongoDB 版本 >= 4.0\n' +
|
|
183
|
-
'3. 用户有 changeStream 权限\n' +
|
|
184
|
-
'原始错误: ' + error.message
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
throw error;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* 初始化备份目标
|
|
193
|
-
*
|
|
194
|
-
* @private
|
|
195
|
-
* @returns {Promise<void>}
|
|
196
|
-
*/
|
|
197
|
-
async _initTargets() {
|
|
198
|
-
this.targets = [];
|
|
199
|
-
|
|
200
|
-
for (const targetConfig of this.config.targets) {
|
|
201
|
-
const target = new SyncTarget({
|
|
202
|
-
name: targetConfig.name,
|
|
203
|
-
poolManager: this.poolManager,
|
|
204
|
-
config: targetConfig,
|
|
205
|
-
logger: this.logger
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
await target.connect();
|
|
209
|
-
this.targets.push(target);
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
this.logger.info('[ChangeStreamSync] 备份目标已初始化', {
|
|
213
|
-
count: this.targets.length,
|
|
214
|
-
names: this.targets.map(t => t.name)
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* 构建过滤管道
|
|
220
|
-
*
|
|
221
|
-
* @private
|
|
222
|
-
* @returns {Array} MongoDB Aggregation Pipeline
|
|
223
|
-
*/
|
|
224
|
-
_buildPipeline() {
|
|
225
|
-
const pipeline = [];
|
|
226
|
-
|
|
227
|
-
// 过滤集合
|
|
228
|
-
if (this.config.collections && this.config.collections[0] !== '*') {
|
|
229
|
-
pipeline.push({
|
|
230
|
-
$match: {
|
|
231
|
-
'ns.coll': { $in: this.config.collections }
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// 过滤操作类型
|
|
237
|
-
pipeline.push({
|
|
238
|
-
$match: {
|
|
239
|
-
operationType: { $in: ['insert', 'update', 'delete', 'replace'] }
|
|
240
|
-
}
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
return pipeline;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* 处理 Change Event
|
|
248
|
-
*
|
|
249
|
-
* @private
|
|
250
|
-
* @param {Object} event - Change Stream 事件
|
|
251
|
-
* @returns {Promise<void>}
|
|
252
|
-
*/
|
|
253
|
-
async _handleChange(event) {
|
|
254
|
-
this.stats.eventCount++;
|
|
255
|
-
this.stats.lastEventTime = new Date();
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
// 1. 应用自定义过滤
|
|
259
|
-
if (this.config.filter && !this.config.filter(event)) {
|
|
260
|
-
this.logger.debug('[ChangeStreamSync] 事件被过滤', {
|
|
261
|
-
operationType: event.operationType,
|
|
262
|
-
collection: event.ns?.coll
|
|
263
|
-
});
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// 2. 数据转换
|
|
268
|
-
let document = event.fullDocument;
|
|
269
|
-
if (this.config.transform) {
|
|
270
|
-
document = this.config.transform(document);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// 3. 同步到所有目标
|
|
274
|
-
const syncPromises = this.targets.map(target =>
|
|
275
|
-
target.apply(event.operationType, document, event.documentKey)
|
|
276
|
-
.catch(err => {
|
|
277
|
-
this.stats.errorCount++;
|
|
278
|
-
this.logger.error('[ChangeStreamSync] 目标同步失败', {
|
|
279
|
-
target: target.name,
|
|
280
|
-
error: err.message
|
|
281
|
-
});
|
|
282
|
-
// 不抛出,继续同步其他目标
|
|
283
|
-
})
|
|
284
|
-
);
|
|
285
|
-
|
|
286
|
-
await Promise.all(syncPromises);
|
|
287
|
-
|
|
288
|
-
this.stats.syncedCount++;
|
|
289
|
-
|
|
290
|
-
// 4. 保存 Resume Token
|
|
291
|
-
await this.tokenStore.save(event._id);
|
|
292
|
-
|
|
293
|
-
this.logger.debug('[ChangeStreamSync] 事件处理完成', {
|
|
294
|
-
operationType: event.operationType,
|
|
295
|
-
collection: event.ns?.coll,
|
|
296
|
-
id: event.documentKey?._id
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
} catch (error) {
|
|
300
|
-
this.stats.errorCount++;
|
|
301
|
-
this.logger.error('[ChangeStreamSync] 事件处理失败', {
|
|
302
|
-
error: error.message,
|
|
303
|
-
event: event.operationType,
|
|
304
|
-
collection: event.ns?.coll
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* 处理 Change Stream 错误
|
|
311
|
-
*
|
|
312
|
-
* @private
|
|
313
|
-
* @param {Error} error - 错误对象
|
|
314
|
-
*/
|
|
315
|
-
async _handleError(error) {
|
|
316
|
-
this.logger.error('[ChangeStreamSync] Change Stream 错误', {
|
|
317
|
-
error: error.message,
|
|
318
|
-
code: error.code
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
// 自动重连
|
|
322
|
-
if (!this.isReconnecting) {
|
|
323
|
-
await this._reconnect();
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* 处理 Change Stream 关闭
|
|
329
|
-
*
|
|
330
|
-
* @private
|
|
331
|
-
*/
|
|
332
|
-
_handleClose() {
|
|
333
|
-
this.logger.warn('[ChangeStreamSync] Change Stream 已关闭');
|
|
334
|
-
|
|
335
|
-
if (this.isRunning && !this.isReconnecting) {
|
|
336
|
-
// 意外关闭,尝试重连
|
|
337
|
-
this._reconnect();
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/**
|
|
342
|
-
* 自动重连
|
|
343
|
-
*
|
|
344
|
-
* @private
|
|
345
|
-
* @returns {Promise<void>}
|
|
346
|
-
*/
|
|
347
|
-
async _reconnect() {
|
|
348
|
-
if (this.isReconnecting) {
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
this.isReconnecting = true;
|
|
353
|
-
const maxRetries = 5;
|
|
354
|
-
let retries = 0;
|
|
355
|
-
|
|
356
|
-
this.logger.info('[ChangeStreamSync] 开始重连...');
|
|
357
|
-
|
|
358
|
-
while (retries < maxRetries && this.isRunning) {
|
|
359
|
-
try {
|
|
360
|
-
await this.stop();
|
|
361
|
-
|
|
362
|
-
// 指数退避
|
|
363
|
-
const delay = 1000 * Math.pow(2, retries);
|
|
364
|
-
this.logger.info(`[ChangeStreamSync] 等待 ${delay}ms 后重连...`);
|
|
365
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
366
|
-
|
|
367
|
-
await this.start();
|
|
368
|
-
|
|
369
|
-
this.logger.info('[ChangeStreamSync] 重连成功');
|
|
370
|
-
this.isReconnecting = false;
|
|
371
|
-
return;
|
|
372
|
-
|
|
373
|
-
} catch (error) {
|
|
374
|
-
retries++;
|
|
375
|
-
this.logger.error(`[ChangeStreamSync] 重连失败 (${retries}/${maxRetries})`, {
|
|
376
|
-
error: error.message
|
|
377
|
-
});
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
this.isReconnecting = false;
|
|
382
|
-
this.logger.error('[ChangeStreamSync] 重连失败,已达到最大重试次数');
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* 获取统计信息
|
|
387
|
-
*
|
|
388
|
-
* @returns {Object} 统计信息
|
|
389
|
-
*/
|
|
390
|
-
getStats() {
|
|
391
|
-
return {
|
|
392
|
-
isRunning: this.isRunning,
|
|
393
|
-
eventCount: this.stats.eventCount,
|
|
394
|
-
syncedCount: this.stats.syncedCount,
|
|
395
|
-
errorCount: this.stats.errorCount,
|
|
396
|
-
startTime: this.stats.startTime,
|
|
397
|
-
lastEventTime: this.stats.lastEventTime,
|
|
398
|
-
targets: this.targets.map(t => t.getStats())
|
|
399
|
-
};
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
module.exports = ChangeStreamSyncManager;
|
|
404
|
-
|
|
405
|
-
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ResumeTokenStore - Resume Token 持久化存储
|
|
3
|
-
*
|
|
4
|
-
* 负责保存和加载 Change Stream Resume Token
|
|
5
|
-
* 支持文件和 Redis 两种存储方式
|
|
6
|
-
*
|
|
7
|
-
* @module lib/sync/ResumeTokenStore
|
|
8
|
-
* @since v1.0.8
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
const fs = require('fs').promises;
|
|
12
|
-
const path = require('path');
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Resume Token 存储器
|
|
16
|
-
*/
|
|
17
|
-
class ResumeTokenStore {
|
|
18
|
-
/**
|
|
19
|
-
* 构造函数
|
|
20
|
-
*
|
|
21
|
-
* @param {Object} options - 配置选项
|
|
22
|
-
* @param {string} options.storage - 存储类型 ('file' | 'redis')
|
|
23
|
-
* @param {string} [options.path] - 文件路径(文件模式)
|
|
24
|
-
* @param {Object} [options.redis] - Redis 实例(Redis 模式)
|
|
25
|
-
* @param {Object} [options.logger] - 日志记录器
|
|
26
|
-
*/
|
|
27
|
-
constructor(options = {}) {
|
|
28
|
-
this.storage = options.storage || 'file';
|
|
29
|
-
this.path = options.path || './.sync-resume-token';
|
|
30
|
-
this.redis = options.redis;
|
|
31
|
-
this.logger = options.logger || console;
|
|
32
|
-
|
|
33
|
-
// Redis Key
|
|
34
|
-
this.redisKey = 'monsqlize:sync:resume-token';
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* 加载 Resume Token
|
|
39
|
-
*
|
|
40
|
-
* @returns {Promise<Object|null>} Resume Token 对象或 null
|
|
41
|
-
*/
|
|
42
|
-
async load() {
|
|
43
|
-
try {
|
|
44
|
-
if (this.storage === 'redis' && this.redis) {
|
|
45
|
-
return await this._loadFromRedis();
|
|
46
|
-
} else {
|
|
47
|
-
return await this._loadFromFile();
|
|
48
|
-
}
|
|
49
|
-
} catch (error) {
|
|
50
|
-
this.logger.warn('[ResumeTokenStore] 加载 Token 失败', {
|
|
51
|
-
storage: this.storage,
|
|
52
|
-
error: error.message
|
|
53
|
-
});
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* 保存 Resume Token
|
|
60
|
-
*
|
|
61
|
-
* @param {Object} token - Resume Token 对象
|
|
62
|
-
* @returns {Promise<void>}
|
|
63
|
-
*/
|
|
64
|
-
async save(token) {
|
|
65
|
-
try {
|
|
66
|
-
if (this.storage === 'redis' && this.redis) {
|
|
67
|
-
await this._saveToRedis(token);
|
|
68
|
-
} else {
|
|
69
|
-
await this._saveToFile(token);
|
|
70
|
-
}
|
|
71
|
-
} catch (error) {
|
|
72
|
-
this.logger.error('[ResumeTokenStore] 保存 Token 失败', {
|
|
73
|
-
storage: this.storage,
|
|
74
|
-
error: error.message
|
|
75
|
-
});
|
|
76
|
-
// 不抛出错误,避免影响同步流程
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* 从文件加载
|
|
82
|
-
*
|
|
83
|
-
* @private
|
|
84
|
-
* @returns {Promise<Object|null>}
|
|
85
|
-
*/
|
|
86
|
-
async _loadFromFile() {
|
|
87
|
-
try {
|
|
88
|
-
const data = await fs.readFile(this.path, 'utf8');
|
|
89
|
-
const token = JSON.parse(data);
|
|
90
|
-
|
|
91
|
-
this.logger.debug('[ResumeTokenStore] 从文件加载 Token', {
|
|
92
|
-
path: this.path
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
return token;
|
|
96
|
-
} catch (error) {
|
|
97
|
-
if (error.code === 'ENOENT') {
|
|
98
|
-
// 文件不存在,返回 null
|
|
99
|
-
this.logger.debug('[ResumeTokenStore] Token 文件不存在', {
|
|
100
|
-
path: this.path
|
|
101
|
-
});
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
throw error;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* 保存到文件
|
|
110
|
-
*
|
|
111
|
-
* @private
|
|
112
|
-
* @param {Object} token - Resume Token
|
|
113
|
-
* @returns {Promise<void>}
|
|
114
|
-
*/
|
|
115
|
-
async _saveToFile(token) {
|
|
116
|
-
// 确保目录存在
|
|
117
|
-
const dir = path.dirname(this.path);
|
|
118
|
-
await fs.mkdir(dir, { recursive: true });
|
|
119
|
-
|
|
120
|
-
// 写入文件
|
|
121
|
-
await fs.writeFile(this.path, JSON.stringify(token, null, 2), 'utf8');
|
|
122
|
-
|
|
123
|
-
this.logger.debug('[ResumeTokenStore] Token 已保存到文件', {
|
|
124
|
-
path: this.path
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* 从 Redis 加载
|
|
130
|
-
*
|
|
131
|
-
* @private
|
|
132
|
-
* @returns {Promise<Object|null>}
|
|
133
|
-
*/
|
|
134
|
-
async _loadFromRedis() {
|
|
135
|
-
const data = await this.redis.get(this.redisKey);
|
|
136
|
-
|
|
137
|
-
if (!data) {
|
|
138
|
-
this.logger.debug('[ResumeTokenStore] Redis 中无 Token');
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const token = JSON.parse(data);
|
|
143
|
-
|
|
144
|
-
this.logger.debug('[ResumeTokenStore] 从 Redis 加载 Token', {
|
|
145
|
-
key: this.redisKey
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
return token;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* 保存到 Redis
|
|
153
|
-
*
|
|
154
|
-
* @private
|
|
155
|
-
* @param {Object} token - Resume Token
|
|
156
|
-
* @returns {Promise<void>}
|
|
157
|
-
*/
|
|
158
|
-
async _saveToRedis(token) {
|
|
159
|
-
await this.redis.set(this.redisKey, JSON.stringify(token));
|
|
160
|
-
|
|
161
|
-
this.logger.debug('[ResumeTokenStore] Token 已保存到 Redis', {
|
|
162
|
-
key: this.redisKey
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* 清除 Resume Token
|
|
168
|
-
*
|
|
169
|
-
* @returns {Promise<void>}
|
|
170
|
-
*/
|
|
171
|
-
async clear() {
|
|
172
|
-
try {
|
|
173
|
-
if (this.storage === 'redis' && this.redis) {
|
|
174
|
-
await this.redis.del(this.redisKey);
|
|
175
|
-
this.logger.info('[ResumeTokenStore] Redis Token 已清除');
|
|
176
|
-
} else {
|
|
177
|
-
await fs.unlink(this.path);
|
|
178
|
-
this.logger.info('[ResumeTokenStore] 文件 Token 已清除');
|
|
179
|
-
}
|
|
180
|
-
} catch (error) {
|
|
181
|
-
if (error.code !== 'ENOENT') {
|
|
182
|
-
this.logger.warn('[ResumeTokenStore] 清除 Token 失败', {
|
|
183
|
-
error: error.message
|
|
184
|
-
});
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
module.exports = ResumeTokenStore;
|
|
191
|
-
|
|
192
|
-
|