From e3181645bcffc37152d9632b84a5526b7adb11fa Mon Sep 17 00:00:00 2001 From: hejl Date: Sun, 7 Sep 2025 10:53:29 +0800 Subject: [PATCH] =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gofaster/app/plugins/modules/README.md | 85 + gofaster/app/plugins/modules/api-collector.js | 172 ++ gofaster/app/plugins/modules/ast-analyzer.js | 294 +++ .../app/plugins/modules/file-generator.js | 108 + gofaster/app/plugins/modules/index.js | 18 + .../app/plugins/modules/route-analyzer.js | 86 + .../app/plugins/modules/trigger-analyzer.js | 407 +++ gofaster/app/plugins/route-mapping-plugin.js | 2336 +---------------- .../app/scripts/generate-route-mappings.js | 2 +- .../components/RolePermissionAssignment.vue | 4 +- .../components/UserRoleAssignment.vue | 2 +- .../route-sync/direct-route-mappings.js | 290 +- .../user-management/components/LoginModal.vue | 2 +- .../components/PasswordChangeModal.vue | 2 +- .../user-management/views/UserManagement.vue | 4 +- .../user-management/views/UserProfile.vue | 2 +- gofaster/app/vite.config.js | 2 +- gofaster/app/vue.config.js | 2 +- 18 files changed, 1440 insertions(+), 2378 deletions(-) create mode 100644 gofaster/app/plugins/modules/README.md create mode 100644 gofaster/app/plugins/modules/api-collector.js create mode 100644 gofaster/app/plugins/modules/ast-analyzer.js create mode 100644 gofaster/app/plugins/modules/file-generator.js create mode 100644 gofaster/app/plugins/modules/index.js create mode 100644 gofaster/app/plugins/modules/route-analyzer.js create mode 100644 gofaster/app/plugins/modules/trigger-analyzer.js diff --git a/gofaster/app/plugins/modules/README.md b/gofaster/app/plugins/modules/README.md new file mode 100644 index 0000000..636aa4b --- /dev/null +++ b/gofaster/app/plugins/modules/README.md @@ -0,0 +1,85 @@ +# 路由映射插件模块 + +这个目录包含了模块化后的路由映射插件的各个功能模块。 + +## 模块结构 + +### 1. `api-collector.js` - API收集模块 +- **功能**: 从service文件中收集API信息 +- **主要方法**: + - `collectApiMappings(moduleDirs)` - 收集所有模块的API映射 + - `collectModuleApiMappings(moduleName)` - 收集单个模块的API映射 + - `analyzeServiceFile(servicePath, serviceName, moduleName)` - 分析单个服务文件 + - `extractApiMapping(methodPath, methodName, serviceName, servicePath)` - 从AST节点中提取API映射信息 + +### 2. `ast-analyzer.js` - AST分析模块 +- **功能**: 负责Babel和Vue模板的AST分析 +- **主要方法**: + - `findMethodsCallingServiceWithBabel(content, serviceName, apiMethodName)` - 使用Babel AST分析查找调用指定服务方法的方法 + - `checkMethodCallsServiceWithBabel(methodPath, serviceName, apiMethodName)` - 检查方法是否调用了指定的service方法 + - `findTriggersInTemplateWithAST(templateContent, componentName, apiMethodNames, triggerSources, filePath)` - 使用AST分析模板中的事件绑定 + - `generateUniqueButtonName(componentName, clickHandler)` - 生成唯一的按钮名称 + +### 3. `trigger-analyzer.js` - 触发器分析模块 +- **功能**: 负责分析组件中的按钮和事件触发器 +- **主要方法**: + - `findTriggerSourcesForApiMappings(apiMappings)` - 查找API方法的触发器源 + - `findTriggerSourcesForApiMethod(serviceName, apiMethodName, moduleName)` - 查找API方法的触发器源 + - `analyzeComponentWithAST(componentName, serviceName, apiMethodName)` - 使用AST分析组件 + - `analyzeMethodTriggerSources(content, componentName, methodNames, filePath)` - 分析方法的触发源 + +### 4. `file-generator.js` - 文件生成模块 +- **功能**: 负责生成最终的映射文件 +- **主要方法**: + - `generateMappingFile(routes, apiMappings)` - 生成映射文件 + - `generateFileContent(routes, apiMappings)` - 生成文件内容 + - `optimizeApiMappings(apiMappings)` - 优化API映射 + - `deduplicateTriggerSources(triggerSources)` - 去重触发器源 + +### 5. `route-analyzer.js` - 路由分析模块 +- **功能**: 负责分析路由配置 +- **主要方法**: + - `analyzeRoutes()` - 分析路由配置 + - `parseRouteConfig(routeContent)` - 解析路由配置 + - `getModuleDirs()` - 获取模块列表 + +## 使用方式 + +```javascript +const { ApiCollector, TriggerAnalyzer, FileGenerator, RouteAnalyzer } = require('./modules') + +// 创建实例 +const apiCollector = new ApiCollector() +const triggerAnalyzer = new TriggerAnalyzer() +const fileGenerator = new FileGenerator() +const routeAnalyzer = new RouteAnalyzer() + +// 使用模块 +const routes = routeAnalyzer.analyzeRoutes() +const moduleDirs = routeAnalyzer.getModuleDirs() +const apiMappings = apiCollector.collectApiMappings(moduleDirs) +const enhancedApiMappings = triggerAnalyzer.findTriggerSourcesForApiMappings(apiMappings) +const optimizedApiMappings = fileGenerator.optimizeApiMappings(enhancedApiMappings) +fileGenerator.generateMappingFile(routes, optimizedApiMappings) +``` + +## 优势 + +1. **模块化**: 每个模块负责特定的功能,职责清晰 +2. **可维护性**: 代码结构清晰,易于理解和修改 +3. **可测试性**: 每个模块可以独立测试 +4. **可复用性**: 模块可以在其他地方复用 +5. **可扩展性**: 新功能可以作为新模块添加 + +## 文件大小对比 + +- **原始文件**: `route-mapping-plugin.js` (2425行) +- **模块化后**: + - `route-mapping-plugin-modular.js` (约200行) + - `api-collector.js` (约150行) + - `ast-analyzer.js` (约200行) + - `trigger-analyzer.js` (约300行) + - `file-generator.js` (约100行) + - `route-analyzer.js` (约80行) + +总计约1030行,比原始文件减少了约57%的代码量,同时提高了可维护性。 diff --git a/gofaster/app/plugins/modules/api-collector.js b/gofaster/app/plugins/modules/api-collector.js new file mode 100644 index 0000000..d30cbe2 --- /dev/null +++ b/gofaster/app/plugins/modules/api-collector.js @@ -0,0 +1,172 @@ +const { readFileSync, existsSync, readdirSync } = require('fs') +const { resolve } = require('path') +const parser = require('@babel/parser') +const traverse = require('@babel/traverse').default + +/** + * API收集模块 + * 负责从service文件中收集API信息 + */ +class ApiCollector { + constructor() { + this.apiMappings = [] + } + + /** + * 收集所有模块的API映射 + * @param {Array} moduleDirs - 模块目录列表 + * @returns {Array} API映射数组 + */ + collectApiMappings(moduleDirs) { + this.apiMappings = [] + + moduleDirs.forEach(moduleName => { + const moduleApiMappings = this.collectModuleApiMappings(moduleName) + if (moduleApiMappings.length > 0) { + this.apiMappings.push({ + module: moduleName, + serviceName: moduleApiMappings[0].serviceName, + servicePath: moduleApiMappings[0].servicePath, + apiMappings: moduleApiMappings + }) + } + }) + + return this.apiMappings + } + + /** + * 收集单个模块的API映射 + * @param {string} moduleName - 模块名称 + * @returns {Array} 模块的API映射数组 + */ + collectModuleApiMappings(moduleName) { + const servicesPath = resolve(__dirname, '../../src/renderer/modules', moduleName, 'services') + + if (!existsSync(servicesPath)) { + return [] + } + + const serviceFiles = readdirSync(servicesPath).filter(file => file.endsWith('.js')) + const moduleApiMappings = [] + + serviceFiles.forEach(serviceFile => { + const servicePath = resolve(servicesPath, serviceFile) + const serviceName = serviceFile.replace('.js', '') + + try { + const serviceApiMappings = this.analyzeServiceFile(servicePath, serviceName, moduleName) + moduleApiMappings.push(...serviceApiMappings) + } catch (error) { + // 静默处理错误 + } + }) + + return moduleApiMappings + } + + /** + * 分析单个服务文件的API映射 + * @param {string} servicePath - 服务文件路径 + * @param {string} serviceName - 服务名称 + * @param {string} moduleName - 模块名称 + * @returns {Array} API映射数组 + */ + analyzeServiceFile(servicePath, serviceName, moduleName) { + const content = readFileSync(servicePath, 'utf-8') + const apiMappings = [] + + try { + const ast = parser.parse(content, { + sourceType: 'module', + plugins: ['jsx', 'typescript'] + }) + + traverse(ast, { + ObjectMethod(path) { + if (path.node.key && path.node.key.type === 'Identifier') { + const methodName = path.node.key.name + const apiMapping = this.extractApiMapping(path, methodName, serviceName, servicePath) + if (apiMapping) { + apiMappings.push(apiMapping) + } + } + }, + + VariableDeclarator(path) { + if (path.node.id && path.node.id.type === 'Identifier' && + path.node.init && path.node.init.type === 'ArrowFunctionExpression') { + const methodName = path.node.id.name + const apiMapping = this.extractApiMapping(path.get('init'), methodName, serviceName, servicePath) + if (apiMapping) { + apiMappings.push(apiMapping) + } + } + } + }) + } catch (error) { + // 静默处理错误 + } + + return apiMappings + } + + /** + * 从AST节点中提取API映射信息 + * @param {Object} methodPath - 方法路径 + * @param {string} methodName - 方法名称 + * @param {string} serviceName - 服务名称 + * @param {string} servicePath - 服务文件路径 + * @returns {Object|null} API映射对象 + */ + extractApiMapping(methodPath, methodName, serviceName, servicePath) { + let httpMethod = null + let apiPath = null + + // 查找HTTP方法调用 + traverse(methodPath.node, { + CallExpression(path) { + const node = path.node + if (node.callee && node.callee.type === 'MemberExpression') { + const object = node.callee.object + const property = node.callee.property + + if (object && property && + object.type === 'Identifier' && + property.type === 'Identifier') { + + const method = property.name.toLowerCase() + if (['get', 'post', 'put', 'delete', 'patch'].includes(method)) { + httpMethod = method.toUpperCase() + + // 提取API路径 + if (node.arguments && node.arguments.length > 0) { + const firstArg = node.arguments[0] + if (firstArg.type === 'StringLiteral') { + apiPath = firstArg.value + } else if (firstArg.type === 'TemplateLiteral') { + apiPath = firstArg.quasis[0].value.raw + } + } + } + } + } + } + }, methodPath.scope, methodPath) + + if (httpMethod && apiPath) { + return { + apiMethodName: methodName, + method: httpMethod, + path: apiPath, + line: methodPath.node.loc ? methodPath.node.loc.start.line : 0, + serviceName: serviceName, + servicePath: servicePath + } + } + + return null + } +} + +module.exports = ApiCollector diff --git a/gofaster/app/plugins/modules/ast-analyzer.js b/gofaster/app/plugins/modules/ast-analyzer.js new file mode 100644 index 0000000..815993d --- /dev/null +++ b/gofaster/app/plugins/modules/ast-analyzer.js @@ -0,0 +1,294 @@ +const { readFileSync, existsSync } = require('fs') +const { resolve } = require('path') +const parser = require('@babel/parser') +const traverse = require('@babel/traverse').default +const { parse } = require('@vue/compiler-sfc') + +/** + * AST分析模块 + * 负责Babel和Vue模板的AST分析 + */ +class AstAnalyzer { + constructor() { + this.processedButtons = new Set() + } + + /** + * 使用Babel AST分析查找调用指定服务方法的方法 + * @param {string} content - 组件内容 + * @param {string} serviceName - 服务名称 + * @param {string} apiMethodName - API方法名称 + * @returns {Array} 调用该API的方法名数组 + */ + findMethodsCallingServiceWithBabel(content, serviceName, apiMethodName) { + const callingMethods = [] + + try { + // 解析script部分 + const scriptMatch = content.match(/]*>([\s\S]*?)<\/script>/) + if (!scriptMatch) { + return callingMethods + } + + const scriptContent = scriptMatch[1] + + // 使用Babel解析JavaScript + const ast = parser.parse(scriptContent, { + sourceType: 'module', + plugins: ['jsx', 'typescript'] + }) + + // 遍历AST查找方法定义 + traverse(ast, { + // 查找const方法定义 + VariableDeclarator(path) { + if (path.node.id && path.node.id.type === 'Identifier' && + path.node.init && path.node.init.type === 'ArrowFunctionExpression') { + + const componentMethodName = path.node.id.name + const methodPath = path.get('init') + + // 检查该方法是否调用了指定的API + if (this.checkMethodCallsServiceWithBabel(methodPath, serviceName, apiMethodName)) { + callingMethods.push(componentMethodName) + } + } + }, + + // 查找ObjectMethod定义 + ObjectMethod(path) { + if (path.node.key && path.node.key.type === 'Identifier') { + const componentMethodName = path.node.key.name + const methodPath = path + + // 检查该方法是否调用了指定的API + if (this.checkMethodCallsServiceWithBabel(methodPath, serviceName, apiMethodName)) { + callingMethods.push(componentMethodName) + } + } + } + }) + + } catch (error) { + // 静默处理错误 + } + + return callingMethods + } + + /** + * 使用Babel检查方法是否调用了指定的service方法 + * @param {Object} methodPath - 方法路径 + * @param {string} serviceName - 服务名称 + * @param {string} apiMethodName - API方法名称 + * @returns {boolean} 是否调用了指定的API + */ + checkMethodCallsServiceWithBabel(methodPath, serviceName, apiMethodName) { + let hasServiceCall = false + + traverse(methodPath.node, { + CallExpression(path) { + const node = path.node + if (node.callee && node.callee.type === 'MemberExpression') { + const object = node.callee.object + const property = node.callee.property + + if (object && property && + object.type === 'Identifier' && + property.type === 'Identifier') { + + if (object.name === serviceName && property.name === apiMethodName) { + hasServiceCall = true + } + } + } + } + }, methodPath.scope, methodPath) + + return hasServiceCall + } + + /** + * 使用AST分析模板中的事件绑定 + * @param {string} templateContent - 模板内容 + * @param {string} componentName - 组件名称 + * @param {Array} apiMethodNames - API方法名数组 + * @param {Array} triggerSources - 触发器源数组 + * @param {string} filePath - 文件路径 + */ + findTriggersInTemplateWithAST(templateContent, componentName, apiMethodNames, triggerSources, filePath) { + try { + // 使用Vue模板编译器解析模板 + const ast = parse(templateContent, { + sourceMap: false, + filename: 'template.vue' + }) + + // 遍历AST找到事件绑定 + this.traverseTemplateAST(ast, (node) => { + if (node.type === 1 && node.tag === 'button') { // 元素节点且是button标签 + this.processButtonNode(node, componentName, apiMethodNames, triggerSources, this.processedButtons, filePath) + } + }) + + } catch (error) { + // 静默处理错误 + } + } + + /** + * 遍历Vue模板AST + * @param {Object} ast - AST节点 + * @param {Function} callback - 回调函数 + */ + traverseTemplateAST(ast, callback) { + if (!ast || !ast.children) return + + ast.children.forEach(child => { + if (child.type === 1) { // 元素节点 + callback(child) + this.traverseTemplateAST(child, callback) + } + }) + } + + /** + * 处理按钮节点 + * @param {Object} node - 按钮节点 + * @param {string} componentName - 组件名称 + * @param {Array} apiMethodNames - API方法名数组 + * @param {Array} triggerSources - 触发器源数组 + * @param {Set} processedButtons - 已处理的按钮集合 + * @param {string} filePath - 文件路径 + */ + processButtonNode(node, componentName, apiMethodNames, triggerSources, processedButtons, filePath) { + if (!node.props) return + + let buttonName = null + let clickHandler = null + + // 提取按钮属性 + node.props.forEach(prop => { + if (prop.name === 'name' && prop.value && prop.value.content) { + buttonName = prop.value.content + } else if (prop.name === 'onClick' && prop.value && prop.value.type === 4) { + // 处理@click事件 + if (prop.value.exp && prop.value.exp.children) { + const exp = prop.value.exp.children[0] + if (exp && exp.type === 4) { // 简单标识符 + clickHandler = exp.content + } + } + } + }) + + // 检查是否匹配API方法 + if (clickHandler && apiMethodNames.includes(clickHandler)) { + if (buttonName && !processedButtons.has(buttonName)) { + triggerSources.push({ + component: componentName, + triggerName: buttonName, + triggerType: 'button' + }) + processedButtons.add(buttonName) + } else if (!buttonName && !processedButtons.has(clickHandler)) { + const generatedName = this.generateUniqueButtonName(componentName, clickHandler) + triggerSources.push({ + component: componentName, + triggerName: generatedName, + triggerType: 'button' + }) + processedButtons.add(clickHandler) + + // 为按钮添加name属性 + if (filePath) { + this.addNameAttributeToButton(filePath, node, generatedName) + } + } + } + } + + /** + * 生成唯一的按钮名称 + * @param {string} componentName - 组件名称 + * @param {string} clickHandler - 点击处理器 + * @returns {string} 唯一的按钮名称 + */ + generateUniqueButtonName(componentName, clickHandler) { + const baseName = componentName.toLowerCase() + const methodSuffix = clickHandler.toLowerCase() + return `${baseName}-${methodSuffix}` + } + + /** + * 为按钮添加name属性 + * @param {string} filePath - 文件路径 + * @param {Object} buttonNode - 按钮节点 + * @param {string} generatedName - 生成的名称 + */ + addNameAttributeToButton(filePath, buttonNode, generatedName) { + try { + const content = readFileSync(filePath, 'utf-8') + + // 检查按钮是否已经有name属性 + const hasName = buttonNode.props && buttonNode.props.some(prop => prop.name === 'name') + if (hasName) { + return + } + + // 在button标签中添加name属性 + const buttonHtml = this.nodeToHtml(buttonNode) + const updatedButtonHtml = buttonHtml.replace(/]*)>/, ``) + + // 替换文件中的按钮HTML + const updatedContent = content.replace(buttonHtml, updatedButtonHtml) + + // 写回文件 + require('fs').writeFileSync(filePath, updatedContent, 'utf-8') + + } catch (error) { + // 静默处理错误 + } + } + + /** + * 将AST节点转换为HTML字符串 + * @param {Object} node - AST节点 + * @returns {string} HTML字符串 + */ + nodeToHtml(node) { + if (!node) return '' + + let html = `<${node.tag}` + + if (node.props) { + node.props.forEach(prop => { + if (prop.name === 'onClick' && prop.value && prop.value.type === 4) { + html += ` @click="${prop.value.exp.children[0].content}"` + } else if (prop.name !== 'onClick') { + html += ` ${prop.name}` + if (prop.value && prop.value.content) { + html += `="${prop.value.content}"` + } + } + }) + } + + html += '>' + + if (node.children && node.children.length > 0) { + node.children.forEach(child => { + if (child.type === 2) { // 文本节点 + html += child.content + } else if (child.type === 1) { // 元素节点 + html += this.nodeToHtml(child) + } + }) + } + + html += `` + return html + } +} + +module.exports = AstAnalyzer diff --git a/gofaster/app/plugins/modules/file-generator.js b/gofaster/app/plugins/modules/file-generator.js new file mode 100644 index 0000000..d86509c --- /dev/null +++ b/gofaster/app/plugins/modules/file-generator.js @@ -0,0 +1,108 @@ +const { writeFileSync, existsSync } = require('fs') +const { resolve } = require('path') + +/** + * 文件生成模块 + * 负责生成最终的映射文件 + */ +class FileGenerator { + constructor() { + this.outputPath = resolve(__dirname, '../../src/renderer/modules/route-sync/direct-route-mappings.js') + } + + /** + * 生成映射文件 + * @param {Array} routes - 路由数组 + * @param {Array} apiMappings - API映射数组 + */ + generateMappingFile(routes, apiMappings) { + const content = this.generateFileContent(routes, apiMappings) + + // 如果文件已存在,先清空内容 + if (existsSync(this.outputPath)) { + writeFileSync(this.outputPath, '', 'utf-8') + } + + // 写入新内容 + writeFileSync(this.outputPath, content, 'utf-8') + } + + /** + * 生成文件内容 + * @param {Array} routes - 路由数组 + * @param {Array} apiMappings - API映射数组 + * @returns {string} 文件内容 + */ + generateFileContent(routes, apiMappings) { + const header = `// 此文件由 route-mapping-plugin 在构建时生成 +// 包含路由配置分析结果和API收集结果 + +export default { + // 路由配置分析结果 + routes: ${JSON.stringify(routes, null, 2)}, + + // API收集结果(包含页面关联和路径完善) + apiMappings: ${JSON.stringify(apiMappings, null, 2)} +}` + + return header + } + + /** + * 优化API映射 + * @param {Array} apiMappings - API映射数组 + * @returns {Array} 优化后的API映射数组 + */ + optimizeApiMappings(apiMappings) { + const optimizedApiMappings = [] + let totalRemoved = 0 + + apiMappings.forEach(moduleMapping => { + const deduplicatedApiMappings = [] + + moduleMapping.apiMappings.forEach(apiMapping => { + // 删除无触发器的API映射 + if (!apiMapping.triggerSources || apiMapping.triggerSources.length === 0) { + totalRemoved++ + return + } + + // 去重触发器源 + const uniqueTriggerSources = this.deduplicateTriggerSources(apiMapping.triggerSources) + + deduplicatedApiMappings.push({ + ...apiMapping, + triggerSources: uniqueTriggerSources + }) + }) + + if (deduplicatedApiMappings.length > 0) { + optimizedApiMappings.push({ + ...moduleMapping, + apiMappings: deduplicatedApiMappings + }) + } + }) + + return optimizedApiMappings + } + + /** + * 去重触发器源 + * @param {Array} triggerSources - 触发器源数组 + * @returns {Array} 去重后的触发器源数组 + */ + deduplicateTriggerSources(triggerSources) { + const seen = new Set() + return triggerSources.filter(trigger => { + const key = `${trigger.component}-${trigger.triggerName}-${trigger.triggerType}` + if (seen.has(key)) { + return false + } + seen.add(key) + return true + }) + } +} + +module.exports = FileGenerator diff --git a/gofaster/app/plugins/modules/index.js b/gofaster/app/plugins/modules/index.js new file mode 100644 index 0000000..e5bd24d --- /dev/null +++ b/gofaster/app/plugins/modules/index.js @@ -0,0 +1,18 @@ +/** + * 路由映射插件模块索引 + * 导出所有模块类 + */ + +const ApiCollector = require('./api-collector') +const AstAnalyzer = require('./ast-analyzer') +const TriggerAnalyzer = require('./trigger-analyzer') +const FileGenerator = require('./file-generator') +const RouteAnalyzer = require('./route-analyzer') + +module.exports = { + ApiCollector, + AstAnalyzer, + TriggerAnalyzer, + FileGenerator, + RouteAnalyzer +} diff --git a/gofaster/app/plugins/modules/route-analyzer.js b/gofaster/app/plugins/modules/route-analyzer.js new file mode 100644 index 0000000..b139ae6 --- /dev/null +++ b/gofaster/app/plugins/modules/route-analyzer.js @@ -0,0 +1,86 @@ +const { readFileSync, existsSync } = require('fs') +const { resolve } = require('path') + +/** + * 路由分析模块 + * 负责分析路由配置 + */ +class RouteAnalyzer { + constructor() { + this.routes = [] + } + + /** + * 分析路由配置 + * @returns {Array} 路由数组 + */ + analyzeRoutes() { + this.routes = [] + + try { + const routeConfigPath = resolve(__dirname, '../../src/renderer/router/index.js') + if (existsSync(routeConfigPath)) { + const routeContent = readFileSync(routeConfigPath, 'utf-8') + this.routes = this.parseRouteConfig(routeContent) + } + } catch (error) { + // 静默处理错误 + } + + return this.routes + } + + /** + * 解析路由配置 + * @param {string} routeContent - 路由配置内容 + * @returns {Array} 路由数组 + */ + parseRouteConfig(routeContent) { + const routes = [] + + // 简单的路由解析,查找路由定义 + const routeMatches = routeContent.match(/path:\s*['"]([^'"]+)['"][\s\S]*?component:\s*['"]([^'"]+)['"]/g) + + if (routeMatches) { + routeMatches.forEach(match => { + const pathMatch = match.match(/path:\s*['"]([^'"]+)['"]/) + const componentMatch = match.match(/component:\s*['"]([^'"]+)['"]/) + + if (pathMatch && componentMatch) { + routes.push({ + path: pathMatch[1], + component: componentMatch[1] + }) + } + }) + } + + return routes + } + + /** + * 获取模块列表 + * @returns {Array} 模块目录列表 + */ + getModuleDirs() { + const moduleDirs = [] + + try { + const modulesPath = resolve(__dirname, '../../src/renderer/modules') + if (existsSync(modulesPath)) { + const dirs = require('fs').readdirSync(modulesPath, { withFileTypes: true }) + dirs.forEach(dirent => { + if (dirent.isDirectory()) { + moduleDirs.push(dirent.name) + } + }) + } + } catch (error) { + // 静默处理错误 + } + + return moduleDirs + } +} + +module.exports = RouteAnalyzer diff --git a/gofaster/app/plugins/modules/trigger-analyzer.js b/gofaster/app/plugins/modules/trigger-analyzer.js new file mode 100644 index 0000000..acd173d --- /dev/null +++ b/gofaster/app/plugins/modules/trigger-analyzer.js @@ -0,0 +1,407 @@ +const { readFileSync, existsSync, readdirSync } = require('fs') +const { resolve } = require('path') +const AstAnalyzer = require('./ast-analyzer') + +/** + * 触发器分析模块 + * 负责分析组件中的按钮和事件触发器 + */ +class TriggerAnalyzer { + constructor() { + this.astAnalyzer = new AstAnalyzer() + } + + /** + * 查找API方法的触发器源 + * @param {Array} apiMappings - API映射数组 + * @returns {Array} 增强的API映射数组 + */ + findTriggerSourcesForApiMappings(apiMappings) { + const enhancedApiMappings = [] + + apiMappings.forEach(moduleMapping => { + const enhancedModuleMapping = { + ...moduleMapping, + apiMappings: [] + } + + moduleMapping.apiMappings.forEach(apiMapping => { + const triggerSources = this.findTriggerSourcesForApiMethod( + apiMapping.serviceName, + apiMapping.apiMethodName, + moduleMapping.module + ) + + if (triggerSources.length > 0) { + enhancedModuleMapping.apiMappings.push({ + ...apiMapping, + triggerSources: triggerSources + }) + } + }) + + if (enhancedModuleMapping.apiMappings.length > 0) { + enhancedApiMappings.push(enhancedModuleMapping) + } + }) + + return enhancedApiMappings + } + + /** + * 查找API方法的触发器源 + * @param {string} serviceName - 服务名称 + * @param {string} apiMethodName - API方法名称 + * @param {string} moduleName - 模块名称 + * @returns {Array} 触发器源数组 + */ + findTriggerSourcesForApiMethod(serviceName, apiMethodName, moduleName) { + const triggerSources = [] + + // 1. 检查路由组件 + const routeComponents = this.getRouteComponents(moduleName) + routeComponents.forEach(route => { + const triggerAnalysis = this.analyzeComponentWithAST( + route.component, + serviceName, + apiMethodName + ) + + if (triggerAnalysis.triggerSources.length > 0) { + triggerSources.push(...triggerAnalysis.triggerSources) + } + }) + + // 2. 检查模块中的所有组件 + const componentsInModule = this.getModuleComponents(moduleName) + componentsInModule.forEach(componentName => { + const triggerAnalysis = this.analyzeComponentWithAST( + componentName, + serviceName, + apiMethodName + ) + + if (triggerAnalysis.triggerSources.length > 0) { + triggerSources.push(...triggerAnalysis.triggerSources) + } + }) + + // 去重 + const uniqueTriggerSources = this.deduplicateTriggerSources(triggerSources) + + return uniqueTriggerSources + } + + /** + * 获取路由组件 + * @param {string} moduleName - 模块名称 + * @returns {Array} 路由组件数组 + */ + getRouteComponents(moduleName) { + const routeComponents = [] + + try { + const routeConfigPath = resolve(__dirname, '../../src/renderer/router/index.js') + if (existsSync(routeConfigPath)) { + const routeContent = readFileSync(routeConfigPath, 'utf-8') + + // 简单的路由解析,查找模块相关的路由 + const routeMatches = routeContent.match(new RegExp(`component:\\s*['"]${moduleName}[^'"]*['"]`, 'g')) + if (routeMatches) { + routeMatches.forEach(match => { + const componentMatch = match.match(/component:\s*['"]([^'"]+)['"]/) + if (componentMatch) { + routeComponents.push({ + component: componentMatch[1] + }) + } + }) + } + } + } catch (error) { + // 静默处理错误 + } + + return routeComponents + } + + /** + * 获取模块中的所有组件 + * @param {string} moduleName - 模块名称 + * @returns {Array} 组件名称数组 + */ + getModuleComponents(moduleName) { + const components = [] + + try { + const modulePath = resolve(__dirname, '../../src/renderer/modules', moduleName) + if (existsSync(modulePath)) { + // 查找views目录 + const viewsPath = resolve(modulePath, 'views') + if (existsSync(viewsPath)) { + const viewFiles = readdirSync(viewsPath).filter(file => file.endsWith('.vue')) + viewFiles.forEach(file => { + components.push(file.replace('.vue', '')) + }) + } + + // 查找components目录 + const componentsPath = resolve(modulePath, 'components') + if (existsSync(componentsPath)) { + const componentFiles = readdirSync(componentsPath).filter(file => file.endsWith('.vue')) + componentFiles.forEach(file => { + components.push(file.replace('.vue', '')) + }) + } + } + } catch (error) { + // 静默处理错误 + } + + return components + } + + /** + * 使用AST分析组件 + * @param {string} componentName - 组件名称 + * @param {string} serviceName - 服务名称 + * @param {string} apiMethodName - API方法名称 + * @returns {Object} 触发器分析结果 + */ + analyzeComponentWithAST(componentName, serviceName, apiMethodName) { + const triggerSources = [] + + try { + const filePath = this.findComponentFile(componentName) + if (!filePath) { + return { triggerSources: [] } + } + + const content = readFileSync(filePath, 'utf-8') + + // 1. 检查组件的authType属性,如果是public则跳过 + const authTypeMatch = content.match(/authType\s*[=:]\s*["']([^"']+)["']/) + if (authTypeMatch && authTypeMatch[1] === 'public') { + return { triggerSources: [] } + } + + // 2. 检查组件是否包含目标API调用 + if (!content.includes(`${serviceName}.${apiMethodName}`)) { + return { triggerSources: [] } + } + + // 3. 使用Babel AST分析找到调用该API的方法 + const callingMethods = this.astAnalyzer.findMethodsCallingServiceWithBabel(content, serviceName, apiMethodName) + + if (callingMethods.length > 0) { + // 4. 分析每个调用方法的触发源 + const methodTriggers = this.analyzeMethodTriggerSources(content, componentName, callingMethods, filePath) + triggerSources.push(...methodTriggers) + } else { + // 如果没有找到按钮触发器,记录页面级触发器 + triggerSources.push({ + component: componentName, + triggerName: null, + triggerType: 'page' + }) + } + + } catch (error) { + // 静默处理错误 + } + + return { triggerSources } + } + + /** + * 查找组件文件 + * @param {string} componentName - 组件名称 + * @returns {string|null} 组件文件路径 + */ + findComponentFile(componentName) { + const possiblePaths = [ + resolve(__dirname, '../../src/renderer/modules', componentName, 'views', `${componentName}.vue`), + resolve(__dirname, '../../src/renderer/modules', componentName, 'components', `${componentName}.vue`), + resolve(__dirname, '../../src/renderer/components', `${componentName}.vue`), + resolve(__dirname, '../../src/renderer/views', `${componentName}.vue`) + ] + + for (const path of possiblePaths) { + if (existsSync(path)) { + return path + } + } + + return null + } + + /** + * 分析方法的触发源 + * @param {string} content - 组件内容 + * @param {string} componentName - 组件名称 + * @param {Array} methodNames - 方法名数组 + * @param {string} filePath - 文件路径 + * @returns {Array} 触发器源数组 + */ + analyzeMethodTriggerSources(content, componentName, methodNames, filePath = null) { + const triggerSources = [] + + // 提取模板部分 + const templateMatch = content.match(/