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基础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) { return '' } } /** * 收集所有模块的API映射 * @param {Array} moduleDirs - 模块目录列表 * @returns {Array} API映射数组 */ collectApiMappings(moduleDirs) { this.apiMappings = [] moduleDirs.forEach(moduleName => { const moduleApiMappings = this.collectModuleApiMappings(moduleName) if (moduleApiMappings.length > 0) { // 将 module 下沉到每个 API 映射中(serviceName已经在extractApiMapping中添加了) moduleApiMappings.forEach(apiMapping => { this.apiMappings.push({ ...apiMapping, module: moduleName }) }) } }) 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 = [] 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) 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'] }) const self = this traverse(ast, { ObjectMethod(path) { if (path.node.key && path.node.key.type === 'Identifier') { const methodName = path.node.key.name const apiMapping = self.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 = self.extractApiMapping(path.get('init'), methodName, serviceName, servicePath) if (apiMapping) { apiMappings.push(apiMapping) } } } }) } catch (error) { } return apiMappings } /** * 从AST节点中提取API映射信息 - 优化版本:只使用@serverApiMethod注解 * @param {Object} methodPath - 方法路径 * @param {string} methodName - 方法名称 * @param {string} serviceName - 服务名称 * @param {string} servicePath - 服务文件路径 * @returns {Object|null} API映射对象 */ extractApiMapping(methodPath, methodName, serviceName, servicePath) { // 只从@serverApiMethod注解中提取信息(强规则) const annotationInfo = this.extractFromAnnotation(methodPath, servicePath) if (annotationInfo) { // 如果method是GET,跳过收集 if (annotationInfo.method === 'GET') { return null } // 获取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, serviceName: serviceName } } // 如果没有注解,返回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() return { method: method, path: path } } // 如果遇到非注释行且不是空行,停止搜索 if (line && !line.startsWith('//') && !line.startsWith('*') && !line.startsWith('/*')) { break } } } catch (error) { } return null } } module.exports = ApiCollector