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, css: Array, names?: Array, } interface InterfaceNameMap { [id: string]: T; } let vertexIdToName: { [pro: string]: string }; /** * 服务(模块)类 */ export class Service extends Vertex { /** * 概念类型 */ @immutable() public readonly level: LEVEL_ENUM = LEVEL_ENUM.service; /** * 服务类型 */ @immutable() public readonly type: SERVICE_TYPE = undefined; /** * Service Id */ @immutable() public readonly id: string = undefined; /** * 前端服务标识 */ @immutable() public readonly name: string = undefined; /** * 前端服务标题 */ @immutable() public readonly title: string = undefined; /** * 服务图标 */ @immutable() public readonly icon: string = undefined; /** * App 的 Id */ @immutable() public readonly appId: string = undefined; /** * 数据节点 */ @immutable() public readonly data: DataNode = undefined; /** * 接口列表 */ @immutable() public readonly interfaces: Array = []; /** * 逻辑节点 http://doc.hz.netease.com/pages/viewpage.action?pageId=310549021 */ @immutable() public readonly globalLogic: GlobalLogicNode = undefined; /** * 所在的 App * 父级引用,便于在树状结构中查找 */ @circular() @immutable() public readonly app: App = undefined; /** * 节点是否为展开状态 * 前端 UI 状态 */ @excludedInJSON() public expanded: boolean = true; /** * @param source 需要合并的部分参数 */ constructor(source?: Partial) { super(); this.data = new DataNode({ service: this }); this.globalLogic = new GlobalLogicNode({ service: this }); source && this.assign(source); } async loadStructures(): Promise> { const structuresOrigin: Array = await structureService.loadList({ query: { id: this.id, }, }); const structures: Array = []; 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 = new Map(); // this.data.structures.forEach((structure, index) => oldStructuresMap.set(structure.id, structure)); const newStructures: Array = 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 = 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 { /** * 服务类型 */ @immutable() public readonly type: SERVICE_TYPE = SERVICE_TYPE.web; /** * 服务状态 */ @immutable() public readonly status: 'UNPUBLISHED' | 'PUBLISHED' | 'REPOSITORYCREATED' | 'FSSTARTED' | 'APPINIT' | 'CODESYNCED' | 'CICDCREATED' = undefined; /** * 是否为官方服务 */ @immutable() public readonly officialType: 'official' | 'nonofficial' = undefined; @immutable() public readonly dnsAddr: string = undefined; /** * packageJSON 的缩减版 */ @immutable() public readonly packageInfo: { template: { name: string, version: string }, ui: { name: string, version: string }, componentDependencies: { [name: string]: string }, themeVariables: { [name: string]: string }, } = undefined; /** * 其他配置 */ @immutable() public readonly config: string = undefined; @immutable() public readonly api: { features: { [name: string]: boolean }; builtInFunctions: { [name: string]: any }; } = undefined; /** * 前端页面 */ @immutable() public readonly pages: Array = []; /** * @param source 需要合并的部分参数 */ constructor(source?: Partial) { 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/@lcap/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/@lcap/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/@lcap/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/@lcap/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 = 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; } @action('添加页面') 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; } @action('添加页面') 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 { /** * 服务类型 */ @immutable() public readonly type: SERVICE_TYPE = SERVICE_TYPE.micro; /** * 包名 */ @immutable() public readonly packageName: string; /** * 服务端口 */ @immutable() public readonly serverPort: number; /** * 接口列表 */ @immutable() public readonly interfaces: Array = []; /** * 流程列表 */ @immutable() public readonly processes: Array = []; // logics: { [name]: Logic }; /** * @param source 需要合并的部分参数 */ constructor(source?: Partial) { 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 = 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; 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; 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; /** * 添加实体 * @param entityOptions 实体参数 */ async addEntity(entityOptions: Partial): Promise; /** * 添加实体 * @param entity 已有的实体实例 */ async addEntity(entity: Entity): Promise; async addEntity(name: string | Partial | 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; /** * 删除实体 * @param id 实体 Id */ async removeEntity(id: string): Promise; /** * 删除实体 * @param entity 已有的实体实例 */ async removeEntity(entity: Entity): Promise; 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 = 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 = 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 = await entityService.loadList({ query: { microServiceId: this.id, }, }); const newEntities: Array = []; const syncEntities: Array = []; entities.forEach((item, index) => { // 已存在的entity直接使用 const tempEntity = 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; 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; 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 = await entityService.loadList({ query: { microServiceId: this.id, }, }); entities.forEach((item, index) => { const tempEntity = vertexsMap.get(item.id); if (tempEntity) { tempEntity.assign({ lastVersionDef: item.lastVersionDef }); } const lastVersionRef = item.lastVersionDef && item.lastVersionDef.properties || {}; const propertyList = tempEntity.propertyList as Array; propertyList.forEach((property, index) => { property = vertexsMap.get(property.id); if (property && lastVersionRef[property.name]) { property.assign({ lastVersion: convert2RefType(lastVersionRef[property.name]) }); } }); }); } /** * 加载服务所有的接口列表 */ async loadInterfaces() { let interfaces: Array = 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 = []; 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 = []; const interfaces: Array = []; const exportInterface: Array = []; const exportLogicId: InterfaceNameMap = {}; // 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;