|
|
|
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']
|
|
|
|
})
|
|
|
|
|
|
|
|
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) {
|
|
|
|
console.error(`❌ 分析服务文件失败: ${servicePath}`, error.message)
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|