oak-backend-base 4.1.26 → 4.1.28
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.d.ts +36 -4
- package/lib/AppLoader.js +191 -34
- package/lib/ClusterAppLoader.d.ts +3 -2
- package/lib/ClusterAppLoader.js +8 -2
- package/lib/DbStore.d.ts +4 -1
- package/lib/DbStore.js +3 -1
- package/lib/utils/dbPriority.d.ts +8 -3
- package/lib/utils/dbPriority.js +15 -8
- package/package.json +3 -3
package/lib/AppLoader.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
|
2
|
-
import { AppLoader as GeneralAppLoader, Trigger, EntityDict, Watcher, OpRecord, FreeTimer, OperationResult } from "oak-domain/lib/types";
|
|
2
|
+
import { AppLoader as GeneralAppLoader, Trigger, EntityDict, Watcher, OpRecord, FreeTimer, OperationResult, BaseTimer } from "oak-domain/lib/types";
|
|
3
3
|
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
|
|
4
4
|
import { IncomingHttpHeaders, IncomingMessage } from 'http';
|
|
5
5
|
import { Namespace } from 'socket.io';
|
|
@@ -19,6 +19,7 @@ export declare class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt exten
|
|
|
19
19
|
private watcherTimerId?;
|
|
20
20
|
private scheduledJobs;
|
|
21
21
|
private internalErrorHandlers;
|
|
22
|
+
private watcherExecutingData;
|
|
22
23
|
regAllExceptionHandler(): void;
|
|
23
24
|
/**
|
|
24
25
|
* 注册一个内部错误处理器
|
|
@@ -32,9 +33,15 @@ export declare class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt exten
|
|
|
32
33
|
private requireSth;
|
|
33
34
|
protected makeContext(cxtStr?: string, headers?: IncomingHttpHeaders): Promise<Cxt>;
|
|
34
35
|
/**
|
|
35
|
-
*
|
|
36
|
+
* 获取数据库配置
|
|
37
|
+
* @returns 读取数据库配置
|
|
36
38
|
*/
|
|
37
|
-
private
|
|
39
|
+
private getDbConfig;
|
|
40
|
+
/**
|
|
41
|
+
* 获取同步配置
|
|
42
|
+
* @returns 读取同步配置
|
|
43
|
+
*/
|
|
44
|
+
private getSyncConfig;
|
|
38
45
|
constructor(path: string, nsSubscribe?: Namespace, nsSocket?: Namespace, nsServer?: Namespace);
|
|
39
46
|
protected registerTrigger(trigger: Trigger<ED, keyof ED, Cxt>): void;
|
|
40
47
|
protected initTriggers(): void;
|
|
@@ -55,11 +62,36 @@ export declare class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt exten
|
|
|
55
62
|
}>][];
|
|
56
63
|
protected operateInWatcher<T extends keyof ED>(entity: T, operation: ED[T]['Update'], context: Cxt, singleton?: true): Promise<OperationResult<ED>>;
|
|
57
64
|
protected selectInWatcher<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: Cxt, forUpdate?: true, singleton?: true): Promise<Partial<ED[T]["Schema"]>[]>;
|
|
65
|
+
/**
|
|
66
|
+
* 检查某个数据是否正在被watcher执行
|
|
67
|
+
* @param name watcher名称
|
|
68
|
+
* @param dataId 数据ID
|
|
69
|
+
* @returns 如果没有正在执行则返回true,否则返回false
|
|
70
|
+
*/
|
|
71
|
+
private checkDataExecuting;
|
|
72
|
+
/**
|
|
73
|
+
* 过滤出未在执行中的数据行,并标记为执行中
|
|
74
|
+
* @returns [过滤后的行, 是否有行被跳过]
|
|
75
|
+
*/
|
|
76
|
+
private filterAndMarkExecutingRows;
|
|
77
|
+
/**
|
|
78
|
+
* 清理执行标记
|
|
79
|
+
*/
|
|
80
|
+
private cleanupExecutingMarks;
|
|
81
|
+
/**
|
|
82
|
+
* 解析 filter 和 projection(支持函数或静态值)
|
|
83
|
+
*/
|
|
84
|
+
private resolveFilterAndProjection;
|
|
85
|
+
/**
|
|
86
|
+
* 执行 WB 类型 watcher 的查询操作
|
|
87
|
+
*/
|
|
88
|
+
private selectForWBWatcher;
|
|
58
89
|
protected execWatcher(watcher: Watcher<ED, keyof ED, Cxt>): Promise<OperationResult<ED> | undefined>;
|
|
59
90
|
protected getCheckpointTs(): number;
|
|
60
91
|
protected checkpoint(): Promise<number>;
|
|
61
92
|
startWatchers(): void;
|
|
62
|
-
protected
|
|
93
|
+
protected execBaseTimer(timer: BaseTimer<ED, Cxt>, context: Cxt): Promise<OperationResult<ED>> | undefined;
|
|
94
|
+
protected execFreeTimer(timer: FreeTimer<ED, Cxt>, contextBuilder: () => Promise<Cxt>): Promise<OperationResult<ED>> | undefined;
|
|
63
95
|
startTimers(): void;
|
|
64
96
|
execStartRoutines(): Promise<void>;
|
|
65
97
|
execStopRoutines(): Promise<void>;
|
package/lib/AppLoader.js
CHANGED
|
@@ -30,6 +30,7 @@ class AppLoader extends types_1.AppLoader {
|
|
|
30
30
|
watcherTimerId;
|
|
31
31
|
scheduledJobs = {};
|
|
32
32
|
internalErrorHandlers = new Array();
|
|
33
|
+
watcherExecutingData = new Map();
|
|
33
34
|
regAllExceptionHandler() {
|
|
34
35
|
const handlers = this.requireSth('lib/configuration/exception');
|
|
35
36
|
if (Array.isArray(handlers)) {
|
|
@@ -57,13 +58,12 @@ class AppLoader extends types_1.AppLoader {
|
|
|
57
58
|
* 发布内部错误事件给注册的处理器
|
|
58
59
|
*/
|
|
59
60
|
async publishInternalError(type, message, err) {
|
|
60
|
-
const errorToPublish = (0, lodash_1.cloneDeep)(err);
|
|
61
61
|
await Promise.all(this.internalErrorHandlers.map((handler) => {
|
|
62
62
|
return new Promise(async (resolve) => {
|
|
63
63
|
const ctx = await this.makeContext();
|
|
64
64
|
try {
|
|
65
65
|
console.log(`调用internalErrorHandler【${handler.name}】处理内部错误事件`);
|
|
66
|
-
await handler.handle(ctx, type, message,
|
|
66
|
+
await handler.handle(ctx, type, message, err);
|
|
67
67
|
await ctx.commit();
|
|
68
68
|
}
|
|
69
69
|
catch (e) {
|
|
@@ -93,20 +93,24 @@ class AppLoader extends types_1.AppLoader {
|
|
|
93
93
|
return context;
|
|
94
94
|
}
|
|
95
95
|
/**
|
|
96
|
-
*
|
|
96
|
+
* 获取数据库配置
|
|
97
|
+
* @returns 读取数据库配置
|
|
97
98
|
*/
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
getDbConfig() {
|
|
100
|
+
return (0, dbPriority_1.getDbConfig)(this.path);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 获取同步配置
|
|
104
|
+
* @returns 读取同步配置
|
|
105
|
+
*/
|
|
106
|
+
getSyncConfig() {
|
|
100
107
|
const syncConfigFile = (0, path_1.join)(this.path, 'lib', 'configuration', 'sync.js');
|
|
101
108
|
const syncConfigs = (0, fs_1.existsSync)(syncConfigFile) && require(syncConfigFile).default;
|
|
102
|
-
return
|
|
103
|
-
dbConfig: dbConfig,
|
|
104
|
-
syncConfig: syncConfigs,
|
|
105
|
-
};
|
|
109
|
+
return syncConfigs;
|
|
106
110
|
}
|
|
107
111
|
constructor(path, nsSubscribe, nsSocket, nsServer) {
|
|
108
112
|
super(path);
|
|
109
|
-
const
|
|
113
|
+
const dbConfig = this.getDbConfig();
|
|
110
114
|
const { storageSchema } = require(`${path}/lib/oak-app-domain/Storage`);
|
|
111
115
|
const depGraph = (0, dependencyBuilder_1.analyzeDepedency)(process.cwd());
|
|
112
116
|
this.externalDependencies = depGraph.ascOrder;
|
|
@@ -174,7 +178,7 @@ class AppLoader extends types_1.AppLoader {
|
|
|
174
178
|
async mount(initialize) {
|
|
175
179
|
const { path } = this;
|
|
176
180
|
if (!initialize) {
|
|
177
|
-
const
|
|
181
|
+
const syncConfig = this.getSyncConfig();
|
|
178
182
|
if (syncConfig) {
|
|
179
183
|
this.synchronizer = new Synchronizer_1.default(syncConfig, this.dbStore.getSchema(), () => this.contextBuilder(this.dbStore));
|
|
180
184
|
}
|
|
@@ -183,7 +187,7 @@ class AppLoader extends types_1.AppLoader {
|
|
|
183
187
|
}
|
|
184
188
|
const { importations, exportations } = require(`${path}/lib/ports/index`);
|
|
185
189
|
(0, index_1.registerPorts)(importations || [], exportations || []);
|
|
186
|
-
this.dbStore.connect();
|
|
190
|
+
return this.dbStore.connect();
|
|
187
191
|
}
|
|
188
192
|
async unmount() {
|
|
189
193
|
if (this.watcherTimerId) {
|
|
@@ -369,46 +373,172 @@ class AppLoader extends types_1.AppLoader {
|
|
|
369
373
|
forUpdate,
|
|
370
374
|
});
|
|
371
375
|
}
|
|
376
|
+
/**
|
|
377
|
+
* 检查某个数据是否正在被watcher执行
|
|
378
|
+
* @param name watcher名称
|
|
379
|
+
* @param dataId 数据ID
|
|
380
|
+
* @returns 如果没有正在执行则返回true,否则返回false
|
|
381
|
+
*/
|
|
382
|
+
checkDataExecuting(name, dataId) {
|
|
383
|
+
let dataSet = this.watcherExecutingData.get(name);
|
|
384
|
+
if (!dataSet) {
|
|
385
|
+
dataSet = new Map();
|
|
386
|
+
this.watcherExecutingData.set(name, dataSet);
|
|
387
|
+
}
|
|
388
|
+
const existingTs = dataSet.get(dataId);
|
|
389
|
+
if (existingTs) {
|
|
390
|
+
const now = Date.now();
|
|
391
|
+
if (now - existingTs > 10 * 60 * 1000) {
|
|
392
|
+
console.error(`检测到执行器【${name}】的数据【${dataId}】标记超时(${Math.floor((now - existingTs) / 1000)}秒),请立即检查是否存在死循环或长时间阻塞的操作`);
|
|
393
|
+
}
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
dataSet.set(dataId, Date.now());
|
|
397
|
+
return true;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* 过滤出未在执行中的数据行,并标记为执行中
|
|
401
|
+
* @returns [过滤后的行, 是否有行被跳过]
|
|
402
|
+
*/
|
|
403
|
+
filterAndMarkExecutingRows(watcher, rows) {
|
|
404
|
+
if (watcher.exclusive !== true) {
|
|
405
|
+
// 不需要排他执行,直接返回所有行
|
|
406
|
+
return [rows, false];
|
|
407
|
+
}
|
|
408
|
+
const rowsWithoutExecuting = [];
|
|
409
|
+
let hasSkiped = false;
|
|
410
|
+
const watcherName = watcher.name;
|
|
411
|
+
for (const row of rows) {
|
|
412
|
+
if (!row.id) {
|
|
413
|
+
console.error(`实例【${process.env.OAK_INSTANCE_ID || '单机'}】执行器【${watcherName}】获取的数据没有ID,跳过此数据的并发检查处理:`, row);
|
|
414
|
+
rowsWithoutExecuting.push(row);
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
if (this.checkDataExecuting(watcherName, row.id)) {
|
|
418
|
+
rowsWithoutExecuting.push(row);
|
|
419
|
+
}
|
|
420
|
+
else {
|
|
421
|
+
hasSkiped = true;
|
|
422
|
+
console.warn(`实例【${process.env.OAK_INSTANCE_ID || '单机'}】执行器【${watcherName}】将跳过正在被执行的数据ID:【${row.id}】,请检查是否执行超时`);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return [rowsWithoutExecuting, hasSkiped];
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* 清理执行标记
|
|
429
|
+
*/
|
|
430
|
+
cleanupExecutingMarks(watcherName, rows) {
|
|
431
|
+
for (const row of rows) {
|
|
432
|
+
if (row.id) {
|
|
433
|
+
const dataSet = this.watcherExecutingData.get(watcherName);
|
|
434
|
+
if (dataSet) {
|
|
435
|
+
dataSet.delete(row.id);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* 解析 filter 和 projection(支持函数或静态值)
|
|
442
|
+
*/
|
|
443
|
+
async resolveFilterAndProjection(filter, projection) {
|
|
444
|
+
const filter2 = typeof filter === 'function' ? await filter() : (0, lodash_1.cloneDeep)(filter);
|
|
445
|
+
const projection2 = typeof projection === 'function' ? await projection() : (0, lodash_1.cloneDeep)(projection);
|
|
446
|
+
return [filter2, projection2];
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* 执行 WB 类型 watcher 的查询操作
|
|
450
|
+
*/
|
|
451
|
+
async selectForWBWatcher(watcher, context) {
|
|
452
|
+
const { entity, projection, filter, singleton, forUpdate } = watcher;
|
|
453
|
+
const [filter2, projection2] = await this.resolveFilterAndProjection(filter, projection);
|
|
454
|
+
return await this.selectInWatcher(entity, {
|
|
455
|
+
data: projection2,
|
|
456
|
+
filter: filter2,
|
|
457
|
+
}, context, forUpdate, singleton);
|
|
458
|
+
}
|
|
372
459
|
async execWatcher(watcher) {
|
|
373
|
-
const context = await this.makeContext();
|
|
374
460
|
let result;
|
|
375
|
-
|
|
376
|
-
|
|
461
|
+
// BBWatcher:直接操作,无需查询
|
|
462
|
+
if (watcher.hasOwnProperty('actionData')) {
|
|
463
|
+
// 如果配置了 exclusive,BBWatcher 不支持
|
|
464
|
+
if (watcher.exclusive === true) {
|
|
465
|
+
console.warn(`BBWatcher【${watcher.name}】配置了 exclusive=true,但 BBWatcher 不支持排他执行,将忽略此配置,请使用 WBWatcher 或 WBFreeWatcher 来实现排他执行`);
|
|
466
|
+
}
|
|
467
|
+
const context = await this.makeContext();
|
|
468
|
+
try {
|
|
377
469
|
const { entity, action, filter, actionData, singleton } = watcher;
|
|
378
470
|
const filter2 = typeof filter === 'function' ? await filter() : (0, lodash_1.cloneDeep)(filter);
|
|
379
|
-
const data = typeof actionData === 'function' ? await
|
|
471
|
+
const data = typeof actionData === 'function' ? await actionData() : (0, lodash_1.cloneDeep)(actionData);
|
|
380
472
|
result = await this.operateInWatcher(entity, {
|
|
381
473
|
id: await (0, uuid_1.generateNewIdAsync)(),
|
|
382
474
|
action: action,
|
|
383
475
|
data,
|
|
384
476
|
filter: filter2,
|
|
385
477
|
}, context, singleton);
|
|
478
|
+
await context.commit();
|
|
479
|
+
return result;
|
|
386
480
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
const projection2 = typeof projection === 'function' ? await projection() : (0, lodash_1.cloneDeep)(projection);
|
|
391
|
-
const rows = await this.selectInWatcher(entity, {
|
|
392
|
-
data: projection2,
|
|
393
|
-
filter: filter2,
|
|
394
|
-
}, context, forUpdate, singleton);
|
|
395
|
-
if (rows.length > 0) {
|
|
396
|
-
result = await fn(context, rows);
|
|
481
|
+
catch (err) {
|
|
482
|
+
if (err instanceof types_1.OakPartialSuccess) {
|
|
483
|
+
await context.commit();
|
|
397
484
|
}
|
|
485
|
+
else {
|
|
486
|
+
await context.rollback();
|
|
487
|
+
}
|
|
488
|
+
throw err;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// WBFreeWatcher 和 WBWatcher:查询后执行
|
|
492
|
+
const isFreeType = watcher.hasOwnProperty('type') &&
|
|
493
|
+
watcher.type === 'free';
|
|
494
|
+
// 1. 执行查询(WBFreeWatcher 使用独立 context)
|
|
495
|
+
const selectContext = isFreeType ? await this.makeContext() : await this.makeContext();
|
|
496
|
+
const rows = await this.selectForWBWatcher(watcher, selectContext);
|
|
497
|
+
if (isFreeType) {
|
|
498
|
+
await selectContext.commit();
|
|
499
|
+
}
|
|
500
|
+
// 2. 并发检查:过滤出未在执行中的数据
|
|
501
|
+
const [rowsWithoutExecuting, hasSkipped] = this.filterAndMarkExecutingRows(watcher, rows);
|
|
502
|
+
if (rowsWithoutExecuting.length === 0) {
|
|
503
|
+
if (!isFreeType) {
|
|
504
|
+
await selectContext.commit();
|
|
505
|
+
}
|
|
506
|
+
// 全部行都被跳过,直接返回,实际没有执行任何操作,hasSkipped也是其他执行流在处理
|
|
507
|
+
// this.cleanupExecutingMarks(watcher.name, hasSkipped);
|
|
508
|
+
if (hasSkipped) {
|
|
509
|
+
console.log(`执行器【${watcher.name}】本次没有可执行的数据行,全部数据行均被跳过`);
|
|
398
510
|
}
|
|
399
|
-
await context.commit();
|
|
400
511
|
return result;
|
|
401
512
|
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
513
|
+
// 3. 执行业务逻辑
|
|
514
|
+
try {
|
|
515
|
+
if (isFreeType) {
|
|
516
|
+
const { fn } = watcher;
|
|
517
|
+
result = await fn(() => this.makeContext(), rowsWithoutExecuting);
|
|
405
518
|
}
|
|
406
519
|
else {
|
|
407
|
-
|
|
520
|
+
const { fn } = watcher;
|
|
521
|
+
result = await fn(selectContext, rowsWithoutExecuting);
|
|
522
|
+
await selectContext.commit();
|
|
523
|
+
}
|
|
524
|
+
return result;
|
|
525
|
+
}
|
|
526
|
+
catch (err) {
|
|
527
|
+
if (!isFreeType) {
|
|
528
|
+
if (err instanceof types_1.OakPartialSuccess) {
|
|
529
|
+
await selectContext.commit();
|
|
530
|
+
}
|
|
531
|
+
else {
|
|
532
|
+
await selectContext.rollback();
|
|
533
|
+
}
|
|
408
534
|
}
|
|
409
|
-
// 不能在这里publish,因为这个方法可能是在timer中调用,也可能是在routine中调用
|
|
410
535
|
throw err;
|
|
411
536
|
}
|
|
537
|
+
finally {
|
|
538
|
+
// 清理执行标记
|
|
539
|
+
this.cleanupExecutingMarks(watcher.name, rowsWithoutExecuting);
|
|
540
|
+
// 这里只需要清理被执行的行,因为被跳过的行本来就不是这一次执行被占用的。
|
|
541
|
+
}
|
|
412
542
|
}
|
|
413
543
|
getCheckpointTs() {
|
|
414
544
|
const now = Date.now();
|
|
@@ -423,7 +553,13 @@ class AppLoader extends types_1.AppLoader {
|
|
|
423
553
|
const { watchers: adWatchers } = (0, IntrinsicLogics_1.makeIntrinsicLogics)(this.dbStore.getSchema(), ActionDefDict);
|
|
424
554
|
const totalWatchers = (watchers || []).concat(adWatchers);
|
|
425
555
|
let count = 0;
|
|
556
|
+
const skipOnceSet = new Set();
|
|
426
557
|
const execOne = async (watcher, start) => {
|
|
558
|
+
if (skipOnceSet.has(watcher.name)) {
|
|
559
|
+
skipOnceSet.delete(watcher.name);
|
|
560
|
+
console.log(`跳过本次执行watcher【${watcher.name}】`);
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
427
563
|
try {
|
|
428
564
|
const result = await this.execWatcher(watcher);
|
|
429
565
|
if (result) {
|
|
@@ -455,12 +591,22 @@ class AppLoader extends types_1.AppLoader {
|
|
|
455
591
|
}
|
|
456
592
|
this.watcherTimerId = setTimeout(() => doWatchers(), 120000);
|
|
457
593
|
};
|
|
594
|
+
// 首次执行时,跳过所有lazy的watcher
|
|
595
|
+
for (const w of totalWatchers) {
|
|
596
|
+
if (w.lazy) {
|
|
597
|
+
skipOnceSet.add(w.name);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
458
600
|
doWatchers();
|
|
459
601
|
}
|
|
460
|
-
|
|
602
|
+
execBaseTimer(timer, context) {
|
|
461
603
|
const { timer: timerFn } = timer;
|
|
462
604
|
return timerFn(context);
|
|
463
605
|
}
|
|
606
|
+
execFreeTimer(timer, contextBuilder) {
|
|
607
|
+
const { timer: timerFn } = timer;
|
|
608
|
+
return timerFn(contextBuilder);
|
|
609
|
+
}
|
|
464
610
|
startTimers() {
|
|
465
611
|
const timers = this.requireSth('lib/timers/index');
|
|
466
612
|
if (timers) {
|
|
@@ -480,9 +626,20 @@ class AppLoader extends types_1.AppLoader {
|
|
|
480
626
|
}
|
|
481
627
|
}
|
|
482
628
|
else {
|
|
629
|
+
if (timer.hasOwnProperty('type') && timer.type === 'free') {
|
|
630
|
+
try {
|
|
631
|
+
const result = await this.execFreeTimer(timer, () => this.makeContext());
|
|
632
|
+
console.log(`定时器【${name}】执行成功,耗时${Date.now() - start}毫秒,结果是`, result);
|
|
633
|
+
}
|
|
634
|
+
catch (err) {
|
|
635
|
+
console.error(`定时器【${name}】执行失败,耗时${Date.now() - start}毫秒,错误是`, err);
|
|
636
|
+
this.publishInternalError(`timer`, `定时器【${name}】执行失败`, err);
|
|
637
|
+
}
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
483
640
|
const context = await this.makeContext();
|
|
484
641
|
try {
|
|
485
|
-
const result = await this.
|
|
642
|
+
const result = await this.execBaseTimer(timer, context);
|
|
486
643
|
if (result) {
|
|
487
644
|
console.log(`定时器【${name}】执行成功,耗时${Date.now() - start}毫秒,结果是`, result);
|
|
488
645
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
|
2
|
-
import { EntityDict, OperationResult, Trigger, Watcher, FreeTimer } from 'oak-domain/lib/types';
|
|
2
|
+
import { EntityDict, OperationResult, Trigger, Watcher, FreeTimer, BaseTimer } from 'oak-domain/lib/types';
|
|
3
3
|
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
|
|
4
4
|
import { AppLoader } from './AppLoader';
|
|
5
5
|
import { Namespace } from 'socket.io';
|
|
@@ -15,6 +15,7 @@ export declare class ClusterAppLoader<ED extends EntityDict & BaseEntityDict, Cx
|
|
|
15
15
|
protected operateInWatcher<T extends keyof ED>(entity: T, operation: ED[T]['Update'], context: Cxt, singleton?: true): Promise<OperationResult<ED>>;
|
|
16
16
|
protected selectInWatcher<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: Cxt, forUpdate?: true, singleton?: true): Promise<Partial<ED[T]['Schema']>[]>;
|
|
17
17
|
protected execWatcher(watcher: Watcher<ED, keyof ED, Cxt>): Promise<OperationResult<ED> | undefined>;
|
|
18
|
-
protected
|
|
18
|
+
protected execBaseTimer(timer: BaseTimer<ED, Cxt>, context: Cxt): Promise<OperationResult<ED>> | undefined;
|
|
19
|
+
protected execFreeTimer(timer: FreeTimer<ED, Cxt>, contextBuilder: () => Promise<Cxt>): Promise<OperationResult<ED>> | undefined;
|
|
19
20
|
protected checkpoint(): Promise<number>;
|
|
20
21
|
}
|
package/lib/ClusterAppLoader.js
CHANGED
|
@@ -168,11 +168,17 @@ class ClusterAppLoader extends AppLoader_1.AppLoader {
|
|
|
168
168
|
}
|
|
169
169
|
return super.execWatcher(watcher);
|
|
170
170
|
}
|
|
171
|
-
|
|
171
|
+
execBaseTimer(timer, context) {
|
|
172
172
|
if (timer.singleton && (0, env_1.getClusterInfo)().instanceId !== 0) {
|
|
173
173
|
return;
|
|
174
174
|
}
|
|
175
|
-
return super.
|
|
175
|
+
return super.execBaseTimer(timer, context);
|
|
176
|
+
}
|
|
177
|
+
execFreeTimer(timer, contextBuilder) {
|
|
178
|
+
if (timer.singleton && (0, env_1.getClusterInfo)().instanceId !== 0) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
return super.execFreeTimer(timer, contextBuilder);
|
|
176
182
|
}
|
|
177
183
|
async checkpoint() {
|
|
178
184
|
const { instanceCount, instanceId } = (0, env_1.getClusterInfo)();
|
package/lib/DbStore.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { DbConfiguration } from 'oak-db/src/types/configuration';
|
|
|
2
2
|
import { EntityDict, StorageSchema, Trigger, Checker, SelectFreeEntities, UpdateFreeDict, AuthDeduceRelationMap, VolatileTrigger, OperateOption } from 'oak-domain/lib/types';
|
|
3
3
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
|
4
4
|
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
|
|
5
|
+
import { DbTypeSymbol } from './utils/dbPriority';
|
|
5
6
|
import { CascadeStore } from 'oak-domain/lib/store/CascadeStore';
|
|
6
7
|
import { DbStore } from 'oak-db/lib/types/dbStore';
|
|
7
8
|
export type TriggerStore<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> = {
|
|
@@ -13,4 +14,6 @@ export type TriggerStore<ED extends EntityDict & BaseEntityDict, Cxt extends Bac
|
|
|
13
14
|
independentCheckPoint(name: string, ts: number, instanceCount?: number, instanceId?: number): Promise<number>;
|
|
14
15
|
};
|
|
15
16
|
export type AppDbStore<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> = DbStore<ED, Cxt> & CascadeStore<ED> & TriggerStore<ED, Cxt>;
|
|
16
|
-
export declare const createDbStore: <ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>>(storageSchema: StorageSchema<ED>, contextBuilder: () => Cxt, dbConfiguration: DbConfiguration
|
|
17
|
+
export declare const createDbStore: <ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>>(storageSchema: StorageSchema<ED>, contextBuilder: () => Cxt, dbConfiguration: DbConfiguration & {
|
|
18
|
+
[DbTypeSymbol]?: string;
|
|
19
|
+
}, authDeduceRelationMap: AuthDeduceRelationMap<ED>, selectFreeEntities?: SelectFreeEntities<ED>, updateFreeDict?: UpdateFreeDict<ED>, onVolatileTrigger?: <T extends keyof ED>(entity: T, trigger: VolatileTrigger<ED, T, Cxt>, ids: string[], cxtStr: string, option: OperateOption) => Promise<void>) => AppDbStore<ED, Cxt>;
|
package/lib/DbStore.js
CHANGED
|
@@ -5,8 +5,10 @@ const TriggerExecutor_1 = require("oak-domain/lib/store/TriggerExecutor");
|
|
|
5
5
|
const RelationAuth_1 = require("oak-domain/lib/store/RelationAuth");
|
|
6
6
|
const dbPriority_1 = require("./utils/dbPriority");
|
|
7
7
|
const createDbStore = (storageSchema, contextBuilder, dbConfiguration, authDeduceRelationMap, selectFreeEntities = [], updateFreeDict = {}, onVolatileTrigger) => {
|
|
8
|
-
|
|
8
|
+
// TODO: 这里的类型检查会过不去,因为ts不知道上层已经实现这个抽象类。
|
|
9
|
+
const BaseStoreClass = (0, dbPriority_1.getDbStoreClass)(dbConfiguration);
|
|
9
10
|
// 动态创建继承类
|
|
11
|
+
// @ts-ignore
|
|
10
12
|
class DynamicDbStore extends BaseStoreClass {
|
|
11
13
|
executor;
|
|
12
14
|
relationAuth;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { MysqlStore, PostgreSQLStore } from "oak-db";
|
|
2
2
|
import { DbConfiguration } from "oak-db/src/types/configuration";
|
|
3
|
-
import { AsyncRowStore } from "oak-domain/lib/store/AsyncRowStore";
|
|
4
3
|
import { EntityDict, StorageSchema } from 'oak-domain/lib/types';
|
|
5
4
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
|
6
5
|
import { BackendRuntimeContext } from 'oak-frontend-base/lib/context/BackendRuntimeContext';
|
|
7
6
|
import { CascadeStore } from "oak-domain/lib/store/CascadeStore";
|
|
7
|
+
import { DbStore } from "oak-db/lib/types/dbStore";
|
|
8
8
|
/**
|
|
9
9
|
* 数据库优先级列表,按顺序尝试获取配置文件
|
|
10
10
|
*/
|
|
@@ -12,5 +12,10 @@ export declare const dbList: {
|
|
|
12
12
|
mysql: typeof MysqlStore;
|
|
13
13
|
postgres: typeof PostgreSQLStore;
|
|
14
14
|
};
|
|
15
|
-
export declare const
|
|
16
|
-
export declare const
|
|
15
|
+
export declare const DbTypeSymbol: unique symbol;
|
|
16
|
+
export declare const getDbConfig: (path: string) => DbConfiguration & {
|
|
17
|
+
[DbTypeSymbol]: string;
|
|
18
|
+
};
|
|
19
|
+
export declare const getDbStoreClass: <ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>>(config: {
|
|
20
|
+
[DbTypeSymbol]?: string;
|
|
21
|
+
}) => new (schema: StorageSchema<ED>, config: DbConfiguration) => DbStore<ED, Cxt> & CascadeStore<ED>;
|
package/lib/utils/dbPriority.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getDbStoreClass = exports.getDbConfig = exports.dbList = void 0;
|
|
3
|
+
exports.getDbStoreClass = exports.getDbConfig = exports.DbTypeSymbol = exports.dbList = void 0;
|
|
4
4
|
const oak_db_1 = require("oak-db");
|
|
5
5
|
const path_1 = require("path");
|
|
6
6
|
const fs_1 = require("fs");
|
|
@@ -11,7 +11,7 @@ exports.dbList = {
|
|
|
11
11
|
mysql: oak_db_1.MysqlStore,
|
|
12
12
|
postgres: oak_db_1.PostgreSQLStore
|
|
13
13
|
};
|
|
14
|
-
|
|
14
|
+
exports.DbTypeSymbol = Symbol.for('oak:backend:db:type');
|
|
15
15
|
const getDbConfig = (path) => {
|
|
16
16
|
for (const db of Object.keys(exports.dbList)) {
|
|
17
17
|
try {
|
|
@@ -25,8 +25,14 @@ const getDbConfig = (path) => {
|
|
|
25
25
|
}
|
|
26
26
|
const config = require(dbConfigFile);
|
|
27
27
|
console.log(`使用${db}作为数据库`);
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
// 定义不可枚举的属性,避免被序列化
|
|
29
|
+
Object.defineProperty(config, exports.DbTypeSymbol, {
|
|
30
|
+
value: db,
|
|
31
|
+
enumerable: false,
|
|
32
|
+
writable: false,
|
|
33
|
+
configurable: false
|
|
34
|
+
});
|
|
35
|
+
return config;
|
|
30
36
|
}
|
|
31
37
|
catch (err) {
|
|
32
38
|
// do nothing
|
|
@@ -35,10 +41,11 @@ const getDbConfig = (path) => {
|
|
|
35
41
|
throw new Error(`没有找到数据库配置文件,请在configuration目录下添加任一配置文件:${Object.keys(exports.dbList).map(ele => `${ele}.json`).join('、')}`);
|
|
36
42
|
};
|
|
37
43
|
exports.getDbConfig = getDbConfig;
|
|
38
|
-
const getDbStoreClass = () => {
|
|
39
|
-
const dbType =
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
const getDbStoreClass = (config) => {
|
|
45
|
+
const dbType = Object.getOwnPropertyDescriptor(config, exports.DbTypeSymbol)?.value;
|
|
46
|
+
if (!dbType) {
|
|
47
|
+
throw new Error('无法获取数据库类型,请确认是否存在数据库配置文件');
|
|
48
|
+
}
|
|
42
49
|
const DbStoreClass = exports.dbList[dbType.toLowerCase()];
|
|
43
50
|
if (!DbStoreClass) {
|
|
44
51
|
throw new Error(`不支持的数据库类型:${dbType},请确认是否存在以下配置文件:${Object.keys(exports.dbList).map(ele => `${ele}.json`).join('、')}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oak-backend-base",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.28",
|
|
4
4
|
"description": "oak-backend-base",
|
|
5
5
|
"main": "lib/index",
|
|
6
6
|
"author": {
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
"mysql2": "^2.3.3",
|
|
23
23
|
"node-schedule": "^2.1.0",
|
|
24
24
|
"oak-common-aspect": "^3.0.5",
|
|
25
|
-
"oak-db": "^3.3.
|
|
26
|
-
"oak-domain": "^5.1.
|
|
25
|
+
"oak-db": "^3.3.13",
|
|
26
|
+
"oak-domain": "^5.1.35",
|
|
27
27
|
"oak-frontend-base": "^5.3.45",
|
|
28
28
|
"socket.io": "^4.8.1",
|
|
29
29
|
"socket.io-client": "^4.7.2",
|