380 lines
18 KiB
JavaScript
380 lines
18 KiB
JavaScript
"use strict";
|
||
Object.defineProperty(exports, "__esModule", { value: true });
|
||
exports.AppLoader = void 0;
|
||
const tslib_1 = require("tslib");
|
||
const fs_1 = require("fs");
|
||
const path_1 = require("path");
|
||
const node_schedule_1 = require("node-schedule");
|
||
const env_1 = require("oak-domain/lib/compiler/env");
|
||
const actionDef_1 = require("oak-domain/lib/store/actionDef");
|
||
const lodash_1 = require("oak-domain/lib/utils/lodash");
|
||
const uuid_1 = require("oak-domain/lib/utils/uuid");
|
||
const types_1 = require("oak-domain/lib/types");
|
||
const DbStore_1 = require("./DbStore");
|
||
const index_1 = tslib_1.__importStar(require("oak-common-aspect/lib/index"));
|
||
const assert_1 = tslib_1.__importDefault(require("assert"));
|
||
const DataSubscriber_1 = tslib_1.__importDefault(require("./cluster/DataSubscriber"));
|
||
const env_2 = require("./cluster/env");
|
||
class AppLoader extends types_1.AppLoader {
|
||
dbStore;
|
||
aspectDict;
|
||
externalDependencies;
|
||
dataSubscriber;
|
||
contextBuilder;
|
||
requireSth(filePath) {
|
||
const depFilePath = (0, path_1.join)(this.path, filePath);
|
||
let sth;
|
||
if ((0, fs_1.existsSync)(`${depFilePath}.js`)) {
|
||
sth = require((0, path_1.join)(this.path, filePath)).default;
|
||
}
|
||
const sthExternal = this.externalDependencies.map(ele => {
|
||
const depFilePath = (0, path_1.join)(this.path, 'node_modules', ele, filePath);
|
||
if ((0, fs_1.existsSync)(`${depFilePath}.js`)) {
|
||
return require(depFilePath).default;
|
||
}
|
||
}).filter(ele => !!ele);
|
||
if (!sth) {
|
||
if (sthExternal.length > 0 && sthExternal[0] instanceof Array) {
|
||
sth = [];
|
||
}
|
||
else {
|
||
sth = {};
|
||
}
|
||
}
|
||
if (sth instanceof Array) {
|
||
sthExternal.forEach((sth2, idx) => {
|
||
(0, assert_1.default)(sth2 instanceof Array, `${(0, path_1.join)(this.path, 'node_modules', this.externalDependencies[idx], filePath)}中的default输出对象不是数组,与项目对应路径的输出不一致`);
|
||
sth.push(...sth2);
|
||
});
|
||
return sth;
|
||
}
|
||
(0, assert_1.default)(typeof sth === 'object');
|
||
const sthOut = {};
|
||
sthExternal.forEach((sth2, idx) => {
|
||
(0, assert_1.default)(typeof sth2 === 'object' && !(sth2 instanceof Array), `${(0, path_1.join)(this.path, 'node_modules', this.externalDependencies[idx], filePath)}中的default输出对象不是非数组对象,与项目对应路径的输出不一致`);
|
||
const inter = (0, lodash_1.intersection)(Object.keys(sthOut), Object.keys(sth2));
|
||
if (inter.length > 0) {
|
||
console.warn(`${(0, path_1.join)(this.path, 'node_modules', this.externalDependencies[idx], filePath)}中的default输出对象中的key值【${inter.join(',')}】与其它对应路径输出的key值有冲突,请仔细检查避免错误`);
|
||
inter.forEach((ele) => {
|
||
if (sth2[ele] instanceof Array && sthOut[ele]) {
|
||
(0, assert_1.default)(sthOut[ele] instanceof Array, `${(0, path_1.join)(this.path, 'node_modules', this.externalDependencies[idx], filePath)}中的default输出对象的${ele}键值是数组,但之前的相应对象的${ele}却不是,请仔细检查以避免错误`);
|
||
console.warn(`${(0, path_1.join)(this.path, 'node_modules', this.externalDependencies[idx], filePath)}中的default输出对象中的key值【${ele}】与其它对应路径输出的key值【${ele}】将以数组格式进行合并,请仔细检查避免错误`);
|
||
sth2[ele].push(...sthOut[ele]);
|
||
}
|
||
else if (!(sth2[ele] instanceof Array) && sthOut[ele]) {
|
||
(0, assert_1.default)(!(sthOut[ele] instanceof Array), `${(0, path_1.join)(this.path, 'node_modules', this.externalDependencies[idx], filePath)}中的default输出对象的${ele}键值不是数组,但之前的相应对象的${ele}却是,请仔细检查以避免错误`);
|
||
console.warn(`${(0, path_1.join)(this.path, 'node_modules', this.externalDependencies[idx], filePath)}中的default输出对象中的key值【${ele}】将对与其它对应路径输出的key值【${ele}】进行覆盖,请仔细检查避免错误`);
|
||
}
|
||
});
|
||
}
|
||
Object.assign(sthOut, sth2);
|
||
});
|
||
const inter = (0, lodash_1.intersection)(Object.keys(sthOut), Object.keys(sth));
|
||
if (inter.length > 0) {
|
||
inter.forEach((ele) => {
|
||
if (sth[ele] instanceof Array && sthOut[ele]) {
|
||
(0, assert_1.default)(sthOut[ele] instanceof Array, `项目${filePath}中的default输出对象的${ele}键值是数组,但之前的相应对象的${ele}却不是,请仔细检查以避免错误`);
|
||
console.warn(`项目${filePath}中的default输出对象中的key值【${ele}】与其它引用包该路径输出的key值【${ele}】将以数组格式进行合并,请仔细检查避免错误`);
|
||
sth[ele].push(...sthOut[ele]);
|
||
}
|
||
else if (!(sth[ele] instanceof Array) && sthOut[ele]) {
|
||
(0, assert_1.default)(!(sthOut[ele] instanceof Array), `项目${filePath}中的default输出对象的${ele}键值不是数组,但之前的相应对象的${ele}却是,请仔细检查以避免错误`);
|
||
console.warn(`项目${filePath}中的default输出对象中的key值【${ele}】将对其它引用包该路径输出的key值【${ele}】进行覆盖,请仔细检查避免错误`);
|
||
}
|
||
});
|
||
}
|
||
Object.assign(sthOut, sth);
|
||
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) {
|
||
super(path);
|
||
const dbConfig = require((0, path_1.join)(path, '/configuration/mysql.json'));
|
||
const { storageSchema } = require(`${path}/lib/oak-app-domain/Storage`);
|
||
const { authDeduceRelationMap, selectFreeEntities, updateFreeDict } = require(`${path}/lib/config/relation`);
|
||
this.externalDependencies = require((0, env_1.OAK_EXTERNAL_LIBS_FILEPATH)((0, path_1.join)(path, 'lib')));
|
||
this.aspectDict = Object.assign({}, index_1.default, this.requireSth('lib/aspects/index'));
|
||
this.dbStore = new DbStore_1.DbStore(storageSchema, contextBuilder, dbConfig, authDeduceRelationMap, selectFreeEntities, updateFreeDict);
|
||
if (ns) {
|
||
this.dataSubscriber = new DataSubscriber_1.default(ns, (scene) => this.contextBuilder(scene)(this.dbStore));
|
||
this.contextBuilder = (scene) => async (store) => {
|
||
const context = await contextBuilder(scene)(store);
|
||
// 注入在提交前向dataSubscribe
|
||
const originCommit = context.commit;
|
||
context.commit = async () => {
|
||
const { eventOperationMap, opRecords } = context;
|
||
await originCommit.call(context);
|
||
Object.keys(eventOperationMap).forEach((event) => {
|
||
const ids = eventOperationMap[event];
|
||
const opRecordsToPublish = opRecords.filter((ele) => !!ele.id && ids.includes(ele.id));
|
||
(0, assert_1.default)(opRecordsToPublish.length === ids.length, '要推送的事件的operation数量不足,请检查确保');
|
||
this.dataSubscriber.publishEvent(event, opRecordsToPublish);
|
||
});
|
||
};
|
||
return context;
|
||
};
|
||
}
|
||
else {
|
||
this.contextBuilder = contextBuilder;
|
||
}
|
||
}
|
||
registerTrigger(trigger) {
|
||
this.dbStore.registerTrigger(trigger);
|
||
}
|
||
initTriggers() {
|
||
const triggers = this.requireSth('lib/triggers/index');
|
||
const checkers = this.requireSth('lib/checkers/index');
|
||
const { ActionDefDict } = require(`${this.path}/lib/oak-app-domain/ActionDefDict`);
|
||
const { triggers: adTriggers, checkers: adCheckers } = (0, actionDef_1.makeIntrinsicCTWs)(this.dbStore.getSchema(), ActionDefDict);
|
||
triggers.forEach((trigger) => this.registerTrigger(trigger));
|
||
adTriggers.forEach((trigger) => this.registerTrigger(trigger));
|
||
checkers.forEach((checker) => this.dbStore.registerChecker(checker));
|
||
adCheckers.forEach((checker) => this.dbStore.registerChecker(checker));
|
||
}
|
||
async mount(initialize) {
|
||
const { path } = this;
|
||
if (!initialize) {
|
||
this.initTriggers();
|
||
}
|
||
const { importations, exportations } = require(`${path}/lib/ports/index`);
|
||
(0, index_1.registerPorts)(importations || [], exportations || []);
|
||
this.dbStore.connect();
|
||
}
|
||
async unmount() {
|
||
(0, index_1.clearPorts)();
|
||
this.dbStore.disconnect();
|
||
}
|
||
async execAspect(name, headers, contextString, params) {
|
||
const context = await this.makeContext(contextString, headers);
|
||
const fn = this.aspectDict[name];
|
||
if (!fn) {
|
||
throw new Error(`不存在的接口名称: ${name}`);
|
||
}
|
||
await context.begin();
|
||
try {
|
||
const result = await fn(params, context);
|
||
await context.commit();
|
||
await context.refineOpRecords();
|
||
return {
|
||
opRecords: context.opRecords.map(ele => (0, lodash_1.omit)(ele, 'id')),
|
||
message: context.getMessage(),
|
||
result,
|
||
};
|
||
}
|
||
catch (err) {
|
||
await context.rollback();
|
||
throw err;
|
||
}
|
||
}
|
||
async initialize(dropIfExists) {
|
||
await this.dbStore.initialize(dropIfExists);
|
||
const data = this.requireSth('lib/data/index');
|
||
const context = await this.contextBuilder()(this.dbStore);
|
||
for (const entity in data) {
|
||
let rows = data[entity];
|
||
if (entity === 'area') {
|
||
// 对area暂时处理一下
|
||
rows = require('./data/area.json');
|
||
}
|
||
if (rows.length > 0) {
|
||
await context.begin();
|
||
try {
|
||
await this.dbStore.operate(entity, {
|
||
data: rows,
|
||
action: 'create',
|
||
}, context, {
|
||
dontCollect: true,
|
||
dontCreateOper: true,
|
||
});
|
||
await context.commit();
|
||
console.log(`data in ${entity} initialized, ${rows.length} rows inserted`);
|
||
}
|
||
catch (err) {
|
||
await context.rollback();
|
||
console.error(`data on ${entity} initilization failed!`);
|
||
throw err;
|
||
}
|
||
}
|
||
}
|
||
this.dbStore.disconnect();
|
||
}
|
||
getStore() {
|
||
return this.dbStore;
|
||
}
|
||
getEndpoints(prefix) {
|
||
const endpoints = this.requireSth('lib/endpoints/index');
|
||
const endPointRouters = [];
|
||
const endPointMap = {};
|
||
const transformEndpointItem = (key, item) => {
|
||
const { name, method, fn, params: itemParams } = item;
|
||
const k = `${key}-${name}-${method}`;
|
||
if (endPointMap[k]) {
|
||
throw new Error(`endpoint中,url为「${key}」、名为${name}的方法「${method}」存在重复定义`);
|
||
}
|
||
endPointMap[k] = true;
|
||
let url = `${prefix}/${key}`;
|
||
if (itemParams) {
|
||
for (const p of itemParams) {
|
||
url += `/:${p}`;
|
||
}
|
||
}
|
||
endPointRouters.push([name, method, url, async (params, headers, req, body) => {
|
||
const context = await this.makeContext(undefined, headers);
|
||
await context.begin();
|
||
try {
|
||
const result = await fn(context, params, headers, req, body);
|
||
await context.commit();
|
||
return result;
|
||
}
|
||
catch (err) {
|
||
await context.rollback();
|
||
console.error(`endpoint「${key}」方法「${method}」出错`, err);
|
||
throw err;
|
||
}
|
||
}]);
|
||
};
|
||
for (const router in endpoints) {
|
||
const item = endpoints[router];
|
||
if (item instanceof Array) {
|
||
item.forEach(ele => transformEndpointItem(router, ele));
|
||
}
|
||
else {
|
||
transformEndpointItem(router, item);
|
||
}
|
||
}
|
||
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() {
|
||
const timers = this.requireSth('lib/timers/index');
|
||
for (const timer of timers) {
|
||
const { cron, name } = timer;
|
||
(0, node_schedule_1.scheduleJob)(name, cron, async (date) => {
|
||
const start = Date.now();
|
||
const context = await this.makeContext();
|
||
await context.begin();
|
||
console.log(`定时器【${name}】开始执行,时间是【${date.toLocaleTimeString()}】`);
|
||
try {
|
||
if (timer.hasOwnProperty('entity')) {
|
||
await this.execWatcher(timer);
|
||
}
|
||
else {
|
||
const { timer: timerFn } = timer;
|
||
const result = await timerFn(context);
|
||
console.log(`定时器【${name}】执行完成,耗时${Date.now() - start}毫秒,结果是【${result}】`);
|
||
}
|
||
await context.commit();
|
||
}
|
||
catch (err) {
|
||
console.warn(`定时器【${name}】执行失败,耗时${Date.now() - start}毫秒,错误是`, err);
|
||
await context.rollback();
|
||
}
|
||
});
|
||
}
|
||
}
|
||
async execStartRoutines() {
|
||
const routines = this.requireSth('lib/routines/start');
|
||
for (const routine of routines) {
|
||
if (routine.hasOwnProperty('entity')) {
|
||
this.execWatcher(routine);
|
||
}
|
||
else {
|
||
const { name, routine: routineFn } = routine;
|
||
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) {
|
||
const context = await this.makeContext();
|
||
await routine(context);
|
||
}
|
||
}
|
||
exports.AppLoader = AppLoader;
|