From a2dca55854a6d2ad584e5c69a42a99b781bc1ad2 Mon Sep 17 00:00:00 2001 From: hejl Date: Sun, 7 Sep 2025 12:07:08 +0800 Subject: [PATCH] =?UTF-8?q?api=E6=94=B6=E9=9B=86=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gofaster/app/plugins/modules/api-collector.js | 164 +++++++++++++----- .../app/plugins/modules/file-generator.js | 24 +++ gofaster/app/plugins/route-mapping-plugin.js | 14 +- .../route-sync/direct-route-mappings.js | 160 ++++++++++++++++- gofaster/app/test-optimized-collection.js | 11 -- 5 files changed, 313 insertions(+), 60 deletions(-) delete mode 100644 gofaster/app/test-optimized-collection.js diff --git a/gofaster/app/plugins/modules/api-collector.js b/gofaster/app/plugins/modules/api-collector.js index 29ac376..59a3865 100644 --- a/gofaster/app/plugins/modules/api-collector.js +++ b/gofaster/app/plugins/modules/api-collector.js @@ -12,6 +12,39 @@ class ApiCollector { this.apiMappings = [] } + /** + * 获取API基础URL + * @returns {string} API基础URL + */ + getApiBaseUrl() { + try { + // 读取配置文件 + const configPath = resolve(__dirname, '../../src/config/app.config.js') + if (existsSync(configPath)) { + const configContent = readFileSync(configPath, 'utf-8') + + // 解析配置文件,提取apiBaseUrl + // 匹配 development 环境中的 apiBaseUrl 配置 + const developmentMatch = configContent.match(/development:\s*\{[^}]*apiBaseUrl:\s*[^:]*:\s*['"`]([^'"`]+)['"`]/s) + if (developmentMatch) { + return developmentMatch[1] + } + + // 如果上面没匹配到,尝试匹配简单的字符串格式 + const simpleMatch = configContent.match(/apiBaseUrl:\s*['"`]([^'"`]+)['"`]/) + if (simpleMatch) { + return simpleMatch[1] + } + } + + // 如果无法从配置文件获取,返回空字符串(使用相对路径) + return '' + } catch (error) { + console.warn('⚠️ 无法获取API基础URL,使用相对路径:', error.message) + return '' + } + } + /** * 收集所有模块的API映射 * @param {Array} moduleDirs - 模块目录列表 @@ -25,8 +58,7 @@ class ApiCollector { if (moduleApiMappings.length > 0) { this.apiMappings.push({ module: moduleName, - serviceName: moduleApiMappings[0].serviceName, - servicePath: moduleApiMappings[0].servicePath, + serviceName: moduleApiMappings.serviceName, apiMappings: moduleApiMappings }) } @@ -50,18 +82,31 @@ class ApiCollector { const serviceFiles = readdirSync(servicesPath).filter(file => file.endsWith('.js')) const moduleApiMappings = [] + let firstServiceName = null + serviceFiles.forEach(serviceFile => { const servicePath = resolve(servicesPath, serviceFile) const serviceName = serviceFile.replace('.js', '') + // 记录第一个服务名称(第一层需要) + if (!firstServiceName) { + firstServiceName = serviceName + } + try { const serviceApiMappings = this.analyzeServiceFile(servicePath, serviceName, moduleName) + // 不再为每个API映射添加serviceName(第二层冗余信息) moduleApiMappings.push(...serviceApiMappings) } catch (error) { // 静默处理错误 } }) + // 为模块API映射添加serviceName(第一层需要) + if (firstServiceName) { + moduleApiMappings.serviceName = firstServiceName + } + return moduleApiMappings } @@ -113,7 +158,7 @@ class ApiCollector { } /** - * 从AST节点中提取API映射信息 + * 从AST节点中提取API映射信息 - 优化版本:只使用@serverApiMethod注解 * @param {Object} methodPath - 方法路径 * @param {string} methodName - 方法名称 * @param {string} serviceName - 服务名称 @@ -121,49 +166,86 @@ class ApiCollector { * @returns {Object|null} API映射对象 */ extractApiMapping(methodPath, methodName, serviceName, servicePath) { - let httpMethod = null - let apiPath = null + // 只从@serverApiMethod注解中提取信息(强规则) + const annotationInfo = this.extractFromAnnotation(methodPath, servicePath) + if (annotationInfo) { + // 获取API基础URL + const apiBaseUrl = this.getApiBaseUrl() + + // 组合完整的API路径 + const fullPath = `${apiBaseUrl}${annotationInfo.path}` + + // 提取路径部分,去掉协议、服务器地址和端口 + const pathOnly = this.extractPathFromUrl(fullPath) + + return { + apiMethodName: methodName, + method: annotationInfo.method, + path: pathOnly + } + } - // 查找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 + // 如果没有注解,返回null(不再回退到代码分析) + return null + } + + /** + * 从完整URL中提取路径部分 + * @param {string} fullUrl - 完整的URL + * @returns {string} 路径部分 + */ + extractPathFromUrl(fullUrl) { + try { + // 如果URL包含协议,使用URL对象解析 + if (fullUrl.includes('://')) { + const url = new URL(fullUrl) + // 解码路径,确保路径参数保持原始格式 + return decodeURIComponent(url.pathname) + } + + // 如果没有协议,直接返回(已经是路径格式) + return fullUrl + } catch (error) { + // 如果解析失败,返回原始字符串 + return fullUrl + } + } + + /** + * 从@serverApiMethod注解中提取API信息 + * @param {Object} methodPath - 方法路径 + * @param {string} servicePath - 服务文件路径 + * @returns {Object|null} 注解信息 + */ + extractFromAnnotation(methodPath, servicePath) { + try { + const content = readFileSync(servicePath, 'utf-8') + const lines = content.split('\n') + const methodLine = methodPath.node.loc ? methodPath.node.loc.start.line : 0 + + // 查找方法定义前的注释行(从方法行开始向上搜索) + for (let i = methodLine - 2; i >= 0; i--) { + const line = lines[i].trim() + + // 查找@serverApiMethod注解 + const annotationMatch = line.match(/\/\/\s*@serverApiMethod\s+(\w+)\s+(.+)/) + if (annotationMatch) { + const method = annotationMatch[1].toUpperCase() + const path = annotationMatch[2].trim() - 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 - } - } - } + return { + method: method, + path: path } } + + // 如果遇到非注释行且不是空行,停止搜索 + if (line && !line.startsWith('//') && !line.startsWith('*') && !line.startsWith('/*')) { + break + } } - }, 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 - } + } catch (error) { + console.error(`❌ 解析注解失败: ${servicePath}`, error.message) } return null diff --git a/gofaster/app/plugins/modules/file-generator.js b/gofaster/app/plugins/modules/file-generator.js index c039758..5a8a588 100644 --- a/gofaster/app/plugins/modules/file-generator.js +++ b/gofaster/app/plugins/modules/file-generator.js @@ -72,6 +72,30 @@ export default { writeFileSync(this.outputPath, content, 'utf-8') } + /** + * 生成路由和API映射文件 - 第三步第一小步:包含API信息 + * @param {Array} routes - 路由数组 + * @param {Array} apiMappings - API映射数组 + */ + generateRouteApiMappingFile(routes, apiMappings) { + const content = `// 路由映射数据 - 构建时生成 +export default { + // 路由配置 + routes: ${JSON.stringify(routes, null, 2)}, + + // API映射配置 + apiMappings: ${JSON.stringify(apiMappings, null, 2)} +}` + + // 如果文件已存在,先清空内容 + if (existsSync(this.outputPath)) { + writeFileSync(this.outputPath, '', 'utf-8') + } + + // 写入新内容 + writeFileSync(this.outputPath, content, 'utf-8') + } + /** * 生成文件内容 * @param {Array} routes - 路由数组 diff --git a/gofaster/app/plugins/route-mapping-plugin.js b/gofaster/app/plugins/route-mapping-plugin.js index 8f6be8f..93e11f8 100644 --- a/gofaster/app/plugins/route-mapping-plugin.js +++ b/gofaster/app/plugins/route-mapping-plugin.js @@ -130,12 +130,16 @@ function routeMappingPlugin() { return } - // 第二步:启用路由分析功能 + // 第三步第一小步:启用API收集功能 // 1. 分析路由配置 const routes = this._routeAnalyzer.analyzeRoutes() - // 生成包含路由信息的文件 - this._fileGenerator.generateRouteMappingFile(routes) + // 2. 收集API信息(第一层) + const moduleDirs = this._routeAnalyzer.getModuleDirs() + const apiMappings = this._apiCollector.collectApiMappings(moduleDirs) + + // 生成包含路由和API信息的文件 + this._fileGenerator.generateRouteApiMappingFile(routes, apiMappings) // 重置生成中标志并更新时间戳 this._generationInProgress = false @@ -148,10 +152,6 @@ function routeMappingPlugin() { // TODO: 后续步骤将在优化过程中逐步添加 - // 2. 收集API信息(第一层) - // const moduleDirs = this._routeAnalyzer.getModuleDirs() - // const apiMappings = this._apiCollector.collectApiMappings(moduleDirs) - // 3. 关联页面与API(第三层) // const enhancedApiMappings = this._triggerAnalyzer.findTriggerSourcesForApiMappings(apiMappings) 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 b067131..8046178 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 @@ -31,5 +31,163 @@ export default { ], // API映射配置 - apiMappings: [] + apiMappings: [ + { + "module": "role-management", + "serviceName": "roleService", + "apiMappings": [ + { + "apiMethodName": "getRoles", + "method": "GET", + "path": "/api/auth/roles" + }, + { + "apiMethodName": "getRole", + "method": "GET", + "path": "/api/auth/roles/{id}" + }, + { + "apiMethodName": "createRole", + "method": "POST", + "path": "/api/auth/roles" + }, + { + "apiMethodName": "updateRole", + "method": "PUT", + "path": "/api/auth/roles/{id}" + }, + { + "apiMethodName": "deleteRole", + "method": "DELETE", + "path": "/api/auth/roles/{id}" + }, + { + "apiMethodName": "getPermissions", + "method": "GET", + "path": "/api/auth/permissions" + }, + { + "apiMethodName": "assignRolesToUser", + "method": "POST", + "path": "/api/auth/roles/users/{userId}/assign" + }, + { + "apiMethodName": "getUserRoles", + "method": "GET", + "path": "/api/auth/roles/users/{userId}" + }, + { + "apiMethodName": "removeRolesFromUser", + "method": "DELETE", + "path": "/api/auth/roles/users/{userId}/remove" + }, + { + "apiMethodName": "getRolePermissions", + "method": "GET", + "path": "/api/auth/permissions/roles/{roleId}" + }, + { + "apiMethodName": "assignPermissionsToRole", + "method": "POST", + "path": "/api/auth/permissions/roles/{roleId}/assign" + }, + { + "apiMethodName": "removePermissionsFromRole", + "method": "DELETE", + "path": "/api/auth/permissions/roles/{roleId}/remove" + } + ] + }, + { + "module": "user-management", + "serviceName": "userService", + "apiMappings": [ + { + "apiMethodName": "getUsers", + "method": "GET", + "path": "/api/auth/admin/users" + }, + { + "apiMethodName": "getUser", + "method": "GET", + "path": "/api/auth/admin/users/{id}" + }, + { + "apiMethodName": "createUser", + "method": "POST", + "path": "/api/auth/admin/users" + }, + { + "apiMethodName": "updateUser", + "method": "PUT", + "path": "/api/auth/admin/users/{id}" + }, + { + "apiMethodName": "deleteUser", + "method": "DELETE", + "path": "/api/auth/admin/users/{id}" + }, + { + "apiMethodName": "getRoles", + "method": "GET", + "path": "/api/auth/admin/roles" + }, + { + "apiMethodName": "changePassword", + "method": "POST", + "path": "/api/auth/change-password" + }, + { + "apiMethodName": "getPasswordPolicy", + "method": "GET", + "path": "/api/auth/password-policy" + }, + { + "apiMethodName": "validatePassword", + "method": "POST", + "path": "/api/auth/validate-password" + }, + { + "apiMethodName": "checkPasswordStatus", + "method": "GET", + "path": "/api/auth/password-status" + }, + { + "apiMethodName": "getPermissions", + "method": "GET", + "path": "/api/permissions" + }, + { + "apiMethodName": "getCaptcha", + "method": "GET", + "path": "/api/auth/captcha" + }, + { + "apiMethodName": "login", + "method": "POST", + "path": "/api/auth/login" + }, + { + "apiMethodName": "logout", + "method": "POST", + "path": "/api/auth/logout" + }, + { + "apiMethodName": "getCurrentUser", + "method": "GET", + "path": "/api/auth/me" + }, + { + "apiMethodName": "changePassword", + "method": "POST", + "path": "/api/auth/change-password" + }, + { + "apiMethodName": "resetPassword", + "method": "POST", + "path": "/api/auth/reset-password" + } + ] + } +] } \ No newline at end of file diff --git a/gofaster/app/test-optimized-collection.js b/gofaster/app/test-optimized-collection.js deleted file mode 100644 index ce1fc5f..0000000 --- a/gofaster/app/test-optimized-collection.js +++ /dev/null @@ -1,11 +0,0 @@ -import { RouteCollector } from './src/renderer/modules/route-sync/RouteCollector.js'; - -const collector = new RouteCollector(); -const routes = collector.collectRoutes(); - -console.log('优化后的路由收集结果:'); -console.log('路由数量:', routes.length); -console.log('路由详情:'); -routes.forEach((route, index) => { - console.log(`${index + 1}. ${route.path} - name: ${route.name} - component: ${route.component} - description: ${route.description}`); -});