#!/usr/bin/env node import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const projectRoot = path.resolve(__dirname, '..'); // CRUD操作映射 const CRUD_OPERATIONS = { list: { method: 'POST', path: '/list', description: '获取列表' }, detail: { method: 'GET', path: '/detail', description: '获取详情' }, add: { method: 'POST', path: '/add', description: '新增' }, edit: { method: 'PUT', path: '/update', description: '更新' }, delete: { method: 'DELETE', path: '/delete', description: '删除' }, export: { method: 'GET', path: '/export', description: '导出' }, report: { method: 'GET', path: '/report', description: '报表' }, }; // 扫描所有service文件 function scanServiceFiles(dir) { const serviceFiles = []; function scanDirectory(currentDir) { const items = fs.readdirSync(currentDir, { withFileTypes: true }); for (const item of items) { const fullPath = path.join(currentDir, item.name); if (item.isDirectory()) { scanDirectory(fullPath); } else if ( item.name.endsWith('.service.js') || (item.name === 'index.js' && (fullPath.includes('/api/') || fullPath.includes('\\api\\'))) ) { serviceFiles.push({ path: fullPath, relativePath: path.relative(projectRoot, fullPath), moduleName: extractModuleName(fullPath), }); } } } scanDirectory(dir); return serviceFiles; } // 提取模块名称 function extractModuleName(filePath) { const parts = filePath.split(path.sep); // 对于 .service.js 文件 const serviceIndex = parts.findIndex((part) => part.endsWith('.service.js')); if (serviceIndex > 0) { return parts[serviceIndex - 1]; } // 对于 api/index.js 文件 const apiIndex = parts.findIndex((part) => part === 'api'); if (apiIndex > 0 && parts[apiIndex + 1] === 'index.js') { // 取api目录的上一级目录作为模块名 return parts[apiIndex - 1]; } return path.basename(filePath, '.js'); } // 解析service文件内容 async function parseServiceFile(filePath) { try { const content = fs.readFileSync(filePath, 'utf-8'); // 提取API对象 - 支持多种格式 let apiContent = null; // 格式1: const api = {...}; let apiMatch = content.match(/const api = \{([\s\S]*?)\};/); if (apiMatch) { apiContent = apiMatch[1]; } else { // 格式2: export default {...} apiMatch = content.match(/export default \{([\s\S]*?)\};?$/m); if (apiMatch) { apiContent = apiMatch[1]; } } if (!apiContent) { return null; } const apis = parseApiContent(apiContent, filePath); return { filePath, apis, }; } catch (error) { console.error(`解析文件失败 ${filePath}:`, error.message); return null; } } // 解析API内容 function parseApiContent(content, filePath) { const apis = []; // 首先解析嵌套的API结构 const nestedApiRegex = /(\w+):\s*\{([\s\S]*?)(?=\w+:\s*\{|$)/g; let match; while ((match = nestedApiRegex.exec(content)) !== null) { const [, apiName, apiBody] = match; // 检查是否包含CRUD或方法定义 if (apiBody.includes('CRUD') || apiBody.includes('=>') || apiBody.includes('request(')) { const cleanApiBody = apiBody.replace(/,\s*$/, '').trim(); const apiInfo = parseApiBody(apiName, cleanApiBody, filePath); if (apiInfo) { apis.push(apiInfo); } } } // 同时解析顶层方法 const topLevelEndpoints = parseCustomEndpoints(content); if (topLevelEndpoints.length > 0) { const topLevelApi = { name: 'api', endpoints: topLevelEndpoints, }; apis.push(topLevelApi); } return apis; } // 解析API主体 function parseApiBody(apiName, body, filePath) { const api = { name: apiName, endpoints: [], }; // 检查是否使用CRUD const crudMatch = body.match(/\.\.\.CRUD\(['"`]([^'"`]+)['"`](?:,\s*\[([^\]]*)\])?\)/); if (crudMatch) { const [, basePath, methodsStr] = crudMatch; const methods = methodsStr ? methodsStr.split(',').map((m) => m.trim().replace(/['"`]/g, '')) : ['list', 'detail', 'add', 'edit', 'delete']; // 添加CRUD端点 methods.forEach((method) => { if (CRUD_OPERATIONS[method]) { const operation = CRUD_OPERATIONS[method]; const fullUrl = basePath + operation.path; // 过滤掉以 shop/admin/ 开头的接口 if (!fullUrl.startsWith('shop/admin/')) { api.endpoints.push({ name: method, method: operation.method, url: fullUrl, description: operation.description, type: 'CRUD', }); } } }); } // 解析自定义端点 const customEndpoints = parseCustomEndpoints(body); api.endpoints.push(...customEndpoints); return api.endpoints.length > 0 ? api : null; } // 解析自定义端点 function parseCustomEndpoints(body) { const endpoints = []; // 匹配自定义方法 - 支持多行格式 const methodPattern = /(\w+):\s*\([^)]*\)\s*=>\s*([\s\S]*?)(?=,\s*\w+:|$)/g; let match; while ((match = methodPattern.exec(body)) !== null) { const [, methodName, methodBody] = match; // 跳过已知的CRUD方法和展开操作 if ( ['list', 'detail', 'add', 'edit', 'delete', 'export', 'report'].includes(methodName) || methodBody.includes('...CRUD') ) { continue; } // 解析request调用 const requestMatch = methodBody.match(/request\(\{([\s\S]*?)\}\)/); if (requestMatch) { const requestConfig = requestMatch[1]; // 提取URL和方法 const urlMatch = requestConfig.match(/url:\s*['"`]([^'"`]+)['"`]/); const methodMatch = requestConfig.match(/method:\s*['"`]([^'"`]+)['"`]/); if (urlMatch) { const url = urlMatch[1]; // 过滤掉以 shop/admin/ 开头的接口 if (!url.startsWith('shop/admin/')) { const description = generateMethodDescription(methodName); endpoints.push({ name: methodName, method: methodMatch ? methodMatch[1] : 'GET', url: url, description, type: 'Custom', }); } } } // 解析SELECT调用 const selectMatch = methodBody.match(/SELECT\(['"`]([^'"`]+)['"`]/); if (selectMatch) { const url = selectMatch[1] + '/select'; // 过滤掉以 shop/admin/ 开头的接口 if (!url.startsWith('shop/admin/')) { endpoints.push({ name: methodName, method: 'GET', url: url, description: generateMethodDescription(methodName), type: 'Custom', }); } } } return endpoints; } // 生成方法描述 function generateMethodDescription(methodName) { const descriptions = { batchShowStatus: '批量上下架商品', getStatusNum: '获取商品状态数量', select: '选择列表', getType: '获取类型', dispatch: '发货', dispatchByUpload: '上传发货单发货', total: '获取总览数据', ranking: '获取排行数据', chart: '获取图表数据', getStats: '获取统计数据', userDetail: '获取用户详情', myUsers: '获取我的用户', getUserAddresses: '获取用户地址', share: '分享相关', coupon: '优惠券相关', couponList: '优惠券列表', }; return descriptions[methodName] || `自定义接口 - ${methodName}`; } // 生成Markdown文档 function generateMarkdown(serviceData) { let markdown = `# 系统API接口文档\n\n`; markdown += `> 自动生成时间: ${new Date().toLocaleString('zh-CN')}\n\n`; markdown += `## 📋 目录\n\n`; // 生成目录 serviceData.forEach((service, index) => { markdown += `${index + 1}. [${service.moduleName}模块](#${index + 1}-${service.moduleName}模块)\n`; }); markdown += `\n---\n\n`; // 生成详细内容 serviceData.forEach((service, index) => { markdown += `## ${index + 1}. ${service.moduleName}模块\n\n`; markdown += `**文件路径**: \`${service.relativePath}\`\n\n`; if (service.data && service.data.apis) { service.data.apis.forEach((api) => { markdown += `### ${api.name}\n\n`; if (api.endpoints.length > 0) { markdown += `| 接口名称 | 请求方法 | 接口地址 | 说明 | 类型 |\n`; markdown += `|---------|---------|---------|------|------|\n`; api.endpoints.forEach((endpoint) => { markdown += `| ${endpoint.name} | ${endpoint.method} | ${endpoint.url} | ${endpoint.description} | ${endpoint.type} |\n`; }); markdown += `\n`; } }); } markdown += `---\n\n`; }); // 添加统计信息 const totalApis = serviceData.reduce((total, service) => { if (service.data && service.data.apis) { return ( total + service.data.apis.reduce((apiTotal, api) => apiTotal + api.endpoints.length, 0) ); } return total; }, 0); markdown += `## 📊 统计信息\n\n`; markdown += `- **模块总数**: ${serviceData.length}\n`; markdown += `- **接口总数**: ${totalApis}\n`; markdown += `- **生成时间**: ${new Date().toLocaleString('zh-CN')}\n\n`; return markdown; } // 主函数 async function main() { console.log('🔍 开始扫描API接口文件...\n'); // 扫描service文件 const srcDir = path.join(projectRoot, 'src'); const serviceFiles = scanServiceFiles(srcDir); console.log(`📁 找到 ${serviceFiles.length} 个service文件:\n`); serviceFiles.forEach((file) => { console.log(` - ${file.relativePath} (${file.moduleName})`); }); console.log('\n🔄 开始解析API定义...\n'); // 解析每个service文件 const serviceData = []; for (const file of serviceFiles) { console.log(`正在解析: ${file.relativePath}`); const data = await parseServiceFile(file.path); serviceData.push({ ...file, data, }); } console.log('\n📝 生成API文档...\n'); // 生成文档 const markdown = generateMarkdown(serviceData); // 保存文档 const outputPath = path.join(projectRoot, 'docs', 'api-interfaces.md'); // 确保docs目录存在 const docsDir = path.dirname(outputPath); if (!fs.existsSync(docsDir)) { fs.mkdirSync(docsDir, { recursive: true }); } fs.writeFileSync(outputPath, markdown, 'utf-8'); console.log(`✅ API文档生成完成!`); console.log(`📄 文档路径: ${outputPath}`); console.log(`📊 统计信息:`); console.log(` - 模块数量: ${serviceData.length}`); const totalApis = serviceData.reduce((total, service) => { if (service.data && service.data.apis) { return ( total + service.data.apis.reduce((apiTotal, api) => apiTotal + api.endpoints.length, 0) ); } return total; }, 0); console.log(` - 接口数量: ${totalApis}`); } main().catch(console.error);