import { immutable, circular, excludedInJSON, action } from '../decorators'; import { config, utils, LEVEL_ENUM, Vertex, MicroService, Logic, Service, ActionOptions, isBasicType, } from '..'; import { interfaceService } from '../../service/data'; import { findUsageService } from '../../service/common'; /** * 接口类 */ export class Interface extends Vertex { /** * 概念类型 */ @immutable() public readonly level: LEVEL_ENUM = LEVEL_ENUM.interface; /** * Id */ @immutable() public readonly id: string = undefined; /** * InterfaceKey */ @immutable() public readonly key: string = undefined; /** * 名称 */ @immutable() public readonly name: string = undefined; /** * 协议 */ @immutable() public readonly protocol: string = undefined; /** * 地址 */ @immutable() public readonly host: string = undefined; /** * 端口 */ @immutable() public readonly port: string = undefined; /** * 路径 */ @immutable() public readonly path: string = undefined; /** * 方法 */ @immutable() public readonly method: string = undefined; /** * 描述 */ @immutable() public readonly description: string = undefined; /** * 请求数据 */ @excludedInJSON() @immutable() public exportedInterface: Interface = undefined; /** * 逻辑 Id */ @immutable() public readonly logicId: string = undefined; /** * 逻辑 * @TODO 使用 excludedInJSON 有点问题,不同 class 相同字段会串 */ @excludedInJSON() @immutable() public logic: Logic = undefined; /** * Service 类型 */ @immutable() public readonly serviceType: 'micro' | 'entity' | 'process' | 'processComponent' | 'external' | 'export' = undefined; /** * Service Id */ @immutable() public readonly serviceId: string = undefined; /** * Service Name */ @immutable() public readonly category: string = undefined; /** * 所属实体 Id */ public entityId: string = undefined; /** * Service 名称 */ @immutable() public readonly serviceName: string = undefined; /** * Service */ @circular() @immutable() public readonly service: MicroService = undefined; /** * 参数 */ @immutable() public parameters: string = undefined; /** * 返回值 */ @immutable() public responses: string = undefined; /** * 请求数据 */ @immutable() public requestBody: string = undefined; /** * 树组件的子节点字段 */ @excludedInJSON() @immutable() public readonly moreChildrenFields: Array = ['logic.params', 'logic.returns', 'logic.variables']; /** * 周边存在的名称 */ @excludedInJSON() public existingNames: Array = []; /** * @param source 需要合并的部分参数 */ constructor(source?: Partial) { super(); source && this.assign(source); } /** * 按当前 id 加载接口数据 * @requires this.id */ async load() { const result = await interfaceService.loadDetail({ path: { id: this.id, }, }); this.assign(result); return this; } /** * 添加接口 */ @action('添加接口') async create(none?: void, actionOptions?: ActionOptions, then?: () => Promise) { config.defaultApp?.emit('saving'); if (!this.logicId) { this.logic.assign({ name: this.name }); await this.logic.create(); this.assign({ path: this.path || '/api/' + this.name, logicId: this.logic.id, }); } const body = this.toPlainJSON(); body.logicId = this.logicId || this.logic.id; utils.logger.debug('添加接口', body); const result: Interface = await interfaceService.create({ headers: { appId: config.defaultApp?.id, operationAction: actionOptions?.actionName || 'Interface.create', operationDesc: actionOptions?.actionDesc || `添加接口"${this.name}"`, }, body, }); this.pick(result, ['id']); this.assign({ key: `#/${this.service.id}/${this.id}`, }); await then?.(); this.service.emit('interfacesChange'); this.service.emit('vertexIdToNameChange', this.id, this.name); await config.defaultApp?.history.load(); config.defaultApp?.emit('saved'); return this; } /** * 删除接口 */ @action('删除接口') async delete(none?: void, actionOptions?: ActionOptions) { config.defaultApp?.emit('saving'); if (this.logic) await this.logic.delete(); if (this.id) { await interfaceService.delete({ headers: { appId: config.defaultApp?.id, operationAction: actionOptions?.actionName || 'Interface.delete', operationDesc: actionOptions?.actionDesc || `删除接口"${this.name}"`, }, path: { id: this.id, }, }); const index = this.service.interfaces.indexOf(this); ~index && this.service.interfaces.splice(index, 1); this.service.globalLogic.removeInterface(this); this.destroy(); this.service.syncStructures(); // 处理 CallQueryComponent 嵌套的情形 this.service.emit('interfacesChange'); await config.defaultApp?.history.load(); config.defaultApp?.emit('saved'); } else { const index = this.service.interfaces.indexOf(this); ~index && this.service.interfaces.splice(index, 1); this.service.globalLogic.removeInterface(this); } } /** * 更新接口 */ async update(none?: void, actionOptions?: ActionOptions, then?: () => Promise) { config.defaultApp?.emit('saving'); const body = this.toPlainJSON(); await interfaceService.update({ headers: { appId: config.defaultApp?.id, operationAction: actionOptions?.actionName || 'Interface.update', operationDesc: actionOptions?.actionDesc || `修改接口"${this.name}"`, }, body, }); await then?.(); await config.defaultApp?.history.load(); config.defaultApp?.emit('saved'); return this; } /** * 设置接口名称 */ @action('设置接口名称') async setName(name: string) { const originalData = { name: this.name, path: this.path, }; this.assign({ name, path: '/api/' + name }); try { await this.update(undefined, { actionDesc: '设置接口名称', }); this.service.emit('interfacesChange'); this.service.emit('vertexIdToNameChange', this.id, this.name); } catch (err) { this.assign(originalData); throw err; } } /** * 设置接口描述 */ @action('设置接口描述') async setDescription(description: string) { this.assign({ description }); await this.update(undefined, { actionDesc: '设置接口描述', }); this.service.emit('interfacesChange'); } /** * 设置接口路径 */ @action('设置接口路径') async setPath(path: string) { this.assign({ path }); await this.update(undefined, { actionDesc: '设置接口路径', }); this.service.emit('interfacesChange'); } /** * 设置接口方法 */ @action('设置接口方法') async setMethod(method: string) { this.assign({ method }); await this.update(undefined, { actionDesc: '设置接口方法', }); this.service.emit('interfacesChange'); } /** * 设置接口参数和返回值类型 */ @action('设置接口参数和返回值') async setParamAndReturn() { if (this.serviceType === 'micro' && this.exportedInterface) { const logic = this.logic; const exportedInterface = this.exportedInterface; let bodyParameters: Array = []; if (exportedInterface.requestBody) { const body = JSON.parse(exportedInterface.requestBody); const schema = body?.content['application/json']?.schema; const name = body?.description?.name || 'content'; const required = body?.required; bodyParameters = [{ name, schema, in: 'body', required, defaultValue: '', }]; } const params: Array = Object.values(JSON.parse(exportedInterface.parameters || '{}')).map((p: utils.ParamStruts) => ({ name: p.name, schema: p.schema, in: p.in, required: true, defaultValue: '', })); const parameters = params.concat(bodyParameters).filter((p) => !!p); const finnalParameter: Array = []; if (logic) { logic.params.forEach((p) => { const finded = parameters.find((param) => param.name === p.name); const isBasic = isBasicType(p.schema); if (finded) { finnalParameter.push({ name: p.name, schema: p.schema, in: isBasic ? 'query' : 'body', required: finded.required, defaultValue: finded.defaultValue, }); } else { finnalParameter.push({ name: p.name, schema: p.schema, in: isBasic ? 'query' : 'body', required: !isBasic, defaultValue: '', }); } }); } let finnalResponses = ''; if (logic.returns.length > 0) { const p = logic.returns[0]; const payload = { description: { name: p.name, }, content: { 'application/json': { schema: p.schema, }, }, }; finnalResponses = JSON.stringify({ 200: payload, }); } const { parameters: finnalP, requestBody: finnalB, }: utils.resolvedParameter = utils.resolveParameter(finnalParameter); exportedInterface.parameters = finnalP; exportedInterface.requestBody = finnalB; exportedInterface.responses = finnalResponses; await exportedInterface.update(); } this.service.emit('interfacesChange'); } /** * 查找引用 */ @action('查找引用') async findUsage() { let id = this.id; if (['entity', 'micro', 'process', 'processComponent'].includes(this.serviceType)) { id = this.logicId; } const result = await findUsageService.findUsage({ query: { id, }, }); return result; } /** * 从后端 JSON 生成规范的 Interface 对象 */ public static from(source: any, service: Service) { const item = new Interface(source); item.assign({ service, expanded: false, key: `#/${service.id}/${item.id}`, }); item.logic && item.assign({ logic: Logic.from(item.logic, item) }); return item; } } export default Interface;