clusterAppLoader
This commit is contained in:
parent
d331802483
commit
296418e796
|
|
@ -1,24 +1,24 @@
|
||||||
/// <reference types="node" />
|
/// <reference types="node" />
|
||||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||||
import { AppLoader as GeneralAppLoader, EntityDict, OpRecord } from "oak-domain/lib/types";
|
import { AppLoader as GeneralAppLoader, Trigger, EntityDict, Watcher, OpRecord } from "oak-domain/lib/types";
|
||||||
import { DbStore } from "./DbStore";
|
import { DbStore } from "./DbStore";
|
||||||
import { BackendRuntimeContext } from 'oak-frontend-base';
|
import { BackendRuntimeContext } from 'oak-frontend-base';
|
||||||
import { IncomingHttpHeaders, IncomingMessage } from 'http';
|
import { IncomingHttpHeaders, IncomingMessage } from 'http';
|
||||||
import { Namespace } from 'socket.io';
|
import { Namespace } from 'socket.io';
|
||||||
import { ClusterInfo } from 'oak-domain/lib/types/Cluster';
|
|
||||||
export declare class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends GeneralAppLoader<ED, Cxt> {
|
export declare class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends GeneralAppLoader<ED, Cxt> {
|
||||||
private dbStore;
|
protected dbStore: DbStore<ED, Cxt>;
|
||||||
private aspectDict;
|
private aspectDict;
|
||||||
private externalDependencies;
|
private externalDependencies;
|
||||||
private dataSubscriber?;
|
private dataSubscriber?;
|
||||||
private contextBuilder;
|
protected contextBuilder: (scene?: string) => (store: DbStore<ED, Cxt>) => Promise<Cxt>;
|
||||||
private requireSth;
|
private requireSth;
|
||||||
constructor(path: string, contextBuilder: (scene?: string) => (store: DbStore<ED, Cxt>, header?: IncomingHttpHeaders, clusterInfo?: ClusterInfo) => Promise<Cxt>, ns?: Namespace);
|
private makeContext;
|
||||||
|
constructor(path: string, contextBuilder: (scene?: string) => (store: DbStore<ED, Cxt>) => Promise<Cxt>, ns?: Namespace);
|
||||||
|
protected registerTrigger(trigger: Trigger<ED, keyof ED, Cxt>): void;
|
||||||
initTriggers(): void;
|
initTriggers(): void;
|
||||||
startWatchers(): void;
|
|
||||||
mount(initialize?: true): Promise<void>;
|
mount(initialize?: true): Promise<void>;
|
||||||
unmount(): Promise<void>;
|
unmount(): Promise<void>;
|
||||||
execAspect(name: string, header?: IncomingHttpHeaders, contextString?: string, params?: any): Promise<{
|
execAspect(name: string, headers?: IncomingHttpHeaders, contextString?: string, params?: any): Promise<{
|
||||||
opRecords: OpRecord<ED>[];
|
opRecords: OpRecord<ED>[];
|
||||||
result: any;
|
result: any;
|
||||||
message?: string;
|
message?: string;
|
||||||
|
|
@ -26,6 +26,10 @@ export declare class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt exten
|
||||||
initialize(dropIfExists?: boolean): Promise<void>;
|
initialize(dropIfExists?: boolean): Promise<void>;
|
||||||
getStore(): DbStore<ED, Cxt>;
|
getStore(): DbStore<ED, Cxt>;
|
||||||
getEndpoints(prefix: string): [string, "get" | "post" | "put" | "delete", string, (params: Record<string, string>, headers: IncomingHttpHeaders, req: IncomingMessage, body?: any) => Promise<any>][];
|
getEndpoints(prefix: string): [string, "get" | "post" | "put" | "delete", string, (params: Record<string, string>, headers: IncomingHttpHeaders, req: IncomingMessage, body?: any) => Promise<any>][];
|
||||||
|
protected operateInWatcher<T extends keyof ED>(entity: T, operation: ED[T]['Update'], context: Cxt): Promise<import("oak-domain/lib/types").OperationResult<ED>>;
|
||||||
|
protected selectInWatcher<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: Cxt): Promise<Partial<ED[T]["Schema"]>[]>;
|
||||||
|
protected execWatcher(watcher: Watcher<ED, keyof ED, Cxt>): Promise<void>;
|
||||||
|
startWatchers(): void;
|
||||||
startTimers(): void;
|
startTimers(): void;
|
||||||
execStartRoutines(): Promise<void>;
|
execStartRoutines(): Promise<void>;
|
||||||
execRoutine(routine: (context: Cxt) => Promise<void>): Promise<void>;
|
execRoutine(routine: (context: Cxt) => Promise<void>): Promise<void>;
|
||||||
|
|
|
||||||
201
lib/AppLoader.js
201
lib/AppLoader.js
|
|
@ -86,6 +86,12 @@ class AppLoader extends types_1.AppLoader {
|
||||||
Object.assign(sthOut, sth);
|
Object.assign(sthOut, sth);
|
||||||
return sthOut;
|
return sthOut;
|
||||||
}
|
}
|
||||||
|
async makeContext(cxtStr, headers) {
|
||||||
|
const context = await this.contextBuilder(cxtStr)(this.dbStore);
|
||||||
|
context.clusterInfo = (0, env_2.getClusterInfo)();
|
||||||
|
context.headers = headers;
|
||||||
|
return context;
|
||||||
|
}
|
||||||
constructor(path, contextBuilder, ns) {
|
constructor(path, contextBuilder, ns) {
|
||||||
super(path);
|
super(path);
|
||||||
const dbConfig = require((0, path_1.join)(path, '/configuration/mysql.json'));
|
const dbConfig = require((0, path_1.join)(path, '/configuration/mysql.json'));
|
||||||
|
|
@ -96,8 +102,8 @@ class AppLoader extends types_1.AppLoader {
|
||||||
this.dbStore = new DbStore_1.DbStore(storageSchema, contextBuilder, dbConfig, authDeduceRelationMap, selectFreeEntities, updateFreeDict);
|
this.dbStore = new DbStore_1.DbStore(storageSchema, contextBuilder, dbConfig, authDeduceRelationMap, selectFreeEntities, updateFreeDict);
|
||||||
if (ns) {
|
if (ns) {
|
||||||
this.dataSubscriber = new DataSubscriber_1.default(ns, (scene) => this.contextBuilder(scene)(this.dbStore));
|
this.dataSubscriber = new DataSubscriber_1.default(ns, (scene) => this.contextBuilder(scene)(this.dbStore));
|
||||||
this.contextBuilder = (scene) => async (store, header, clusterInfo) => {
|
this.contextBuilder = (scene) => async (store) => {
|
||||||
const context = await contextBuilder(scene)(store, header, clusterInfo);
|
const context = await contextBuilder(scene)(store);
|
||||||
// 注入在提交前向dataSubscribe
|
// 注入在提交前向dataSubscribe
|
||||||
const originCommit = context.commit;
|
const originCommit = context.commit;
|
||||||
context.commit = async () => {
|
context.commit = async () => {
|
||||||
|
|
@ -117,79 +123,19 @@ class AppLoader extends types_1.AppLoader {
|
||||||
this.contextBuilder = contextBuilder;
|
this.contextBuilder = contextBuilder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
registerTrigger(trigger) {
|
||||||
|
this.dbStore.registerTrigger(trigger);
|
||||||
|
}
|
||||||
initTriggers() {
|
initTriggers() {
|
||||||
const triggers = this.requireSth('lib/triggers/index');
|
const triggers = this.requireSth('lib/triggers/index');
|
||||||
const checkers = this.requireSth('lib/checkers/index');
|
const checkers = this.requireSth('lib/checkers/index');
|
||||||
const { ActionDefDict } = require(`${this.path}/lib/oak-app-domain/ActionDefDict`);
|
const { ActionDefDict } = require(`${this.path}/lib/oak-app-domain/ActionDefDict`);
|
||||||
const { triggers: adTriggers, checkers: adCheckers } = (0, actionDef_1.makeIntrinsicCTWs)(this.dbStore.getSchema(), ActionDefDict);
|
const { triggers: adTriggers, checkers: adCheckers } = (0, actionDef_1.makeIntrinsicCTWs)(this.dbStore.getSchema(), ActionDefDict);
|
||||||
triggers.forEach((trigger) => this.dbStore.registerTrigger(trigger));
|
triggers.forEach((trigger) => this.registerTrigger(trigger));
|
||||||
adTriggers.forEach((trigger) => this.dbStore.registerTrigger(trigger));
|
adTriggers.forEach((trigger) => this.registerTrigger(trigger));
|
||||||
checkers.forEach((checker) => this.dbStore.registerChecker(checker));
|
checkers.forEach((checker) => this.dbStore.registerChecker(checker));
|
||||||
adCheckers.forEach((checker) => this.dbStore.registerChecker(checker));
|
adCheckers.forEach((checker) => this.dbStore.registerChecker(checker));
|
||||||
}
|
}
|
||||||
startWatchers() {
|
|
||||||
const watchers = this.requireSth('lib/watchers/index');
|
|
||||||
const { ActionDefDict } = require(`${this.path}/lib/oak-app-domain/ActionDefDict`);
|
|
||||||
const { watchers: adWatchers } = (0, actionDef_1.makeIntrinsicCTWs)(this.dbStore.getSchema(), ActionDefDict);
|
|
||||||
const totalWatchers = watchers.concat(adWatchers);
|
|
||||||
let count = 0;
|
|
||||||
const doWatchers = async () => {
|
|
||||||
count++;
|
|
||||||
const start = Date.now();
|
|
||||||
const context = await this.contextBuilder()(this.dbStore, undefined, (0, env_2.getClusterInfo)());
|
|
||||||
for (const w of totalWatchers) {
|
|
||||||
await context.begin();
|
|
||||||
try {
|
|
||||||
if (w.hasOwnProperty('actionData')) {
|
|
||||||
const { entity, action, filter, actionData } = w;
|
|
||||||
const filter2 = typeof filter === 'function' ? await filter() : filter;
|
|
||||||
const data = typeof actionData === 'function' ? await actionData() : actionData; // 这里有个奇怪的编译错误,不理解 by Xc
|
|
||||||
const result = await this.dbStore.operate(entity, {
|
|
||||||
id: await (0, uuid_1.generateNewIdAsync)(),
|
|
||||||
action,
|
|
||||||
data,
|
|
||||||
filter: filter2
|
|
||||||
}, context, {
|
|
||||||
dontCollect: true,
|
|
||||||
});
|
|
||||||
console.log(`执行了watcher【${w.name}】,结果是:`, result);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const { entity, projection, fn, filter } = w;
|
|
||||||
const filter2 = typeof filter === 'function' ? await filter() : filter;
|
|
||||||
const projection2 = typeof projection === 'function' ? await projection() : projection;
|
|
||||||
const rows = await this.dbStore.select(entity, {
|
|
||||||
data: projection2,
|
|
||||||
filter: filter2,
|
|
||||||
}, context, {
|
|
||||||
dontCollect: true,
|
|
||||||
blockTrigger: true,
|
|
||||||
});
|
|
||||||
if (rows.length > 0) {
|
|
||||||
const result = await fn(context, rows);
|
|
||||||
console.log(`执行了watcher【${w.name}】,结果是:`, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await context.commit();
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
await context.rollback();
|
|
||||||
console.error(`执行了watcher【${w.name}】,发生错误:`, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const duration = Date.now() - start;
|
|
||||||
console.log(`第${count}次执行watchers,共执行${watchers.length}个,耗时${duration}毫秒`);
|
|
||||||
const now = Date.now();
|
|
||||||
try {
|
|
||||||
await this.dbStore.checkpoint(process.env.NODE_ENV === 'development' ? now - 30 * 1000 : now - 120 * 1000);
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
console.error(`执行了checkpoint,发生错误:`, err);
|
|
||||||
}
|
|
||||||
setTimeout(() => doWatchers(), 120000);
|
|
||||||
};
|
|
||||||
doWatchers();
|
|
||||||
}
|
|
||||||
async mount(initialize) {
|
async mount(initialize) {
|
||||||
const { path } = this;
|
const { path } = this;
|
||||||
if (!initialize) {
|
if (!initialize) {
|
||||||
|
|
@ -203,8 +149,8 @@ class AppLoader extends types_1.AppLoader {
|
||||||
(0, index_1.clearPorts)();
|
(0, index_1.clearPorts)();
|
||||||
this.dbStore.disconnect();
|
this.dbStore.disconnect();
|
||||||
}
|
}
|
||||||
async execAspect(name, header, contextString, params) {
|
async execAspect(name, headers, contextString, params) {
|
||||||
const context = await this.contextBuilder(contextString)(this.dbStore, header, (0, env_2.getClusterInfo)());
|
const context = await this.makeContext(contextString, headers);
|
||||||
const fn = this.aspectDict[name];
|
const fn = this.aspectDict[name];
|
||||||
if (!fn) {
|
if (!fn) {
|
||||||
throw new Error(`不存在的接口名称: ${name}`);
|
throw new Error(`不存在的接口名称: ${name}`);
|
||||||
|
|
@ -278,7 +224,7 @@ class AppLoader extends types_1.AppLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
endPointRouters.push([name, method, url, async (params, headers, req, body) => {
|
endPointRouters.push([name, method, url, async (params, headers, req, body) => {
|
||||||
const context = await this.contextBuilder()(this.dbStore, headers, (0, env_2.getClusterInfo)());
|
const context = await this.makeContext(undefined, headers);
|
||||||
await context.begin();
|
await context.begin();
|
||||||
try {
|
try {
|
||||||
const result = await fn(context, params, headers, req, body);
|
const result = await fn(context, params, headers, req, body);
|
||||||
|
|
@ -303,18 +249,96 @@ class AppLoader extends types_1.AppLoader {
|
||||||
}
|
}
|
||||||
return endPointRouters;
|
return endPointRouters;
|
||||||
}
|
}
|
||||||
|
operateInWatcher(entity, operation, context) {
|
||||||
|
return this.dbStore.operate(entity, operation, context, {
|
||||||
|
dontCollect: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
selectInWatcher(entity, selection, context) {
|
||||||
|
return this.dbStore.select(entity, selection, context, {
|
||||||
|
dontCollect: true,
|
||||||
|
blockTrigger: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async execWatcher(watcher) {
|
||||||
|
const context = await this.makeContext();
|
||||||
|
await context.begin();
|
||||||
|
try {
|
||||||
|
if (watcher.hasOwnProperty('actionData')) {
|
||||||
|
const { entity, action, filter, actionData } = watcher;
|
||||||
|
const filter2 = typeof filter === 'function' ? await filter() : filter;
|
||||||
|
const data = typeof actionData === 'function' ? await (actionData)() : actionData;
|
||||||
|
const result = await this.operateInWatcher(entity, {
|
||||||
|
id: await (0, uuid_1.generateNewIdAsync)(),
|
||||||
|
action,
|
||||||
|
data,
|
||||||
|
filter: filter2
|
||||||
|
}, context);
|
||||||
|
console.log(`执行了watcher【${watcher.name}】,结果是:`, result);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const { entity, projection, fn, filter } = watcher;
|
||||||
|
const filter2 = typeof filter === 'function' ? await filter() : filter;
|
||||||
|
const projection2 = typeof projection === 'function' ? await projection() : projection;
|
||||||
|
const rows = await this.selectInWatcher(entity, {
|
||||||
|
data: projection2,
|
||||||
|
filter: filter2,
|
||||||
|
}, context);
|
||||||
|
if (rows.length > 0) {
|
||||||
|
const result = await fn(context, rows);
|
||||||
|
console.log(`执行了watcher【${watcher.name}】,结果是:`, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await context.commit();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
await context.rollback();
|
||||||
|
console.error(`执行了watcher【${watcher.name}】,发生错误:`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startWatchers() {
|
||||||
|
const watchers = this.requireSth('lib/watchers/index');
|
||||||
|
const { ActionDefDict } = require(`${this.path}/lib/oak-app-domain/ActionDefDict`);
|
||||||
|
const { watchers: adWatchers } = (0, actionDef_1.makeIntrinsicCTWs)(this.dbStore.getSchema(), ActionDefDict);
|
||||||
|
const totalWatchers = watchers.concat(adWatchers);
|
||||||
|
let count = 0;
|
||||||
|
const doWatchers = async () => {
|
||||||
|
count++;
|
||||||
|
const start = Date.now();
|
||||||
|
for (const w of totalWatchers) {
|
||||||
|
await this.execWatcher(w);
|
||||||
|
}
|
||||||
|
const duration = Date.now() - start;
|
||||||
|
console.log(`第${count}次执行watchers,共执行${watchers.length}个,耗时${duration}毫秒`);
|
||||||
|
const now = Date.now();
|
||||||
|
try {
|
||||||
|
await this.dbStore.checkpoint(process.env.NODE_ENV === 'development' ? now - 30 * 1000 : now - 120 * 1000);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(`执行了checkpoint,发生错误:`, err);
|
||||||
|
}
|
||||||
|
setTimeout(() => doWatchers(), 120000);
|
||||||
|
};
|
||||||
|
doWatchers();
|
||||||
|
}
|
||||||
startTimers() {
|
startTimers() {
|
||||||
const timers = this.requireSth('lib/timers/index');
|
const timers = this.requireSth('lib/timers/index');
|
||||||
for (const timer of timers) {
|
for (const timer of timers) {
|
||||||
const { cron, fn, name } = timer;
|
const { cron, name } = timer;
|
||||||
(0, node_schedule_1.scheduleJob)(name, cron, async (date) => {
|
(0, node_schedule_1.scheduleJob)(name, cron, async (date) => {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const context = await this.contextBuilder()(this.dbStore, undefined, (0, env_2.getClusterInfo)());
|
const context = await this.makeContext();
|
||||||
await context.begin();
|
await context.begin();
|
||||||
console.log(`定时器【${name}】开始执行,时间是【${date.toLocaleTimeString()}】`);
|
console.log(`定时器【${name}】开始执行,时间是【${date.toLocaleTimeString()}】`);
|
||||||
try {
|
try {
|
||||||
const result = await fn(context);
|
if (timer.hasOwnProperty('entity')) {
|
||||||
console.log(`定时器【${name}】执行完成,耗时${Date.now() - start}毫秒,结果是【${result}】`);
|
await this.execWatcher(timer);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const { timer: timerFn } = timer;
|
||||||
|
const result = await timerFn(context);
|
||||||
|
console.log(`定时器【${name}】执行完成,耗时${Date.now() - start}毫秒,结果是【${result}】`);
|
||||||
|
}
|
||||||
await context.commit();
|
await context.commit();
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
|
|
@ -327,23 +351,28 @@ class AppLoader extends types_1.AppLoader {
|
||||||
async execStartRoutines() {
|
async execStartRoutines() {
|
||||||
const routines = this.requireSth('lib/routines/start');
|
const routines = this.requireSth('lib/routines/start');
|
||||||
for (const routine of routines) {
|
for (const routine of routines) {
|
||||||
const { name, fn } = routine;
|
if (routine.hasOwnProperty('entity')) {
|
||||||
const context = await this.contextBuilder()(this.dbStore, undefined, (0, env_2.getClusterInfo)());
|
this.execWatcher(routine);
|
||||||
const start = Date.now();
|
|
||||||
await context.begin();
|
|
||||||
try {
|
|
||||||
const result = await fn(context);
|
|
||||||
console.log(`例程【${name}】执行完成,耗时${Date.now() - start}毫秒,结果是【${result}】`);
|
|
||||||
await context.commit();
|
|
||||||
}
|
}
|
||||||
catch (err) {
|
else {
|
||||||
console.warn(`例程【${name}】执行失败,耗时${Date.now() - start}毫秒,错误是`, err);
|
const { name, routine: routineFn } = routine;
|
||||||
await context.rollback();
|
const context = await this.makeContext();
|
||||||
|
const start = Date.now();
|
||||||
|
await context.begin();
|
||||||
|
try {
|
||||||
|
const result = await routineFn(context);
|
||||||
|
console.log(`例程【${name}】执行完成,耗时${Date.now() - start}毫秒,结果是【${result}】`);
|
||||||
|
await context.commit();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.warn(`例程【${name}】执行失败,耗时${Date.now() - start}毫秒,错误是`, err);
|
||||||
|
await context.rollback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async execRoutine(routine) {
|
async execRoutine(routine) {
|
||||||
const context = await this.contextBuilder()(this.dbStore, undefined, (0, env_2.getClusterInfo)());
|
const context = await this.makeContext();
|
||||||
await routine(context);
|
await routine(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||||
|
import { EntityDict, OperationResult } from 'oak-domain/lib/types';
|
||||||
|
import { BackendRuntimeContext } from 'oak-frontend-base';
|
||||||
|
import { AppLoader } from './AppLoader';
|
||||||
|
import { DbStore } from './DbStore';
|
||||||
|
import { Namespace } from 'socket.io';
|
||||||
|
export declare class ClusterAppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends AppLoader<ED, Cxt> {
|
||||||
|
constructor(path: string, contextBuilder: (scene?: string) => (store: DbStore<ED, Cxt>) => Promise<Cxt>, ns?: Namespace);
|
||||||
|
protected operateInWatcher<T extends keyof ED>(entity: T, operation: ED[T]['Update'], context: Cxt): Promise<OperationResult<ED>>;
|
||||||
|
protected selectInWatcher<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: Cxt): Promise<Partial<ED[T]['Schema']>[]>;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
|
exports.ClusterAppLoader = void 0;
|
||||||
|
const tslib_1 = require("tslib");
|
||||||
|
const filter_1 = require("oak-domain/lib/store/filter");
|
||||||
|
const env_1 = require("./cluster/env");
|
||||||
|
const AppLoader_1 = require("./AppLoader");
|
||||||
|
const assert_1 = tslib_1.__importDefault(require("assert"));
|
||||||
|
class ClusterAppLoader extends AppLoader_1.AppLoader {
|
||||||
|
constructor(path, contextBuilder, ns) {
|
||||||
|
super(path, contextBuilder, ns);
|
||||||
|
this.dbStore.setOnVolatileTrigger(async (entity, trigger, ids, cxtStr, option) => {
|
||||||
|
if (trigger.cs) {
|
||||||
|
// 如果是cluster sensative的触发器,需要发送到相应的instance上被处理
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const context = await this.contextBuilder(cxtStr)(this.dbStore);
|
||||||
|
await context.begin();
|
||||||
|
try {
|
||||||
|
await this.dbStore.execVolatileTrigger(entity, trigger.name, ids, context, option);
|
||||||
|
await context.commit();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
await context.rollback();
|
||||||
|
console.error('execVolatileTrigger异常', entity, trigger.name, ids, option, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
operateInWatcher(entity, operation, context) {
|
||||||
|
const { instanceCount, instanceId } = (0, env_1.getClusterInfo)();
|
||||||
|
(0, assert_1.default)(instanceCount && typeof instanceId === 'number');
|
||||||
|
const { filter } = operation;
|
||||||
|
const filter2 = (0, filter_1.combineFilters)(entity, this.dbStore.getSchema(), [filter, {
|
||||||
|
$$seq$$: {
|
||||||
|
$mod: [instanceCount, instanceId]
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
return super.operateInWatcher(entity, {
|
||||||
|
...operation,
|
||||||
|
filter: filter2,
|
||||||
|
}, context);
|
||||||
|
}
|
||||||
|
selectInWatcher(entity, selection, context) {
|
||||||
|
const { instanceCount, instanceId } = (0, env_1.getClusterInfo)();
|
||||||
|
(0, assert_1.default)(instanceCount && typeof instanceId === 'number');
|
||||||
|
const { filter } = selection;
|
||||||
|
const filter2 = (0, filter_1.combineFilters)(entity, this.dbStore.getSchema(), [filter, {
|
||||||
|
$$seq$$: {
|
||||||
|
$mod: [instanceCount, instanceId]
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
return super.selectInWatcher(entity, {
|
||||||
|
...selection,
|
||||||
|
filter: filter2,
|
||||||
|
}, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exports.ClusterAppLoader = ClusterAppLoader;
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { MysqlStore, MySqlSelectOption, MysqlOperateOption } from 'oak-db';
|
import { MysqlStore, MySqlSelectOption, MysqlOperateOption } from 'oak-db';
|
||||||
import { EntityDict, StorageSchema, Trigger, Checker, SelectOption, SelectFreeEntities, UpdateFreeDict, AuthDeduceRelationMap } from 'oak-domain/lib/types';
|
import { EntityDict, StorageSchema, Trigger, Checker, SelectOption, SelectFreeEntities, UpdateFreeDict, AuthDeduceRelationMap, VolatileTrigger, OperateOption } from 'oak-domain/lib/types';
|
||||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||||
import { MySQLConfiguration } from 'oak-db/lib/MySQL/types/Configuration';
|
import { MySQLConfiguration } from 'oak-db/lib/MySQL/types/Configuration';
|
||||||
import { BackendRuntimeContext } from 'oak-frontend-base';
|
import { BackendRuntimeContext } from 'oak-frontend-base';
|
||||||
|
|
@ -7,12 +7,14 @@ import { AsyncContext, AsyncRowStore } from 'oak-domain/lib/store/AsyncRowStore'
|
||||||
export declare class DbStore<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends MysqlStore<ED, Cxt> implements AsyncRowStore<ED, Cxt> {
|
export declare class DbStore<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends MysqlStore<ED, Cxt> implements AsyncRowStore<ED, Cxt> {
|
||||||
private executor;
|
private executor;
|
||||||
private relationAuth;
|
private relationAuth;
|
||||||
constructor(storageSchema: StorageSchema<ED>, contextBuilder: (scene?: string) => (store: DbStore<ED, Cxt>) => Promise<Cxt>, mysqlConfiguration: MySQLConfiguration, authDeduceRelationMap: AuthDeduceRelationMap<ED>, selectFreeEntities?: SelectFreeEntities<ED>, updateFreeDict?: UpdateFreeDict<ED>);
|
constructor(storageSchema: StorageSchema<ED>, contextBuilder: (scene?: string) => (store: DbStore<ED, Cxt>) => Promise<Cxt>, mysqlConfiguration: MySQLConfiguration, 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>);
|
||||||
protected cascadeUpdateAsync<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: AsyncContext<ED>, option: MysqlOperateOption): Promise<import("oak-domain/lib/types").OperationResult<ED>>;
|
protected cascadeUpdateAsync<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: AsyncContext<ED>, option: MysqlOperateOption): Promise<import("oak-domain/lib/types").OperationResult<ED>>;
|
||||||
operate<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: MysqlOperateOption): Promise<import("oak-domain/lib/types").OperationResult<ED>>;
|
operate<T extends keyof ED>(entity: T, operation: ED[T]['Operation'], context: Cxt, option: MysqlOperateOption): Promise<import("oak-domain/lib/types").OperationResult<ED>>;
|
||||||
select<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: MySqlSelectOption): Promise<Partial<ED[T]["Schema"]>[]>;
|
select<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: Cxt, option: MySqlSelectOption): Promise<Partial<ED[T]["Schema"]>[]>;
|
||||||
count<T extends keyof ED>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: Cxt, option: SelectOption): Promise<number>;
|
count<T extends keyof ED>(entity: T, selection: Pick<ED[T]['Selection'], 'filter' | 'count'>, context: Cxt, option: SelectOption): Promise<number>;
|
||||||
registerTrigger<T extends keyof ED>(trigger: Trigger<ED, T, Cxt>): void;
|
registerTrigger<T extends keyof ED>(trigger: Trigger<ED, T, Cxt>): void;
|
||||||
registerChecker<T extends keyof ED>(checker: Checker<ED, T, Cxt>): void;
|
registerChecker<T extends keyof ED>(checker: Checker<ED, T, Cxt>): void;
|
||||||
|
setOnVolatileTrigger(onVolatileTrigger: <T extends keyof ED>(entity: T, trigger: VolatileTrigger<ED, T, Cxt>, ids: string[], cxtStr: string, option: OperateOption) => Promise<void>): void;
|
||||||
|
execVolatileTrigger<T extends keyof ED>(entity: T, name: string, ids: string[], context: Cxt, option: OperateOption): Promise<void>;
|
||||||
checkpoint(ts: number): Promise<number>;
|
checkpoint(ts: number): Promise<number>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ const RelationAuth_1 = require("oak-domain/lib/store/RelationAuth");
|
||||||
class DbStore extends oak_db_1.MysqlStore {
|
class DbStore extends oak_db_1.MysqlStore {
|
||||||
executor;
|
executor;
|
||||||
relationAuth;
|
relationAuth;
|
||||||
constructor(storageSchema, contextBuilder, mysqlConfiguration, authDeduceRelationMap, selectFreeEntities = [], updateFreeDict = {}) {
|
constructor(storageSchema, contextBuilder, mysqlConfiguration, authDeduceRelationMap, selectFreeEntities = [], updateFreeDict = {}, onVolatileTrigger) {
|
||||||
super(storageSchema, mysqlConfiguration);
|
super(storageSchema, mysqlConfiguration);
|
||||||
this.executor = new TriggerExecutor_1.TriggerExecutor((scene) => contextBuilder(scene)(this));
|
this.executor = new TriggerExecutor_1.TriggerExecutor((scene) => contextBuilder(scene)(this), undefined, onVolatileTrigger);
|
||||||
this.relationAuth = new RelationAuth_1.RelationAuth(storageSchema, authDeduceRelationMap, selectFreeEntities, updateFreeDict);
|
this.relationAuth = new RelationAuth_1.RelationAuth(storageSchema, authDeduceRelationMap, selectFreeEntities, updateFreeDict);
|
||||||
}
|
}
|
||||||
async cascadeUpdateAsync(entity, operation, context, option) {
|
async cascadeUpdateAsync(entity, operation, context, option) {
|
||||||
|
|
@ -109,6 +109,12 @@ class DbStore extends oak_db_1.MysqlStore {
|
||||||
registerChecker(checker) {
|
registerChecker(checker) {
|
||||||
this.executor.registerChecker(checker);
|
this.executor.registerChecker(checker);
|
||||||
}
|
}
|
||||||
|
setOnVolatileTrigger(onVolatileTrigger) {
|
||||||
|
this.executor.setOnVolatileTrigger(onVolatileTrigger);
|
||||||
|
}
|
||||||
|
async execVolatileTrigger(entity, name, ids, context, option) {
|
||||||
|
return this.executor.execVolatileTrigger(entity, name, ids, context, option);
|
||||||
|
}
|
||||||
checkpoint(ts) {
|
checkpoint(ts) {
|
||||||
return this.executor.checkpoint(ts);
|
return this.executor.checkpoint(ts);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
export { AppLoader } from './AppLoader';
|
export { AppLoader } from './AppLoader';
|
||||||
|
export { ClusterAppLoader } from './ClusterAppLoader';
|
||||||
export * from './cluster/env';
|
export * from './cluster/env';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.AppLoader = void 0;
|
exports.ClusterAppLoader = exports.AppLoader = void 0;
|
||||||
const tslib_1 = require("tslib");
|
const tslib_1 = require("tslib");
|
||||||
var AppLoader_1 = require("./AppLoader");
|
var AppLoader_1 = require("./AppLoader");
|
||||||
Object.defineProperty(exports, "AppLoader", { enumerable: true, get: function () { return AppLoader_1.AppLoader; } });
|
Object.defineProperty(exports, "AppLoader", { enumerable: true, get: function () { return AppLoader_1.AppLoader; } });
|
||||||
|
var ClusterAppLoader_1 = require("./ClusterAppLoader");
|
||||||
|
Object.defineProperty(exports, "ClusterAppLoader", { enumerable: true, get: function () { return ClusterAppLoader_1.ClusterAppLoader; } });
|
||||||
tslib_1.__exportStar(require("./cluster/env"), exports);
|
tslib_1.__exportStar(require("./cluster/env"), exports);
|
||||||
|
|
|
||||||
242
src/AppLoader.ts
242
src/AppLoader.ts
|
|
@ -6,7 +6,7 @@ import { makeIntrinsicCTWs } from "oak-domain/lib/store/actionDef";
|
||||||
import { intersection, omit } from 'oak-domain/lib/utils/lodash';
|
import { intersection, omit } from 'oak-domain/lib/utils/lodash';
|
||||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||||
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
import { generateNewIdAsync } from 'oak-domain/lib/utils/uuid';
|
||||||
import { AppLoader as GeneralAppLoader, Trigger, Checker, Aspect, CreateOpResult, Context, EntityDict, Watcher, BBWatcher, WBWatcher, OpRecord } from "oak-domain/lib/types";
|
import { AppLoader as GeneralAppLoader, Trigger, Checker, Aspect, CreateOpResult, Context, EntityDict, Watcher, BBWatcher, WBWatcher, OpRecord, Routine, FreeRoutine, Timer, FreeTimer, StorageSchema } from "oak-domain/lib/types";
|
||||||
import { DbStore } from "./DbStore";
|
import { DbStore } from "./DbStore";
|
||||||
import generalAspectDict, { clearPorts, registerPorts } from 'oak-common-aspect/lib/index';
|
import generalAspectDict, { clearPorts, registerPorts } from 'oak-common-aspect/lib/index';
|
||||||
import { MySQLConfiguration } from 'oak-db/lib/MySQL/types/Configuration';
|
import { MySQLConfiguration } from 'oak-db/lib/MySQL/types/Configuration';
|
||||||
|
|
@ -17,16 +17,15 @@ import { IncomingHttpHeaders, IncomingMessage } from 'http';
|
||||||
import { Server as SocketIoServer, Namespace } from 'socket.io';
|
import { Server as SocketIoServer, Namespace } from 'socket.io';
|
||||||
|
|
||||||
import DataSubscriber from './cluster/DataSubscriber';
|
import DataSubscriber from './cluster/DataSubscriber';
|
||||||
import { ClusterInfo } from 'oak-domain/lib/types/Cluster';
|
|
||||||
import { getClusterInfo } from './cluster/env';
|
import { getClusterInfo } from './cluster/env';
|
||||||
|
|
||||||
|
|
||||||
export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends GeneralAppLoader<ED, Cxt> {
|
export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends GeneralAppLoader<ED, Cxt> {
|
||||||
private dbStore: DbStore<ED, Cxt>;
|
protected dbStore: DbStore<ED, Cxt>;
|
||||||
private aspectDict: Record<string, Aspect<ED, Cxt>>;
|
private aspectDict: Record<string, Aspect<ED, Cxt>>;
|
||||||
private externalDependencies: string[];
|
private externalDependencies: string[];
|
||||||
private dataSubscriber?: DataSubscriber<ED, Cxt>;
|
private dataSubscriber?: DataSubscriber<ED, Cxt>;
|
||||||
private contextBuilder: (scene?: string) => (store: DbStore<ED, Cxt>, header?: IncomingHttpHeaders, clusterInfo?: ClusterInfo) => Promise<Cxt>;
|
protected contextBuilder: (scene?: string) => (store: DbStore<ED, Cxt>) => Promise<Cxt>;
|
||||||
|
|
||||||
private requireSth(filePath: string): any {
|
private requireSth(filePath: string): any {
|
||||||
const depFilePath = join(this.path, filePath);
|
const depFilePath = join(this.path, filePath);
|
||||||
|
|
@ -110,7 +109,15 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
|
||||||
return sthOut;
|
return sthOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(path: string, contextBuilder: (scene?: string) => (store: DbStore<ED, Cxt>, header?: IncomingHttpHeaders, clusterInfo?: ClusterInfo) => Promise<Cxt>, ns?: Namespace) {
|
private async makeContext(cxtStr?: string, headers?: IncomingHttpHeaders) {
|
||||||
|
const context = await this.contextBuilder(cxtStr)(this.dbStore);
|
||||||
|
context.clusterInfo = getClusterInfo();
|
||||||
|
context.headers = headers;
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(path: string, contextBuilder: (scene?: string) => (store: DbStore<ED, Cxt>) => Promise<Cxt>, ns?: Namespace) {
|
||||||
super(path);
|
super(path);
|
||||||
const dbConfig: MySQLConfiguration = require(join(path, '/configuration/mysql.json'));
|
const dbConfig: MySQLConfiguration = require(join(path, '/configuration/mysql.json'));
|
||||||
const { storageSchema } = require(`${path}/lib/oak-app-domain/Storage`);
|
const { storageSchema } = require(`${path}/lib/oak-app-domain/Storage`);
|
||||||
|
|
@ -120,8 +127,8 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
|
||||||
this.dbStore = new DbStore<ED, Cxt>(storageSchema, contextBuilder, dbConfig, authDeduceRelationMap, selectFreeEntities, updateFreeDict);
|
this.dbStore = new DbStore<ED, Cxt>(storageSchema, contextBuilder, dbConfig, authDeduceRelationMap, selectFreeEntities, updateFreeDict);
|
||||||
if (ns) {
|
if (ns) {
|
||||||
this.dataSubscriber = new DataSubscriber(ns, (scene) => this.contextBuilder(scene)(this.dbStore));
|
this.dataSubscriber = new DataSubscriber(ns, (scene) => this.contextBuilder(scene)(this.dbStore));
|
||||||
this.contextBuilder = (scene) => async (store, header, clusterInfo) => {
|
this.contextBuilder = (scene) => async (store) => {
|
||||||
const context = await contextBuilder(scene)(store, header, clusterInfo);
|
const context = await contextBuilder(scene)(store);
|
||||||
|
|
||||||
// 注入在提交前向dataSubscribe
|
// 注入在提交前向dataSubscribe
|
||||||
const originCommit = context.commit;
|
const originCommit = context.commit;
|
||||||
|
|
@ -150,96 +157,34 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected registerTrigger(trigger: Trigger<ED, keyof ED, Cxt>) {
|
||||||
|
this.dbStore.registerTrigger(trigger);
|
||||||
|
}
|
||||||
|
|
||||||
initTriggers() {
|
initTriggers() {
|
||||||
const triggers = this.requireSth('lib/triggers/index');
|
const triggers = this.requireSth('lib/triggers/index');
|
||||||
const checkers = this.requireSth('lib/checkers/index');
|
const checkers = this.requireSth('lib/checkers/index');
|
||||||
const { ActionDefDict } = require(`${this.path}/lib/oak-app-domain/ActionDefDict`);
|
const { ActionDefDict } = require(`${this.path}/lib/oak-app-domain/ActionDefDict`);
|
||||||
|
|
||||||
const { triggers: adTriggers, checkers: adCheckers } = makeIntrinsicCTWs(this.dbStore.getSchema(), ActionDefDict);
|
const { triggers: adTriggers, checkers: adCheckers } = makeIntrinsicCTWs(this.dbStore.getSchema(), ActionDefDict);
|
||||||
|
|
||||||
triggers.forEach(
|
triggers.forEach(
|
||||||
(trigger: Trigger<ED, keyof ED, Cxt>) => this.dbStore.registerTrigger(trigger)
|
(trigger: Trigger<ED, keyof ED, Cxt>) => this.registerTrigger(trigger)
|
||||||
);
|
);
|
||||||
|
|
||||||
adTriggers.forEach(
|
adTriggers.forEach(
|
||||||
(trigger) => this.dbStore.registerTrigger(trigger)
|
(trigger) => this.registerTrigger(trigger)
|
||||||
);
|
);
|
||||||
|
|
||||||
checkers.forEach(
|
checkers.forEach(
|
||||||
(checker: Checker<ED, keyof ED, Cxt>) => this.dbStore.registerChecker(checker)
|
(checker: Checker<ED, keyof ED, Cxt>) => this.dbStore.registerChecker(checker)
|
||||||
);
|
);
|
||||||
|
|
||||||
adCheckers.forEach(
|
adCheckers.forEach(
|
||||||
(checker) => this.dbStore.registerChecker(checker)
|
(checker) => this.dbStore.registerChecker(checker)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
startWatchers() {
|
|
||||||
const watchers = this.requireSth('lib/watchers/index');
|
|
||||||
const { ActionDefDict } = require(`${this.path}/lib/oak-app-domain/ActionDefDict`);
|
|
||||||
|
|
||||||
const { watchers: adWatchers } = makeIntrinsicCTWs(this.dbStore.getSchema(), ActionDefDict);
|
|
||||||
const totalWatchers = (<Watcher<ED, keyof ED, Cxt>[]>watchers).concat(adWatchers);
|
|
||||||
|
|
||||||
let count = 0;
|
|
||||||
const doWatchers = async () => {
|
|
||||||
count++;
|
|
||||||
const start = Date.now();
|
|
||||||
const context = await this.contextBuilder()(this.dbStore, undefined, getClusterInfo());
|
|
||||||
for (const w of totalWatchers) {
|
|
||||||
await context.begin();
|
|
||||||
try {
|
|
||||||
if (w.hasOwnProperty('actionData')) {
|
|
||||||
const { entity, action, filter, actionData } = <BBWatcher<ED, keyof ED>>w;
|
|
||||||
const filter2 = typeof filter === 'function' ? await filter() : filter;
|
|
||||||
const data = typeof actionData === 'function' ? await (actionData as any)() : actionData; // 这里有个奇怪的编译错误,不理解 by Xc
|
|
||||||
const result = await this.dbStore.operate(entity, {
|
|
||||||
id: await generateNewIdAsync(),
|
|
||||||
action,
|
|
||||||
data,
|
|
||||||
filter: filter2
|
|
||||||
}, context, {
|
|
||||||
dontCollect: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`执行了watcher【${w.name}】,结果是:`, result);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const { entity, projection, fn, filter } = <WBWatcher<ED, keyof ED, Cxt>>w;
|
|
||||||
const filter2 = typeof filter === 'function' ? await filter() : filter;
|
|
||||||
const projection2 = typeof projection === 'function' ? await (projection as Function)() : projection;
|
|
||||||
const rows = await this.dbStore.select(entity, {
|
|
||||||
data: projection2 as any,
|
|
||||||
filter: filter2,
|
|
||||||
}, context, {
|
|
||||||
dontCollect: true,
|
|
||||||
blockTrigger: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (rows.length > 0) {
|
|
||||||
const result = await fn(context, rows);
|
|
||||||
console.log(`执行了watcher【${w.name}】,结果是:`, result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await context.commit();
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
await context.rollback();
|
|
||||||
console.error(`执行了watcher【${w.name}】,发生错误:`, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const duration = Date.now() - start;
|
|
||||||
console.log(`第${count}次执行watchers,共执行${watchers.length}个,耗时${duration}毫秒`);
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
try {
|
|
||||||
await this.dbStore.checkpoint(process.env.NODE_ENV === 'development' ? now - 30 * 1000 : now - 120 * 1000);
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
console.error(`执行了checkpoint,发生错误:`, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(() => doWatchers(), 120000);
|
|
||||||
};
|
|
||||||
doWatchers();
|
|
||||||
}
|
|
||||||
|
|
||||||
async mount(initialize?: true) {
|
async mount(initialize?: true) {
|
||||||
const { path } = this;
|
const { path } = this;
|
||||||
if (!initialize) {
|
if (!initialize) {
|
||||||
|
|
@ -255,12 +200,13 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
|
||||||
this.dbStore.disconnect();
|
this.dbStore.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
async execAspect(name: string, header?: IncomingHttpHeaders, contextString?: string, params?: any): Promise<{
|
async execAspect(name: string, headers?: IncomingHttpHeaders, contextString?: string, params?: any): Promise<{
|
||||||
opRecords: OpRecord<ED>[];
|
opRecords: OpRecord<ED>[];
|
||||||
result: any;
|
result: any;
|
||||||
message?: string;
|
message?: string;
|
||||||
}> {
|
}> {
|
||||||
const context = await this.contextBuilder(contextString)(this.dbStore, header, getClusterInfo());
|
const context = await this.makeContext(contextString, headers);
|
||||||
|
|
||||||
const fn = this.aspectDict[name];
|
const fn = this.aspectDict[name];
|
||||||
if (!fn) {
|
if (!fn) {
|
||||||
throw new Error(`不存在的接口名称: ${name}`);
|
throw new Error(`不存在的接口名称: ${name}`);
|
||||||
|
|
@ -340,7 +286,8 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
|
||||||
}
|
}
|
||||||
endPointRouters.push(
|
endPointRouters.push(
|
||||||
[name, method, url, async (params, headers, req, body) => {
|
[name, method, url, async (params, headers, req, body) => {
|
||||||
const context = await this.contextBuilder()(this.dbStore, headers, getClusterInfo());
|
const context = await this.makeContext(undefined, headers);
|
||||||
|
|
||||||
await context.begin();
|
await context.begin();
|
||||||
try {
|
try {
|
||||||
const result = await fn(context, params, headers, req, body);
|
const result = await fn(context, params, headers, req, body);
|
||||||
|
|
@ -369,18 +316,106 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
|
||||||
return endPointRouters;
|
return endPointRouters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected operateInWatcher<T extends keyof ED>(entity: T, operation: ED[T]['Update'], context: Cxt) {
|
||||||
|
return this.dbStore.operate(entity, operation, context, {
|
||||||
|
dontCollect: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected selectInWatcher<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: Cxt) {
|
||||||
|
return this.dbStore.select(entity, selection, context, {
|
||||||
|
dontCollect: true,
|
||||||
|
blockTrigger: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async execWatcher(watcher: Watcher<ED, keyof ED, Cxt>) {
|
||||||
|
const context = await this.makeContext();
|
||||||
|
await context.begin();
|
||||||
|
try {
|
||||||
|
if (watcher.hasOwnProperty('actionData')) {
|
||||||
|
const { entity, action, filter, actionData } = <BBWatcher<ED, keyof ED>>watcher;
|
||||||
|
const filter2 = typeof filter === 'function' ? await filter() : filter;
|
||||||
|
const data = typeof actionData === 'function' ? await (actionData)() : actionData;
|
||||||
|
const result = await this.operateInWatcher(entity, {
|
||||||
|
id: await generateNewIdAsync(),
|
||||||
|
action,
|
||||||
|
data,
|
||||||
|
filter: filter2
|
||||||
|
}, context);
|
||||||
|
|
||||||
|
console.log(`执行了watcher【${watcher.name}】,结果是:`, result);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const { entity, projection, fn, filter } = <WBWatcher<ED, keyof ED, Cxt>>watcher;
|
||||||
|
const filter2 = typeof filter === 'function' ? await filter() : filter;
|
||||||
|
const projection2 = typeof projection === 'function' ? await projection () : projection;
|
||||||
|
const rows = await this.selectInWatcher(entity, {
|
||||||
|
data: projection2,
|
||||||
|
filter: filter2,
|
||||||
|
}, context);
|
||||||
|
|
||||||
|
if (rows.length > 0) {
|
||||||
|
const result = await fn(context, rows);
|
||||||
|
console.log(`执行了watcher【${watcher.name}】,结果是:`, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await context.commit();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
await context.rollback();
|
||||||
|
console.error(`执行了watcher【${watcher.name}】,发生错误:`, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
startWatchers() {
|
||||||
|
const watchers = this.requireSth('lib/watchers/index');
|
||||||
|
const { ActionDefDict } = require(`${this.path}/lib/oak-app-domain/ActionDefDict`);
|
||||||
|
|
||||||
|
const { watchers: adWatchers } = makeIntrinsicCTWs(this.dbStore.getSchema(), ActionDefDict);
|
||||||
|
const totalWatchers = (<Watcher<ED, keyof ED, Cxt>[]>watchers).concat(adWatchers);
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
const doWatchers = async () => {
|
||||||
|
count++;
|
||||||
|
const start = Date.now();
|
||||||
|
for (const w of totalWatchers) {
|
||||||
|
await this.execWatcher(w);
|
||||||
|
}
|
||||||
|
const duration = Date.now() - start;
|
||||||
|
console.log(`第${count}次执行watchers,共执行${watchers.length}个,耗时${duration}毫秒`);
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
try {
|
||||||
|
await this.dbStore.checkpoint(process.env.NODE_ENV === 'development' ? now - 30 * 1000 : now - 120 * 1000);
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.error(`执行了checkpoint,发生错误:`, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => doWatchers(), 120000);
|
||||||
|
};
|
||||||
|
doWatchers();
|
||||||
|
}
|
||||||
|
|
||||||
startTimers() {
|
startTimers() {
|
||||||
const timers = this.requireSth('lib/timers/index');
|
const timers: Timer<ED, keyof ED, Cxt>[] = this.requireSth('lib/timers/index');
|
||||||
for (const timer of timers) {
|
for (const timer of timers) {
|
||||||
const { cron, fn, name } = timer;
|
const { cron, name } = timer;
|
||||||
scheduleJob(name, cron, async (date) => {
|
scheduleJob(name, cron, async (date) => {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const context = await this.contextBuilder()(this.dbStore, undefined, getClusterInfo());
|
const context = await this.makeContext();
|
||||||
await context.begin();
|
await context.begin();
|
||||||
console.log(`定时器【${name}】开始执行,时间是【${date.toLocaleTimeString()}】`);
|
console.log(`定时器【${name}】开始执行,时间是【${date.toLocaleTimeString()}】`);
|
||||||
try {
|
try {
|
||||||
const result = await fn(context);
|
if (timer.hasOwnProperty('entity')) {
|
||||||
console.log(`定时器【${name}】执行完成,耗时${Date.now() - start}毫秒,结果是【${result}】`);
|
await this.execWatcher(timer as Watcher<ED, keyof ED, Cxt>);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const { timer: timerFn } = timer as FreeTimer<ED, Cxt>;
|
||||||
|
const result = await timerFn(context);
|
||||||
|
console.log(`定时器【${name}】执行完成,耗时${Date.now() - start}毫秒,结果是【${result}】`);
|
||||||
|
}
|
||||||
await context.commit();
|
await context.commit();
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
|
|
@ -392,26 +427,33 @@ export class AppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends Backe
|
||||||
}
|
}
|
||||||
|
|
||||||
async execStartRoutines() {
|
async execStartRoutines() {
|
||||||
const routines = this.requireSth('lib/routines/start');
|
const routines: Routine<ED, keyof ED, Cxt>[] = this.requireSth('lib/routines/start');
|
||||||
for (const routine of routines) {
|
for (const routine of routines) {
|
||||||
const { name, fn } = routine;
|
if (routine.hasOwnProperty('entity')) {
|
||||||
const context = await this.contextBuilder()(this.dbStore, undefined, getClusterInfo());
|
this.execWatcher(routine as Watcher<ED, keyof ED, Cxt>);
|
||||||
const start = Date.now();
|
|
||||||
await context.begin();
|
|
||||||
try {
|
|
||||||
const result = await fn(context);
|
|
||||||
console.log(`例程【${name}】执行完成,耗时${Date.now() - start}毫秒,结果是【${result}】`);
|
|
||||||
await context.commit();
|
|
||||||
}
|
}
|
||||||
catch (err) {
|
else {
|
||||||
console.warn(`例程【${name}】执行失败,耗时${Date.now() - start}毫秒,错误是`, err);
|
const { name, routine: routineFn } = routine as FreeRoutine<ED, Cxt>;
|
||||||
await context.rollback();
|
const context = await this.makeContext();
|
||||||
|
|
||||||
|
const start = Date.now();
|
||||||
|
await context.begin();
|
||||||
|
try {
|
||||||
|
const result = await routineFn(context);
|
||||||
|
console.log(`例程【${name}】执行完成,耗时${Date.now() - start}毫秒,结果是【${result}】`);
|
||||||
|
await context.commit();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
console.warn(`例程【${name}】执行失败,耗时${Date.now() - start}毫秒,错误是`, err);
|
||||||
|
await context.rollback();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async execRoutine(routine: (context: Cxt) => Promise<void>) {
|
async execRoutine(routine: (context: Cxt) => Promise<void>) {
|
||||||
const context = await this.contextBuilder()(this.dbStore, undefined, getClusterInfo());
|
const context = await this.makeContext();
|
||||||
|
|
||||||
await routine(context);
|
await routine(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { combineFilters } from 'oak-domain/lib/store/filter';
|
||||||
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||||
|
import { EntityDict, OperationResult, VolatileTrigger, Trigger } from 'oak-domain/lib/types';
|
||||||
|
import { BackendRuntimeContext } from 'oak-frontend-base';
|
||||||
|
import { getClusterInfo } from './cluster/env';
|
||||||
|
|
||||||
|
import { AppLoader } from './AppLoader';
|
||||||
|
import assert from 'assert';
|
||||||
|
import { DbStore } from './DbStore';
|
||||||
|
import { Namespace } from 'socket.io';
|
||||||
|
|
||||||
|
export class ClusterAppLoader<ED extends EntityDict & BaseEntityDict, Cxt extends BackendRuntimeContext<ED>> extends AppLoader<ED, Cxt> {
|
||||||
|
constructor(path: string, contextBuilder: (scene?: string) => (store: DbStore<ED, Cxt>) => Promise<Cxt>, ns?: Namespace) {
|
||||||
|
super(path, contextBuilder, ns);
|
||||||
|
this.dbStore.setOnVolatileTrigger(
|
||||||
|
async (entity, trigger, ids, cxtStr, option) => {
|
||||||
|
if (trigger.cs) {
|
||||||
|
// 如果是cluster sensative的触发器,需要发送到相应的instance上被处理
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const context = await this.contextBuilder(cxtStr)(this.dbStore);
|
||||||
|
await context.begin();
|
||||||
|
try {
|
||||||
|
await this.dbStore.execVolatileTrigger(entity, trigger.name, ids, context, option);
|
||||||
|
await context.commit();
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
await context.rollback();
|
||||||
|
console.error('execVolatileTrigger异常', entity, trigger.name, ids, option, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
protected operateInWatcher<T extends keyof ED>(entity: T, operation: ED[T]['Update'], context: Cxt): Promise<OperationResult<ED>> {
|
||||||
|
const { instanceCount, instanceId } = getClusterInfo()!;
|
||||||
|
assert (instanceCount && typeof instanceId === 'number');
|
||||||
|
const { filter } = operation;
|
||||||
|
const filter2 = combineFilters<ED, T>(entity, this.dbStore.getSchema(), [filter, {
|
||||||
|
$$seq$$: {
|
||||||
|
$mod: [instanceCount, instanceId]
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
return super.operateInWatcher(entity, {
|
||||||
|
...operation,
|
||||||
|
filter: filter2,
|
||||||
|
}, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected selectInWatcher<T extends keyof ED>(entity: T, selection: ED[T]['Selection'], context: Cxt): Promise<Partial<ED[T]['Schema']>[]> {
|
||||||
|
const { instanceCount, instanceId } = getClusterInfo()!;
|
||||||
|
assert (instanceCount && typeof instanceId === 'number');
|
||||||
|
const { filter } = selection;
|
||||||
|
const filter2 = combineFilters<ED, T>(entity, this.dbStore.getSchema(), [filter, {
|
||||||
|
$$seq$$: {
|
||||||
|
$mod: [instanceCount, instanceId]
|
||||||
|
}
|
||||||
|
}]);
|
||||||
|
return super.selectInWatcher(entity, {
|
||||||
|
...selection,
|
||||||
|
filter: filter2,
|
||||||
|
}, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { MysqlStore, MySqlSelectOption, MysqlOperateOption } from 'oak-db';
|
import { MysqlStore, MySqlSelectOption, MysqlOperateOption } from 'oak-db';
|
||||||
import { EntityDict, StorageSchema, Trigger, Checker, SelectOption, SelectFreeEntities, UpdateFreeDict, AuthDeduceRelationMap } from 'oak-domain/lib/types';
|
import { EntityDict, StorageSchema, Trigger, Checker, SelectOption, SelectFreeEntities, UpdateFreeDict, AuthDeduceRelationMap, VolatileTrigger, OperateOption } from 'oak-domain/lib/types';
|
||||||
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
import { EntityDict as BaseEntityDict } from 'oak-domain/lib/base-app-domain';
|
||||||
import { TriggerExecutor } from 'oak-domain/lib/store/TriggerExecutor';
|
import { TriggerExecutor } from 'oak-domain/lib/store/TriggerExecutor';
|
||||||
import { MySQLConfiguration, } from 'oak-db/lib/MySQL/types/Configuration';
|
import { MySQLConfiguration, } from 'oak-db/lib/MySQL/types/Configuration';
|
||||||
|
|
@ -18,9 +18,10 @@ export class DbStore<ED extends EntityDict & BaseEntityDict, Cxt extends Backend
|
||||||
mysqlConfiguration: MySQLConfiguration,
|
mysqlConfiguration: MySQLConfiguration,
|
||||||
authDeduceRelationMap: AuthDeduceRelationMap<ED>,
|
authDeduceRelationMap: AuthDeduceRelationMap<ED>,
|
||||||
selectFreeEntities: SelectFreeEntities<ED> = [],
|
selectFreeEntities: SelectFreeEntities<ED> = [],
|
||||||
updateFreeDict: UpdateFreeDict<ED> = {}) {
|
updateFreeDict: UpdateFreeDict<ED> = {},
|
||||||
|
onVolatileTrigger?: <T extends keyof ED>(entity: T, trigger: VolatileTrigger<ED, T, Cxt>, ids: string[], cxtStr: string, option: OperateOption) => Promise<void>) {
|
||||||
super(storageSchema, mysqlConfiguration);
|
super(storageSchema, mysqlConfiguration);
|
||||||
this.executor = new TriggerExecutor((scene) => contextBuilder(scene)(this));
|
this.executor = new TriggerExecutor((scene) => contextBuilder(scene)(this), undefined, onVolatileTrigger);
|
||||||
this.relationAuth = new RelationAuth(storageSchema, authDeduceRelationMap, selectFreeEntities, updateFreeDict);
|
this.relationAuth = new RelationAuth(storageSchema, authDeduceRelationMap, selectFreeEntities, updateFreeDict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,6 +142,27 @@ export class DbStore<ED extends EntityDict & BaseEntityDict, Cxt extends Backend
|
||||||
this.executor.registerChecker(checker);
|
this.executor.registerChecker(checker);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setOnVolatileTrigger(
|
||||||
|
onVolatileTrigger: <T extends keyof ED>(
|
||||||
|
entity: T,
|
||||||
|
trigger: VolatileTrigger<ED, T, Cxt>,
|
||||||
|
ids: string[],
|
||||||
|
cxtStr: string,
|
||||||
|
option: OperateOption) => Promise<void>
|
||||||
|
) {
|
||||||
|
this.executor.setOnVolatileTrigger(onVolatileTrigger);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execVolatileTrigger<T extends keyof ED>(
|
||||||
|
entity: T,
|
||||||
|
name: string,
|
||||||
|
ids: string[],
|
||||||
|
context: Cxt,
|
||||||
|
option: OperateOption
|
||||||
|
) {
|
||||||
|
return this.executor.execVolatileTrigger(entity, name, ids, context, option);
|
||||||
|
}
|
||||||
|
|
||||||
checkpoint(ts: number) {
|
checkpoint(ts: number) {
|
||||||
return this.executor.checkpoint(ts);
|
return this.executor.checkpoint(ts);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
export { AppLoader } from './AppLoader';
|
export { AppLoader } from './AppLoader';
|
||||||
|
export { ClusterAppLoader } from './ClusterAppLoader';
|
||||||
export * from './cluster/env';
|
export * from './cluster/env';
|
||||||
Loading…
Reference in New Issue