import { immutable, excludedInJSON, circular, action } from '../decorators';
import { config, utils, Vertex, LEVEL_ENUM, Interface, Param, Return, Variable, vertexsMap, typeCheck, ProcessComponent, ProcessInterface, Process, ActionOptions } from '..';
import { logicService, paramService } from '../../service/logic';
import View from '../page/View';
import { ExpressionNode, LogicItem, LogicNode } from './LogicItem';
import { convert2SchemaType } from '../data/dataTypeUtils';
import { isPlainObject, throttle } from 'lodash';
import { refreshInterfaces, refreshView, refreshPages } from '../cache';
import { BusinessCode } from '../enum';
export function catchFn(logic: Logic) {
return async (err: any) => {
const code = err?.code;
if ([BusinessCode.ElementExist, BusinessCode.ElementNotExist].includes(code)) {
if (logic.interface) {
await refreshInterfaces();
} else if (logic.view) {
await refreshView(logic.view);
}
} else if (code === BusinessCode.ParentElementNotExist) { // 父节点不存在。视图添加逻辑,发现视图不存在
if (logic.view) {
await refreshPages();
}
} else
config.defaultApp?.emit('saved', err);
config.defaultApp?.history.load();
throw err;
};
}
/**
* 逻辑类
*/
export class Logic extends Vertex {
/**
* 概念类型
*/
@immutable()
public readonly level: LEVEL_ENUM = LEVEL_ENUM.logic;
/**
* Id
*/
@immutable()
public readonly id: string = undefined;
/**
* 名称
*/
@immutable()
public readonly name: string = undefined;
/**
* 描述
*/
@immutable()
public readonly description: string = undefined;
/**
* serviceId 或者 viewId
*/
@immutable()
public readonly moduleId: string = undefined;
/**
* 值为 view 或者 microService 或者 processComponent
*/
@immutable()
public readonly moduleType: 'view' | 'microService' | 'process' = undefined;
/**
* serviceId 或者 viewId
*/
@immutable()
public readonly serviceId: string = undefined;
/**
* 值为 view 或者 microService
*/
@immutable()
public readonly serviceType: 'view' | 'entity' | 'microService' = undefined;
/**
* 所属实体 Id
*/
@immutable()
public readonly entityId: string = undefined;
/**
* 输入参数列表
*/
@immutable()
public readonly params: Array = [];
/**
* 输出参数列表
*/
@immutable()
public readonly returns: Array = [];
/**
* 局部变量列表
*/
@immutable()
public readonly variables: Array = [];
/**
* 逻辑体
*/
@immutable()
public readonly body: Array = [];
/**
* 画布
*/
@immutable()
public readonly playgroundId: string = undefined;
/**
* 画布
*/
@immutable()
public readonly playground: Array = [];
/**
* 接口
*/
@circular()
@immutable()
public readonly interface: Interface = undefined;
/**
* 视图
*/
@circular()
@immutable()
public readonly view: View = undefined;
/**
* 流程组件
*/
@circular()
@immutable()
public readonly processComponent: ProcessComponent = undefined;
/**
* 流程组件
*/
@circular()
@immutable()
public readonly process: Process = undefined;
/**
* 正在请求的 Promise
* 前端 UI 状态
*/
@excludedInJSON()
public contentPromise: Promise = undefined;
/**
* 树组件的子节点字段
*/
@immutable()
public readonly moreChildrenFields: Array = ['params', 'returns', 'variables'];
/**
* 归属的模块
*/
@immutable()
public readonly moduleName: string = undefined;
/**
* @param source 需要合并的部分参数
*/
/**
* 逻辑配置的cron表达式
*/
@immutable()
public cron: string = undefined;
/**
* 定时类型,当前只能是cronTrigger
*/
@immutable()
public triggerType: string = undefined;
/**
* 事务
* {
* "enabled": "事务是否生效; true | false", // 目前只有这一个属性有使用到
* "propagation": "事务的传播机制",
* "isolation": "事务的隔离级别",
* "rollbackFor": ["事务需要捕获且回滚的异常"],
* "noRollbackFor": ["事务无需回滚的异常"]
*
* }
*/
@immutable()
public transactional: Record = {};
/**
* 定时任务
*/
constructor(source?: Partial) {
super();
source && this.assign(source);
this.on('change', throttle(
this._onChange.bind(this),
config.throttleWait,
{ leading: true, trailing: true },
));
}
/**
* 添加逻辑
*/
@action('添加逻辑')
async create(none?: void, actionOptions?: ActionOptions) {
config.defaultApp?.emit('saving');
const body = this.toJSON();
utils.logger.debug('添加逻辑', body);
const result: Logic = await logicService.create({
headers: {
moduleType: this.moduleType,
appId: config.defaultApp?.id,
operationAction: actionOptions?.actionName || 'Logic.create',
operationDesc: actionOptions?.actionDesc || `添加逻辑"${this.name}"`,
},
body,
}).catch(catchFn(this));
this.deepPick(result, ['id']);
this.pick(result, ['playgroundId']);
this.assign({ body: result.body.map((logicNode) => LogicNode.from(logicNode, this, null)) });
this.params.forEach((param) => {
param.assign({ code: `ID_${param.id}` });
});
this.returns.forEach((returns) => {
returns.assign({ code: `ID_${returns.id}` });
});
this.variables.forEach((variable) => {
variable.assign({ code: `ID_${variable.id}` });
});
if (this.view) {
this.view.page.service.emit('pageTreeChange');
this.emit('change');
}
await config.defaultApp?.history.load();
config.defaultApp?.emit('saved');
return this;
}
/**
* 删除逻辑
*/
@action('删除逻辑')
async delete(none?: void, actionOptions?: ActionOptions) {
if (this.id) {
config.defaultApp?.emit('saving');
const body = this.toPlainJSON();
if (this.view) {
body.moduleId = this.view.id;
body.moduleType = 'view';
} else {
body.moduleId = this.interface.service.id;
body.moduleType = 'microService';
}
await logicService.delete({
headers: {
moduleType: this.moduleType,
appId: config.defaultApp?.id,
operationAction: actionOptions?.actionName || 'Logic.delete',
operationDesc: actionOptions?.actionDesc || `删除逻辑"${this.name}"`,
},
body,
}).catch(catchFn(this));
if (this.view) {
const { logics } = this.view.$def;
const pos = logics.indexOf(this);
if (pos >= 0)
logics.splice(pos, 1);
}
[...this.body, ...this.playground].forEach((node) => {
typeCheck.delete(node.id);
});
this.destroy();
if (this.view) {
this.view.page.service.emit('pageTreeChange');
this.emit('change');
}
await config.defaultApp?.history.load();
config.defaultApp?.emit('saved');
} else {
if (this.view) {
const { logics } = this.view.$def;
const pos = logics.indexOf(this);
if (pos >= 0)
logics.splice(pos, 1);
}
}
}
/**
* 修改逻辑
*/
async update(none?: void, actionOptions?: ActionOptions, then?: () => Promise) {
config.defaultApp?.emit('saving');
const body = this.toPlainJSON();
// body.transactional = this.transactional;
utils.logger.debug('修改逻辑', body);
await logicService.update({
headers: {
moduleType: this.moduleType,
appId: config.defaultApp?.id,
operationAction: actionOptions?.actionName || 'Logic.update',
operationDesc: actionOptions?.actionDesc || `修改逻辑"${this.name}"`,
},
body,
}).catch(catchFn(this));
await then?.();
await config.defaultApp?.history.load();
config.defaultApp?.emit('saved');
return this;
}
/**
* 更新所有调用了此Logic的LogicNode
*/
async callLogicUpdate(actionOptions?: ActionOptions) {
config.defaultApp?.emit('saving');
const body = this.toPlainJSON();
body.params = this.params;
const result = await logicService.callLogicUpdate({
headers: {
appId: config.defaultApp?.id,
operationAction: actionOptions?.actionName || 'Logic.callLogicUpdate',
operationDesc: actionOptions?.actionDesc || `修改调用逻辑"${this.name}"`,
},
body,
}).catch((err: Error) => console.log(err)); // 报错不应影响参数的添加流程
(result || []).forEach(async (item: LogicNode) => {
if (vertexsMap.has(item.id)) {
const logicNode = vertexsMap.get(item.id) as LogicNode;
await logicNode.logic.load();
await logicNode.checkType();
}
});
// await config.defaultApp?.history.load();
config.defaultApp?.emit('saved');
return this;
}
/**
* 设置逻辑名称
*/
@action('设置逻辑名称')
async setName(name: string) {
const oldName = this.name;
this.assign({ name });
await this.update(undefined, {
actionDesc: `设置逻辑"${oldName}"的名称为"${name}"`,
});
if (this.view) {
this.view.page.service.emit('pageTreeChange');
this.emit('change');
}
return this;
}
/**
* 设置逻辑描述
*/
@action('设置逻辑描述')
async setDescription(description: string) {
this.assign({ description });
await this.update(undefined, {
actionDesc: `设置逻辑"${this.name}"的描述为"${description}"`,
});
if (this.view) {
this.view.page.service.emit('pageTreeChange');
this.emit('change');
}
return this;
}
_onChange() {
this.view && this.view.emit('change');
}
/**
* 按当前 id 加载逻辑数据
*/
load() {
if (!this.id)
return;
// 如果有正在进行的 Promise,则直接返回它
if (this.contentPromise)
return this.contentPromise;
return this.contentPromise = (async () => {
const result = await logicService.loadDetail({
query: {
moduleId: this.moduleId,
moduleType: this.moduleType,
id: this.id,
},
config: { noErrorTip: true },
}).catch(catchFn(this));
const newLogic = Logic.from(result, this.interface || this.view || this.processComponent, this);
this.assign(newLogic);
return this;
})().finally(() => this.contentPromise = undefined);
}
isContentLoaded() {
return !!this.body.length;
}
/**
* 批量添加参数
*/
async addParamList(params: Array) {
const body = params.map((param) => {
param = param.toJSON();
convert2SchemaType(param.schema);
return param;
});
const results = await paramService.addList({
headers: {
moduleType: this.moduleType,
appId: config.defaultApp?.id,
},
body,
});
params.forEach((param, index) => param.pick(results[index], ['id']));
this.params.push(...params);
}
/**
* 添加逻辑节点
* @param item
*/
addItem(item: LogicItem, actionOptions?: ActionOptions) {
if (isPlainObject(item)) {
item = LogicItem.from(item, this, null);
}
const parent = item.parentAttr ? vertexsMap.get(item.parentId) as LogicItem : undefined;
return item.create({
parent,
parentId: item.parentId,
parentAttr: item.parentAttr,
_posIndex: item._posIndex,
cache: false,
}, actionOptions);
}
/**
* 删除逻辑节点
* @param item
*/
removeItem(item: LogicItem, actionOptions?: ActionOptions) {
if (isPlainObject(item)) {
item = vertexsMap.get(item.id) as LogicItem;
}
return item.delete(undefined, actionOptions);
}
/**
*
* @param type
* @returns
*/
public findLogicItemByType(type: string) {
let result: any;
utils.traverse((current) => {
if ((current.node as any).type === type)
return result = current.node;
}, { node: this }, {
mode: 'anyObject',
excludedKeySet: this.JSON_EXCLUDED_KEYS,
});
return result as LogicItem;
}
/**
* 从后端 JSON 生成规范的 Logic 对象
*/
public static from(source: any, parent: Interface | View | ProcessComponent | ProcessInterface, currentLogic?: Logic) {
const logic = new Logic(source);
currentLogic = currentLogic || logic;
logic.assign({ params: logic.params.map((param) => Param.from(param, currentLogic)) });
logic.assign({ returns: logic.returns.map((ret) => Return.from(ret, currentLogic)) });
logic.assign({ variables: logic.variables.map((variable) => Variable.from(variable, currentLogic)) });
logic.body.forEach((node, index) => {
if (node.level === LEVEL_ENUM.logicNode)
node = logic.body[index] = LogicNode.from(node, currentLogic, null);
else if (node.level === LEVEL_ENUM.expressionNode)
node = logic.body[index] = ExpressionNode.from(node, currentLogic, null);
});
logic.playground.forEach((node, index) => {
if (node.level === LEVEL_ENUM.logicNode)
node = logic.playground[index] = LogicNode.from(node, currentLogic, null);
else if (node.level === LEVEL_ENUM.expressionNode)
node = logic.playground[index] = ExpressionNode.from(node, currentLogic, null);
});
if (parent instanceof Interface)
logic.assign({
interface: parent,
moduleType: 'microService',
moduleId: parent.id,
});
else if (parent instanceof View)
logic.assign({
view: parent,
moduleType: 'view',
moduleId: parent.id,
});
else if (parent instanceof ProcessComponent)
logic.assign({
processComponent: parent,
moduleType: 'process',
moduleId: parent.id,
});
else if (parent instanceof ProcessInterface) {
logic.assign({
process: parent,
});
}
return logic;
}
/**
* 更新定时任务
*/
@action('更新定时任务')
async updateTimer(cron: string) {
this.assign({ cron });
this.triggerType = 'cronTrigger';
await this.update(undefined, {
actionDesc: `设置定时任务"为"${cron}"`,
});
if (this.view) {
this.view.page.service.emit('pageTreeChange');
this.emit('change');
}
return this;
}
/**
* 更新事务属性
*/
@action('更新事务属性')
async setTransaction(transactional: Record) {
const body = {
logicId: this.id,
id: transactional.id,
enabled: transactional.enabled,
};
this.assign({ transactional });
await logicService.updateTransactional({ body });
await this.update(undefined, {
actionDesc: '更新事务属性',
});
if (this.view) {
this.view.page.service.emit('pageTreeChange');
this.emit('change');
}
return this;
}
/**
* 验证表达式正确与否
*/
async validateTimer() {
const body = this.toPlainJSON();
const result = await logicService.validateTimer({
body,
config: {
noErrorTip: true,
},
});
return result;
}
/**
* 输出最近十次运行结果
*/
async testTimer() {
const body = this.toPlainJSON();
const result = await logicService.testTimer({
body,
});
return result;
}
/**
* 更新接口缓存
*/
async updateCronCache() {
const body = this.toPlainJSON();
const result = await logicService.updateCronCache({
body,
});
return result;
}
}
export default Logic;