diff --git a/gofaster/app/plugins/modules/api-collector.js b/gofaster/app/plugins/modules/api-collector.js index 8089edc..3ee65e8 100644 --- a/gofaster/app/plugins/modules/api-collector.js +++ b/gofaster/app/plugins/modules/api-collector.js @@ -53,26 +53,20 @@ class ApiCollector { collectApiMappings(moduleDirs) { this.apiMappings = [] - console.log('🔍 开始收集API映射,模块列表:', moduleDirs) - moduleDirs.forEach(moduleName => { - console.log(`🔍 处理模块: ${moduleName}`) const moduleApiMappings = this.collectModuleApiMappings(moduleName) - console.log(`🔍 模块 ${moduleName} 收集到 ${moduleApiMappings.length} 个API映射`) if (moduleApiMappings.length > 0) { - // 将 serviceName 和 module 下沉到每个 API 映射中 + // 将 module 下沉到每个 API 映射中(serviceName已经在extractApiMapping中添加了) moduleApiMappings.forEach(apiMapping => { this.apiMappings.push({ ...apiMapping, - serviceName: moduleApiMappings.serviceName, module: moduleName }) }) } }) - console.log(`🔍 总共收集到 ${this.apiMappings.length} 个API映射`) return this.apiMappings } @@ -84,15 +78,11 @@ class ApiCollector { collectModuleApiMappings(moduleName) { const servicesPath = resolve(__dirname, '../../src/renderer/modules', moduleName, 'services') - console.log(`🔍 检查服务路径: ${servicesPath}`) - if (!existsSync(servicesPath)) { - console.log(`🔍 服务路径不存在: ${servicesPath}`) return [] } const serviceFiles = readdirSync(servicesPath).filter(file => file.endsWith('.js')) - console.log(`🔍 找到服务文件:`, serviceFiles) const moduleApiMappings = [] @@ -102,8 +92,6 @@ class ApiCollector { const servicePath = resolve(servicesPath, serviceFile) const serviceName = serviceFile.replace('.js', '') - console.log(`🔍 分析服务文件: ${serviceFile}`) - // 记录第一个服务名称(第一层需要) if (!firstServiceName) { firstServiceName = serviceName @@ -111,16 +99,12 @@ class ApiCollector { try { const serviceApiMappings = this.analyzeServiceFile(servicePath, serviceName, moduleName) - console.log(`🔍 服务 ${serviceName} 收集到 ${serviceApiMappings.length} 个API映射`) - // 不再为每个API映射添加serviceName(第二层冗余信息) moduleApiMappings.push(...serviceApiMappings) } catch (error) { - console.error(`🔍 分析服务文件失败: ${serviceFile}`, error.message) + console.error(`分析服务文件失败: ${serviceFile}`, error.message) } }) - console.log(`🔍 模块 ${moduleName} 总共收集到 ${moduleApiMappings.length} 个API映射`) - // 返回模块API映射数组,serviceName 将在 collectApiMappings 中添加到每个API映射 return moduleApiMappings } @@ -200,7 +184,8 @@ class ApiCollector { return { apiMethodName: methodName, method: annotationInfo.method, - path: pathOnly + path: pathOnly, + serviceName: serviceName } } diff --git a/gofaster/app/plugins/modules/component-relationship-analyzer-ast.js b/gofaster/app/plugins/modules/component-relationship-analyzer-ast.js new file mode 100644 index 0000000..afe5187 --- /dev/null +++ b/gofaster/app/plugins/modules/component-relationship-analyzer-ast.js @@ -0,0 +1,465 @@ +const { readFileSync, existsSync, readdirSync } = require('fs') +const { resolve } = require('path') +const parser = require('@babel/parser') +const traverse = require('@babel/traverse').default +const { parse } = require('@vue/compiler-dom') + +/** + * 基于AST的组件关系分析器 + * 使用vue-template-compiler解析模板,babel解析JS部分 + */ +class ComponentRelationshipAnalyzerAST { + constructor() { + this.componentRelationships = [] + this.processedComponents = new Set() + } + + /** + * 分析组件关系 + * @param {Array} routes - 路由数组 + * @param {Array} apiMappings - API映射数组 + * @returns {Array} 组件关系列表 + */ + analyzeComponentRelationships(routes, apiMappings) { + this.componentRelationships = [] + this.processedComponents.clear() + + // 获取所有顶级组件(routes中的组件)并分配ID + const topLevelComponents = routes.map((route, index) => ({ + id: index + 1, // 为routes分配ID + component: route.component, + module: this.getModuleFromRoute(route), + path: route.path + })) + + // 获取所有触发源组件并分配ID + let triggerSourceId = 1 + const triggerSourceComponents = this.extractTriggerSourceComponents(apiMappings).map(component => ({ + id: triggerSourceId++, // 为triggerSource分配ID + ...component + })) + + console.log(`🔍 开始分析组件关系,触发源组件数量: ${triggerSourceComponents.length}`) + + // 为每个触发源组件分析其与顶级组件的关系 + triggerSourceComponents.forEach(triggerComponent => { + this.analyzeComponentToTopLevel(triggerComponent, topLevelComponents) + }) + + console.log(`🔍 分析完成,找到 ${this.componentRelationships.length} 个组件关系`) + return this.componentRelationships + } + + /** + * 从路由中获取模块信息 + * @param {Object} route - 路由对象 + * @returns {string} 模块名称 + */ + getModuleFromRoute(route) { + // 直接使用路由中的module字段,不再进行推断 + return route.module || 'unknown' + } + + /** + * 提取所有触发源组件 + * @param {Array} apiMappings - API映射数组 + * @returns {Array} 触发源组件数组 + */ + extractTriggerSourceComponents(apiMappings) { + const components = [] + + apiMappings.forEach(apiMapping => { + if (apiMapping.triggerSources) { + apiMapping.triggerSources.forEach(triggerSource => { + components.push({ + component: triggerSource.component, + module: triggerSource.module, + triggerName: triggerSource.triggerName, + triggerType: triggerSource.triggerType, + apiMethodName: apiMapping.apiMethodName, + apiModule: apiMapping.module + }) + }) + } + }) + + return components + } + + /** + * 分析组件到顶级组件的关系 + * @param {Object} triggerComponent - 触发源组件 + * @param {Array} topLevelComponents - 顶级组件数组 + */ + analyzeComponentToTopLevel(triggerComponent, topLevelComponents) { + const triggerKey = `${triggerComponent.id}:${triggerComponent.module}:${triggerComponent.component}` + + // 避免重复处理同一个触发源 + if (this.processedComponents.has(triggerKey)) { + console.log(` ⏭️ 跳过重复处理: ${triggerKey}`) + return + } + + this.processedComponents.add(triggerKey) + + // 如果触发源组件本身就是顶级组件,直接建立关系 + const isTopLevel = topLevelComponents.find(top => + top.component === triggerComponent.component && top.module === triggerComponent.module + ) + + if (isTopLevel) { + this.componentRelationships.push({ + triggerSourceId: triggerComponent.id, // 使用ID引用 + routeId: isTopLevel.id, // 使用ID引用 + relationshipType: 'self' + }) + return + } + + // 查找父组件关系 + const parentComponents = this.findParentComponentsAST(triggerComponent) + let hasRelationship = false + + + if (parentComponents.length > 0) { + parentComponents.forEach(parent => { + // 递归查找父组件的父组件,直到找到顶级组件 + const found = this.findTopLevelAncestors(parent, triggerComponent, topLevelComponents) + if (found) hasRelationship = true + }) + } + + // 如果没有找到父组件关系,尝试直接匹配顶级组件 + if (!hasRelationship) { + const directMatch = topLevelComponents.find(top => + top.component === triggerComponent.component || + top.module === triggerComponent.module + ) + + if (directMatch) { + this.componentRelationships.push({ + triggerSourceId: triggerComponent.id, + routeId: directMatch.id, + relationshipType: 'direct' + }) + hasRelationship = true + } + } + + // 最后的兜底:确保每个触发源都至少有一个关系记录 + if (!hasRelationship) { + this.componentRelationships.push({ + triggerSourceId: triggerComponent.id, + routeId: 1, // 默认关联到第一个路由 + relationshipType: 'fallback' + }) + } + } + + /** + * 使用AST解析查找组件的父组件 + * @param {Object} component - 组件信息 + * @returns {Array} 父组件数组 + */ + findParentComponentsAST(component) { + const parents = [] + + try { + // 查找所有模块中的组件,看哪些引用了当前组件 + const modulesPath = resolve(__dirname, '../../src/renderer/modules') + if (existsSync(modulesPath)) { + const moduleDirs = readdirSync(modulesPath, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name) + + moduleDirs.forEach(moduleName => { + const parentComponents = this.findComponentsInModuleAST(moduleName, component) + parents.push(...parentComponents) + }) + } + } catch (error) { + // 静默处理错误 + } + + return parents + } + + /** + * 在指定模块中使用AST解析查找引用目标组件的组件 + * @param {string} moduleName - 模块名称 + * @param {Object} targetComponent - 目标组件 + * @returns {Array} 父组件数组 + */ + findComponentsInModuleAST(moduleName, targetComponent) { + const parents = [] + + try { + const modulePath = resolve(__dirname, '../../src/renderer/modules', moduleName) + if (!existsSync(modulePath)) return parents + + // 查找views目录 + const viewsPath = resolve(modulePath, 'views') + if (existsSync(viewsPath)) { + const viewFiles = readdirSync(viewsPath).filter(file => file.endsWith('.vue')) + viewFiles.forEach(file => { + const componentName = file.replace('.vue', '') + if (this.componentReferencesTargetAST(viewsPath, file, targetComponent)) { + console.log(`🔍 找到父组件: ${moduleName}:${componentName} -> ${targetComponent.module}:${targetComponent.component}`) + parents.push({ + component: componentName, + module: moduleName, + type: 'view' + }) + } + }) + } + + // 查找components目录 + const componentsPath = resolve(modulePath, 'components') + if (existsSync(componentsPath)) { + const componentFiles = readdirSync(componentsPath).filter(file => file.endsWith('.vue')) + componentFiles.forEach(file => { + const componentName = file.replace('.vue', '') + if (this.componentReferencesTargetAST(componentsPath, file, targetComponent)) { + console.log(`🔍 找到父组件: ${moduleName}:${componentName} -> ${targetComponent.module}:${targetComponent.component}`) + parents.push({ + component: componentName, + module: moduleName, + type: 'component' + }) + } + }) + } + } catch (error) { + // 静默处理错误 + } + + return parents + } + + /** + * 使用AST解析检查组件是否引用了目标组件 + * @param {string} dirPath - 目录路径 + * @param {string} fileName - 文件名 + * @param {Object} targetComponent - 目标组件 + * @returns {boolean} 是否引用 + */ + componentReferencesTargetAST(dirPath, fileName, targetComponent) { + try { + const filePath = resolve(dirPath, fileName) + const content = readFileSync(filePath, 'utf-8') + + // 解析Vue单文件组件 + const { script, template } = this.parseVueSFC(content) + + // 检查script部分的import和components注册 + if (script && this.analyzeScriptAST(script, targetComponent)) { + return true + } + + // 检查template部分的使用 + if (template && this.analyzeTemplateAST(template, targetComponent)) { + return true + } + + return false + } catch (error) { + return false + } + } + + /** + * 解析Vue单文件组件 + * @param {string} content - 文件内容 + * @returns {Object} 解析结果 + */ + parseVueSFC(content) { + const result = { script: null, template: null } + + try { + // 提取script部分 + const scriptMatch = content.match(/]*>([\s\S]*?)<\/script>/) + if (scriptMatch) { + result.script = scriptMatch[1] + } + + // 提取template部分 + const templateMatch = content.match(/]*>([\s\S]*?)<\/template>/) + if (templateMatch) { + result.template = templateMatch[1] + } + } catch (error) { + // 静默处理错误 + } + + return result + } + + /** + * 使用Babel AST分析script部分 + * @param {string} scriptContent - script内容 + * @param {Object} targetComponent - 目标组件 + * @returns {boolean} 是否引用 + */ + analyzeScriptAST(scriptContent, targetComponent) { + try { + const ast = parser.parse(scriptContent, { + sourceType: 'module', + plugins: ['jsx', 'typescript'] + }) + + let hasReference = false + + traverse(ast, { + // 检查import语句 - 更精确的匹配 + ImportDeclaration(path) { + const source = path.node.source.value + // 检查import的默认导入 + if (path.node.specifiers) { + path.node.specifiers.forEach(spec => { + if (spec.type === 'ImportDefaultSpecifier' && + spec.local.name === targetComponent.component) { + hasReference = true + } + // 检查命名导入 + if (spec.type === 'ImportSpecifier' && + spec.imported.name === targetComponent.component) { + hasReference = true + } + }) + } + }, + + // 检查components注册 - 支持多种格式 + ObjectProperty(path) { + if (path.node.key.name === 'components') { + const value = path.node.value + if (value.type === 'ObjectExpression') { + value.properties.forEach(prop => { + // 支持 components: { ComponentName: ComponentName } + if (prop.key.name === targetComponent.component) { + hasReference = true + } + // 支持 components: { ComponentName } + if (prop.type === 'ObjectProperty' && + prop.key.name === targetComponent.component) { + hasReference = true + } + }) + } + } + }, + + // 检查Vue 3 Composition API的组件注册 + CallExpression(path) { + if (path.node.callee.name === 'defineComponent') { + const arg = path.node.arguments[0] + if (arg && arg.type === 'ObjectExpression') { + arg.properties.forEach(prop => { + if (prop.key.name === 'components') { + const componentsValue = prop.value + if (componentsValue.type === 'ObjectExpression') { + componentsValue.properties.forEach(compProp => { + if (compProp.key.name === targetComponent.component) { + hasReference = true + } + }) + } + } + }) + } + } + } + }) + + return hasReference + } catch (error) { + return false + } + } + + /** + * 使用Vue template compiler分析template部分 + * @param {string} templateContent - template内容 + * @param {Object} targetComponent - 目标组件 + * @returns {boolean} 是否引用 + */ + analyzeTemplateAST(templateContent, targetComponent) { + try { + const ast = parse(templateContent) + + let hasReference = false + + // 遍历AST查找组件使用 + const traverse = (node) => { + if (node.type === 1) { // Element + // 检查标签名是否匹配目标组件 + if (node.tag === targetComponent.component) { + hasReference = true + return + } + + // 检查动态组件 + if (node.tag === 'component' && node.props) { + node.props.forEach(prop => { + if (prop.name === 'is' && prop.value && + prop.value.content === targetComponent.component) { + hasReference = true + return + } + }) + } + + // 递归遍历子节点 + if (node.children) { + node.children.forEach(child => { + if (child.type === 1) { // Element + traverse(child) + } + }) + } + } + } + + traverse(ast) + return hasReference + } catch (error) { + return false + } + } + + /** + * 查找顶级祖先组件 + * @param {Object} parentComponent - 父组件 + * @param {Object} originalTrigger - 原始触发组件 + * @param {Array} topLevelComponents - 顶级组件数组 + * @returns {boolean} 是否找到了关系 + */ + findTopLevelAncestors(parentComponent, originalTrigger, topLevelComponents) { + // 检查父组件是否是顶级组件 + const isTopLevel = topLevelComponents.find(top => + top.component === parentComponent.component && top.module === parentComponent.module + ) + + if (isTopLevel) { + // 找到顶级组件,建立关系 + this.componentRelationships.push({ + triggerSourceId: originalTrigger.id, // 使用ID引用 + routeId: isTopLevel.id, // 使用ID引用 + relationshipType: 'ancestor' + }) + return true + } + + // 继续向上查找 + const grandParents = this.findParentComponentsAST(parentComponent) + let found = false + grandParents.forEach(grandParent => { + const result = this.findTopLevelAncestors(grandParent, originalTrigger, topLevelComponents) + if (result) found = true + }) + return found + } +} + +module.exports = ComponentRelationshipAnalyzerAST diff --git a/gofaster/app/plugins/modules/component-relationship-analyzer.js b/gofaster/app/plugins/modules/component-relationship-analyzer.js new file mode 100644 index 0000000..c4e040a --- /dev/null +++ b/gofaster/app/plugins/modules/component-relationship-analyzer.js @@ -0,0 +1,280 @@ +const { readFileSync, existsSync, readdirSync } = require('fs') +const { resolve } = require('path') +const parser = require('@babel/parser') +const traverse = require('@babel/traverse').default + +/** + * 组件关系分析器 + * 分析组件之间的层级关系,从子组件追溯到顶级组件 + */ +class ComponentRelationshipAnalyzer { + constructor() { + this.componentRelationships = [] + this.processedComponents = new Set() + } + + /** + * 分析组件关系 + * @param {Array} routes - 路由数组 + * @param {Array} apiMappings - API映射数组 + * @returns {Array} 组件关系列表 + */ + analyzeComponentRelationships(routes, apiMappings) { + this.componentRelationships = [] + this.processedComponents.clear() + + // 获取所有顶级组件(routes中的组件) + const topLevelComponents = routes.map(route => ({ + component: route.component, + module: this.getModuleFromRoute(route), + path: route.path + })) + + // 获取所有触发源组件 + const triggerSourceComponents = this.extractTriggerSourceComponents(apiMappings) + + // 为每个触发源组件分析其与顶级组件的关系 + triggerSourceComponents.forEach(triggerComponent => { + this.analyzeComponentToTopLevel(triggerComponent, topLevelComponents) + }) + + return this.componentRelationships + } + + /** + * 从路由中获取模块信息 + * @param {Object} route - 路由对象 + * @returns {string} 模块名称 + */ + getModuleFromRoute(route) { + // 根据路径推断模块 + if (route.path === '/') return 'core' + if (route.path.includes('user-management')) return 'user-management' + if (route.path.includes('role-management')) return 'role-management' + if (route.path.includes('settings')) return 'system-settings' + if (route.path.includes('user-profile')) return 'user-management' + return 'core' + } + + /** + * 提取所有触发源组件 + * @param {Array} apiMappings - API映射数组 + * @returns {Array} 触发源组件数组 + */ + extractTriggerSourceComponents(apiMappings) { + const components = [] + + apiMappings.forEach(apiMapping => { + if (apiMapping.triggerSources) { + apiMapping.triggerSources.forEach(triggerSource => { + components.push({ + component: triggerSource.component, + module: triggerSource.module, + triggerName: triggerSource.triggerName, + triggerType: triggerSource.triggerType, + apiMethodName: apiMapping.apiMethodName, + apiModule: apiMapping.module + }) + }) + } + }) + + return components + } + + /** + * 分析组件到顶级组件的关系 + * @param {Object} triggerComponent - 触发源组件 + * @param {Array} topLevelComponents - 顶级组件数组 + */ + analyzeComponentToTopLevel(triggerComponent, topLevelComponents) { + const componentKey = `${triggerComponent.module}:${triggerComponent.component}` + + // 避免重复处理 + if (this.processedComponents.has(componentKey)) { + return + } + + this.processedComponents.add(componentKey) + + // 如果触发源组件本身就是顶级组件,直接建立关系 + const isTopLevel = topLevelComponents.find(top => + top.component === triggerComponent.component && top.module === triggerComponent.module + ) + + if (isTopLevel) { + this.componentRelationships.push({ + startComponent: triggerComponent.component, + startModule: triggerComponent.module, + endComponent: triggerComponent.component, + endModule: triggerComponent.module, + relationshipType: 'self', + path: isTopLevel.path, + triggerName: triggerComponent.triggerName, + triggerType: triggerComponent.triggerType, + apiMethodName: triggerComponent.apiMethodName + }) + return + } + + // 查找父组件关系 + const parentComponents = this.findParentComponents(triggerComponent) + + parentComponents.forEach(parent => { + // 递归查找父组件的父组件,直到找到顶级组件 + this.findTopLevelAncestors(parent, triggerComponent, topLevelComponents) + }) + } + + /** + * 查找组件的父组件 + * @param {Object} component - 组件信息 + * @returns {Array} 父组件数组 + */ + findParentComponents(component) { + const parents = [] + + try { + // 查找所有模块中的组件,看哪些引用了当前组件 + const modulesPath = resolve(__dirname, '../../src/renderer/modules') + if (existsSync(modulesPath)) { + const moduleDirs = readdirSync(modulesPath, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name) + + moduleDirs.forEach(moduleName => { + const parentComponents = this.findComponentsInModule(moduleName, component) + parents.push(...parentComponents) + }) + } + } catch (error) { + // 静默处理错误 + } + + return parents + } + + /** + * 在指定模块中查找引用目标组件的组件 + * @param {string} moduleName - 模块名称 + * @param {Object} targetComponent - 目标组件 + * @returns {Array} 父组件数组 + */ + findComponentsInModule(moduleName, targetComponent) { + const parents = [] + + try { + const modulePath = resolve(__dirname, '../../src/renderer/modules', moduleName) + if (!existsSync(modulePath)) return parents + + // 查找views目录 + const viewsPath = resolve(modulePath, 'views') + if (existsSync(viewsPath)) { + const viewFiles = readdirSync(viewsPath).filter(file => file.endsWith('.vue')) + viewFiles.forEach(file => { + const componentName = file.replace('.vue', '') + if (this.componentReferencesTarget(viewsPath, file, targetComponent)) { + parents.push({ + component: componentName, + module: moduleName, + type: 'view' + }) + } + }) + } + + // 查找components目录 + const componentsPath = resolve(modulePath, 'components') + if (existsSync(componentsPath)) { + const componentFiles = readdirSync(componentsPath).filter(file => file.endsWith('.vue')) + componentFiles.forEach(file => { + const componentName = file.replace('.vue', '') + if (this.componentReferencesTarget(componentsPath, file, targetComponent)) { + parents.push({ + component: componentName, + module: moduleName, + type: 'component' + }) + } + }) + } + } catch (error) { + // 静默处理错误 + } + + return parents + } + + /** + * 检查组件是否引用了目标组件 + * @param {string} dirPath - 目录路径 + * @param {string} fileName - 文件名 + * @param {Object} targetComponent - 目标组件 + * @returns {boolean} 是否引用 + */ + componentReferencesTarget(dirPath, fileName, targetComponent) { + try { + const filePath = resolve(dirPath, fileName) + const content = readFileSync(filePath, 'utf-8') + + // 检查import语句 + const importPattern = new RegExp(`import.*${targetComponent.component}.*from`, 'g') + if (importPattern.test(content)) { + return true + } + + // 检查components注册 + const componentPattern = new RegExp(`${targetComponent.component}\\s*:`, 'g') + if (componentPattern.test(content)) { + return true + } + + // 检查模板中的使用 + const templatePattern = new RegExp(`<${targetComponent.component}[\\s>]`, 'g') + if (templatePattern.test(content)) { + return true + } + + return false + } catch (error) { + return false + } + } + + /** + * 查找顶级祖先组件 + * @param {Object} parentComponent - 父组件 + * @param {Object} originalTrigger - 原始触发组件 + * @param {Array} topLevelComponents - 顶级组件数组 + */ + findTopLevelAncestors(parentComponent, originalTrigger, topLevelComponents) { + // 检查父组件是否是顶级组件 + const isTopLevel = topLevelComponents.find(top => + top.component === parentComponent.component && top.module === parentComponent.module + ) + + if (isTopLevel) { + // 找到顶级组件,建立关系 + this.componentRelationships.push({ + startComponent: originalTrigger.component, + startModule: originalTrigger.module, + endComponent: parentComponent.component, + endModule: parentComponent.module, + relationshipType: 'ancestor', + path: isTopLevel.path, + triggerName: originalTrigger.triggerName, + triggerType: originalTrigger.triggerType, + apiMethodName: originalTrigger.apiMethodName + }) + return + } + + // 继续向上查找 + const grandParents = this.findParentComponents(parentComponent) + grandParents.forEach(grandParent => { + this.findTopLevelAncestors(grandParent, originalTrigger, topLevelComponents) + }) + } +} + +module.exports = ComponentRelationshipAnalyzer diff --git a/gofaster/app/plugins/modules/data-cleaner.js b/gofaster/app/plugins/modules/data-cleaner.js index 1503844..400fc39 100644 --- a/gofaster/app/plugins/modules/data-cleaner.js +++ b/gofaster/app/plugins/modules/data-cleaner.js @@ -68,28 +68,12 @@ class DataCleaner { return cleanedMappings } - mappings.apiMappings.forEach(module => { - if (!module.apiMappings || module.apiMappings.length === 0) { - return - } - - const cleanedApiMappings = [] - - module.apiMappings.forEach(api => { - // 仅删除triggerSources列表为空的apiMapping - // 保留所有有triggerSources的apiMapping,不管triggerType是什么 - if (api.triggerSources && api.triggerSources.length > 0) { - cleanedApiMappings.push(api) - } - }) - - // 如果还有apiMappings,保留这个模块 - if (cleanedApiMappings.length > 0) { - const cleanedModule = { - ...module, - apiMappings: cleanedApiMappings - } - cleanedMappings.apiMappings.push(cleanedModule) + // 新的数据结构:apiMappings直接是API映射数组 + mappings.apiMappings.forEach(api => { + // 仅删除triggerSources列表为空的apiMapping + // 保留所有有triggerSources的apiMapping,不管triggerType是什么 + if (api.triggerSources && api.triggerSources.length > 0) { + cleanedMappings.apiMappings.push(api) } }) diff --git a/gofaster/app/plugins/modules/file-generator.js b/gofaster/app/plugins/modules/file-generator.js index 7eae9dd..08f8c24 100644 --- a/gofaster/app/plugins/modules/file-generator.js +++ b/gofaster/app/plugins/modules/file-generator.js @@ -81,10 +81,67 @@ export default { const content = `// 路由映射数据 - 构建时生成 export default { // 路由配置 - routes: ${JSON.stringify(routes, null, 2)}, + "routes": ${JSON.stringify(routes, null, 2)}, // API映射配置 - apiMappings: ${JSON.stringify(apiMappings, null, 2)} + "apiMappings": ${JSON.stringify(apiMappings, null, 2)} +}` + + // 如果文件已存在,先清空内容 + if (existsSync(this.outputPath)) { + writeFileSync(this.outputPath, '', 'utf-8') + } + + // 写入新内容 + writeFileSync(this.outputPath, content, 'utf-8') + } + + /** + * 生成包含组件关系的路由和API映射文件 + * @param {Array} routes - 路由数组 + * @param {Array} apiMappings - API映射数组 + * @param {Array} componentRelationships - 组件关系数组 + */ + generateRouteApiMappingFileWithRelationships(routes, apiMappings, componentRelationships) { + // 为routes添加编号 + const routesWithIds = routes.map((route, index) => ({ + id: index + 1, + ...route + })) + + // 为apiMappings中的triggerSources添加编号 + let triggerSourceId = 1 + const apiMappingsWithIds = apiMappings.map(apiMapping => { + if (apiMapping.triggerSources) { + const triggerSourcesWithIds = apiMapping.triggerSources.map(triggerSource => ({ + id: triggerSourceId++, + ...triggerSource + })) + return { + ...apiMapping, + triggerSources: triggerSourcesWithIds + } + } + return apiMapping + }) + + // 简化componentRelationships结构 + const simplifiedRelationships = componentRelationships.map(rel => ({ + triggerSourceId: rel.triggerSourceId, + routeId: rel.routeId, + relationshipType: rel.relationshipType + })) + + const content = `// 路由映射数据 - 构建时生成 +export default { + // 路由配置 + "routes": ${JSON.stringify(routesWithIds, null, 2)}, + + // API映射配置 + "apiMappings": ${JSON.stringify(apiMappingsWithIds, null, 2)}, + + // 组件关系配置(简化版) + "componentRelationships": ${JSON.stringify(simplifiedRelationships, null, 2)} }` // 如果文件已存在,先清空内容 diff --git a/gofaster/app/plugins/modules/route-analyzer.js b/gofaster/app/plugins/modules/route-analyzer.js index 9584de8..da0acf7 100644 --- a/gofaster/app/plugins/modules/route-analyzer.js +++ b/gofaster/app/plugins/modules/route-analyzer.js @@ -1,13 +1,16 @@ const { readFileSync, existsSync } = require('fs') const { resolve } = require('path') +const parser = require('@babel/parser') +const traverse = require('@babel/traverse').default /** * 路由分析模块 - * 负责分析路由配置 + * 负责分析路由配置,从import语句中提取模块信息 */ class RouteAnalyzer { constructor() { this.routes = [] + this.componentModuleMap = new Map() // 组件名 -> 模块名的映射 } /** @@ -16,11 +19,17 @@ class RouteAnalyzer { */ analyzeRoutes() { this.routes = [] + this.componentModuleMap.clear() try { const routeConfigPath = resolve(__dirname, '../../src/renderer/router/index.js') if (existsSync(routeConfigPath)) { const routeContent = readFileSync(routeConfigPath, 'utf-8') + + // 第一步:解析import语句,建立组件与模块的映射关系 + this.parseImportStatements(routeContent) + + // 第二步:解析路由配置,为每个路由添加模块信息 this.routes = this.parseRouteConfig(routeContent) } } catch (error) { @@ -31,54 +40,160 @@ class RouteAnalyzer { } /** - * 解析路由配置 - 优化版本:直接从路由配置中收集信息 + * 解析import语句,建立组件与模块的映射关系 + * @param {string} routeContent - 路由配置内容 + */ + parseImportStatements(routeContent) { + try { + const ast = parser.parse(routeContent, { + sourceType: 'module', + plugins: ['jsx', 'typescript'] + }) + + const self = this + traverse(ast, { + ImportDeclaration(path) { + const source = path.node.source.value + + // 解析模块路径,提取模块名 + const moduleName = self.extractModuleName(source) + if (moduleName) { + // 处理导入的组件 + path.node.specifiers.forEach(spec => { + let componentName = null + + if (spec.type === 'ImportDefaultSpecifier') { + // 默认导入:import Component from '...' + componentName = spec.local.name + } else if (spec.type === 'ImportSpecifier') { + // 命名导入:import { Component } from '...' + componentName = spec.imported.name + } + + if (componentName) { + self.componentModuleMap.set(componentName, moduleName) + } + }) + } + } + }) + } catch (error) { + // 静默处理错误 + } + } + + /** + * 从import路径中提取模块名 + * @param {string} importPath - import路径 + * @returns {string|null} 模块名 + */ + extractModuleName(importPath) { + // 匹配 @/modules/module-name 格式 + const moduleMatch = importPath.match(/@\/modules\/([^\/]+)/) + if (moduleMatch) { + return moduleMatch[1] + } + return null + } + + /** + * 解析路由配置,为每个路由添加模块信息 * @param {string} routeContent - 路由配置内容 * @returns {Array} 路由数组 */ parseRouteConfig(routeContent) { const routes = [] - // 解析Vue Router格式的路由配置 - // 查找所有路由定义,包括嵌套路由 - const routeMatches = routeContent.match(/\{[^}]*path:\s*['"]([^'"]+)['"][^}]*\}/g) - - if (routeMatches) { - routeMatches.forEach(match => { - const pathMatch = match.match(/path:\s*['"]([^'"]+)['"]/) - const componentMatch = match.match(/component:\s*([A-Za-z][A-Za-z0-9]*)/) - const nameMatch = match.match(/name:\s*['"]([^'"]+)['"]/) - const descriptionMatch = match.match(/description:\s*['"]([^'"]+)['"]/) - const authTypeMatch = match.match(/authType:\s*['"]([^'"]+)['"]/) - - if (pathMatch && componentMatch) { - const route = { - path: pathMatch[1], - component: componentMatch[1] - } - - // 只有当name与component不同时才保留name字段 - if (nameMatch && nameMatch[1] !== componentMatch[1]) { - route.name = nameMatch[1] - } - - // 优先使用路由配置中的description,如果没有则生成 - if (descriptionMatch) { - route.description = descriptionMatch[1] - } - - // 收集authType字段 - if (authTypeMatch) { - route.authType = authTypeMatch[1] + try { + const ast = parser.parse(routeContent, { + sourceType: 'module', + plugins: ['jsx', 'typescript'] + }) + + const self = this + traverse(ast, { + ObjectExpression(path) { + // 查找路由对象 + if (self.isRouteObject(path)) { + const route = self.extractRouteInfo(path) + if (route) { + routes.push(route) + } } - - routes.push(route) } }) + } catch (error) { + // 静默处理错误 } return routes } + /** + * 判断是否是路由对象 + * @param {Object} path - AST路径 + * @returns {boolean} 是否是路由对象 + */ + isRouteObject(path) { + const properties = path.node.properties + return properties.some(prop => + prop.key && prop.key.name === 'path' && + prop.value && prop.value.type === 'StringLiteral' + ) + } + + /** + * 从路由对象中提取路由信息 + * @param {Object} path - AST路径 + * @returns {Object|null} 路由信息 + */ + extractRouteInfo(path) { + const route = {} + const properties = path.node.properties + + properties.forEach(prop => { + if (prop.key && prop.value) { + const key = prop.key.name + const value = prop.value + + switch (key) { + case 'path': + if (value.type === 'StringLiteral') { + route.path = value.value + } + break + case 'component': + if (value.type === 'Identifier') { + route.component = value.name + // 从组件名获取模块信息 + const moduleName = this.componentModuleMap.get(value.name) + if (moduleName) { + route.module = moduleName + } + } + break + case 'name': + if (value.type === 'StringLiteral') { + route.name = value.value + } + break + case 'description': + if (value.type === 'StringLiteral') { + route.description = value.value + } + break + } + } + }) + + // 只有当path和component都存在时才返回路由信息 + if (route.path && route.component) { + return route + } + + return null + } + /** * 获取模块列表 * @returns {Array} 模块目录列表 diff --git a/gofaster/app/plugins/modules/trigger-analyzer-simple.js b/gofaster/app/plugins/modules/trigger-analyzer-simple.js index fe24c34..7e092fa 100644 --- a/gofaster/app/plugins/modules/trigger-analyzer-simple.js +++ b/gofaster/app/plugins/modules/trigger-analyzer-simple.js @@ -138,6 +138,7 @@ class TriggerAnalyzerSimple { apiCalls.forEach(call => { triggerSources.push({ component: componentName, + module: moduleName, // 添加模块信息 triggerName: call.triggerName || null, // 没有名称则为null triggerType: call.triggerType || 'function' }) @@ -157,7 +158,7 @@ class TriggerAnalyzerSimple { * @returns {string|null} 组件文件路径 */ findComponentFile(componentName, moduleName = null) { - // 如果提供了模块名称,优先在该模块中查找 + // 如果提供了模块名称,只在该模块中查找,不允许跨模块查找 if (moduleName) { // 检查views目录 const viewPath = resolve(__dirname, `../../src/renderer/modules/${moduleName}/views/${componentName}.vue`) @@ -170,10 +171,12 @@ class TriggerAnalyzerSimple { if (existsSync(componentPath)) { return componentPath } + + // 如果在指定模块中没有找到,返回null(不允许跨模块查找) + return null } - // 如果没有找到或没有提供模块名称,则在所有模块中查找 - // 这里我们动态获取所有模块目录 + // 如果没有提供模块名称,则在所有模块中查找 const modulesPath = resolve(__dirname, '../../src/renderer/modules') if (existsSync(modulesPath)) { const { readdirSync } = require('fs') diff --git a/gofaster/app/plugins/route-mapping-plugin.js b/gofaster/app/plugins/route-mapping-plugin.js index 21f3024..6447b99 100644 --- a/gofaster/app/plugins/route-mapping-plugin.js +++ b/gofaster/app/plugins/route-mapping-plugin.js @@ -5,6 +5,7 @@ const TriggerAnalyzerSimple = require('./modules/trigger-analyzer-simple') const CallChainTracer = require('./modules/call-chain-tracer') const FileGenerator = require('./modules/file-generator') const RouteAnalyzer = require('./modules/route-analyzer') +const ComponentRelationshipAnalyzerAST = require('./modules/component-relationship-analyzer-ast') /** * 模块化路由映射插件 @@ -38,6 +39,7 @@ function routeMappingPlugin() { _callChainTracer: new CallChainTracer(), _fileGenerator: new FileGenerator(), _routeAnalyzer: new RouteAnalyzer(), + _componentRelationshipAnalyzer: new ComponentRelationshipAnalyzerAST(), // Webpack插件接口 apply(compiler) { @@ -154,7 +156,13 @@ function routeMappingPlugin() { // ========== 第三步:删除triggerSources为空的apiMapping ========== const DataCleaner = require('./modules/data-cleaner.js') const dataCleaner = new DataCleaner() - const cleanedMappings = dataCleaner.cleanData() + const cleanedMappings = dataCleaner.cleanData() + + // ========== 第四步:分析组件关系 ========== + const componentRelationships = this._componentRelationshipAnalyzer.analyzeComponentRelationships(routes, cleanedMappings.apiMappings) + + // 生成包含组件关系的最终文件 + this._fileGenerator.generateRouteApiMappingFileWithRelationships(routes, cleanedMappings.apiMappings, componentRelationships) // 重置生成中标志并更新时间戳 this._generationInProgress = false diff --git a/gofaster/app/src/renderer/modules/route-sync/direct-route-mappings.js b/gofaster/app/src/renderer/modules/route-sync/direct-route-mappings.js index aa75bb3..c6be9d8 100644 --- a/gofaster/app/src/renderer/modules/route-sync/direct-route-mappings.js +++ b/gofaster/app/src/renderer/modules/route-sync/direct-route-mappings.js @@ -1,32 +1,376 @@ // 路由映射数据 - 构建时生成 export default { + // 路由配置 "routes": [ - { - "path": "/", - "component": "MainLayout", - "name": "Home", - "description": "首页" - }, - { - "path": "/user-management", - "component": "UserManagement", - "description": "用户管理" - }, - { - "path": "/settings", - "component": "Settings", - "description": "系统设置" - }, - { - "path": "/user-profile", - "component": "UserProfile", - "description": "个人资料" - }, - { - "path": "/role-management", - "component": "RoleManagement", - "description": "角色管理" - } - ], - "apiMappings": [] -}; \ No newline at end of file + { + "id": 1, + "path": "/", + "component": "MainLayout", + "module": "core" + }, + { + "id": 2, + "path": "/user-management", + "name": "UserManagement", + "component": "UserManagement", + "module": "user-management", + "description": "用户管理" + }, + { + "id": 3, + "path": "/settings", + "name": "Settings", + "component": "Settings", + "module": "system-settings", + "description": "系统设置" + }, + { + "id": 4, + "path": "/user-profile", + "name": "UserProfile", + "component": "UserProfile", + "module": "user-management", + "description": "个人资料" + }, + { + "id": 5, + "path": "/role-management", + "name": "RoleManagement", + "component": "RoleManagement", + "module": "role-management", + "description": "角色管理" + } +], + + // API映射配置 + "apiMappings": [ + { + "apiMethodName": "createRole", + "method": "POST", + "path": "/api/auth/roles", + "serviceName": "roleService", + "module": "role-management", + "triggerSources": [ + { + "id": 1, + "component": "RoleManagement", + "module": "role-management", + "triggerName": "rolemanagement-submit-lhm6fs", + "triggerType": "button" + } + ] + }, + { + "apiMethodName": "updateRole", + "method": "PUT", + "path": "/api/auth/roles/{id}", + "serviceName": "roleService", + "module": "role-management", + "triggerSources": [ + { + "id": 2, + "component": "RoleManagement", + "module": "role-management", + "triggerName": "rolemanagement-submit-lhm6fs", + "triggerType": "button" + }, + { + "id": 3, + "component": "PermissionManager", + "module": "role-management", + "triggerName": "permissionmanager-button-k7wqyp", + "triggerType": "button" + } + ] + }, + { + "apiMethodName": "deleteRole", + "method": "DELETE", + "path": "/api/auth/roles/{id}", + "serviceName": "roleService", + "module": "role-management", + "triggerSources": [ + { + "id": 4, + "component": "RoleManagement", + "module": "role-management", + "triggerName": "rolemanagement-button-0eqx80", + "triggerType": "button" + } + ] + }, + { + "apiMethodName": "assignRolesToUser", + "method": "POST", + "path": "/api/auth/roles/users/{userId}/assign", + "serviceName": "roleService", + "module": "role-management", + "triggerSources": [ + { + "id": 5, + "component": "UserRoleAssignment", + "module": "role-management", + "triggerName": "userroleassignment-button-3npmn8", + "triggerType": "button" + } + ] + }, + { + "apiMethodName": "removeRolesFromUser", + "method": "DELETE", + "path": "/api/auth/roles/users/{userId}/remove", + "serviceName": "roleService", + "module": "role-management", + "triggerSources": [ + { + "id": 6, + "component": "UserRoleAssignment", + "module": "role-management", + "triggerName": "userroleassignment-button-3npmn8", + "triggerType": "button" + } + ] + }, + { + "apiMethodName": "assignPermissionsToRole", + "method": "POST", + "path": "/api/auth/permissions/roles/{roleId}/assign", + "serviceName": "roleService", + "module": "role-management", + "triggerSources": [ + { + "id": 7, + "component": "RolePermissionAssignment", + "module": "role-management", + "triggerName": "rolepermissionassignment-button-7uizm5", + "triggerType": "button" + }, + { + "id": 8, + "component": "RolePermissionAssignment", + "module": "role-management", + "triggerName": "rolepermissionassignment-button-a0nyh8", + "triggerType": "button" + } + ] + }, + { + "apiMethodName": "removePermissionsFromRole", + "method": "DELETE", + "path": "/api/auth/permissions/roles/{roleId}/remove", + "serviceName": "roleService", + "module": "role-management", + "triggerSources": [ + { + "id": 9, + "component": "RolePermissionAssignment", + "module": "role-management", + "triggerName": "rolepermissionassignment-button-q86qi4", + "triggerType": "button" + }, + { + "id": 10, + "component": "RolePermissionAssignment", + "module": "role-management", + "triggerName": "rolepermissionassignment-button-sj6qb7", + "triggerType": "button" + } + ] + }, + { + "apiMethodName": "createUser", + "method": "POST", + "path": "/api/auth/admin/users", + "serviceName": "userService", + "module": "user-management", + "triggerSources": [ + { + "id": 11, + "component": "UserManagement", + "module": "user-management", + "triggerName": "usermanagement-submit-fksbqh", + "triggerType": "button" + } + ] + }, + { + "apiMethodName": "updateUser", + "method": "PUT", + "path": "/api/auth/admin/users/{id}", + "serviceName": "userService", + "module": "user-management", + "triggerSources": [ + { + "id": 12, + "component": "UserManagement", + "module": "user-management", + "triggerName": "usermanagement-submit-fksbqh", + "triggerType": "button" + } + ] + }, + { + "apiMethodName": "deleteUser", + "method": "DELETE", + "path": "/api/auth/admin/users/{id}", + "serviceName": "userService", + "module": "user-management", + "triggerSources": [ + { + "id": 13, + "component": "UserManagement", + "module": "user-management", + "triggerName": "usermanagement-button-tvncrr", + "triggerType": "button" + } + ] + }, + { + "apiMethodName": "changePassword", + "method": "POST", + "path": "/api/auth/change-password", + "serviceName": "userService", + "module": "user-management", + "triggerSources": [ + { + "id": 14, + "component": "PasswordChangeModal", + "module": "user-management", + "triggerName": "passwordchangemodal-submit-2p704c", + "triggerType": "button" + } + ] + }, + { + "apiMethodName": "validatePassword", + "method": "POST", + "path": "/api/auth/validate-password", + "serviceName": "userService", + "module": "user-management", + "triggerSources": [ + { + "id": 15, + "component": "PasswordChangeModal", + "module": "user-management", + "triggerName": "passwordchangemodal-input-0xe3cd", + "triggerType": "input" + } + ] + }, + { + "apiMethodName": "changePassword", + "method": "POST", + "path": "/api/auth/change-password", + "serviceName": "userService", + "module": "user-management", + "triggerSources": [ + { + "id": 16, + "component": "PasswordChangeModal", + "module": "user-management", + "triggerName": "passwordchangemodal-submit-2p704c", + "triggerType": "button" + } + ] + } +], + + // 组件关系配置(简化版) + "componentRelationships": [ + { + "triggerSourceId": 1, + "routeId": 5, + "relationshipType": "self" + }, + { + "triggerSourceId": 2, + "routeId": 5, + "relationshipType": "self" + }, + { + "triggerSourceId": 3, + "routeId": 5, + "relationshipType": "direct" + }, + { + "triggerSourceId": 4, + "routeId": 5, + "relationshipType": "self" + }, + { + "triggerSourceId": 5, + "routeId": 2, + "relationshipType": "ancestor" + }, + { + "triggerSourceId": 6, + "routeId": 2, + "relationshipType": "ancestor" + }, + { + "triggerSourceId": 7, + "routeId": 5, + "relationshipType": "ancestor" + }, + { + "triggerSourceId": 8, + "routeId": 5, + "relationshipType": "ancestor" + }, + { + "triggerSourceId": 9, + "routeId": 5, + "relationshipType": "ancestor" + }, + { + "triggerSourceId": 10, + "routeId": 5, + "relationshipType": "ancestor" + }, + { + "triggerSourceId": 11, + "routeId": 2, + "relationshipType": "self" + }, + { + "triggerSourceId": 12, + "routeId": 2, + "relationshipType": "self" + }, + { + "triggerSourceId": 13, + "routeId": 2, + "relationshipType": "self" + }, + { + "triggerSourceId": 14, + "routeId": 1, + "relationshipType": "ancestor" + }, + { + "triggerSourceId": 14, + "routeId": 4, + "relationshipType": "ancestor" + }, + { + "triggerSourceId": 15, + "routeId": 1, + "relationshipType": "ancestor" + }, + { + "triggerSourceId": 15, + "routeId": 4, + "relationshipType": "ancestor" + }, + { + "triggerSourceId": 16, + "routeId": 1, + "relationshipType": "ancestor" + }, + { + "triggerSourceId": 16, + "routeId": 4, + "relationshipType": "ancestor" + } +] +} \ No newline at end of file