From abcb8e44fbdc9d4e6c785f363113d7207f9c1432 Mon Sep 17 00:00:00 2001 From: hejl Date: Sun, 7 Sep 2025 00:08:57 +0800 Subject: [PATCH] =?UTF-8?q?button=E5=A2=9E=E5=8A=A0name=E5=B1=9E=E6=80=A7?= =?UTF-8?q?=EF=BC=8Cservice=20api=20=E5=87=BD=E6=95=B0=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gofaster/app/plugins/route-mapping-plugin.js | 313 +++++++++++++++++- .../app/scripts/generate-route-mappings.js | 9 +- .../src/renderer/components/LoginModal.vue | 3 +- .../src/renderer/components/MainLayout.vue | 22 +- .../components/PasswordChangeModal.vue | 1 + .../modules/core/components/MainLayout.vue | 20 +- .../modules/core/components/StatusBar.vue | 4 +- .../modules/core/components/Toast.vue | 4 +- .../modules/core/views/ConfigTest.vue | 2 +- .../src/renderer/modules/core/views/Home.vue | 6 +- .../components/PermissionManager.vue | 6 +- .../components/RolePermissionAssignment.vue | 4 +- 12 files changed, 353 insertions(+), 41 deletions(-) diff --git a/gofaster/app/plugins/route-mapping-plugin.js b/gofaster/app/plugins/route-mapping-plugin.js index b9a88a8..6277e82 100644 --- a/gofaster/app/plugins/route-mapping-plugin.js +++ b/gofaster/app/plugins/route-mapping-plugin.js @@ -7,13 +7,26 @@ const { parse } = require('@vue/compiler-sfc') // 第一阶段路由映射插件 - 收集直接映射关系 function routeMappingPlugin() { + // 检查是否在静默模式下运行 + const isSilentMode = process.env.ROUTE_MAPPING_SILENT === 'true' || process.argv.includes('--silent') + + // 日志函数 + const log = (message) => { + if (!isSilentMode) { + console.log(message) + } + } + return { name: 'route-mapping-phase1', // 防重复生成机制 _lastGenerationTime: 0, _generationInProgress: false, - _generationCooldown: 5000, // 5秒冷却时间 + _generationCooldown: 10000, // 10秒冷却时间,增加冷却时间 + _maxGenerationsPerMinute: 3, // 每分钟最多生成3次 + _generationCount: 0, + _generationWindowStart: 0, // Webpack插件接口 apply(compiler) { @@ -22,12 +35,12 @@ function routeMappingPlugin() { // 在编译开始前就生成映射文件 compiler.hooks.beforeCompile.tapAsync('RouteMappingPlugin', (params, callback) => { if (self._shouldSkipGeneration()) { - console.log('⏭️ 跳过路由映射生成(冷却期内)') + log('⏭️ 跳过路由映射生成(冷却期内)') callback() return } - console.log('🔧 第一阶段:开始收集直接路由-API映射关系...') + log('🔧 第一阶段:开始收集直接路由-API映射关系...') try { self.collectDirectMappings() callback() @@ -78,11 +91,26 @@ function routeMappingPlugin() { // 如果正在生成中,跳过 if (this._generationInProgress) { + console.log('⏭️ 跳过生成:正在生成中') return true } // 如果在冷却期内,跳过 if (now - this._lastGenerationTime < this._generationCooldown) { + const remainingTime = Math.ceil((this._generationCooldown - (now - this._lastGenerationTime)) / 1000) + console.log(`⏭️ 跳过生成:冷却期内,还需等待 ${remainingTime} 秒`) + return true + } + + // 检查每分钟生成次数限制 + if (now - this._generationWindowStart > 60000) { + // 重置计数窗口 + this._generationCount = 0 + this._generationWindowStart = now + } + + if (this._generationCount >= this._maxGenerationsPerMinute) { + console.log(`⏭️ 跳过生成:已达到每分钟最大生成次数限制 (${this._maxGenerationsPerMinute} 次)`) return true } @@ -204,6 +232,11 @@ function routeMappingPlugin() { // 重置生成中标志并更新时间戳 this._generationInProgress = false this._lastGenerationTime = Date.now() + this._generationCount++ + + if (this._generationWindowStart === 0) { + this._generationWindowStart = Date.now() + } } }, @@ -259,7 +292,8 @@ function routeMappingPlugin() { const content = readFileSync(servicePath, 'utf-8') const ast = parser.parse(content, { sourceType: 'module', - plugins: ['jsx'] + plugins: ['jsx'], + attachComments: true }) const apiMappings = [] @@ -288,7 +322,8 @@ function routeMappingPlugin() { if (path.node.init && (path.node.init.type === 'ArrowFunctionExpression' || path.node.init.type === 'FunctionExpression')) { const functionName = path.node.id?.name if (functionName) { - const apiCalls = self.extractApiCallsFromFunction(path.get('init')) + const functionPath = path.get('init') + const apiCalls = self.extractApiCallsFromFunction(functionPath) apiCalls.forEach(apiCall => { apiMappings.push({ methodName: functionName, @@ -348,7 +383,14 @@ function routeMappingPlugin() { const apiCalls = [] const self = this - // 使用正确的traverse调用方式 + // 首先检查函数注释中是否有 @serverApiMethod 标记 + const commentApiInfo = self.extractApiFromComment(functionPath) + if (commentApiInfo) { + apiCalls.push(commentApiInfo) + return apiCalls + } + + // 如果没有注释信息,则使用原来的AST遍历方式 if (functionPath.node) { traverse(functionPath.node, { CallExpression(path) { @@ -363,22 +405,55 @@ function routeMappingPlugin() { return apiCalls }, + // 从注释中提取API信息 + extractApiFromComment(functionPath) { + const node = functionPath.node + + // 首先检查 leadingComments + if (node && node.leadingComments) { + for (const comment of node.leadingComments) { + const commentText = comment.value.trim() + const match = commentText.match(/@serverApiMethod\s+(\w+)\s+(.+)/) + if (match) { + const method = match[1].toUpperCase() + const path = match[2].trim() + return { + method, + path, + line: node.loc ? node.loc.start.line : 0, + source: 'comment' + } + } + } + } else { + } + + // 如果没有找到注释,尝试从函数名推断API信息 + // 这是一个备用方案,用于测试 + return null + }, + // 第二步(第一层):收集API信息 collectApiMappings(routes) { const apiMappings = [] const moduleDirs = this.readModuleNamesFromConfig() + console.log('🔍 开始收集API映射,模块列表:', moduleDirs) + moduleDirs.forEach(moduleName => { const servicesPath = resolve(__dirname, `../src/renderer/modules/${moduleName}/services`) if (existsSync(servicesPath)) { const serviceFiles = readdirSync(servicesPath).filter(f => f.endsWith('.js')) + console.log(`🔍 模块 ${moduleName} 的service文件:`, serviceFiles) serviceFiles.forEach(serviceFile => { const serviceName = serviceFile.replace('.js', '') const servicePath = resolve(servicesPath, serviceFile) const serviceApiMappings = this.analyzeServiceFileForApiMappings(servicePath, serviceName, moduleName) + console.log(`🔍 服务 ${serviceName} 的API映射数量:`, serviceApiMappings.length) + if (serviceApiMappings.length > 0) { apiMappings.push({ module: moduleName, @@ -393,23 +468,29 @@ function routeMappingPlugin() { } }) + console.log('🔍 最终收集到的API映射数量:', apiMappings.length) return apiMappings }, // 第三步:关联页面与API associatePagesWithApi(routes, apiMappings) { + console.log('🔍 开始关联页面与API,输入API映射数量:', apiMappings.length) // 为每个API映射添加调用该API的页面信息 const enhancedApiMappings = apiMappings.map(moduleMapping => { + console.log(`🔍 处理模块 ${moduleMapping.module},API映射数量:`, moduleMapping.apiMappings.length) + const enhancedApiMappings = moduleMapping.apiMappings.map(apiMapping => { - // 查找调用该API方法的组件 - const callingComponents = this.findComponentsCallingApiMethod( + // 查找调用该API方法的组件,并收集button触发器信息 + const callingComponents = this.findComponentsCallingApiMethodWithTriggers( routes, moduleMapping.module, moduleMapping.serviceName, apiMapping.methodName ) + console.log(`🔍 API方法 ${apiMapping.methodName} 的调用组件数量:`, callingComponents.length) + return { ...apiMapping, callingComponents: callingComponents @@ -422,6 +503,7 @@ function routeMappingPlugin() { } }) + console.log('🔍 关联完成,输出API映射数量:', enhancedApiMappings.length) return enhancedApiMappings }, @@ -480,6 +562,55 @@ function routeMappingPlugin() { return [...new Set(callingComponents)] }, + // 查找调用特定API方法的组件,并收集button触发器信息 + findComponentsCallingApiMethodWithTriggers(routes, moduleName, serviceName, methodName) { + const callingComponents = [] + + // 1. 遍历所有路由,查找调用该API方法的页面组件 + routes.forEach(route => { + if (route.module === moduleName) { + const componentAnalysis = this.analyzeComponentForServiceCallsWithTriggers( + route.component, + route.path, + moduleName, + serviceName, + methodName + ) + + if (componentAnalysis && componentAnalysis.hasServiceCall) { + callingComponents.push({ + component: route.component, + path: route.path, + trigger_source: componentAnalysis.triggerSources + }) + } + } + }) + + // 2. 搜索模块的components目录,查找调用该API方法的组件 + const componentsInModule = this.findComponentsInModule(moduleName) + componentsInModule.forEach(componentName => { + const componentAnalysis = this.analyzeComponentForServiceCallsWithTriggers( + componentName, + null, // components目录中的组件没有路由路径 + moduleName, + serviceName, + methodName + ) + + if (componentAnalysis && componentAnalysis.hasServiceCall) { + callingComponents.push({ + component: componentName, + path: null, + trigger_source: componentAnalysis.triggerSources + }) + } + }) + + return callingComponents + }, + + // 查找模块中的所有组件 findComponentsInModule(moduleName) { const components = [] @@ -513,9 +644,9 @@ function routeMappingPlugin() { optimizeApiMappings(routes, apiMappings) { const optimizedApiMappings = apiMappings.map(moduleMapping => { - // 过滤掉callingComponents为空的API映射 + // 暂时不过滤空的调用组件,保留所有API映射 const filteredApiMappings = moduleMapping.apiMappings.filter(apiMapping => { - return apiMapping.callingComponents && apiMapping.callingComponents.length > 0 + return true // 保留所有API映射 }) // 为每个API映射的callingComponents添加路径信息 @@ -829,6 +960,164 @@ function routeMappingPlugin() { } }, + // 分析组件中调用特定API方法的服务调用和触发器 + analyzeComponentForServiceCallsWithTriggers(componentName, componentPath, moduleName, serviceName, methodName) { + try { + let filePath = componentPath + + // 如果是组件名,需要找到对应的文件 + if (!filePath || !existsSync(filePath)) { + filePath = this.findComponentFile(componentName) + } + + if (!filePath || !existsSync(filePath)) { + console.warn(`⚠️ 组件文件未找到: ${componentName}`) + return { hasServiceCall: false, triggerSources: [] } + } + + const content = readFileSync(filePath, 'utf-8') + + if (filePath.endsWith('.vue')) { + return this.analyzeVueComponentForServiceCallsWithTriggers(content, componentName, moduleName, serviceName, methodName) + } else { + return this.analyzeJavaScriptComponentForServiceCallsWithTriggers(content, componentName, moduleName, serviceName, methodName) + } + } catch (error) { + console.warn(`⚠️ 分析组件服务调用和触发器失败: ${componentName}`, error.message) + return { hasServiceCall: false, triggerSources: [] } + } + }, + + // 分析Vue组件中的服务调用和触发器 + analyzeVueComponentForServiceCallsWithTriggers(content, componentName, moduleName, serviceName, methodName) { + const { descriptor } = parse(content) + const scriptContent = descriptor.script?.content || '' + const templateContent = descriptor.template?.content || '' + + if (!scriptContent) { + return { hasServiceCall: false, triggerSources: [] } + } + + // 解析script部分,找到调用指定service方法的方法 + const ast = parser.parse(scriptContent, { + sourceType: 'module', + plugins: ['jsx'] + }) + + const callingMethods = this.findMethodsCallingService(ast, serviceName, methodName) + + if (callingMethods.length === 0) { + return { hasServiceCall: false, triggerSources: [] } + } + + // 解析template部分,找到调用这些方法的button + const triggerSources = this.findButtonsCallingMethods(templateContent, callingMethods, componentName) + + return { + hasServiceCall: true, + triggerSources: triggerSources + } + }, + + // 分析JavaScript组件中的服务调用和触发器 + analyzeJavaScriptComponentForServiceCallsWithTriggers(content, componentName, moduleName, serviceName, methodName) { + const ast = parser.parse(content, { + sourceType: 'module', + plugins: ['jsx'] + }) + + const callingMethods = this.findMethodsCallingService(ast, serviceName, methodName) + + if (callingMethods.length === 0) { + return { hasServiceCall: false, triggerSources: [] } + } + + // 对于JS组件,我们假设方法名就是触发器 + const triggerSources = callingMethods.map(method => `${componentName}#${method}`) + + return { + hasServiceCall: true, + triggerSources: triggerSources + } + }, + + + // 查找调用指定service方法的方法 + findMethodsCallingService(ast, serviceName, methodName) { + const callingMethods = [] + const self = this + + traverse(ast, { + ObjectMethod(path) { + const methodName = path.node.key?.name + if (methodName) { + const hasServiceCall = self.checkMethodCallsService(path, serviceName, methodName) + if (hasServiceCall) { + callingMethods.push(methodName) + } + } + }, + + ObjectProperty(path) { + if (path.node.value && (path.node.value.type === 'ArrowFunctionExpression' || path.node.value.type === 'FunctionExpression')) { + const methodName = path.node.key?.name + if (methodName) { + const hasServiceCall = self.checkMethodCallsService(path.get('value'), serviceName, methodName) + if (hasServiceCall) { + callingMethods.push(methodName) + } + } + } + } + }) + + return callingMethods + }, + + // 检查方法是否调用了指定的service方法 + checkMethodCallsService(methodPath, serviceName, methodName) { + 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.name === serviceName && + property.name === methodName) { + hasServiceCall = true + } + } + } + }, methodPath.scope, methodPath) + + return hasServiceCall + }, + + // 在template中查找调用指定方法的button + findButtonsCallingMethods(templateContent, methodNames, componentName) { + const triggerSources = [] + + // 使用正则表达式查找button元素 + const buttonRegex = /]*name\s*=\s*["']([^"']+)["'][^>]*@click\s*=\s*["']([^"']+)["'][^>]*>/g + let match + + while ((match = buttonRegex.exec(templateContent)) !== null) { + const buttonName = match[1] + const clickHandler = match[2] + + // 检查clickHandler是否在methodNames中 + if (methodNames.includes(clickHandler)) { + triggerSources.push(`${componentName}#${buttonName}`) + } + } + + return triggerSources + }, + // 分析Vue组件中的服务调用 analyzeVueComponentForServiceCalls(content, componentName, moduleName) { const { descriptor } = parse(content) @@ -1060,6 +1349,7 @@ function routeMappingPlugin() { try { const configPath = resolve(__dirname, '../src/renderer/modules/config.js') if (!existsSync(configPath)) { + console.warn('⚠️ 模块配置文件不存在:', configPath) return [] } @@ -1142,6 +1432,9 @@ function routeMappingPlugin() { generateMappingFile(routes, apiMappings) { const outputPath = resolve(__dirname, '../src/renderer/modules/route-sync/direct-route-mappings.js') + console.log('🔍 生成映射文件,API映射数量:', apiMappings.length) + console.log('🔍 API映射内容:', JSON.stringify(apiMappings, null, 2)) + // 如果文件已存在,先删除文件内容(清空文件) if (existsSync(outputPath)) { console.log('🗑️ 检测到已存在的映射文件,正在清空文件内容...') diff --git a/gofaster/app/scripts/generate-route-mappings.js b/gofaster/app/scripts/generate-route-mappings.js index 7e1fbf8..34786ff 100644 --- a/gofaster/app/scripts/generate-route-mappings.js +++ b/gofaster/app/scripts/generate-route-mappings.js @@ -4,6 +4,12 @@ const { resolve } = require('path') // 检查命令行参数 const isCheckOnly = process.argv.includes('--check-only') +const isSilent = process.argv.includes('--silent') + +// 设置静默模式环境变量 +if (isSilent) { + process.env.ROUTE_MAPPING_SILENT = 'true' +} // 独立运行路由映射生成 if (isCheckOnly) { @@ -16,7 +22,8 @@ if (isCheckOnly) { process.exit(0) } else { console.log('⚠️ 路由映射文件不存在,需要生成') - // 继续执行生成逻辑 + console.log('💡 请运行不带 --check-only 参数的脚本来生成文件') + process.exit(1) } } else { console.log('🔧 独立生成路由映射文件...') diff --git a/gofaster/app/src/renderer/components/LoginModal.vue b/gofaster/app/src/renderer/components/LoginModal.vue index 99ffe72..631f1bb 100644 --- a/gofaster/app/src/renderer/components/LoginModal.vue +++ b/gofaster/app/src/renderer/components/LoginModal.vue @@ -1,4 +1,4 @@ -