oak-backend-base 3.4.1 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,8 +15,8 @@ class Synchronizer {
15
15
  config;
16
16
  schema;
17
17
  remotePullInfoMap = {};
18
- pullMaxBornAtMap = {};
19
18
  channelDict = {};
19
+ contextBuilder;
20
20
  pushAccessMap = {};
21
21
  /**
22
22
  * 向某一个远端对象push opers。根据幂等性,这里如果失败了必须反复推送
@@ -30,7 +30,12 @@ class Synchronizer {
30
30
  // todo 加密
31
31
  const queue = channel.queue;
32
32
  const opers = queue.map(ele => ele.oper);
33
- console.log('向远端结点sync数据', api, JSON.stringify(opers));
33
+ if (process.env.NODE_ENV === 'development') {
34
+ console.log('向远端结点sync oper', JSON.stringify(opers.map(ele => ({
35
+ id: ele.id,
36
+ seq: ele.$$seq$$,
37
+ }))), 'txnId:', context.getCurrentTxnId());
38
+ }
34
39
  const finalApi = (0, path_1.join)(api, selfEncryptInfo.id);
35
40
  const res = await fetch(finalApi, {
36
41
  method: 'post',
@@ -61,6 +66,9 @@ class Synchronizer {
61
66
  /**
62
67
  * 返回结构见this.getSelfEndpoint
63
68
  */
69
+ if (process.env.NODE_ENV === 'development') {
70
+ console.log('同步oper返回结果', JSON.stringify(json), 'txnId:', context.getCurrentTxnId());
71
+ }
64
72
  const { successIds, failed, redundantIds } = json;
65
73
  if (failed) {
66
74
  const { id, error } = failed;
@@ -76,7 +84,7 @@ class Synchronizer {
76
84
  aliveOperIds.push(...this.channelDict[k].queue.map(ele => ele.oper.id));
77
85
  }
78
86
  }
79
- const overIds = (0, lodash_1.difference)(successIds, aliveOperIds);
87
+ const overIds = (0, lodash_1.difference)(successIds.concat(redundantIds), aliveOperIds);
80
88
  if (overIds.length > 0) {
81
89
  await context.operate('oper', {
82
90
  id: await (0, uuid_1.generateNewIdAsync)(),
@@ -112,7 +120,7 @@ class Synchronizer {
112
120
  if (channel.queue.length > 0) {
113
121
  // 最大延迟redo时间512秒
114
122
  const retryDelay = Math.pow(2, Math.min(9, retry)) * 1000;
115
- console.error(`有${channel.queue.length}个oper同步失败,将于${retryDelay}毫秒后重试`);
123
+ console.error(`有${channel.queue.length}个oper同步失败,id是「${channel.queue.map(ele => ele.oper.id).join(',')}」,将于${retryDelay}毫秒后重试`);
116
124
  return new Promise((resolve) => {
117
125
  setTimeout(async () => {
118
126
  await this.startChannel(context, channel, retry + 1);
@@ -149,11 +157,32 @@ class Synchronizer {
149
157
  (0, assert_1.default)(channel.api === (0, path_1.join)(url, 'endpoint', endpoint));
150
158
  (0, assert_1.default)(channel.entity === remoteEntity);
151
159
  (0, assert_1.default)(channel.entityId === remoteEntityId);
160
+ if (channel.queue.find(ele => ele.oper.id === oper.id)) {
161
+ console.error('aaaaa');
162
+ }
152
163
  channel.queue.push({
153
164
  oper,
154
165
  onSynchronized,
155
166
  });
156
167
  }
168
+ refineOperData(oper, rowIds) {
169
+ const { action, id, targetEntity, data, $$seq$$, filter } = oper;
170
+ const data2 = (action === 'create' && data instanceof Array) ? data.filter(ele => rowIds.includes(ele.id)) : data;
171
+ // 过滤掉数据中的跨事务trigger信息
172
+ if (data2 instanceof Array) {
173
+ data2.forEach((d) => {
174
+ (0, lodash_2.unset)(d, types_1.TriggerDataAttribute);
175
+ (0, lodash_2.unset)(d, types_1.TriggerUuidAttribute);
176
+ });
177
+ }
178
+ else {
179
+ (0, lodash_2.unset)(data2, types_1.TriggerDataAttribute);
180
+ (0, lodash_2.unset)(data2, types_1.TriggerUuidAttribute);
181
+ }
182
+ return {
183
+ id, action, targetEntity, data: data2, $$seq$$, filter,
184
+ };
185
+ }
157
186
  async dispatchOperToChannels(oper, context) {
158
187
  const { operatorId, targetEntity, filter, action, data } = oper;
159
188
  const entityIds = (0, filter_1.getRelevantIds)(filter);
@@ -192,18 +221,7 @@ class Synchronizer {
192
221
  }
193
222
  const selfEncryptInfo = encryptInfoDict[selfEntityId];
194
223
  // 推送到远端结点的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
- };
224
+ const oper2 = this.refineOperData(oper, rowIds);
207
225
  const { url } = await getRemoteAccessInfo(context, {
208
226
  userId,
209
227
  remoteEntityId: entityId,
@@ -227,69 +245,85 @@ class Synchronizer {
227
245
  * 每个进程都保证把当前所有的oper顺序处理掉,就不会有乱序的问题,大家通过database上的锁来完成同步
228
246
  * @param context
229
247
  */
230
- async trySynchronizeOpers(context) {
231
- let dirtyOpers = await context.select('oper', {
232
- data: {
233
- id: 1,
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', {
248
+ async trySynchronizeOpers() {
249
+ const context = this.contextBuilder();
250
+ await context.begin();
251
+ // 暂时全用root身份去执行(未来不一定对)
252
+ await context.initialize();
253
+ context.openRootMode();
254
+ try {
255
+ let dirtyOpers = await context.select('oper', {
245
256
  data: {
246
257
  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
258
  },
257
259
  filter: {
258
- id: { $in: ids },
259
- },
260
- }, { dontCollect: true, forUpdate: true });
261
- dirtyOpers = dirtyOpers.filter(ele => !!ele[types_1.TriggerDataAttribute]);
260
+ [types_1.TriggerDataAttribute]: {
261
+ $exists: true,
262
+ },
263
+ }
264
+ }, { dontCollect: true });
262
265
  if (dirtyOpers.length > 0) {
263
- const pushedIds = [];
264
- const unpushedIds = [];
265
- await Promise.all(dirtyOpers.map(async (oper) => {
266
- const result = await this.dispatchOperToChannels(oper, context);
267
- if (result) {
268
- pushedIds.push(oper.id);
269
- }
270
- else {
271
- unpushedIds.push(oper.id);
266
+ // 这一步是加锁,保证只有一个进程完成推送,推送者提交前会将$$triggerData$$清零
267
+ const ids = dirtyOpers.map(ele => ele.id);
268
+ dirtyOpers = await context.select('oper', {
269
+ data: {
270
+ id: 1,
271
+ action: 1,
272
+ data: 1,
273
+ targetEntity: 1,
274
+ operatorId: 1,
275
+ [types_1.TriggerDataAttribute]: 1,
276
+ bornAt: 1,
277
+ $$createAt$$: 1,
278
+ $$seq$$: 1,
279
+ filter: 1,
280
+ },
281
+ filter: {
282
+ id: { $in: ids },
283
+ },
284
+ }, { dontCollect: true, forUpdate: true });
285
+ dirtyOpers = dirtyOpers.filter(ele => !!ele[types_1.TriggerDataAttribute]);
286
+ if (dirtyOpers.length > 0) {
287
+ for (const c in this.channelDict) {
288
+ (0, assert_1.default)(this.channelDict[c].queue.length === 0);
272
289
  }
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
- }
290
+ const pushedIds = [];
291
+ const unpushedIds = [];
292
+ await Promise.all(dirtyOpers.map(async (oper) => {
293
+ const result = await this.dispatchOperToChannels(oper, context);
294
+ if (result) {
295
+ pushedIds.push(oper.id);
286
296
  }
287
- }, {});
288
- }
289
- if (pushedIds.length > 0) {
290
- await this.startAllChannel(context);
297
+ else {
298
+ unpushedIds.push(oper.id);
299
+ }
300
+ }));
301
+ if (unpushedIds.length > 0) {
302
+ await context.operate('oper', {
303
+ id: await (0, uuid_1.generateNewIdAsync)(),
304
+ action: 'update',
305
+ data: {
306
+ [types_1.TriggerDataAttribute]: null,
307
+ [types_1.TriggerUuidAttribute]: null,
308
+ },
309
+ filter: {
310
+ id: {
311
+ $in: unpushedIds,
312
+ }
313
+ }
314
+ }, {});
315
+ }
316
+ if (pushedIds.length > 0) {
317
+ await this.startAllChannel(context);
318
+ }
291
319
  }
292
320
  }
321
+ await context.commit();
322
+ }
323
+ catch (err) {
324
+ await context.rollback();
325
+ console.error(err);
326
+ throw err;
293
327
  }
294
328
  }
295
329
  makeCreateOperTrigger() {
@@ -395,18 +429,19 @@ class Synchronizer {
395
429
  return pushEntities.includes(data.targetEntity)
396
430
  && !!this.pushAccessMap[targetEntity].find(({ actions }) => !actions || actions.includes(action));
397
431
  },
398
- fn: async ({ ids }, context) => {
432
+ fn: async ({ ids }) => {
399
433
  (0, assert_1.default)(ids.length === 1);
400
- this.trySynchronizeOpers(context);
434
+ this.trySynchronizeOpers();
401
435
  // 内部自主处理triggerData,因此不需要让triggerExecutor处理
402
436
  throw new types_1.OakMakeSureByMySelfException();
403
437
  }
404
438
  };
405
439
  return createOperTrigger;
406
440
  }
407
- constructor(config, schema) {
441
+ constructor(config, schema, contextBuilder) {
408
442
  this.config = config;
409
443
  this.schema = schema;
444
+ this.contextBuilder = contextBuilder;
410
445
  }
411
446
  /**
412
447
  * 根据sync的定义,生成对应的 commit triggers
@@ -418,8 +453,8 @@ class Synchronizer {
418
453
  getSyncRoutine() {
419
454
  return {
420
455
  name: 'checkpoint routine for sync',
421
- routine: async (context) => {
422
- this.trySynchronizeOpers(context);
456
+ routine: async () => {
457
+ this.trySynchronizeOpers();
423
458
  return {};
424
459
  },
425
460
  };
@@ -433,7 +468,9 @@ class Synchronizer {
433
468
  // body中是传过来的oper数组信息
434
469
  const { entity, entityId } = params;
435
470
  const { [OAK_SYNC_HEADER_ENTITY]: meEntity, [OAK_SYNC_HEADER_ENTITYID]: meEntityId } = headers;
436
- console.log('接收到来自远端的sync数据', entity, JSON.stringify(body));
471
+ if (process.env.NODE_ENV === 'development') {
472
+ console.log('接收到来自远端的sync数据', entity, JSON.stringify(body));
473
+ }
437
474
  const successIds = [];
438
475
  const redundantIds = [];
439
476
  let failed;
@@ -447,6 +484,7 @@ class Synchronizer {
447
484
  if (pullEntities) {
448
485
  pullEntities.forEach((def) => pullEntityDict[def.entity] = def);
449
486
  }
487
+ const closeFn = context.openRootMode();
450
488
  this.remotePullInfoMap[entity][entityId] = {
451
489
  pullInfo: await getPullInfo(context, {
452
490
  selfId: meEntityId,
@@ -454,6 +492,7 @@ class Synchronizer {
454
492
  }),
455
493
  pullEntityDict,
456
494
  };
495
+ closeFn();
457
496
  }
458
497
  const { pullInfo, pullEntityDict } = this.remotePullInfoMap[entity][entityId];
459
498
  const { userId, algorithm, publicKey, cxtInfo } = pullInfo;
@@ -463,36 +502,30 @@ class Synchronizer {
463
502
  await context.initialize(cxtInfo);
464
503
  }
465
504
  // todo 解密
466
- if (!this.pullMaxBornAtMap.hasOwnProperty(entityId)) {
467
- const [maxHisOper] = await context.select('oper', {
468
- data: {
469
- id: 1,
470
- bornAt: 1,
471
- },
472
- filter: {
473
- operatorId: userId,
505
+ const opers = body;
506
+ const ids = opers.map(ele => ele.id);
507
+ const existeIds = (await context.select('oper', {
508
+ data: {
509
+ id: 1,
510
+ },
511
+ filter: {
512
+ id: {
513
+ $in: ids,
474
514
  },
475
- sorter: [
476
- {
477
- $attr: {
478
- bornAt: 1,
479
- },
480
- $direction: 'desc',
481
- },
482
- ],
483
- indexFrom: 0,
484
- count: 1,
485
- }, { dontCollect: true });
486
- this.pullMaxBornAtMap[entityId] = maxHisOper?.bornAt || 0;
515
+ }
516
+ }, {})).map(ele => ele.id);
517
+ const staleOpers = opers.filter(ele => existeIds.includes(ele.id));
518
+ const freshOpers = opers.filter(ele => !existeIds.includes(ele.id));
519
+ if (process.env.NODE_ENV !== 'production') {
520
+ const maxStaleSeq = Math.max(...staleOpers.map(ele => ele.$$seq$$));
521
+ for (const oper of freshOpers) {
522
+ (0, assert_1.default)(oper.$$seq$$ > maxStaleSeq, '发现了seq没有按序进行同步');
523
+ }
487
524
  }
488
- let maxBornAt = this.pullMaxBornAtMap[entityId];
489
- const opers = body;
490
- const outdatedOpers = opers.filter(ele => ele.$$seq$$ <= maxBornAt);
491
- const freshOpers = opers.filter(ele => ele.$$seq$$ > maxBornAt);
492
525
  await Promise.all([
493
526
  // 无法严格保证推送按bornAt,所以一旦还有outdatedOpers,检查其已经被apply
494
527
  (async () => {
495
- const ids = outdatedOpers.map(ele => ele.id);
528
+ const ids = staleOpers.map(ele => ele.id);
496
529
  if (ids.length > 0) {
497
530
  const opersExisted = await context.select('oper', {
498
531
  data: {
@@ -538,7 +571,6 @@ class Synchronizer {
538
571
  };
539
572
  await context.operate(targetEntity, operation, {});
540
573
  successIds.push(id);
541
- maxBornAt = $$seq$$;
542
574
  }
543
575
  catch (err) {
544
576
  console.error(err);
@@ -552,7 +584,6 @@ class Synchronizer {
552
584
  }
553
585
  })()
554
586
  ]);
555
- this.pullMaxBornAtMap[entityId] = maxBornAt;
556
587
  return {
557
588
  successIds,
558
589
  failed,
@@ -12,8 +12,7 @@ import { Namespace } from 'socket.io';
12
12
  export default class DataSubscriber<ED extends EntityDict & BaseEntityDict, Context extends BackendRuntimeContext<ED>> {
13
13
  private ns;
14
14
  private nsServer?;
15
- private contextBuilder;
16
- constructor(ns: Namespace, contextBuilder: (scene?: string) => Promise<Context>, nsServer?: Namespace);
15
+ constructor(ns: Namespace, nsServer?: Namespace);
17
16
  /**
18
17
  * 来自外部的socket连接,监听数据变化
19
18
  */
@@ -12,11 +12,9 @@ const console_1 = require("console");
12
12
  class DataSubscriber {
13
13
  ns;
14
14
  nsServer;
15
- contextBuilder;
16
- constructor(ns, contextBuilder, nsServer) {
15
+ constructor(ns, nsServer) {
17
16
  this.ns = ns;
18
17
  this.nsServer = nsServer;
19
- this.contextBuilder = contextBuilder;
20
18
  this.startup();
21
19
  }
22
20
  /**
@@ -1,16 +1,16 @@
1
- import { EntityDict } from 'oak-domain/lib/types';
2
- import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
3
- import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
4
- import { RemotePushInfo, RemotePullInfo, SelfEncryptInfo, SyncRemoteConfigBase, SyncSelfConfigBase, SyncConfig } from 'oak-domain/lib/types/Sync';
5
- interface SyncRemoteConfigWrapper<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends SyncRemoteConfigBase<ED, Cxt> {
6
- getRemotePushInfo: (userId: string) => Promise<RemotePushInfo>;
7
- getRemotePullInfo: (id: string) => Promise<RemotePullInfo>;
8
- }
9
- interface SyncSelfConfigWrapper<ED extends EntityDict & BaseEntityDict> extends SyncSelfConfigBase<ED> {
10
- getSelfEncryptInfo: () => Promise<SelfEncryptInfo>;
11
- }
12
- export interface SyncConfigWrapper<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> {
13
- self: SyncSelfConfigWrapper<ED>;
14
- remotes: Array<SyncRemoteConfigWrapper<ED, Cxt>>;
15
- }
16
- export { RemotePushInfo, RemotePullInfo, SelfEncryptInfo, SyncConfig, };
1
+ import { EntityDict } from 'oak-domain/lib/types';
2
+ import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
3
+ import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
4
+ import { RemotePushInfo, RemotePullInfo, SelfEncryptInfo, SyncRemoteConfigBase, SyncSelfConfigBase, SyncConfig } from 'oak-domain/lib/types/Sync';
5
+ interface SyncRemoteConfigWrapper<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends SyncRemoteConfigBase<ED, Cxt> {
6
+ getRemotePushInfo: (userId: string) => Promise<RemotePushInfo>;
7
+ getRemotePullInfo: (id: string) => Promise<RemotePullInfo>;
8
+ }
9
+ interface SyncSelfConfigWrapper<ED extends EntityDict & BaseEntityDict> extends SyncSelfConfigBase<ED> {
10
+ getSelfEncryptInfo: () => Promise<SelfEncryptInfo>;
11
+ }
12
+ export interface SyncConfigWrapper<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> {
13
+ self: SyncSelfConfigWrapper<ED>;
14
+ remotes: Array<SyncRemoteConfigWrapper<ED, Cxt>>;
15
+ }
16
+ export { RemotePushInfo, RemotePullInfo, SelfEncryptInfo, SyncConfig, };
package/lib/types/Sync.js CHANGED
@@ -1,5 +1,5 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- ;
4
- ;
5
- ;
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ ;
4
+ ;
5
+ ;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oak-backend-base",
3
- "version": "3.4.1",
3
+ "version": "3.5.0",
4
4
  "description": "oak-backend-base",
5
5
  "main": "lib/index",
6
6
  "author": {
@@ -20,10 +20,10 @@
20
20
  "mysql": "^2.18.1",
21
21
  "mysql2": "^2.3.3",
22
22
  "node-schedule": "^2.1.0",
23
- "oak-common-aspect": "^2.3.0",
24
- "oak-db": "^3.2.0",
25
- "oak-domain": "^4.3.1",
26
- "oak-frontend-base": "^4.3.0",
23
+ "oak-common-aspect": "^2.3.1",
24
+ "oak-db": "^3.2.1",
25
+ "oak-domain": "^4.5.0",
26
+ "oak-frontend-base": "^4.5.0",
27
27
  "socket.io": "^4.7.2",
28
28
  "socket.io-client": "^4.7.2",
29
29
  "uuid": "^8.3.2"