UNPKG

goldpankit

Version:

GoldPanKit是一款极速研发套件,可在套件中快速构建各类技术框架和项目。开源作者可发布自己的项目,并为其设定金额,实现开源项目的盈利。

1,323 lines (1,304 loc) 54.3 kB
// 安装服务 const response = require('./constants/response') const service = require('./service') const serviceApi = require("./api/service"); const fs = require("./utils/fs"); const Const = require("./constants/constants"); const projectService = require("./project"); const projectDatabase = require("./project.database"); const object = require("./utils/object"); const serviceBuild = require("./service.build"); const mysql = require("./utils/db/mysql"); const serviceTranslator = require('./service.translator') const path = require('path') const ignore = require("ignore"); const env = require('../env').getConfig() const log = require('./utils/log') const InstallParameterHandler = require("./service.install.parameter.handler"); class Kit { constructor() { } /** * 安装服务或插件 * @param dto = { * projectId: '', // 当前选择的项目ID * database: '', // 当前选择的数据库名称 * space: '', * service: '', * plugin: '', * version: '', * variables: [] * } * @returns {Promise<void>} */ install (dto) { const isPlugin = dto.plugin != null return new Promise((resolve, reject) => { // 验证安装 const checkResult = this.#checkInstall(dto.projectId, dto.space, dto.service, dto.plugin != null) if (checkResult != null) { reject(checkResult) return } dto.operaType = 'INSTALL' this.#install(dto) .then(({ data, project, service, database, variables}) => { // 如果项目代码目录不存在,则创建 if (!fs.exists(project.codespace)) { log.debug(`the project code space '${project.codespace}' does not exist and will be created automatically.`) fs.createDirectory(project.codespace) log.debug(`project code space '${project.codespace}' is created.`) } // 写入文件 const diffFiles = fs.writeFiles(data.files, project, service, data.versionPath) // 写入配置 // 获取配置格式 const config = JSON.parse(JSON.stringify(Const.PROJECT_CONFIG_FILE_CONTENT)) // 获取项目配置 let projectConfig = projectService.getProjectConfigById(project.id) if (projectConfig != null) { object.merge(projectConfig, config) } // 安装的是服务 if (!isPlugin) { config.name = project.name config.space = dto.space config.service[dto.service] = { version: dto.version, variables: this.#getSimpleMainServiceVariables(dto.variables) } /** * 兼容2.0.0之前的services(插件配置列表) * 如果是2.0.0之前使用的项目,配置文件中的services用于存放插件配置,而新配置plugins目前为空 * 此时将services赋值给plugins */ if (projectConfig != null && projectConfig.services != null) { config.plugins = projectConfig.services } log.debug(`sync preset plugins to the project config...`) // 补充预置插件安装信息到到plugins中 for (const presetPlugin of data.presetPlugins) { if (config.plugins[presetPlugin.name] == null) { log.debug(` added ${presetPlugin.name}.`) } else { log.debug(` updated ${presetPlugin.name}.`) } config.plugins[presetPlugin.name] = { version: presetPlugin.version, variables: this.#getSimpleMainServiceVariables(presetPlugin.variables).map(variable => { variable.value = this.#getPresetPluginVariableValue(variable) return variable }) } } /* 同步预置插件,预置插件为项目所有已安装的插件(除了带有查询模型和表变量的插件),如果预置插件在项目已安装插件中已不存在,则需要删除配置文件中对该插件的配置 例如:已安装redis缓存,在做插件升级时,把redis去掉了,并选择了本地缓存插件,则需要删除配置文件中的redis配置 */ for (const localPluginName in config.plugins) { // 如果已安装的插件不是预置插件,则跳过 if (!this.#isPresetPlugin(config.plugins[localPluginName])) { continue } // 如果已安装的插件可能是预置插件,则验证在预置插件中是否存在,不存在则删除 if (data.presetPlugins.find(plugin => plugin.name === localPluginName) == null) { log.debug(` deleted ${localPluginName}.`) delete config.plugins[localPluginName] } } } // 安装的是插件 else { /** * 兼容2.0.0之前的main(服务配置) * 如果是2.0.0之前使用的项目,配置文件中的main用于存放服务配置,而新配置service目前为空 * 此时需要将main赋值给service,services赋值给plugins */ if (projectConfig != null && projectConfig.main != null) { config.service = projectConfig.main } if (projectConfig != null && projectConfig.services != null) { config.plugins = projectConfig.services } const pluginVariables = this.#getSimpleMainServiceVariables(dto.variables) // 获取原始的查询模型记忆参数和表记忆参数 const pluginConfig = config.plugins[dto.plugin] const qmValues = InstallParameterHandler.getPluginQueryModelParameters(dto.plugin, pluginVariables, pluginConfig == null ? null : pluginConfig['qm-values']) const tableValues = InstallParameterHandler.getPluginTableParameters(dto.plugin, pluginVariables, pluginConfig == null ? null : pluginConfig['table-values']) config.plugins[dto.plugin] = { version: dto.version, variables: pluginVariables } // 记录查询模型的插件变量,用于实现根据查询模型记忆并回写插件参数功能 if (qmValues != null) { config.plugins[dto.plugin]['qm-values'] = qmValues } // 记录表的插件变量,用于实现根据表记忆并回写插件参数功能 if (tableValues != null) { config.plugins[dto.plugin]['table-values'] = tableValues } } log.debug(`write kit.json...`) fs.createFile(projectService.getConfigPath(project.id), fs.toJSONFileString(config), true) // 写入数据库配置文件 const dbConfig = projectDatabase.getProjectDatabaseConfigByIdWithDefaultBlankArray(project.id) // - 从变量中获取数据库参数 const databaseVariable = dto.variables.find(v=>v.inputType === 'datasource') if (databaseVariable != null && databaseVariable.value != null && databaseVariable.value !== '') { // 从全局数据库中找到数据库信息 const db = projectDatabase.getDatabase(project.id, databaseVariable.value) if (db != null) { // 如果数据库信息不存在,则添加 if (dbConfig.find(item => item.id === db.id) == null) { dbConfig.push(db) } } } log.debug(`write kit.db.json...`) fs.createFile(projectDatabase.getDatabaseConfigPath(project.id), fs.toJSONFileString(dbConfig), true) // 获取服务的构建详情(也可能是插件的构建详情) const builds = data.version.builds == null || data.version.builds === '' ? [] : JSON.parse(data.version.builds) const getBuildsDetailsPromises = [serviceBuild .getBuildDetails('INSTALL', project, builds, diffFiles, data.version.compiler, variables)] // 获取预置插件的构建详情 for (const presetPlugin of data.presetPlugins) { getBuildsDetailsPromises.push(serviceBuild .getBuildDetails('INSTALL', project, JSON.parse(presetPlugin.builds), diffFiles, presetPlugin.compiler, presetPlugin.variables)) } // 获取构建详情并返回 Promise.all(getBuildsDetailsPromises) .then(buildsList => { // 将服务构建(插件构建)和预置插件构建合并到一个数组中 const builds = buildsList.flat().map((build, index) => { return { ...build, index } }) // 返回构建信息 const result = { projectId: project.id, dataSourceId: database == null ? null : database.id, builds } resolve({ diff: { projectId: project.id, diffFiles }, build: result }) }) .catch(e => { reject(e) }) }) .catch(e => { log.error('install throw an exception', e) reject(e) }) }) } /** * 卸载插件 */ uninstall (dto) { return new Promise((resolve, reject) => { dto.operaType = 'UNINSTALL' // 获取项目配置 const project = projectService.findDetailById(dto.projectId) const configPath = projectService.getConfigPath(project.id) const projectConfig = fs.readJSONFile(configPath) const plugins = projectConfig.services || projectConfig.plugins const installedService = plugins[dto.plugin] if (installedService == null || installedService.version == null) { reject(response.UNINSTALL.PLUGIN_NOT_EXISTS) return } dto.version = installedService.version this.#install(dto) .then(({ data, project, database, variables}) => { // 获取构建详情并返回 const unbuilds = data.version.unbuilds == null || data.version.unbuilds === '' ? [] : JSON.parse(data.version.unbuilds) serviceBuild.getBuildDetails('UNINSTALL', project, unbuilds, data.files, data.version.compiler, variables) .then(builds => { builds = builds.map((build, index) => { return { ...build, index } }) // 删除文件 const diffFiles = fs.deleteFiles(data.files, project) // 删除项目配置中插件的配置 const plugins = projectConfig.services || projectConfig.plugins const targetPlugin = plugins[dto.plugin] // - 如果目标插件中不存在qm-values(查询模型的记忆参数)和table-values(单表的记忆参数),则直接删除插件 if (targetPlugin != null && targetPlugin['qm-values'] === undefined && targetPlugin['table-values'] === undefined) { delete plugins[dto.plugin] } // 存在查询模型记忆参数,将当前插件的变量赋值为目标模型的记忆参数,防止卸载后选中了其它模型 else if (targetPlugin != null && targetPlugin['qm-values'] != null) { const queryModelVariable = variables.find(v => v.inputType === 'query_model') if (queryModelVariable != null && queryModelVariable.value != null) { targetPlugin.variables = targetPlugin['qm-values'][queryModelVariable.value.modelId] } } // 重新写入项目配置文件中 fs.createFile(projectService.getConfigPath(project.id), fs.toJSONFileString(projectConfig), true) // 返回构建信息 const result = { projectId: project.id, dataSourceId: database == null ? null : database.id, builds } resolve({ diff: { projectId: project.id, diffFiles }, build: result }) }) .catch(e => { log.error('get builds throw an exception.', e) reject(e) }) }) .catch(e => { log.error('uninstall throw an exception.', e) reject(e) }) }) } /** * 编译服务代码 */ compile(dto) { return new Promise((resolve, reject) => { const checkResult = this.#checkInstall(dto.projectId, dto.space, dto.service, dto.plugin != null) if (checkResult != null) { reject(checkResult) return } this.#compile(dto) .then(data => { try { // 写入文件 const diffFiles = fs.writeFiles(data.files, data.project) // 获取服务的构建详情(也可能是插件的构建详情) const getBuildsDetailsPromises = [serviceBuild .getBuildDetails('COMPILE', data.project, data.serviceConfig.builds, diffFiles, data.serviceConfig.compiler, data.variables)] // 获取预置插件的构建详情 for (const presetPlugin of data.presetPlugins) { getBuildsDetailsPromises.push(serviceBuild .getBuildDetails('COMPILE', data.project, JSON.parse(presetPlugin.builds), diffFiles, presetPlugin.compiler, presetPlugin.variables)) } Promise.all(getBuildsDetailsPromises) .then(buildsList => { // 将服务构建(插件构建)和预置插件构建合并到一个数组中 const builds = buildsList.flat().map((build, index) => { return { ...build, index } }) // 返回构建信息 const result = { projectId: data.project.id, dataSourceId: data.database == null ? null : data.database.id, builds } resolve({ diff: { projectId: data.project.id, diffFiles }, build: result }) }) .catch(e => { reject(e) }) } catch (e) { log.error('compiled successfully, but write files throw an exception.', e) reject(e) } }) .catch(e => { log.error('compile throw an exception.', e) reject(e) }) }) } /** * 清空编译代码 */ cleanCompile(dto) { return new Promise((resolve, reject) => { this.#compile(dto) .then(data => { // 获取服务的构建详情(也可能是插件的构建详情) const getBuildsDetailsPromises = [serviceBuild .getBuildDetails('CLEAN_COMPILE', data.project, data.serviceConfig.unbuilds, data.files, data.serviceConfig.compiler, data.variables)] // 获取预置插件的构建详情 for (const presetPlugin of data.presetPlugins) { getBuildsDetailsPromises.push(serviceBuild .getBuildDetails('CLEAN_COMPILE', data.project, JSON.parse(presetPlugin.unbuilds), data.files, presetPlugin.compiler, presetPlugin.variables)) } // 获取构建详情并返回 Promise.all(getBuildsDetailsPromises) .then(buildsList => { // 将服务构建(插件构建)和预置插件构建合并到一个数组中 const builds = buildsList.flat().map((build, index) => { return { ...build, index } }) // 删除文件 const diffFiles = fs.deleteFiles(data.files, data.project) // 返回构建信息 const result = { projectId: data.project.id, dataSourceId: data.database == null ? null : data.database.id, builds } resolve({ diff: { projectId: data.project.id, diffFiles }, build: result }) }) .catch(e => { reject(e) }) }) .catch(e => { log.error('compile throw an exception.', e) reject(e) }) }) } /** * 检查安装 * @param projectId 安装的目标项目ID * @param space 需安装的服务空间名称 * @param service 需安装的服务名称 * @param isPlugin 安装的是否为插件 */ #checkInstall (projectId, space, service, isPlugin) { const project = projectService.findDetailById(projectId) // 插件 if (isPlugin) { // 项目中必须已安装服务 const projectService = project.service == null ? {} : project.service if (projectService[service] == null) { return '当前所选项目中没有该插件对应的服务,请安装服务后重试!' } return } // 服务 if (project == null) { return response.INSTALL.MISSING_PROJECT } // - 允许没有安装过任何服务 const serviceObject = project.main || project.service if (serviceObject === undefined) { return } // - 验证当前项目是否安装了别的主服务,如果是,则做出提醒 if (project.space !== space || (serviceObject != null && serviceObject[service] == null)) { return response.INSTALL.PROJECT_NOT_ALLOWED } } /** * 编译服务 * @param dto = { * projectId: '', // 当前选择的项目ID * database: '', // 当前选择的数据库名称 * space: '', * service: '', * plugin: '', // 如果存在,则为编译插件 * variables: [] * } */ #compile(dto) { return new Promise((resolve, reject) => { try { // 获取项目信息 const project = projectService.findDetailById(dto.projectId) if (project == null) { reject(new Error('请选择代码编译后输出的目标项目!')) return } // 获取项目安装的服务 let projectInstallService = null if (project.service != null) { projectInstallService = project.service[dto.service] } // 获取项目已安装的插件 let installedPlugins = [] if (project.plugins != null) { for (const pluginName in project.plugins) { installedPlugins.push({ name: pluginName }) } } // 获取服务信息 const serviceConfig = service.getServiceConfig({space: dto.space, service: dto.service, plugin: dto.plugin}) // 如果存在翻译器,则先进行翻译 if (serviceConfig.translator != null) { serviceTranslator.translate({ space: dto.space, service: dto.service, plugin: dto.plugin }) } // 获取文件列表 const files = this.#getFileConfigList(dto.space, dto.service, dto.plugin) if (files.length > env.limitFiles) { return reject(`编译失败,代码文件不能超过${env.limitFiles}个!`) } // 获取数据库信息 const database = projectDatabase.getDatabase(project.id, dto.database) // 组装变量 const variables = this.#getVariables(project, database, dto.variables) Promise.all(variables) .then(vars => { serviceApi.compile({ space: dto.space, service: dto.service, // 服务的主版本号,用于自动编译预置插件时根据主版本号查询最新的预置插件版本(v2.11.0增加) majorVersion: serviceConfig.version.split('.')[0], projectServiceVersion: projectInstallService == null ? null : projectInstallService.version, minServiceVersion: serviceConfig.minServiceVersion, defaultCompiler: serviceConfig.compiler, // 服务预置的插件,用于编译时自动编译预置插件(只有编译服务时才传递)(v2.11.0增加) presetPlugins: dto.plugin == null ? serviceConfig.presetPlugins : [], // 已安装的插件,用于获取框架插件安装情况(v2.11.0增加) installedPlugins, variables: vars, files }) .then(data => { resolve({ files: data.files, presetPlugins: data.presetPlugins, project, database, serviceConfig, variables: vars, }) }) .catch(e => { reject(e) }) }) .catch(e => { reject(e) }) } catch (e) { reject(e) } }) } /** * 安装服务 * @param dto = { * projectId: '', // 当前选择的项目ID * database: '', // 当前选择的数据库名称 * space: '', * service: '', * plugin: '', // 如果存在则为安装插件 * version: '', * variables: [] * } * @returns {Promise<void>} */ #install (dto) { try { const projectId = dto.projectId const project = projectService.findDetailById(projectId) const isPlugin = dto.plugin != null if (project == null) { return Promise.reject(response.INSTALL.MISSING_PROJECT) } // 获取项目安装的服务配置 let projectInstallService = null // - 首次安装时,项目中没有服务配置 if (project.service != null) { if (project.service[dto.service] == null) { return Promise.reject(response.INSTALL.PROJECT_NOT_ALLOWED) } projectInstallService = project.service[dto.service] } if (isPlugin) { log.debug(`ready to install plugin: ${dto.plugin}`) } else { log.debug(`ready to install framework: ${dto.service}`) } // 获取数据库信息 const database = projectDatabase.getDatabase(projectId, dto.database) // 组装变量 const variables = this.#getVariables(project, database, dto.variables, dto.plugin != null) let serviceVars = null // 获取预置插件 const presetPlugins = dto.plugin == null ? dto.plugins : [] // 获取项目已安装的插件 let installedPlugins = [] if (project.plugins != null) { for (const pluginName in project.plugins) { installedPlugins.push({ name: pluginName }) } } /* 重置已安装插件 服务:已安装插件 = 预置插件 插件:已安装插件 = 项目已安装的插件 */ installedPlugins = isPlugin ? installedPlugins : presetPlugins // debug if (!isPlugin) { log.debug(`framework preset plugins:${JSON.stringify(presetPlugins, null, 2)}`) log.debug(`project installed plugins:${JSON.stringify(installedPlugins, null, 2)}`) } else { log.debug(`project installed plugins:${JSON.stringify(installedPlugins, null, 2)}`) } return Promise.all(variables) .then(vars => { log.debug('variables ready to install service...', vars) serviceVars = vars // 执行安装 return serviceApi.install({ space: dto.space, // 安装的服务或者插件 service: dto.service, plugin: dto.plugin, version: dto.version, // 项目使用的服务版本,安装服务时应该为null projectServiceVersion: projectInstallService == null ? null : projectInstallService.version, // 服务预置的插件,用于编译时自动编译预置插件(只有编译服务时才传递)(v2.11.0增加) plugins: presetPlugins, /* 已安装的插件,用于获取框架插件安装情况(v2.11.0增加) 服务:如果是安装服务,则已安装插件为预置插件 插件:如果是安装插件,则已安装插件为服务中已安装的插件 */ installedPlugins, operaType: dto.operaType, variables: vars }) }) .then(data => { return Promise.resolve({ data, service: dto.plugin == null ? dto.service : dto.plugin, project, database, variables: serviceVars }) }) .catch(e => { return Promise.reject(e) }) } catch (e) { return Promise.reject(e) } } /** * 判断插件是否可以是预置插件 * * @param pluginConfig 插件配置 */ #isPresetPlugin (pluginConfig) { if (pluginConfig == null) { return false } const variables = pluginConfig.variables == null ? [] : pluginConfig.variables for (const variable of variables) { if (variable.inputType === 'table') { return false } if (variable.inputType === 'query_model') { return false } } return true } // 获取主服务简化变量 #getSimpleMainServiceVariables (variables) { return variables.map(v => { return { name: v.name, // 用于在初始化工作台时,获取到数据库变量,从而初始化选中项目数据库 inputType: v.inputType, value: this.#getInstallVariableValue(v) } }) } // 获取简化变量 #getInstallVariableValue (variable) { // 变量组 if (variable.type === 'group') { const installVariables = {} variable.children.forEach(v => { installVariables[v.name] = this.#getInstallVariableValue(v) }) return installVariables } /** * 表和查询模型 * 表和查询模型需要记录字段变量的内容,以实现初始化,存储结构为 * { * value: 选中的表或模型, * settings: { * 字段变量组1如查询字段queryFields: [选中的字段和字段变量值信息], * 字段变量组2如列表字段tableFields: [选中的字段和字段变量值信息] * } * } */ if (variable.inputType === 'table' || variable.inputType === 'query_model') { const settings = {} if (variable.children != null && variable.children.length > 0) { variable.children.map(group => { settings[group.name] = group.value.map(selectedField => { return { ...selectedField, // 清空非必要字段 // - type无需保存 type: undefined, // - 字段会有origin,表示原始字段信息 origin: undefined, // - 模型中的字段会有table,不能清空,否则无法标记出字段属于哪张表 // table: undefined, // - visible用于在SQL窗口中控制是否展示,此处需要优化,这个字段压根就不应该传过来,会导致字段中存在visible变量时被清空 visible: undefined } }) }) } return { value: variable.value, settings } } return variable.value } // 获取预置插件的简化变量值(预置插件的变量值是完整的变量数据,因为是从后端返回的,后端需要完整的变量数据) #getPresetPluginVariableValue (variable) { // 数据库,直接返回库ID if (variable.inputType === 'datasource') { return variable.value == null ? null : variable.value.id } return variable.value } // 获取文件配置列表 #getFileConfigList (space, serviceName, plugin) { const serviceConfig = service.getServiceConfig({ space: space, service: serviceName, plugin: plugin}) // 获取文件真实存放的路径 let fileStoragePath = serviceConfig.codespace if ((serviceConfig.translator.filepath != null && serviceConfig.translator.filepath !== '') || (serviceConfig.translator.content != null && serviceConfig.translator.content !== '')) { fileStoragePath = path.join(fileStoragePath, serviceConfig.translator.output) if (!fs.exists(fileStoragePath)) { fs.createDirectory(fileStoragePath, true) } } const fullpaths = fs.getFilesWithChildren(fileStoragePath) const configs = [] for (const fullpath of fullpaths) { // 获取文件配置 const relativePath = fs.getRelativePath(fullpath, fileStoragePath) const fileSettings = service.getFileSetting(serviceConfig.codespace, relativePath) // 构建文件对象 const isDirectory = fs.isDirectory(fullpath) const fileInfo = isDirectory ? { encode: null, content: null } : fs.readFile(fullpath) configs.push({ filepath: relativePath, content: fileInfo.content, contentEncode: fileInfo.encode, enableExpress: fileSettings.enableExpress, withoutIfNotExists: fileSettings.withoutIfNotExists, compiler: fileSettings.compiler, filetype: isDirectory ? 'DIRECTORY' : 'FILE', variables: JSON.stringify(fileSettings.variables) }) } return configs } // 获取安装/编译变量 #getVariables (project, database, variables, withMainServiceVariables = true) { // 补充服务变量 if (withMainServiceVariables) { const projectConfig = fs.readJSONFile(projectService.__getConfigPath(project.codespace)) // 获取安装的服务配置 let service = null if (projectConfig != null) { service = projectConfig.service || projectConfig.main } if (service != null) { let serviceName = null for (const name in service) { serviceName = name break } // 将项目主服务的变量添加到最前 const serviceVariables = service[serviceName].variables.reverse() for (const variable of serviceVariables) { // group,table和query_model安装存储的变量结构与生成时所需的结构不一样,暂不做支持 if (variable.type === 'group' || variable.inputType === 'table' || variable.inputType === 'query_model') { continue } variables.unshift(variable) } } } // 扩展变量 const extVariables = [] return variables.map(variable => { return new Promise(async (resolve, reject) => { try { // 如果类型为数据源,则查询出库信息 if (variable.inputType === 'datasource') { const databaseId = variable.value === undefined ? variable.defaultValue : variable.value const database = projectDatabase.getDatabase(project.id, databaseId) resolve({ ...variable, value: database }) return } // 输入类型为表,则查询出表信息 if (variable.inputType === 'table') { const tableName = variable.value === undefined ? variable.defaultValue : variable.value if (database == null || tableName == null || tableName === '') { resolve({ ...variable, value: null }) return } mysql.getTable({ host: database.host, port: database.port, user: database.username, password: database.password, database: database.schema }, variable.value === undefined ? variable.defaultValue : variable.value) .then(value => { log.debug('table info', value) // 补充动态字段,children为字段变量组 if (variable.children != null && variable.children.length > 0) { this.#paddingFieldVariablesWithResolve(project, database, variable, value, null, null, null, resolve, reject) return } resolve({ ...variable, value }) }) .catch(e => { reject(e) }) return } // 如果类型为查询模型,则查询出模型信息 if (variable.inputType === 'query_model') { // 获取模型ID,如果为编译代码,value为undefined const modelId = variable.value === undefined ? variable.defaultValue : variable.value if (modelId == null || modelId === '') { resolve({ ...variable, value: null }) return } // 从数据库中获取模型对象 const model = database.models.find(m => m.id === modelId) if (model == null) { return reject(`「${variable.label}」参数错误,找不到查询模型!`) } if (model.tables == null) { log.error(`missing field 'tables', please check the query model on the 'kit.db.json' for Model ID = '${modelId}'.`) return reject(`「${variable.label}」参数错误,模型缺少关联表!`) } // 获取数据库表(没有时会连接数据库) let tables = database.tables if (tables == null) { tables = await mysql.getTables({ host: database.host, port: database.port, user: database.username, password: database.password, database: database.schema }, true) database.tables = tables } /* 补充模型中的tables信息 模型中的表信息结构为{id, name, alias, type, fields, x, y},其中fields的结构为[{ name, alias, visible? }],在编译代码时需要完整的字段信息,所以需要补充字段信息 */ model.tables = model.tables.map(simpleTable => { // 在数据库表中未找到模型中的表 const tableDetail = tables.find(t => t.name === simpleTable.name) if (tableDetail == null) { return null } return { id: simpleTable.id, type: simpleTable.type, name: tableDetail.name, alias: simpleTable.alias, comment: tableDetail.comment, fields: tableDetail.fields.map(fieldDetail => { // 从模型中获取当前字段(获取不到时,说明数据库中当前表新增了字段) const simpleField = simpleTable.fields.find(f => f.name === fieldDetail.name) return { ...fieldDetail, // 标记为非虚拟字段(此字段暂不可缺少) isVirtual: false, // 为字段补充别名,默认为(表别名_字段名) alias: simpleField == null ? `${simpleTable.alias}_${fieldDetail.name}` : simpleField.alias } }) } }) // 如果模型中存在数据库不存在的表,作出提示 if (model.tables.filter(t => t == null).length > 0) { return reject(`「${variable.label}」参数所使用的「${model.name}」模型中包含了已被移除的数据库表,请确认模型是否正确!`) } // 找到主表 const mainTable = model.tables.find(t => t.type === 'MAIN') if (mainTable == null) { return reject(`「${variable.label}」参数所使用的「${model.name}」模型中缺少主表,请确认模型是否正确!`) } // 找到所有的子表 const subTables = model.tables.filter(t => t.type !== 'MAIN') // 补充并修复join信息(即补充join的表信息和字段信息,修复join的table和targetTable,让targetTable始终为被关联的表) const joins = this.#getPaddingAndRepairedJoins(model, mainTable, model.joins) const value = { // 增加查询模型ID,用于插件时根据模型ID查询记忆参数并赋值给插件参数 modelId: model.id, name: model.name, comment: model.comment, mainTable, subTables, // join关联 joins, // SQL语句 statement: this.#getQueryModelStatement(variable, model, mainTable, joins) } // 处理字段变量 if (variable.children != null && variable.children.length > 0) { this.#paddingFieldVariablesWithResolve(project, database, variable, value, model, mainTable, joins, resolve, reject) return } resolve({ ...variable, value }) return } // 如果为服务变量组,则修改子变量值 if (variable.type === 'group') { Promise.all(this.#getVariables(project, database, variable.children, false)) .then(vars => { resolve({ ...variable, children: vars }) }) .catch(e => { reject(e) }) return } // 如果输入类型为select,扩展出Settings选项设置变量 if (variable.inputType === 'select') { const value = variable.value === undefined ? variable.defaultValue : variable.value extVariables.push({ name: `${variable.name}Settings`, type: 'ext', // 标记为扩展变量 value: value.settings }) // select的存储结构为{value: null, settings: {}},所以value最终为value.value resolve({ ...variable, value: value.value }) return } // 其他 resolve({ ...variable, value: variable.value === undefined ? variable.defaultValue : variable.value }) } catch (e) { reject(e) } }) }).concat(extVariables) } /** * 获取补充并修复后的joins关系 * 补充:补充join的table和targetTable为具体的表信息;补充on中的table、targetTable、field、targetField为具体的表或字段信息 * 修复:使得join.targetTable一直为被关联的表,且targetTable在已修复的joins中不可重复(注意这里的重复指的是table.id不重复,使得可以关联多张相同的表) * e.g A.a1 => B.b1, B.b2 => C.c1,此时应得到joins为[{ table:A, targetTable:B }, {table: B, targetTable: C}],可的语句为JOIN B, JOIN C * @returns {*} */ #getPaddingAndRepairedJoins (model, mainTable, joins) { // 已修复的join let repairedJoins = [] /* 如果joins中没有主表,则视为没有关联关系 e.g 存在主表M1,子表S1和S2,S1和S2建立了关联关系,但并没有与M1建立关联关系,此时不产生SQL语句。因为SQL语句展示的是当前表的关联关系 */ if (!joins.find(join => join.table === mainTable.id || join.targetTable === mainTable.id)) { return [] } for (const join of joins) { // 此处只需复制join的引用,需要保留join内部对象的引用,避免表和字段发生变化时未能及时修改join中的信息 const copyJoin = { ...join } copyJoin.table = model.tables.find(t => t.id === join.table) copyJoin.targetTable = model.tables.find(t => t.id === join.targetTable) // 主表关联了子表,不做处理 if (copyJoin.table.id === mainTable.id) { repairedJoins.push(copyJoin) continue } // 子表关联了主表,则targetTable为主表,则将table作为targetTable(此时table为子表) if (copyJoin.targetTable.id === mainTable.id) { const targetTable = copyJoin.table copyJoin.table = copyJoin.targetTable copyJoin.targetTable = targetTable repairedJoins.push(copyJoin) continue } // 子表关联了子表,则判断已修复的joins中,是否存在当前targetTable,如果存在,则将table作为targetTable const existJoin = repairedJoins.find(join => join.targetTable.id === copyJoin.targetTable.id) if (existJoin) { const targetTable = copyJoin.table copyJoin.table = copyJoin.targetTable copyJoin.targetTable = targetTable } repairedJoins.push(copyJoin) } // 为join中的on补充表和字段信息 repairedJoins = repairedJoins.map(join => { join.ons = join.ons.map(on => { // 找到on的关联表和被关联表 const onTable = model.tables.find(t => t.id === on.table) const onTargetTable = model.tables.find(t => t.id === on.targetTable) if (onTable == null || onTargetTable == null) { return null } // 找到on的关联字段和被关联字段 const field = onTable.fields.find(f => f.name === on.field) const targetField = onTargetTable.fields.find(f => f.name === on.targetField) if (field == null || targetField == null) { return null } // 添加字段信息 return { ...on, table: onTable, targetTable: onTargetTable, field, targetField } }) // 过滤掉无效的on join.ons = join.ons.filter(on => on != null) // 如果没有有效的on,则返回null if (join.ons.length === 0) { return null } return join }) // 过滤掉无效的join repairedJoins = repairedJoins.filter(join => join != null) return repairedJoins } /** * 处理字段变量 * 输入类型为select的字段变量处理 * 输入类型为select的字段变量,得到的value格式为{value: null, settings: {}} * 所以需要修改字段值为value.value,并且为字段增加xxxSettings为value.settings * e.g * 根变量:table 表 * 字段变量组: 查询条件 queryFields * 变量1: 输入类型 inputType(输入类型为select) * 查询条件选择某个字段,那么这个字段会有inputType,数据结构如下 * { * name: 'table', * defaultValue: '已选的表名', * children: [ // 字段变量组 * { * name: 'queryFields', * defaultValue: [ // 已选的字段 * // 调整前 * { ... 已选字段信息1, inputType: { value: 'input', settings: {maxlength: 10} }}, * // 调整后 * { ... 已选字段信息1, inputType: 'input', inputTypeSettings: {maxlength: 10} } * ] * } * ] * } */ #paddingFieldVariablesWithResolve (project, database, variable, value, model, mainTable, joins, resolve, reject) { const groupPromises = [] if (variable.children != null && variable.children.length > 0) { for (const group of variable.children) { let selectedFields = group.value === undefined ? group.defaultValue : group.value // 如果查询模型不为空,说明是为模型补充动态变量组信息,此处用于过滤掉不存在的字段 if (model != null) { selectedFields = selectedFields .map(selectedField => { // 获取到字段所在的表信息 const fieldTable = model.tables.find(t => t.id === selectedField.table.id) // 获取在数据库中对应的字段信息 const dbField = fieldTable.fields.find(f => f.name === selectedField.name) // 如果表中该字段已被移除,则不做处理 if (dbField == null) { return null } // 字段的表一定存在于主表或joins表中,如果不存在,说明join已失效,那么对应的字段也需要失效 if ( fieldTable.id !== mainTable.id && // 不是主表 joins.find(join => join.table.id === fieldTable.id || join.targetTable.id === fieldTable.id) == null // 也不是joins中的表 ) { return null } // 在使用配置文件中的参数时,字段中是不含有type属性的,此处需要补充。 // 此处还存在引用问题,不可修改selectedField的引用(字段均不要修改引用) selectedField.type = dbField.type return selectedField }) .filter(field => field != null) } // 否则是为表补充动态变量组信息,此处用于过滤掉不存在的字段 else { const tableFields = value.fields // 过滤掉已被删除的字段 selectedFields = selectedFields .map(selectedField => { // 获取数据库表中对应的字段 const dbField = tableFields.find(f => f.name === selectedField.name) if (dbField == null) { return null } // 在使用配置文件中的参数时,字段中是不含有type属性的,此处需要补充。 // 此处还存在引用问题,不可修改selectedField的引用(字段均不要修改引用) selectedField.type = dbField.type return selectedField }) .filter(field => field != null) } value[group.name] = selectedFields // group.children为字段变量组中的变量设定(例如查询条件queryFields中的字段定义) groupPromises.push(Promise.all(this.#getVariables(project, database, group.children, false)) .then(vars => { group.children = vars return Promise.resolve(group) }) .catch(e => { return Promise.reject(e) }) ) } } Promise.all(groupPromises) .then(groups => { for (const group of groups) { /** * 输入类型为select的字段变量处理 * 输入类型为select的字段变量,得到的value格式为{value: null, settings: {}} * 所以需要修改字段值为value.value,并且为字段增加xxxSettings为value.settings */ for (const groupVariable of group.children) { if (groupVariable.inputType !== 'select') { continue } for (const field of value[group.name]) { const varValue = field[groupVariable.name] field[groupVariable.name] = varValue.value field[`${groupVariable.name}Settings`] = varValue.settings } } /** * 补充字段信息 * 所有字段增加sql,表示字段的sql语句 * 对于虚拟字段,补充聚合信息 */ if (model != null) { const tables = [value.mainTable, ...value.subTables] for (const field of value[group.name]) { /** * 非虚拟字段 */ if (!field.isVirtual) { let aliasSql = ` AS \`${field.alias}\`` if (field.name === field.alias) { aliasSql = '' } field.statements = `\`${field.name}\`${aliasSql}` continue } /** * 为字段补充SQL语句 * 拿到聚合信息。。。。 */ const agg = model.aggregates.find(agg => agg.field === field.name) if (agg != null) { const targetTable = tables.find(t => t.id === agg.targetTable) const targetField = targetTable.fields.find(f => f.name === agg.targetField) // 补充聚合信息 field.aggregate = { table: targetTable, field: targetField, function: agg.function } // 补充字段SQL field.statements = this.#getAggregateSQL(field, field.aggregate, model.joins) } } } resolve({ ...variable, value }) } }) .catch(e => { reject(e) }) } /** * 获取模型语句 * @param variable 模型变量 * @param model 模型信息 * @param mainTable 主表 * @param joins join关系列表 */ #getQueryModelStatement (variable, model, mainTable, joins) { let tableAlias = ` \`${mainTable.alias}\`` if (mainTable.alias === mainTable.name) { tableAlias = '' } const statement = { from: `FROM \`${mainTable.name}\`${tableAlias}`, joins: this.#getJoinSQL(mainTable, joins, ''), } // 虚拟表from语句为空 if (mainTable.isVirtual) { statement.from = null } // 动态添加字段变量组语句,例如queryFields,则可以通过queryModel.statement.queryFields来获得字段语句列表 if (variable.children != null && variable.children.length > 0) { for (const group of variable.children) { statement[group.name] = [] // 获取变量组已选中的字段 const selectedFields = group.value === undefined ? group.defaultValue : group.value for (let i = 0; i < selectedFields.length; i++) { const field = selectedFields[i] // 非虚拟字段 if (!field.isVirtual) { // 获取到字段所在的表信息 const fieldTable = model.tables.find(t => t.id === field.table.id) // 如果表中该字段已被移除,则不做处理 if (fieldTable.fields.find(f => f.name === field.name) == null) { continue } // 字段的表一定存在于主表或joins表中,如果不存在,说明join已失效,那么对应的字段也需要失效 if ( fieldTable.id !== mainTable.id && // 不是主表 joins.find(join => join.table.id === fieldTable.id || join.targetTable.id === fieldTable.id) == null // 也不是joins中的表 ) { continue } // 生成字段别名 let aliasSql = ` AS \`${field.alias}\`` if (field.name === field.alias) { aliasSql = '' } let sql = `\`${field.table.alias}\`.\`${field.name}\`${aliasSql}` if (i < selectedFields.length - 1) { sql += ',' } statement[group.name].push(sql) continue } // 虚拟字段 let agg = model.aggregates.find(agg => agg.field === field.name) // - 没有聚合信息 if (agg == null) { let sql = `\`${field.table.alias}\`.\`${field.name}\` AS \`${field.alias}\`` if (i < selectedFields.length - 1) { sql += ',' } statement[group.name].push(sql) continue } // - 存在聚合信息 const targetTable = model.tables.find(t => t.id === agg.targetTable) const targetField = targetTable.fields.find(f => f.name === agg.targetField) agg = { table: targetTable, field: targetField, function: agg.function } const lines = this.#getAggregateSQL(field, agg, joins) if (i < selectedFields.length - 1) { lines[lines.length - 1] += ',' } statement[group.name] = statement[group.name].concat(lines) } } } return statement } /** * 获取聚合语句 * @param field 聚合字段(虚拟字段) * @param aggregate 聚合信息 * @param joins 模型joins * @returns {*[]} */ #getAggregateSQL (field, aggregate, joins) { let sqlLines = [] sqlLines.push('(') sqlLines.push(` SELECT`) sqlLines.push(` ${aggregate.function}(\`${aggregate.table.alias}\`.\`${aggregate.field.name}\`)`) sqlLines.push(` FROM \`${aggregate.table.name}\` \`${aggregate.table.alias}\``) sqlLines = sqlLines.concat(this.#getJoinSQL(aggregate.table, joins, ' ')) sqlLines.push(`) AS \`${field.alias}\``) return sqlLines } /** * 获取表的join语句 * @param table 主表 * @param joins 模型joins * @param indent 缩进 * @returns {*[]} */ #getJoinSQL (table, joins, indent) { const joinLines = [] const copyJoins = JSON.parse(JSON.stringify(joins)) for (const join of joins) { let joinTableAlias = ` \`${join.targetTable.alias}\`` if (join.targetTable.alias === join.targetTable.name) { joinTableAlias = '' } joinLines.push(`${indent}${join.joinType} \`${join.targetTable.name}\`${joinTableAlias}`) for (let i = 0; i < join.ons.length; i++) { const on = join.ons[i] let relationText = i === 0 ? 'ON ': `${on.relation} ` joinLines.push(` ${indent}${relationText}\`${on.table.alias}\`.\`${on.field.name}\` = \`${on.targetTable.alias}\`.\`${on.targetField.name}\``) } } return joinLines } } module.exports = new Kit()