oak-backend-base 3.4.1 → 3.4.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/lib/AppLoader.js +1 -1
- package/lib/Synchronizer.d.ts +3 -1
- package/lib/Synchronizer.js +93 -74
- package/package.json +3 -3
package/lib/AppLoader.js
CHANGED
|
@@ -160,7 +160,7 @@ class AppLoader extends types_1.AppLoader {
|
|
|
160
160
|
if (!initialize) {
|
|
161
161
|
const { syncConfig: syncConfig } = this.getConfiguration();
|
|
162
162
|
if (syncConfig) {
|
|
163
|
-
this.synchronizer = new Synchronizer_1.default(syncConfig, this.dbStore.getSchema());
|
|
163
|
+
this.synchronizer = new Synchronizer_1.default(syncConfig, this.dbStore.getSchema(), () => this.contextBuilder()(this.dbStore));
|
|
164
164
|
}
|
|
165
165
|
this.initTriggers();
|
|
166
166
|
}
|
package/lib/Synchronizer.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export default class Synchronizer<ED extends EntityDict & BaseEntityDict, Cxt ex
|
|
|
8
8
|
private remotePullInfoMap;
|
|
9
9
|
private pullMaxBornAtMap;
|
|
10
10
|
private channelDict;
|
|
11
|
+
private contextBuilder;
|
|
11
12
|
private pushAccessMap;
|
|
12
13
|
/**
|
|
13
14
|
* 向某一个远端对象push opers。根据幂等性,这里如果失败了必须反复推送
|
|
@@ -17,6 +18,7 @@ export default class Synchronizer<ED extends EntityDict & BaseEntityDict, Cxt ex
|
|
|
17
18
|
private startChannel;
|
|
18
19
|
private startAllChannel;
|
|
19
20
|
private pushOperToChannel;
|
|
21
|
+
private refineOperData;
|
|
20
22
|
private dispatchOperToChannels;
|
|
21
23
|
/**
|
|
22
24
|
* 为了保证推送的oper序,采用从database中顺序读取所有需要推送的oper来进行推送
|
|
@@ -25,7 +27,7 @@ export default class Synchronizer<ED extends EntityDict & BaseEntityDict, Cxt ex
|
|
|
25
27
|
*/
|
|
26
28
|
private trySynchronizeOpers;
|
|
27
29
|
private makeCreateOperTrigger;
|
|
28
|
-
constructor(config: SyncConfig<ED, Cxt>, schema: StorageSchema<ED>);
|
|
30
|
+
constructor(config: SyncConfig<ED, Cxt>, schema: StorageSchema<ED>, contextBuilder: () => Promise<Cxt>);
|
|
29
31
|
/**
|
|
30
32
|
* 根据sync的定义,生成对应的 commit triggers
|
|
31
33
|
* @returns
|
package/lib/Synchronizer.js
CHANGED
|
@@ -17,6 +17,7 @@ class Synchronizer {
|
|
|
17
17
|
remotePullInfoMap = {};
|
|
18
18
|
pullMaxBornAtMap = {};
|
|
19
19
|
channelDict = {};
|
|
20
|
+
contextBuilder;
|
|
20
21
|
pushAccessMap = {};
|
|
21
22
|
/**
|
|
22
23
|
* 向某一个远端对象push opers。根据幂等性,这里如果失败了必须反复推送
|
|
@@ -76,7 +77,7 @@ class Synchronizer {
|
|
|
76
77
|
aliveOperIds.push(...this.channelDict[k].queue.map(ele => ele.oper.id));
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
|
-
const overIds = (0, lodash_1.difference)(successIds, aliveOperIds);
|
|
80
|
+
const overIds = (0, lodash_1.difference)(successIds.concat(redundantIds), aliveOperIds);
|
|
80
81
|
if (overIds.length > 0) {
|
|
81
82
|
await context.operate('oper', {
|
|
82
83
|
id: await (0, uuid_1.generateNewIdAsync)(),
|
|
@@ -154,6 +155,24 @@ class Synchronizer {
|
|
|
154
155
|
onSynchronized,
|
|
155
156
|
});
|
|
156
157
|
}
|
|
158
|
+
refineOperData(oper, rowIds) {
|
|
159
|
+
const { action, id, targetEntity, data, $$seq$$, filter } = oper;
|
|
160
|
+
const data2 = (action === 'create' && data instanceof Array) ? data.filter(ele => rowIds.includes(ele.id)) : data;
|
|
161
|
+
// 过滤掉数据中的跨事务trigger信息
|
|
162
|
+
if (data2 instanceof Array) {
|
|
163
|
+
data2.forEach((d) => {
|
|
164
|
+
(0, lodash_2.unset)(d, types_1.TriggerDataAttribute);
|
|
165
|
+
(0, lodash_2.unset)(d, types_1.TriggerUuidAttribute);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
(0, lodash_2.unset)(data2, types_1.TriggerDataAttribute);
|
|
170
|
+
(0, lodash_2.unset)(data2, types_1.TriggerUuidAttribute);
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
id, action, targetEntity, data: data2, $$seq$$, filter,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
157
176
|
async dispatchOperToChannels(oper, context) {
|
|
158
177
|
const { operatorId, targetEntity, filter, action, data } = oper;
|
|
159
178
|
const entityIds = (0, filter_1.getRelevantIds)(filter);
|
|
@@ -192,18 +211,7 @@ class Synchronizer {
|
|
|
192
211
|
}
|
|
193
212
|
const selfEncryptInfo = encryptInfoDict[selfEntityId];
|
|
194
213
|
// 推送到远端结点的oper
|
|
195
|
-
const oper2 =
|
|
196
|
-
id: oper.id,
|
|
197
|
-
action: action,
|
|
198
|
-
data: (action === 'create' && data instanceof Array) ? data.filter(ele => rowIds.includes(ele.id)) : data,
|
|
199
|
-
filter: {
|
|
200
|
-
id: rowIds.length === 1 ? rowIds[0] : {
|
|
201
|
-
$in: rowIds,
|
|
202
|
-
}
|
|
203
|
-
},
|
|
204
|
-
$$seq$$: oper.$$seq$$,
|
|
205
|
-
targetEntity,
|
|
206
|
-
};
|
|
214
|
+
const oper2 = this.refineOperData(oper, rowIds);
|
|
207
215
|
const { url } = await getRemoteAccessInfo(context, {
|
|
208
216
|
userId,
|
|
209
217
|
remoteEntityId: entityId,
|
|
@@ -227,69 +235,79 @@ class Synchronizer {
|
|
|
227
235
|
* 每个进程都保证把当前所有的oper顺序处理掉,就不会有乱序的问题,大家通过database上的锁来完成同步
|
|
228
236
|
* @param context
|
|
229
237
|
*/
|
|
230
|
-
async trySynchronizeOpers(
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
filter: {
|
|
236
|
-
[types_1.TriggerDataAttribute]: {
|
|
237
|
-
$exists: true,
|
|
238
|
-
},
|
|
239
|
-
}
|
|
240
|
-
}, { dontCollect: true });
|
|
241
|
-
if (dirtyOpers.length > 0) {
|
|
242
|
-
// 这一步是加锁,保证只有一个进程完成推送,推送者提交前会将$$triggerData$$清零
|
|
243
|
-
const ids = dirtyOpers.map(ele => ele.id);
|
|
244
|
-
dirtyOpers = await context.select('oper', {
|
|
238
|
+
async trySynchronizeOpers() {
|
|
239
|
+
const context = await this.contextBuilder();
|
|
240
|
+
await context.begin();
|
|
241
|
+
try {
|
|
242
|
+
let dirtyOpers = await context.select('oper', {
|
|
245
243
|
data: {
|
|
246
244
|
id: 1,
|
|
247
|
-
action: 1,
|
|
248
|
-
data: 1,
|
|
249
|
-
targetEntity: 1,
|
|
250
|
-
operatorId: 1,
|
|
251
|
-
[types_1.TriggerDataAttribute]: 1,
|
|
252
|
-
bornAt: 1,
|
|
253
|
-
$$createAt$$: 1,
|
|
254
|
-
$$seq$$: 1,
|
|
255
|
-
filter: 1,
|
|
256
245
|
},
|
|
257
246
|
filter: {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
247
|
+
[types_1.TriggerDataAttribute]: {
|
|
248
|
+
$exists: true,
|
|
249
|
+
},
|
|
250
|
+
}
|
|
251
|
+
}, { dontCollect: true });
|
|
262
252
|
if (dirtyOpers.length > 0) {
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
await
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
253
|
+
// 这一步是加锁,保证只有一个进程完成推送,推送者提交前会将$$triggerData$$清零
|
|
254
|
+
const ids = dirtyOpers.map(ele => ele.id);
|
|
255
|
+
dirtyOpers = await context.select('oper', {
|
|
256
|
+
data: {
|
|
257
|
+
id: 1,
|
|
258
|
+
action: 1,
|
|
259
|
+
data: 1,
|
|
260
|
+
targetEntity: 1,
|
|
261
|
+
operatorId: 1,
|
|
262
|
+
[types_1.TriggerDataAttribute]: 1,
|
|
263
|
+
bornAt: 1,
|
|
264
|
+
$$createAt$$: 1,
|
|
265
|
+
$$seq$$: 1,
|
|
266
|
+
filter: 1,
|
|
267
|
+
},
|
|
268
|
+
filter: {
|
|
269
|
+
id: { $in: ids },
|
|
270
|
+
},
|
|
271
|
+
}, { dontCollect: true, forUpdate: true });
|
|
272
|
+
dirtyOpers = dirtyOpers.filter(ele => !!ele[types_1.TriggerDataAttribute]);
|
|
273
|
+
if (dirtyOpers.length > 0) {
|
|
274
|
+
const pushedIds = [];
|
|
275
|
+
const unpushedIds = [];
|
|
276
|
+
await Promise.all(dirtyOpers.map(async (oper) => {
|
|
277
|
+
const result = await this.dispatchOperToChannels(oper, context);
|
|
278
|
+
if (result) {
|
|
279
|
+
pushedIds.push(oper.id);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
unpushedIds.push(oper.id);
|
|
283
|
+
}
|
|
284
|
+
}));
|
|
285
|
+
if (unpushedIds.length > 0) {
|
|
286
|
+
await context.operate('oper', {
|
|
287
|
+
id: await (0, uuid_1.generateNewIdAsync)(),
|
|
288
|
+
action: 'update',
|
|
289
|
+
data: {
|
|
290
|
+
[types_1.TriggerDataAttribute]: null,
|
|
291
|
+
[types_1.TriggerUuidAttribute]: null,
|
|
292
|
+
},
|
|
293
|
+
filter: {
|
|
294
|
+
id: {
|
|
295
|
+
$in: unpushedIds,
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}, {});
|
|
269
299
|
}
|
|
270
|
-
|
|
271
|
-
|
|
300
|
+
if (pushedIds.length > 0) {
|
|
301
|
+
await this.startAllChannel(context);
|
|
272
302
|
}
|
|
273
|
-
}));
|
|
274
|
-
if (unpushedIds.length > 0) {
|
|
275
|
-
await context.operate('oper', {
|
|
276
|
-
id: await (0, uuid_1.generateNewIdAsync)(),
|
|
277
|
-
action: 'update',
|
|
278
|
-
data: {
|
|
279
|
-
[types_1.TriggerDataAttribute]: null,
|
|
280
|
-
[types_1.TriggerUuidAttribute]: null,
|
|
281
|
-
},
|
|
282
|
-
filter: {
|
|
283
|
-
id: {
|
|
284
|
-
$in: unpushedIds,
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}, {});
|
|
288
|
-
}
|
|
289
|
-
if (pushedIds.length > 0) {
|
|
290
|
-
await this.startAllChannel(context);
|
|
291
303
|
}
|
|
292
304
|
}
|
|
305
|
+
await context.commit();
|
|
306
|
+
}
|
|
307
|
+
catch (err) {
|
|
308
|
+
await context.rollback();
|
|
309
|
+
console.error(err);
|
|
310
|
+
throw err;
|
|
293
311
|
}
|
|
294
312
|
}
|
|
295
313
|
makeCreateOperTrigger() {
|
|
@@ -395,18 +413,19 @@ class Synchronizer {
|
|
|
395
413
|
return pushEntities.includes(data.targetEntity)
|
|
396
414
|
&& !!this.pushAccessMap[targetEntity].find(({ actions }) => !actions || actions.includes(action));
|
|
397
415
|
},
|
|
398
|
-
fn: async ({ ids }
|
|
416
|
+
fn: async ({ ids }) => {
|
|
399
417
|
(0, assert_1.default)(ids.length === 1);
|
|
400
|
-
this.trySynchronizeOpers(
|
|
418
|
+
this.trySynchronizeOpers();
|
|
401
419
|
// 内部自主处理triggerData,因此不需要让triggerExecutor处理
|
|
402
420
|
throw new types_1.OakMakeSureByMySelfException();
|
|
403
421
|
}
|
|
404
422
|
};
|
|
405
423
|
return createOperTrigger;
|
|
406
424
|
}
|
|
407
|
-
constructor(config, schema) {
|
|
425
|
+
constructor(config, schema, contextBuilder) {
|
|
408
426
|
this.config = config;
|
|
409
427
|
this.schema = schema;
|
|
428
|
+
this.contextBuilder = contextBuilder;
|
|
410
429
|
}
|
|
411
430
|
/**
|
|
412
431
|
* 根据sync的定义,生成对应的 commit triggers
|
|
@@ -418,8 +437,8 @@ class Synchronizer {
|
|
|
418
437
|
getSyncRoutine() {
|
|
419
438
|
return {
|
|
420
439
|
name: 'checkpoint routine for sync',
|
|
421
|
-
routine: async (
|
|
422
|
-
this.trySynchronizeOpers(
|
|
440
|
+
routine: async () => {
|
|
441
|
+
this.trySynchronizeOpers();
|
|
423
442
|
return {};
|
|
424
443
|
},
|
|
425
444
|
};
|
|
@@ -487,12 +506,12 @@ class Synchronizer {
|
|
|
487
506
|
}
|
|
488
507
|
let maxBornAt = this.pullMaxBornAtMap[entityId];
|
|
489
508
|
const opers = body;
|
|
490
|
-
const
|
|
509
|
+
const staleOpers = opers.filter(ele => ele.$$seq$$ <= maxBornAt);
|
|
491
510
|
const freshOpers = opers.filter(ele => ele.$$seq$$ > maxBornAt);
|
|
492
511
|
await Promise.all([
|
|
493
512
|
// 无法严格保证推送按bornAt,所以一旦还有outdatedOpers,检查其已经被apply
|
|
494
513
|
(async () => {
|
|
495
|
-
const ids =
|
|
514
|
+
const ids = staleOpers.map(ele => ele.id);
|
|
496
515
|
if (ids.length > 0) {
|
|
497
516
|
const opersExisted = await context.select('oper', {
|
|
498
517
|
data: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oak-backend-base",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.2",
|
|
4
4
|
"description": "oak-backend-base",
|
|
5
5
|
"main": "lib/index",
|
|
6
6
|
"author": {
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"node-schedule": "^2.1.0",
|
|
23
23
|
"oak-common-aspect": "^2.3.0",
|
|
24
24
|
"oak-db": "^3.2.0",
|
|
25
|
-
"oak-domain": "^4.
|
|
26
|
-
"oak-frontend-base": "^4.
|
|
25
|
+
"oak-domain": "^4.4.0",
|
|
26
|
+
"oak-frontend-base": "^4.4.0",
|
|
27
27
|
"socket.io": "^4.7.2",
|
|
28
28
|
"socket.io-client": "^4.7.2",
|
|
29
29
|
"uuid": "^8.3.2"
|