@lcap/asl
Version:
NetEase Application Specific Language
921 lines (846 loc) • 30.6 kB
text/typescript
import { immutable, circular, action, excludedInJSON } from '../decorators';
import { Logic, config, LEVEL_ENUM, Vertex, App, Page, DataNode, GlobalLogicNode, Schema, Entity, EntityProperty, Structure, Enum, EnumItem, Interface, dataTypesMap, generator, vertexsMap, Process, EntityIndex } from '..';
import appService from '../../service/app';
import pageService from '../../service/page';
import webFileService from '../../service/webFile';
import processService from '../../service/process';
import { entityService, enumService, interfaceService, structureService } from '../../service/data';
import { convert2RefType } from '../data/dataTypeUtils';
import { updateDataTypeList } from '../data/dataTypes';
import { throttle } from 'lodash';
export enum SERVICE_TYPE {
web = 'web',
micro = 'micro',
}
export interface AssetsInfo {
js: Array<string>,
css: Array<string>,
names?: Array<string>,
}
interface InterfaceNameMap<T> {
[id: string]: T;
}
let vertexIdToName: { [pro: string]: string };
/**
* 服务(模块)类
*/
export class Service extends Vertex {
/**
* 概念类型
*/
public readonly level: LEVEL_ENUM = LEVEL_ENUM.service;
/**
* 服务类型
*/
public readonly type: SERVICE_TYPE = undefined;
/**
* Service Id
*/
public readonly id: string = undefined;
/**
* 前端服务标识
*/
public readonly name: string = undefined;
/**
* 前端服务标题
*/
public readonly title: string = undefined;
/**
* 服务图标
*/
public readonly icon: string = undefined;
/**
* App 的 Id
*/
public readonly appId: string = undefined;
/**
* 数据节点
*/
public readonly data: DataNode = undefined;
/**
* 接口列表
*/
public readonly interfaces: Array<Interface> = [];
/**
* 逻辑节点 http://doc.hz.netease.com/pages/viewpage.action?pageId=310549021
*/
public readonly globalLogic: GlobalLogicNode = undefined;
/**
* 所在的 App
* 父级引用,便于在树状结构中查找
*/
public readonly app: App = undefined;
/**
* 节点是否为展开状态
* 前端 UI 状态
*/
public expanded: boolean = true;
/**
* @param source 需要合并的部分参数
*/
constructor(source?: Partial<Service>) {
super();
this.data = new DataNode({ service: this });
this.globalLogic = new GlobalLogicNode({ service: this });
source && this.assign(source);
}
async loadStructures(): Promise<Array<Structure>> {
const structuresOrigin: Array<Structure> = await structureService.loadList({
query: {
id: this.id,
},
});
const structures: Array<Structure> = [];
structuresOrigin.forEach((item) => {
const structureNode = Structure.from(item, this);
if (structureNode.serviceType === 'external' && structureNode.category) {
this.globalLogic.addCategoryStructure(structureNode);
} else {
structures.push(structureNode);
}
});
updateDataTypeList();
this.data.assign({ structures });
this.globalLogic.assign({ structures });
return structures;
}
async syncStructures() {
// this.data.structures.forEach((structure) => {
// delete dataTypesMap[structure.schemaRef];
// });
// const oldStructuresMap: Map<string, Structure> = new Map();
// this.data.structures.forEach((structure, index) => oldStructuresMap.set(structure.id, structure));
const newStructures: Array<Structure> = await structureService.loadList({
query: {
id: this.id,
},
});
this.globalLogic.syncStructures(newStructures);
// newStructures.forEach((item, index) => {
// const newStructure = Structure.from(item, this);
// if (oldStructuresMap.has(newStructure.id)) {
// const oldIndex = this.data.structures.indexOf(oldStructuresMap.get(newStructure.id));
// this.data.structures.splice(oldIndex, 1, newStructure);
// oldStructuresMap.delete(newStructure.id);
// } else {
// this.data.structures.push(newStructure);
// }
// });
// // Map 直接 forEach 会有问题
// const oldRemains = Array.from(oldStructuresMap, ([id, index]) => ({ id, index }));
// oldRemains.forEach((item) => {
// const structure = oldStructuresMap.get(item.id);
// delete dataTypesMap[structure.schemaRef];
// const oldIndex = this.data.structures.indexOf(structure);
// this.data.structures.splice(oldIndex, 1);
// structure.destroy();
// });
// updateDataTypeList();
// this.globalLogic.assign({ structures: this.data.structures });
return this.data.structures;
}
/**
* 加载服务所有的接口列表
*/
async loadInterfaces() {
const RESOLVER_NAMES = ['getAll', 'get', 'create', 'update', 'delete', 'count', 'import', 'export', 'batchDelete', 'batchCreate', 'batchUpdate'];
let interfaces: Array<Interface> = await interfaceService.loadList({
query: {
serviceId: this.id,
},
});
interfaces = interfaces.map((item) => {
item = Interface.from(item, this);
if (item.logic && item.logic.entityId) {
const entity = Entity.getVertexByRef(item.logic.entityId) as Entity;
if (entity)
entity.assign({ resolvers: entity.resolvers || [] });
const resolverName = RESOLVER_NAMES.find((name) => item.name.startsWith(name));
if (entity)
entity.resolvers.push({
level: 'resolver',
name: resolverName,
interface: item,
entity,
isLeaf: true,
});
}
return item;
});
this.assign({ interfaces });
return interfaces;
}
}
/**
* 前端服务(模块)类
*/
export class WebService extends Service {
/**
* 服务类型
*/
public readonly type: SERVICE_TYPE = SERVICE_TYPE.web;
/**
* 服务状态
*/
public readonly status: 'UNPUBLISHED' | 'PUBLISHED' | 'REPOSITORYCREATED' | 'FSSTARTED' | 'APPINIT' | 'CODESYNCED' | 'CICDCREATED' = undefined;
/**
* 是否为官方服务
*/
public readonly officialType: 'official' | 'nonofficial' = undefined;
public readonly dnsAddr: string = undefined;
/**
* packageJSON 的缩减版
*/
public readonly packageInfo: {
template: { name: string, version: string },
ui: { name: string, version: string },
componentDependencies: { [name: string]: string },
themeVariables: { [name: string]: string },
} = undefined;
/**
* 其他配置
*/
public readonly config: string = undefined;
public readonly api: {
features: { [name: string]: boolean };
builtInFunctions: { [name: string]: any };
} = undefined;
/**
* 前端页面
*/
public readonly pages: Array<Page> = [];
/**
* @param source 需要合并的部分参数
*/
constructor(source?: Partial<WebService>) {
super();
source && this.assign(source);
this.on('pageTreeChange', throttle(
this._onPageTreeChange.bind(this),
config.throttleWait,
{ leading: true, trailing: true },
));
this.on('vertexIdToNameChange', () => {
this.app.firstMicroService.emit('vertexIdToNameChange');
});
}
async load() {
const result = await appService.loadWebService({
query: {
appId: this.id,
},
});
// result.id = result.appId;
this.assign(result);
return this;
}
async loadPackageInfo() {
try {
const res = await webFileService.loadFile({
query: {
serviceId: this.id,
path: `package.partial.json`,
},
});
if (res.content) {
this.assign({ packageInfo: JSON.parse(res.content) });
}
} catch (e) { } finally {
if (!this.packageInfo) {
this.assign({
packageInfo: {
template: {
name: 'cloud-admin-template',
version: '0.2.23',
},
ui: {
name: 'cloud-ui.vusion',
version: '0.11.30',
},
componentDependencies: {
'lcap-login': '0.3.5',
},
themeVariables: {},
},
});
}
}
}
async savePackageInfo() {
const serviceId = this.id;
const path = `package.partial.json`;
const promise = webFileService.saveFile({
body: {
serviceId,
type: 'config',
path,
content: JSON.stringify(this.packageInfo),
},
});
generator.saveLastModified(serviceId, path);
return promise;
}
/**
* 根据 packageInfo 的信息生成用于 load 的 assetsInfo
* 在环境中需要 basic 和 custom 分开 load,否则容易报错
* @param prefix
*/
genAllAssetsInfo(prefix: string) {
const packageInfo = this.packageInfo;
const scope = this.app.scope;
const result: {
basic: AssetsInfo,
custom?: AssetsInfo,
} = {
basic: {
js: [
`${prefix}/packages/vue@2/dist/vue.min.js`,
// `${prefix}/packages/vue-router@3/dist/vue-router.min.js`,
scope === 'h5' ? `${prefix}/packages//mobile-ui@${packageInfo.ui.version}/dist-theme/index.js` : `${prefix}/packages/cloud-ui.vusion@${packageInfo.ui.version}/dist-theme/index.js`,
scope === 'h5' ? `${prefix}/packages//mobile-template@${packageInfo.template.version}/cloudAdminDesigner.umd.min.js` : `${prefix}/packages/lcap-pc-template@${packageInfo.template.version}/cloudAdminDesigner.umd.min.js`,
],
css: [
scope === 'h5' ? `${prefix}/packages//mobile-ui@${packageInfo.ui.version}/dist-theme/index.css` : `${prefix}/packages/cloud-ui.vusion@${packageInfo.ui.version}/dist-theme/index.css`,
scope === 'h5' ? `${prefix}/packages//mobile-template@${packageInfo.template.version}/cloudAdminDesigner.css` : `${prefix}/packages/lcap-pc-template@${packageInfo.template.version}/cloudAdminDesigner.css`,
],
},
custom: {
js: [],
css: [],
names: [],
},
};
Object.keys(packageInfo.componentDependencies).forEach((name) => {
const version = packageInfo.componentDependencies[name].replace(/^[^0-9]+/, '');
result.custom.js.push(`${prefix}/packages/${name}@${version}/dist-theme/index.js`);
result.custom.css.push(`${prefix}/packages/${name}@${version}/dist-theme/index.css`);
result.custom.names.push(name);
});
return result;
}
genThemeCSS() {
const themeVariables: { [name: string]: string } = this.packageInfo.themeVariables || {};
const keys = Object.keys(themeVariables);
if (!keys.length)
return;
return `:root {
${Object.keys(themeVariables).map((key) => ` ${key}: ${themeVariables[key]};`).join('\n')}
}
`;
}
async loadPages() {
const result: Array<Page> = await pageService.loadPages({
query: {
serviceId: this.id,
},
config: {
mock: config.mock,
},
});
const pages = result.map((page) => Page.from(page, this));
this.assign({ pages });
return pages;
}
async addPage(page: Page) {
page.assign({ serviceId: this.id });
await page.create(undefined, undefined, async () => {
page.assign({ service: this });
this.pages.unshift(page);
});
return page;
}
async importPage(page: Page) {
page.assign({ serviceId: this.id });
await page.importPage(undefined, undefined, async () => {
page.assign({ service: this });
this.pages.unshift(page);
});
return page;
}
private _onPageTreeChange() {
generator.savePageTreeCache(this);
}
/**
* 从后端 JSON 生成规范的 WebService 对象
* @param source JSON
* @param app 父级 App
*/
public static from(source: any, app: App) {
source.app = app;
// if (!source.packageInfo) {
// source.packageInfo = {
// template: {
// name: 'cloud-admin-template',
// version: '0.2.22',
// },
// ui: {
// name: 'cloud-ui.vusion',
// version: '0.11.23',
// },
// componentDependencies: {
// 'lcap-login': '^0.3.3',
// },
// };
// } else if (typeof source.packageInfo === 'string')
// source.packageInfo = JSON.parse(source.packageInfo);
return new WebService(source);
}
}
/**
* 后端服务(模块)类
*/
export class MicroService extends Service {
/**
* 服务类型
*/
public readonly type: SERVICE_TYPE = SERVICE_TYPE.micro;
/**
* 包名
*/
public readonly packageName: string;
/**
* 服务端口
*/
public readonly serverPort: number;
/**
* 接口列表
*/
public readonly interfaces: Array<Interface> = [];
/**
* 流程列表
*/
public readonly processes: Array<Process> = [];
// logics: { [name]: Logic };
/**
* @param source 需要合并的部分参数
*/
constructor(source?: Partial<MicroService>) {
super();
source && this.assign(source);
this.on('dataTypesChange', throttle(
this._onDataTypesChange.bind(this),
config.throttleWait,
{ leading: true, trailing: true },
));
this.on('enumsChange', throttle(
this._onEnumsChange.bind(this),
config.throttleWait,
{ leading: true, trailing: true },
));
this.on('interfacesChange', throttle(
this._onInterfacesChange.bind(this),
config.throttleWait,
{ leading: true, trailing: true },
));
this._onVertexIdToNameChange = throttle(
this._onVertexIdToNameChange,
config.throttleWait,
{ leading: true, trailing: true },
);
this.on('vertexIdToNameChange', this._onVertexIdToNameChange.bind(this));
this._saveVertexIdToName = throttle(
async () => {
await generator.saveVertexIdToName(this.app);
vertexIdToName = generator.getVertexIdToName();
},
config.throttleWait,
{ leading: true, trailing: true },
);
}
private async _saveVertexIdToName() { }
async loadEntities() {
const entities: Array<Entity> = await entityService.loadList({
query: {
microServiceId: this.id,
},
});
entities.forEach((item, index) => {
convert2RefType(item);
entities[index] = item = new Entity(item);
item.assign({
dataNode: this.data,
schemaRef: `#/${this.id}/${item.id}`,
// schemaNameRef: `#/${this.name}/entities/${entity.name}`
});
const lastVersionRef = item.lastVersionDef && item.lastVersionDef.properties || {};
const propertyList = item.propertyList as Array<EntityProperty>;
propertyList.sort((a, b) => (a as any)._posIndex - (b as any)._posIndex);
propertyList.forEach((property, index) => {
property = propertyList[index] = new EntityProperty(property);
if (lastVersionRef[property.name]) {
property.assign({ lastVersion: convert2RefType(lastVersionRef[property.name]) });
}
property.assign({ root: item });
});
item.assign({ resolvers: [] });
const indexList = item.indexList as Array<EntityIndex>;
indexList.forEach((indexItem, index) => {
indexItem = indexList[index] = new EntityIndex(indexItem);
indexItem.assign({ root: item });
});
dataTypesMap[item.schemaRef] = item as Schema;
});
updateDataTypeList();
this.data.assign({ entities });
this.mountResolverOnEntity();
return entities;
}
/**
* 添加实体
* @param name 实体名称
*/
async addEntity(name: string): Promise<Entity>;
/**
* 添加实体
* @param entityOptions 实体参数
*/
async addEntity(entityOptions: Partial<Entity>): Promise<Entity>;
/**
* 添加实体
* @param entity 已有的实体实例
*/
async addEntity(entity: Entity): Promise<Entity>;
async addEntity(name: string | Partial<Entity> | Entity) {
const entityOptions = {
dataNode: this.data,
serviceId: this.id,
serviceType: this.type,
};
let entity: Entity;
if (typeof name === 'string') {
entity = new Entity(Object.assign(entityOptions, {
name,
}));
} else if (name instanceof Entity) {
entity = name;
} else {
entity = new Entity(Object.assign(entityOptions, name));
}
entity.loading = true;
try {
await entity.create();
entity.loading = false;
if (!this.data.entities.includes(entity))
this.data.entities.push(entity);
entity.propertyList.forEach((property) => property.loading = false);
this.globalLogic.syncEntityLogics();
} catch (err) {
const index = this.data.entities.indexOf(entity);
~index && this.data.entities.splice(index, 1);
}
return entity;
}
/**
* 删除实体
* @param name 实体名称
*/
async removeEntity(name: string): Promise<void>;
/**
* 删除实体
* @param id 实体 Id
*/
async removeEntity(id: string): Promise<void>;
/**
* 删除实体
* @param entity 已有的实体实例
*/
async removeEntity(entity: Entity): Promise<void>;
async removeEntity(name: string | Entity) {
let entity: Entity;
if (typeof name === 'string') {
entity = this.data.entities.find((item) => item.name === name || item.id === name);
if (!entity)
throw new Error('找不到实体 ' + name);
} else {
entity = name;
}
entity.loading = true;
await entity.delete();
entity.loading = false;
this.globalLogic.syncEntityLogics();
}
/**
* 加载所有枚举
*/
async loadEnums() {
const enums: Array<Enum> = await enumService.loadList({
query: {
microServiceId: this.id,
},
});
enums.forEach((item, index) => {
convert2RefType(item as any);
enums[index] = item = new Enum(item);
item.assign({
dataNode: this.data,
schemaRef: `#/${this.id}/${item.id}`,
// schemaNameRef: `#/${this.name}/enums/${entity.name}`
});
item.enumItemList.forEach((enumItem, index) => {
enumItem = item.enumItemList[index] = new EnumItem(enumItem);
enumItem.assign({ root: item });
});
dataTypesMap[item.schemaRef] = item as any;
});
updateDataTypeList();
this.data.assign({ enums });
return enums;
}
/**
* 加载所有流程
*/
async loadProcesses() {
const result: Array<Process> = await processService.loadAll({
query: {
serviceId: this.id,
},
});
const processes = result.map((process) => Process.from(process, this));
this.assign({ processes });
this.mountResolverOnProcess();
return processes;
}
mountResolverOnProcess() {
this.globalLogic.assign({ processLogics: this.processes });
}
/**
* 导入sql或其他需要同步entity
*/
async syncEntities() {
const entities: Array<Entity> = await entityService.loadList({
query: {
microServiceId: this.id,
},
});
const newEntities: Array<Entity> = [];
const syncEntities: Array<Entity> = [];
entities.forEach((item, index) => {
// 已存在的entity直接使用
const tempEntity = <Entity>vertexsMap.get(item.id);
if (tempEntity) {
newEntities[index] = tempEntity;
} else {
convert2RefType(item);
newEntities[index] = item = new Entity(item);
item.assign({
dataNode: this.data,
schemaRef: `#/${this.id}/${item.id}`,
});
const propertyList = item.propertyList as Array<EntityProperty>;
propertyList.sort((a, b) => (a as any)._posIndex - (b as any)._posIndex);
propertyList.forEach((property, index) => {
property = propertyList[index] = new EntityProperty(property);
property.assign({ root: item });
});
item.assign({ resolvers: [] });
const indexList = item.indexList as Array<EntityIndex>;
indexList.forEach((indexItem, index) => {
indexItem = indexList[index] = new EntityIndex(indexItem);
indexItem.assign({ root: item });
});
dataTypesMap[item.schemaRef] = item as Schema;
syncEntities.push(item);
}
});
this.data.assign({ entities: newEntities });
const tasks = syncEntities.map((entity) => Promise.all([
// entity.syncStructures(),
entity.syncInterfaces(),
]));
await Promise.all(tasks).then(() => {
updateDataTypeList();
this._onDataTypesChange();
this._saveVertexIdToName();
});
return newEntities;
}
async syncEntityPropertyVersion() {
const entities: Array<Entity> = await entityService.loadList({
query: {
microServiceId: this.id,
},
});
entities.forEach((item, index) => {
const tempEntity = <Entity>vertexsMap.get(item.id);
if (tempEntity) {
tempEntity.assign({ lastVersionDef: item.lastVersionDef });
}
const lastVersionRef = item.lastVersionDef && item.lastVersionDef.properties || {};
const propertyList = tempEntity.propertyList as Array<EntityProperty>;
propertyList.forEach((property, index) => {
property = <EntityProperty>vertexsMap.get(property.id);
if (property && lastVersionRef[property.name]) {
property.assign({ lastVersion: convert2RefType(lastVersionRef[property.name]) });
}
});
});
}
/**
* 加载服务所有的接口列表
*/
async loadInterfaces() {
let interfaces: Array<Interface> = await interfaceService.loadList({
query: {
serviceId: this.id,
},
headers: {
IdeVersion: 2.3,
},
});
interfaces = interfaces.map((item) => Interface.from(item, this));
interfaces.forEach((i) => {
if (i.serviceType === 'export') {
const logic = Vertex.getVertexByRef(i.logicId) as Logic;
i.logic = logic;
}
});
this.assign({ interfaces });
this.mountResolverOnEntity();
this.mountResolverOnInterface();
// this.generateLogics();
return interfaces;
}
mountResolverOnEntity() {
const RESOLVER_NAMES = ['getAll', 'get', 'create', 'update', 'delete', 'count', 'import', 'export', 'batchDelete', 'batchCreate', 'batchUpdate'];
if (this.interfaces.length && this.data.entities.length) {
const entityLogics: Array<Entity> = [];
this.interfaces.forEach((item) => {
if (item.logic && item.logic.entityId) {
const entity = Entity.getVertexByRef(item.logic.entityId) as Entity;
if (entity)
entity.assign({ resolvers: entity.resolvers || [] });
const resolverName = RESOLVER_NAMES.find((name) => item.name.startsWith(name));
if (entity) {
const exist = entity.resolvers.find((r) => r.name === resolverName);
if (exist) {
exist.interface = item;
exist.entity = entity;
} else {
entity.resolvers.push({
level: 'resolver',
name: resolverName,
interface: item,
entity,
isLeaf: true,
});
}
item.entityId = entity.id;
}
}
});
this.data.entities.forEach((entity) => {
entityLogics.push(entity);
});
this.globalLogic.assign({ entityLogics });
}
}
mountResolverOnInterface() {
const globalLogics: Array<Interface> = [];
const interfaces: Array<Interface> = [];
const exportInterface: Array<Interface> = [];
const exportLogicId: InterfaceNameMap<Interface> = {};
// const categoryInterfaceMap = {};
// const categoryProccessMap = {};
this.interfaces.forEach((item) => {
if (item.serviceType === 'micro') {
globalLogics.push(item);
} else if (item.serviceType === 'external') {
const category = item.category;
if (!category) {
interfaces.push(item);
} else {
this.globalLogic.addCategoryInterface(item);
// if (!categoryInterfaceMap[category]) {
// categoryInterfaceMap[category] = [];
// }
// categoryInterfaceMap[category].push(item);
}
} else if (item.serviceType === 'export') {
exportInterface.push(item);
exportLogicId[item.logicId] = item;
}
});
// const categories = [];
// Object.keys(categoryInterfaceMap).forEach((category) => {
// categories.push({
// level: LEVEL_ENUM.category,
// name: category,
// categoryInterfaces: categoryInterfaceMap[category],
// categoryStructures: [],
// isLeaf: false,
// expanded: false,
// });
// });
const exportLogicIdList = Object.keys(exportLogicId);
this.interfaces.forEach((item) => {
if (exportLogicIdList.includes(item.logicId)) {
item.exportedInterface = exportLogicId[item.logicId];
}
});
this.globalLogic.assign({ globalLogics, interfaces });
}
private _onDataTypesChange() {
config.webFileCache && generator.saveDataTypesCache(this.app);
}
private _onEnumsChange() {
config.webFileCache && generator.saveEnumsCache(this.app);
}
private _onInterfacesChange() {
config.webFileCache && generator.saveApisCache(this.app);
generator.saveInterfacesCache(this);
}
private async _onVertexIdToNameChange(id: string, name: string) {
if (!config.webFileCache)
return;
if (id && name) {
if (!vertexIdToName) {
vertexIdToName = await generator.loadVertexIdToName(this.app);
vertexIdToName = vertexIdToName || {};
}
if (vertexIdToName[id] === name)
return;
}
// id 和 name 都没传时,跳过上一步的判断逻辑,直接更新
if ((id && name) || (!id && !name)) {
this._saveVertexIdToName();
}
}
/**
* 从后端 JSON 生成规范的 MicroService 对象
* @param source JSON
* @param app 父级 App
*/
public static from(source: any, app: App) {
source.app = app;
return new MicroService(source);
}
}
export default Service;