UNPKG

@lcap/nasl

Version:

NetEase Application Specific Language

1,312 lines 51.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.changeTabTimestamp = exports.loadAppSync = exports.mountDatabaseTypes = exports.loadApp = exports.setStore = exports.handleApp = exports.taskQueue = exports.databaseTypes = exports.aiCoachActionRecord = exports.breakpoint = exports.batchInstruct = exports.batchAction = exports.batchQuery = void 0; const breakpoint_1 = require("../../breakpoint"); const config_1 = require("../../config"); const service_1 = __importDefault(require("./service")); const uuid_1 = require("uuid"); const stepRecorder_1 = __importDefault(require("../../manager/stepRecorder")); const checkRules_1 = require("../../checkRules"); const instruct_ruler_1 = require("../instruct-ruler"); const jsoner = __importStar(require("./jsoner")); const compare_versions_1 = __importDefault(require("compare-versions")); /// #if !process.env.NODE_ENV || process.env.BUILD_TARGET === 'node' const fs = __importStar(require("fs-extra")); /// #endif const types_1 = require("./types"); const utils_1 = require("./utils"); const concepts_1 = require("../../concepts"); const operation_1 = require("./operation"); const cache_1 = require("./cache"); exports.batchQuery = service_1.default.batchQuery; exports.batchAction = service_1.default.batchAction; exports.batchInstruct = service_1.default.batchInstruct; exports.breakpoint = service_1.default.breakpoint; exports.aiCoachActionRecord = service_1.default.aiCoachActionRecord; let globalBatchInstructResult = null; let tabTimestamp; exports.databaseTypes = service_1.default.databaseTypes; function getLogic(key, app, diffLogicList) { if (!diffLogicList.includes(key)) { diffLogicList.push(key); if (key.startsWith('app')) { const index = key.lastIndexOf('.'); const name = key.slice(index + 1); const logic = app.logics.find((item) => item.name === name); if (logic && logic.body && logic.body.length > 2) { logic.body.map((item) => getStatement(item, app, diffLogicList)); } } else if (key.startsWith('extensions')) { const _pathArr = key.split('.'); const _name = _pathArr[1]; const _path = _pathArr[3]; const _list = app.dependencies; const _module = _list.find((it) => { const name = it.name.replaceAll('-', '_'); return name === _name; }); const logic = _module?.logics?.find((item) => item.name === _path); if (logic && logic.body && logic.body.length > 2) { logic.body.map((item) => getStatement(item, app, diffLogicList)); } } } } /* 获取LogicITEM */ function getStatement(state, app, diffLogicList) { let logicItems = []; switch (state.concept) { case 'Assignment': logicItems = [state.left, state.right]; break; case 'IfStatement': logicItems = [...state.consequent, ...state.alternate]; break; case 'ForEachStatement': logicItems = [...state.body, state.each, state.start, state.end]; break; case 'Match': logicItems = [state.expression, ...state.cases.map(getStatement).flat(1)]; break; case 'MatchCase': logicItems = [...state.patterns, ...state.body]; break; case 'SwitchStatement': logicItems = state.cases.map(getStatement).flat(1); break; case 'SwitchCase': logicItems = [state.test, ...state.consequent]; break; case 'WhileStatement': logicItems = [state.test, ...state.body]; break; case 'CallLogic': logicItems = state.arguments?.map((item) => item.expression); getLogic(state.calleewholeKey, app, diffLogicList); break; case 'CallFunction': logicItems = state.arguments?.map((item) => item.expression); break; case 'CallInterface': logicItems = state.arguments?.map((item) => item.expression); break; case 'ExternalDestination': logicItems = [state.anchor, state.link]; break; case 'BatchAssignment': logicItems = [state.left?.expression, ...state.left?.members, ...state.rights.map((item) => [item?.expression, ...item?.members]).flat(1)]; break; case 'MemberExpression': logicItems = [state.object, state.property]; break; case 'StringInterpolation': logicItems = [...state.expressions]; break; case 'NewComposite': logicItems = [...state.rights.map((item) => item.expression)]; break; case 'NewList': logicItems = [...state.items]; break; default: break; } return logicItems.filter((item) => !!item); } /* 小程序影响的服务端发布 */ function getMiniAppChange(target, obj, action) { if (!target.app.miniEnable) { return false; } /* 分端以后需要改 */ const len = target.nodePath?.split('.').length; if (len > 2) { return false; } if (action === 'delete') { return true; } else { if ('name' in obj || 'title' in obj) { return true; } } } let aiExecuted = 0; let sessionPath = ''; // AI 生成内容是否被采纳 const handleAIPoint = (app, actionItem) => { const { actionMsg, action } = actionItem || {}; if (aiExecuted > 0) { app.emit('logic:accepted', { ...actionItem, sessionPath }); aiExecuted -= 1; } if (actionMsg.includes('自然语言生成代码') && !action) { aiExecuted += 1; // eslint-disable-next-line prefer-destructuring sessionPath = actionMsg?.match(/:(.*)/)?.[1]; } }; /** * 代码补齐 batchAction 埋点 */ const handleAICompletionPoint = (instructList) => { const store = getStore(); const { aiCompletionData } = store || {}; const { actions, uuid, realAction } = instructList?.[0] || {}; const { focusedNodePath, position } = aiCompletionData?.request || {}; let nodePath = focusedNodePath; if (position?.includes('clickInNode-Identifier')) { nodePath = nodePath.substring(0, nodePath.lastIndexOf('.')); } if (!nodePath || !actions || !actions.length && realAction) { return; } // 遍历 actions,比较 nodePath let needReport = false; let needReportAction = {}; actions.forEach((action) => { if (action.action !== 'delete' && action.path === nodePath) { needReport = true; needReportAction = { action, uuid }; } }); if (needReport) { let requestId = aiCompletionData?.oldRequestId || aiCompletionData?.requestId; const params = { coachType: aiCompletionData?.oldRequestId ? 'ai_completion' : aiCompletionData?.completionType, requestId, timestamp: Date.now(), ...needReportAction, }; if (!aiCompletionData.requestId) return; service_1.default.aiCoachActionRecord({ body: params, }); } }; const handleAICoachPoint = (instructList) => { // 有 batchAction 时需要更新 tsContext const store = getStore(); store.setReplaceAITsContext(true); store.setNaslChange(true); const supportPoint = window?.appInfo?.saasEnv; if (!supportPoint) return; const aiCompletionSupport = window?.appInfo?.aiTenantResource?.['aiNaslCompletionSupport']?.resourceValue === 'true'; const aiSpecCompletionSupport = window?.appInfo?.aiTenantResource?.['aiSpecificCompletionSupport']?.resourceValue === 'true'; if (!aiCompletionSupport && !aiSpecCompletionSupport) return; const { aiCompletionData, aiRecommendData } = store || {}; if (!!aiCompletionData) { if (aiCompletionData?.action === '1') { // 采纳补齐推荐内容 instructList[0].sourceType = aiCompletionData?.completionType; if (aiCompletionData?.completionType === 'ai_specific_completion') { handleAICompletionPoint(instructList); } } else { handleAICompletionPoint(instructList); } } if (!!aiRecommendData) { if (['1', '2', '3', '4', '5'].includes(aiRecommendData?.action)) { instructList[0].sourceType = 'ai_recommend'; } } // nasl 变化,只比对一次,清空相关数据 store.setAICompletionData({}); store.setAIRecommendData({}); }; const isBreakpointEvent = (event) => { const originEvent = event?.originEvent; const object = originEvent?.object || {}; const oldObject = originEvent?.oldObject || {}; if (typeof object !== 'object') { return false; } if (typeof oldObject !== 'object') { return false; } const source = { ...object, ...oldObject }; const keys = Object.keys(source); if (keys.length !== 1) { return false; } return keys[0] === 'breakpoint'; }; /** * 执行更新 */ async function doAction(app, actionItem) { const isOperationRecord = operation_1.operationRecordInfoMap.get('isOperationRecord'); if (isOperationRecord) { console.log('正在进行操作栈回放操作,如需行任何Nasl操作请刷新页面'); return; } let hasFrontEnd = false; let hasBackEnd = false; const actionList = []; const allRiskList = []; const { list, actionMsg, sourceType, action, extra } = actionItem || {}; handleAIPoint(app, actionItem); const itemloop = (_i, app, diffArr) => { const _l = getStatement(_i, app, diffArr); if (_l.length) { _l.map((_ii) => itemloop(_ii, app, diffArr)); } }; const loopEle = (view, checkAuth) => { if (view instanceof concepts_1.View) { view?.elements?.forEach((item) => loopEle(item, checkAuth)); } view?.children?.forEach((item) => loopEle(item, checkAuth)); if (view instanceof concepts_1.ViewElement) { if (view.bindEvents.length) { const diffArr = []; view.bindEvents.forEach((bindEvent) => { bindEvent.logics.forEach((logic) => { logic.body.forEach((logicItem) => itemloop(logicItem, app, diffArr)); }); }); const result = diffArr.filter((item) => item.startsWith('app') || item.startsWith('extensions')); if (result.length) { hasBackEnd = true; } } if (view.tag === 'u-uploader' || view.tag === 'van-uploader') { const bute = view?.bindAttrs.find((item) => item.name === 'url'); const strVal = bute?.value; const needUpdateBackEnd = strVal?.endsWith('/import') || strVal?.includes('/upload/'); if (checkAuth) { if (needUpdateBackEnd && !view.view?.parentAuth) { hasBackEnd = true; } } else { if (needUpdateBackEnd) { hasBackEnd = true; } } } } }; const loopEleAuth = (viewElement) => { viewElement?.children?.forEach((item) => { const bindDirective = item?.bindDirectives?.find((item) => item.name === 'auth'); if (bindDirective) { hasBackEnd = true; } loopEleAuth(item); }); }; try { if (Array.isArray(list)) { list.forEach((event) => { const emitTarget = event?.originEvent?.target; const _path = event.originEvent.path; const _root = _path.split('.')?.[1] || ''; const breakabled = isBreakpointEvent(event); if (['backend', 'overriddenLogics', 'dataSources', 'processes', 'roles', 'logics', 'enums', 'structures', 'interfaces', 'interfaceDependencies', 'configuration', 'dependencies', 'authLogics', 'authLogicsForCallInterface', 'triggerLaunchers', 'connections', 'processV2s', 'connectorDependencies', 'sharedAppDependencies', 'directories'].some((item) => _root.startsWith(item))) { if (!breakabled) { hasBackEnd = true; } } if (emitTarget) { if (emitTarget.concept === 'App') { if (event?.object?.preferenceMap || event?.originEvent?.object?.preferenceMap) { hasBackEnd = true; hasFrontEnd = true; } } if (emitTarget.concept === 'ValidationRule') { if (event?.object?.enableServerValidation || event?.originEvent?.object?.enableServerValidation) { hasBackEnd = true; hasFrontEnd = true; } } if (emitTarget.concept === 'Logic') { const diffArr = []; if (emitTarget.body.length > 2) { emitTarget.body.forEach((it) => { itemloop(it, app, diffArr); }); } const result = diffArr.filter((item) => item.startsWith('app') || item.startsWith('extensions')); if (result.length) { hasBackEnd = true; } } if ('view' in emitTarget && emitTarget.view) { hasFrontEnd = true; } if (emitTarget.concept === 'BindDirective') { const isDelete = event?.originEvent?.action === 'delete'; if (isDelete) { const isAuto = ~event?.originEvent?.path?.indexOf('name=auth'); if (isAuto) { hasBackEnd = true; } } if (event?.originEvent?.object?.name === 'auth') { hasBackEnd = true; } } if (emitTarget.concept === 'View') { if (getMiniAppChange(emitTarget, event?.originEvent?.object, event?.originEvent?.action)) { hasBackEnd = true; } const isDelete = event?.originEvent?.action === 'delete'; const isAuthView = emitTarget.auth && event?.originEvent?.object?.name; const isUpdateAuth = event?.originEvent?.action === 'update' && "auth" in (event?.originEvent?.object || {}); const isUpdateAuthDes = event?.originEvent?.action === 'update' && "authDescription" in (event?.originEvent?.object || {}); const isUpdateBindRoles = event?.originEvent?.action === 'update' && event?.originEvent?.object && event?.originEvent?.object?.bindRoles; if (isDelete || isUpdateAuth || isUpdateBindRoles || isAuthView || isUpdateAuthDes) { hasBackEnd = true; // const app = emitTarget.app; // const diffArr: string[] = []; // emitTarget.logics.forEach((logic: Logic) => { // if (logic.body.length > 2) { // logic.body.forEach((it: LogicItem) => { // itemloop(it, app, diffArr); // }); // } // }); // const result = diffArr.filter((item) => item.startsWith('app') || item.startsWith('extensions')); // if (result.length) { // hasBackEnd = true; // } // emitTarget.children.forEach((item: View | ViewElement) => loopEle(item, isDelete)); // emitTarget.elements.forEach((item: View | ViewElement) => loopEle(item, isDelete)); } hasFrontEnd = true; } if (emitTarget.concept === 'ViewElement') { if (event?.originEvent?.action === 'update' || event?.originEvent?.action === 'delete') { if ('authDescription' in (event?.originEvent?.object ?? {})) { hasBackEnd = true; } } if (event?.originEvent?.object?.name) { const bindDirective = emitTarget.bindDirectives.find((item) => item.name === 'auth'); if (bindDirective) { hasBackEnd = true; } } if (event?.originEvent?.action === 'delete') { const bindDirective = emitTarget?.bindDirectives?.find((item) => item.name === 'auth'); if (bindDirective) { hasBackEnd = true; } loopEleAuth(emitTarget); } if (event?.originEvent?.action !== 'create') { if (event?.originEvent?.object && event?.originEvent?.object?.bindRoles) { hasBackEnd = true; } } loopEle(emitTarget, true); } if (emitTarget.concept === 'BindAttribute' && emitTarget.name === 'url') { const strVal = emitTarget?.value; const needUpdateBackEnd = strVal?.endsWith('/import') || strVal?.includes('/upload/'); if (!emitTarget?.view?.parentAuth && needUpdateBackEnd) { hasBackEnd = true; } } if (emitTarget.concept === 'CallLogic') { const arrlist = []; getLogic(emitTarget.calleeKey, emitTarget.app, arrlist); const result = arrlist.filter((item) => item.startsWith('app') || item.startsWith('extensions')); // console.log(result, 'CallLogic'); if (result.length) { hasBackEnd = true; } } // 更新端时需要出发后端更新 if (emitTarget.concept === 'Frontend') { hasBackEnd = true; } } event.eventList.forEach(({ action, riskList, path, object: objItem }) => { if (riskList) { allRiskList.push(...(riskList || [])); } if (Array.isArray(objItem)) { if (action === 'update') { actionList.push({ action, path, node: objItem[0], }); } else { objItem.forEach((item, index) => { actionList.push({ action, path, node: item, }); }); } } else { actionList.push({ action, path, object: objItem, }); } }); }); } } catch (error) { console.log(error); exports.taskQueue.refreshNasl(app, { errorInfo: { message: error?.message, stack: error?.stack, }, }); return; } const uuid = (0, uuid_1.v4)().replace(/-/g, ''); const instructList = [{ uuid, realAction: action, actions: actionList, riskList: allRiskList, actionMsg, sourceType, }]; if (actionMsg?.includes('自然语言生成代码') && action !== 'undo' && action !== 'redo') { if (actionMsg.includes('-ui:')) { instructList[0].sourceType = 'ai_nl2ui'; } if (actionMsg.includes('-logic:')) { instructList[0].sourceType = 'ai_nl2logic'; } if (actionMsg.includes('-partial:')) { instructList[0].sourceType = 'ai_nl2partial'; } } const operation = { type: 'doAction', uuid, actionItem: { actionMsg, list: list.map((item) => { const { path, action, object, oldObject, parentKey, index } = item?.originEvent || {}; return { path, action, parentKey, index, object: object?.toJSON?.() || object?.__v_raw || object, oldObject: oldObject?.toJSON?.() || oldObject?.__v_raw || oldObject, }; }), }, }; // 当前选中的编辑器和节点 const store = getStore(); const { activeDesigner, selectedNode } = store || {}; handleAICoachPoint(instructList); const activeDesignerPath = activeDesigner?.node?.nodePath; if (activeDesignerPath) { operation.activeDesignerPath = activeDesignerPath; } const selectedNodePath = selectedNode?.nodePath; if (selectedNodePath) { operation.selectedNodePath = selectedNodePath; } saveNasl({ app, hasFrontEnd, hasBackEnd, instructList, operationList: [operation] }); // 进行nasl操作后需要将Index重置 operation_1.operationRecordInfoMap.set('operationRecordIndex', null); if (!app._historying) { if (app._historyIndex !== app._historyList.length) { app._historyList = app._historyList.splice(0, app._historyIndex); } app._historyList.push({ actionMsg, extra: extra, list: list.map((item) => item?.originEvent), }); app._historyIndex = app._historyList.length; } else { const actionMap = { undo: '已撤销操作:', redo: '已重做操作:', }; let msg = actionMap[action]; if (actionMsg?.includes('自然语言生成代码')) { msg += '自然语言生成代码'; } else { msg += actionMsg; } if (process.env.BUILD_TARGET !== 'node') { require('element-plus').ElMessage.info(msg); } app._historying = false; } stepRecorder_1.default.initialized && stepRecorder_1.default.record(actionMsg, actionItem, actionList); } class TaskQueue { constructor() { this.queue = []; this.lastQueue = []; this.running = false; this.maxTaskCount = 10; this.status = undefined; } addTask(task) { this.queue.push(task); if (this.queue.length >= this.maxTaskCount) { this.status = types_1.TaskQueueStatus.ExceedMaxTaskCount; task.app.emit('ExceedMaxTaskCount'); } this.run(); } async clear() { this.queue = []; this.lastQueue = []; this.status = undefined; this.running = false; } async run(queue = this.queue) { if (this.running) return; if (queue.length === 0) return; const app = queue[0].app; let hasFrontEnd = false; let hasBackEnd = false; let instructList = []; let operationList = []; for (const task of queue) { if (task.hasFrontEnd) hasFrontEnd = true; if (task.hasBackEnd) hasBackEnd = true; instructList = instructList.concat(task.instructList); operationList = operationList.concat(task.operationList); } if (queue === this.queue) { this.lastQueue = [...queue]; this.queue = []; } this.running = true; const error = await _saveNasl({ app, hasFrontEnd, hasBackEnd, instructList, operationList }); // code: 401650, 检测到当前应用拉取操作已被强制结束, 用户确认后再刷新 // code: 500502 msg: '该应用已在新tab 页打开,请刷新!' 不重试或刷新 // code: 500505 需要刷新浏览器 if (!error) this.recover(app); else if (![401650, 500502, 500505].includes(error.code)) { // 连接失败,或超时,或后端持久化异常 if ((error.message === 'Network Error' || error.message?.includes('timeout') || error.code === 500500) && this.status !== types_1.TaskQueueStatus.Retrying) { this.running = false; this.retry(); } else this.refreshNasl(app); return; } this.running = false; this.run(); } retry() { if (this.lastQueue.length === 0) return; const app = this.lastQueue[0].app; this.status = types_1.TaskQueueStatus.Retrying; app.emit('Retrying'); this.run(this.lastQueue); } async refreshNasl(app, options = { errorInfo: {} }) { const failedQueue = this.lastQueue.map(({ hasFrontEnd, hasBackEnd, instructList }) => ({ hasFrontEnd, hasBackEnd, instructList, })); const queue = this.queue.map(({ hasFrontEnd, hasBackEnd, instructList }) => ({ hasFrontEnd, hasBackEnd, instructList, })); await service_1.default.saveFrontendNasl({ body: { nasl: app?.toJSON?.(), failedQueue, queue, errorInfo: options.errorInfo, }, }).catch((err) => { console.log('备份 nasl 失败:', err); }); await this.clear(); app?.emit?.('refresh'); } recover(app) { switch (this.status) { case types_1.TaskQueueStatus.ExceedMaxTaskCount: app.emit('BelowMaxTaskCount'); this.status = undefined; break; case types_1.TaskQueueStatus.Retrying: app.emit('SuccessRetry'); this.status = undefined; break; } } } exports.taskQueue = new TaskQueue(); async function saveNasl(options) { exports.taskQueue.addTask(options); } // 确保操作非高危 function ensureSafe(instructList) { const riskList = []; instructList?.forEach((instruct) => { if (instruct?.riskList) { riskList.push(...(instruct.riskList || [])); } instruct?.actions?.forEach((actionItem) => { const { path, action, object } = actionItem || {}; if (path === 'app') { switch (action) { case 'create': riskList.push('重复创建App'); break; case 'delete': riskList.push('删除App'); break; case 'update': if (object?.hasOwnProperty('id')) { riskList.push('修改AppId'); } break; } } }); }); return riskList; } async function _saveNasl(options) { const { app, hasFrontEnd, hasBackEnd, instructList, operationList } = options; app.emit('saving'); let ChangedNASLType = ''; if (hasFrontEnd && hasBackEnd) { ChangedNASLType = 'both'; } else if (hasFrontEnd) { ChangedNASLType = 'web'; } else if (hasBackEnd) { ChangedNASLType = 'backend'; } let res; let err; if (config_1.config.storage.protocol === 'http') { try { const riskList = ensureSafe(instructList); if (Array.isArray(riskList) && riskList.length) { throw Error(`存在以下高危操作:\n${riskList.map((risk, index) => { return `${index + 1}.${risk};`; }).join('\n')}\n请及时排查原因`); } const transformedInstructList = (0, utils_1.splitInstructList)((0, utils_1.setChangedTimeForInstruct)(instructList)); // 接口请求 res = await service_1.default.batchInstruct({ body: transformedInstructList, headers: { appId: app.id, ChangedNASLType, tabTimestamp, // 其他封装在 storageService 里了 }, }); } catch (error) { err = error; console.error(err); } try { (0, cache_1.update)({ res, err, instructList, operationList, app }); } catch (err) { console.log(err); } const { code, msg, result, success } = err || {}; operationList.forEach((operation) => { (0, operation_1.doOperationRecord)({ ...operation, res, err: { code, msg, result, success, }, }); }); if (res) { await (0, instruct_ruler_1.assertBatchInstruct)(operationList, { app, naslServer: app.naslServer, instructStatus: res, }); } } else if (config_1.config.storage.protocol === 'mock') { // Do nothing } else { const json = await fs.readJSON(config_1.config.storage.basePath); let actionList = []; for (const { actions } of instructList) { actionList = actionList.concat(actions); } jsoner.batchAction(json, actionList); await fs.writeJSON(config_1.config.storage.basePath, json, { spaces: 4, }); } app.emit?.('saved', err); globalBatchInstructResult = res; return err; } function handleApp(app) { app._isCollectingCount = 0; app._collectingList = []; app._historyList = []; app._historyIndex = 0; app._historyName = 'IDE'; app._historying = false; app._timer = null; app.on('undo', function () { const proxyApp = this; proxyApp._historying = true; }); app.on('redo', function () { const proxyApp = this; proxyApp._historying = true; }); /** * 开启收集 */ app.on('collect:start', function (event) { const proxyApp = this; if (proxyApp._timer) { clearTimeout(proxyApp._timer); } if (!proxyApp._noTimer) { proxyApp._noTimer = event.noTimer; } if (!proxyApp._noTimer) { proxyApp._timer = setTimeout(() => { console.error('收集超时,请及时排查原因', event); exports.taskQueue.refreshNasl(proxyApp, { errorInfo: { message: '收集超时,请及时排查原因', }, }); }, 2500); } if (event?.action === 'move') proxyApp._action = event.action; if (!proxyApp._isCollectingCount) { proxyApp._actionMsg = event?.actionMsg; proxyApp._sourceType = event?.sourceType; proxyApp._action = event?.action; proxyApp._extra = event?.extra; } proxyApp._isCollectingCount++; }); /** * 结束收集 */ app.on('collect:end', async function () { const proxyApp = this; proxyApp._isCollectingCount--; if (proxyApp._isCollectingCount === 0) { if (proxyApp._timer) { clearTimeout(proxyApp._timer); } // 结束收集时,立即将全局收集变量状态重置,防止下次收集数据混乱 const collectingList = proxyApp._collectingList; const actionMsg = proxyApp._actionMsg; const sourceType = proxyApp._sourceType || 'manual'; const action = proxyApp._action; const extra = proxyApp._extra; proxyApp._collectingList = []; proxyApp._actionMsg = ''; proxyApp._sourceType = undefined; proxyApp._action = ''; proxyApp._noTimer = undefined; if (Array.isArray(collectingList) && collectingList.length) { mountDatabaseTypes(proxyApp); proxyApp.naslServer?.embeddedTSEmitter?.emit('change', { value: collectingList, }); await doAction(proxyApp, { list: collectingList, actionMsg, sourceType, action, extra }); } } else if (proxyApp._isCollectingCount < 0) { proxyApp._isCollectingCount = 0; throw Error('关闭收集有问题,请排查'); } }); /** * 有变更 */ app.on('storage', function (event) { const proxyApp = this; // 是否正在收集 if (proxyApp._isCollectingCount === 0) { proxyApp.naslServer?.embeddedTSEmitter?.emit('change', { value: [event], }); let actionMsg = ''; const { action, target } = event.originEvent || {}; const { concept, name } = target || {}; switch (action) { case 'create': actionMsg = '添加'; break; case 'delete': actionMsg = '删除'; break; case 'update': actionMsg = '修改'; break; } actionMsg += (0, concepts_1.getConceptConstructor)(concept).nodeTitle; if (name) { actionMsg += `"${name}"`; } doAction(proxyApp, { list: [event], actionMsg, }); } else { proxyApp._collectingList.push(event); } }); } exports.handleApp = handleApp; async function getBatchAdditionalData(appId) { const { data: { result: [globalChangedTime] }, headers } = await service_1.default.batchQuery({ body: [ { path: 'app.globalChangedTime', }, ], headers: { appId, checkTabOpenTime: 'true', }, config: { shortResponse: false }, }); return { globalChangedTime, preGlobalChangedTime: 0, tabTimestamp: headers['tabtimestamp'], }; } async function getNaslFromServer(appId) { const [naslData] = await service_1.default.batchQuery({ body: [ { path: 'app', }, ], headers: { appId, }, }); return naslData; } async function getNaslIncrQueryServer(appId, body) { const res = await service_1.default.incrQuery({ body, headers: { appId, }, }); return res; } async function getNaslFromCache(appId) { console.time('读取本地nasl数据库'); const allData = await window?.$IndexedDBHelper.getAllData(); console.timeEnd('读取本地nasl数据库'); const pathTimestampMap = {}; const currentDate = Date.now(); allData.forEach((item) => { const globalChangedKey = 'appGlobalChanged'; if ([globalChangedKey].includes(item.nodePath)) return; if (item._changedTime) { pathTimestampMap[item.nodePath] = item._changedTime; } else { // 没有就传当前时间,服务端会比较时间不一致,返回更新 pathTimestampMap[item.nodePath] = currentDate; console.warn('没有_changedTime', item, item.nodePath); } }); // 拿到所有的文件和时间, 去请求服务端, const { type, incrNasls, fullNasl, appTree } = await getNaslIncrQueryServer(appId, { pathTimestampMap }); if (type === 'full') { console.dir('差距太大,返回全部数据,不去命中 Nasl 前端缓存'); return { type, naslData: fullNasl }; } const naslData = await (0, cache_1.getNaslCacheWithoutCheck)(allData, incrNasls, appTree); return { type, naslData }; } let getStore = () => { return {}; }; const setStore = (store) => { getStore = () => { return store; }; }; exports.setStore = setStore; /** * 加载 app * @param appId 如果是从文件读,就不需要传 * @returns app 对象 */ async function loadApp(appId, needEqual = false) { try { // 删除失效数据 (0, operation_1.deleteExpiredRecords)(); } catch (err) { } let app; if (config_1.config.storage.protocol === 'http') { console.time('batchQuery'); /** * 1. 获取到 服务端的更新的时间戳 * 2. 判断是否需要读缓存, * 本地有缓存, 而且本地的时间 和 服务端一致, 而且,没有本地操作需要重新请求的标识 * 3. 如果有,就拿本地数据库的内容出来, 传给服务端,获取增量 nasl * 返回差异的地方, 更新局部 nasl ,合并 nasl,同步本地数据库 * 如果差异过大,服务端就会返回全量 nasl,同步本地数据库 * 4. 如果没有,就请求全量 nasl。 ,同步本地数据库 */ const additionalData = await getBatchAdditionalData(appId); const useCache = await (0, cache_1.canUseNaslCache)(additionalData.globalChangedTime); let batchQueryRes = null; let needAllUpdateCache = false; if (useCache) { const cacheData = await getNaslFromCache(appId); batchQueryRes = cacheData.naslData; // 全量的需要后续拆分一下更新缓存,其余的都在热更新中处理过了 if (cacheData.type === 'full') { needAllUpdateCache = true; } console.dir('命中 Nasl 前端缓存'); if (needEqual) { function deepEqual(obj1, obj2, path = '') { if (obj1 === obj2) { return true; } // 上班 qingyan 整的 上班逻辑, 直接把 原始 store 中的 app 给赋值 改了,字符串变成了 boolean ,又跟服务端不一致,先过滤掉 if (path === 'preferenceMap.metadataTypeEnable' && obj1 + '' === obj2 + '') { return true; } if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) { console.dir(`Difference found at path: ${path}`); return false; } if (typeof obj2 === 'object' && typeof obj1 === 'object' && Object.keys(obj1 || {}).length && Object.keys(obj2 || {}).length) { ['_changedTime', 'changedTime'].forEach((key) => { delete obj1[key]; delete obj2[key]; }); Object.keys(obj1).forEach(key => { if (obj1[key] === undefined || obj1 === null) { delete obj1[key]; } }); Object.keys(obj2).forEach(key => { if (obj2[key] === undefined || obj2 === null) { delete obj2[key]; } }); } const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) { console.dir(`Different number of keys at path: ${path}`); return false; } for (let key of keys1) { if (!keys2.includes(key)) { console.dir(`Key ${key} not found in both objects at path: ${path}`); return false; } const newPath = path ? `${path}.${key}` : key; if (!deepEqual(obj1[key], obj2[key], newPath)) { return false; } } return true; } // 先在请求下服务端,回来的数据做一个比较看看是不是完全一致 const newData = await getNaslFromServer(appId); const currentJSON = new concepts_1.App(batchQueryRes).toJSON(); const apiJSON = new concepts_1.App(newData).toJSON(); console.dir({ currentJSON, apiJSON }); if (!deepEqual(currentJSON, apiJSON)) { console.dir('命中 Nasl 前端缓存,但是和服务端数据不一致'); } else { console.dir('命中 Nasl 前端缓存,和服务端数据一致'); } } } else { console.dir('未命中 Nasl 前端缓存'); batchQueryRes = await getNaslFromServer(appId); needAllUpdateCache = true; } tabTimestamp = additionalData.tabTimestamp; console.time('new App'); app = new concepts_1.App(Object.assign(batchQueryRes, { id: appId })); app.frontendTypes.forEach(ft => { if (!ft.frameworkKind) { ft.frameworkKind = 'vue2'; } }); await (0, instruct_ruler_1.assertIdeInitialize)({ app, instructStatus: additionalData, }); (async () => { const breakpointRes = await service_1.default.breakpoint({ body: { appId } }); breakpointRes?.forEach?.((item) => { const { path, breakpointStatus } = item; if (breakpointStatus) { const node = app.findNodeByPath(path); if (node) { node.breakpoint = breakpointStatus; } } }); })(); // 在 new 完 app 之后,在去更新 if (needAllUpdateCache) { (0, cache_1.updateNaslCache)(batchQueryRes, additionalData.globalChangedTime); } console.timeEnd('new App'); if (!app.id) { app.id = appId; } if (process.env.NODE_ENV !== 'testing') { (0, breakpoint_1.addBreakpointNodesFromApp)(app); } } else if (config_1.config.storage.protocol === 'mock') { app = new concepts_1.App({ id: appId, concept: 'App', name: 'devapp', title: '开发应用', envs: 'dev,online', hasAuth: true, hasUserCenter: true, dataSources: [], structures: [], enums: [], logics: [], interfaces: [], processes: [], roles: [], dependencies: [], interfaceDependencies: [], frontendTypes: [], metadataTypes: [] }); } else { const json = await fs.readJSON(config_1.config.storage.basePath); app = new concepts_1.App(appId ? Object.assign(json, { id: appId }) : json); } // config.scope = app.scope; handleApp(app); app._isCollectingCount = 0; mountDatabaseTypes(app); initializeCheckRules(app); return app; } exports.loadApp = loadApp; /** * 初始化检查规则 * @param app */ function initializeCheckRules(app) { const { createdIdeVersion, preferenceMap } = app; const rulesToApply = getRulesForVersion(createdIdeVersion); // 初始化 preferenceMap.checkRules 如果不存在 if (!preferenceMap.checkRules) { preferenceMap.checkRules = JSON.stringify({}); } // 解析现有的 checkRules const checkRulesObj = JSON.parse(preferenceMap.checkRules || '{}'); // 对于每个规则,如果不在 checkRulesObj 中,则添加并设置为 true rulesToApply.forEach(rule => { if (!(rule in checkRulesObj)) { checkRulesObj[rule] = true; } }); // 将更新后的 checkRules 保存回 preferenceMap preferenceMap.checkRules = JSON.stringify(checkRulesObj); } function getRulesForVersion(version) { if (!version) return []; const versions = Object.keys(checkRules_1.checkRules).sort((a, b) => (0, compare_versions_1.default)(a, b)); if (versions.length === 0) return []; // 如果版本小于最小版本,不应用规则 if ((0, compare_versions_1.default)(version, versions[0]) < 0) return []; // 如果版本大于最大版本,使用最大版本的规则 if ((0, compare_versions_1.default)(version, versions[versions.length - 1]) > 0) { return checkRules_1.checkRules[versions[versions.length - 1]]; } // 找到第一个大于等于当前版本的版本 for (let i = 0; i < versions.length; i++) { if ((0, compare_versions_1.default)(version, versions[i]) <= 0) { return checkRules_1.checkRules[versions[i]]; } } return []; } // 在app上挂载所有的数据库类型 async function mountDatabaseTypes(app) { if (!app.__databaseTypeMap) { app.__databaseTypeMap = {}; } const dataSources = app?.dataSources; const dbTypeSet = new Set(); if (Array.isArray(dataSources)) { dataSources.forEach((dataSource) => { const dataSourceSqlType = JSON.parse(dataSource.dataSourceConfig?.devProperty?.value || '{}')?.type; if (dataSourceSqlType && !app.__databaseTypeMap[dataSourceSqlType]) { dbTypeSet.add(dataSourceSqlType); } }); } const dbTypes = [...dbTypeSet]; if (Array.isArray(dbTypes) && dbTypes.length) { const res = await (0, exports.databaseTypes)({ query: { dbTypes: dbTypes.join(','), }, }); if (Array.isArray(res)) { res.forEach((databaseType) => { const { dbType, columnTypes } = databaseType || {}; const columnTypeMap = {}; if (Array.isArray(columnTypes)) { columnTypes.forEach((columnType) => { const { naslType, dataBaseTypes } = columnType; const dataBaseTypeMap = {}; let defaultDataBaseType; if (Array.isArray(dataBaseTypes)) { dataBaseTypes.forEach((dataBaseType) => { const { type } = dataBaseType || {}; if (type) { dataBaseTypeMap[type] = dataBaseType; const { defaultType } = dataBaseType; if (defaultType) { defaultDataBaseType = dataBaseType; } } }); } columnTypeMap[naslType] = { dataBaseTypes, dataBaseTypeMap, defaultDataBaseType, }; }); } app.__databaseTypeMap[dbType] = columnTypeMap; }); } } if (Array.isArray(dataSources)) { dataSources.forEach((dataSource) => { dataSource.__hasDatabaseConfig = !!(app.__databaseTypeMap?.[dataSource.dataSourceSqlType]); }); } } exports.mountDatabaseTypes = mountDatabaseTypes; /** * 加载 app * @returns app 对象 */ function loadAppSync() { const json = fs.readJSONSync(config_1.config.storage.basePath); const app = new concepts_1.App(json); handleApp(app); return app; } exports.loadAppSync = loadAppSync; async function changeTabTimestamp(appId) { const additionalData = await getBatchAdditionalData(appId); tabTimestamp = additionalData.tabTimestamp; } exports.changeTabTimestamp = changeTabTimestamp; //# sourceMappingURL=init.js.map