#!/usr/bin/env node /** * API 文档自动生成脚本 * 使用 OpenAPI 从后端 Swagger 自动生成前端文档 */ const fs = require('fs'); const path = require('path'); const axios = require('axios'); // 配置 const config = { // 后端服务地址 backendUrl: process.env.BACKEND_URL || 'http://localhost:3000', // 文档输出目录 docsDir: path.join(__dirname, '../src/wwjcloud/openapi/api'), // API 分组配置 apiGroups: { api: { name: '前台 API', swaggerPath: '/api/api-json', outputDir: 'frontend', description: '前台用户访问的 API 接口', prefix: '/api' }, adminapi: { name: '后台 API', swaggerPath: '/api/adminapi-json', outputDir: 'adminapi', description: '后台管理员访问的 API 接口', prefix: '/adminapi' } } }; /** * 获取 Swagger JSON */ async function getSwaggerJson(group) { try { const url = `${config.backendUrl}${group.swaggerPath}`; console.log(`正在获取 ${group.name} API 文档: ${url}`); const response = await axios.get(url); return response.data; } catch (error) { console.error(`获取 ${group.name} API 文档失败:`, error.message); return null; } } /** * 生成 API 概览文档 */ function generateOverview(group) { return `--- title: ${group.name} description: ${group.description} --- # ${group.name} ::: info ${group.name} 说明 ${group.description} ::: ## API 基础信息 - **基础路径**: \`${group.prefix}/\` - **认证方式**: JWT Bearer Token - **内容类型**: \`application/json\` - **字符编码**: \`UTF-8\` ## 模块列表 ::: tip 自动生成 此文档由后端 Swagger 自动生成,包含最新的 API 接口信息。 ::: ## 认证流程 ### 获取 Token \`\`\`http POST ${group.prefix}/auth/login Content-Type: application/json { "username": "user@example.com", "password": "password123" } \`\`\` ## 响应格式 所有 API 都遵循统一的响应格式: \`\`\`json { "code": 200, "message": "success", "data": { // 具体数据 } } \`\`\` ## 错误处理 API 错误响应格式: \`\`\`json { "code": 400, "message": "参数错误", "data": null } \`\`\` ## 状态码说明 | 状态码 | 说明 | |--------|------| | 200 | 请求成功 | | 201 | 创建成功 | | 400 | 请求参数错误 | | 401 | 未授权 | | 403 | 禁止访问 | | 404 | 资源不存在 | | 500 | 服务器内部错误 | ## 开发工具 ### Swagger UI 访问 \`${config.backendUrl}${group.prefix}\` 查看完整的 API 文档和在线测试功能。 ### 在线测试 每个 API 分组都提供 Swagger UI 界面,支持: - API 文档查看 - 在线测试 - 参数验证 - 响应示例 `; } /** * 生成模块 API 文档 */ function generateModuleDoc(group, tag, paths) { const moduleName = tag.name.toLowerCase().replace(/\s+/g, '-'); const moduleDesc = tag.description || `${moduleName} 相关接口`; let markdown = `--- title: ${moduleName} API description: ${moduleDesc} --- # ${moduleName} API ::: info ${moduleName} API ${moduleDesc} ::: ## 基础信息 - **模块路径**: \`${group.prefix}/${moduleName.toLowerCase()}/\` - **认证方式**: JWT Bearer Token(部分接口需要) - **内容类型**: \`application/json\` ## API 列表 `; // 查找该标签下的所有接口 Object.keys(paths).forEach(path => { Object.keys(paths[path]).forEach(method => { const operation = paths[path][method]; if (operation.tags && operation.tags.includes(tag.name)) { markdown += generateApiDoc(path, method, operation, group.prefix); } }); }); return markdown; } /** * 生成单个 API 文档 */ function generateApiDoc(path, method, operation, prefix) { const summary = operation.summary || operation.operationId || '未命名接口'; const description = operation.description || ''; let doc = `### ${summary}\n\n`; // 接口地址 doc += `**接口地址**: \`${method.toUpperCase()} ${path}\`\n\n`; // 接口描述 if (description) { doc += `**接口描述**: ${description}\n\n`; } // 请求参数 if (operation.parameters && operation.parameters.length > 0) { doc += `**请求参数**:\n\n`; if (method === 'get') { doc += `| 参数 | 类型 | 必填 | 说明 |\n`; doc += `|------|------|------|------|\n`; operation.parameters.forEach(param => { const required = param.required ? '是' : '否'; const type = param.type || param.schema?.type || 'string'; doc += `| ${param.name} | ${type} | ${required} | ${param.description || ''} |\n`; }); doc += `\n`; } else { doc += `\`\`\`json\n`; doc += `{\n`; operation.parameters.forEach((param, index) => { const comma = index < operation.parameters.length - 1 ? ',' : ''; const type = param.type || param.schema?.type || 'string'; doc += ` "${param.name}": "${type}"${comma}\n`; }); doc += `}\n`; doc += `\`\`\`\n\n`; } } // 请求体 if (operation.requestBody) { doc += `**请求体**:\n\n`; doc += `\`\`\`json\n`; if (operation.requestBody.content && operation.requestBody.content['application/json']) { const schema = operation.requestBody.content['application/json'].schema; if (schema && schema.properties) { doc += `{\n`; Object.keys(schema.properties).forEach((prop, index) => { const comma = index < Object.keys(schema.properties).length - 1 ? ',' : ''; const type = schema.properties[prop].type || 'string'; doc += ` "${prop}": "${type}"${comma}\n`; }); doc += `}\n`; } else { doc += `{\n`; doc += ` // 请求体内容\n`; doc += `}\n`; } } else { doc += `{\n`; doc += ` // 请求体内容\n`; doc += `}\n`; } doc += `\`\`\`\n\n`; } // 响应示例 if (operation.responses) { doc += `**响应示例**:\n\n`; const successResponse = operation.responses['200'] || operation.responses['201']; if (successResponse) { doc += `\`\`\`json\n`; doc += `{\n`; doc += ` "code": 200,\n`; doc += ` "message": "success",\n`; doc += ` "data": {\n`; if (successResponse.content && successResponse.content['application/json']) { const schema = successResponse.content['application/json'].schema; if (schema && schema.properties) { Object.keys(schema.properties).forEach((prop, index) => { const comma = index < Object.keys(schema.properties).length - 1 ? ',' : ''; const type = schema.properties[prop].type || 'string'; doc += ` "${prop}": "${type}"${comma}\n`; }); } else { doc += ` // 响应数据\n`; } } else { doc += ` // 响应数据\n`; } doc += ` }\n`; doc += `}\n`; doc += `\`\`\`\n\n`; } } doc += `---\n\n`; return doc; } /** * 保存文档到文件 */ function saveDocument(outputPath, content) { try { // 确保目录存在 const dir = path.dirname(outputPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } fs.writeFileSync(outputPath, content, 'utf8'); console.log(`文档已保存: ${outputPath}`); } catch (error) { console.error(`保存文档失败: ${outputPath}`, error.message); } } /** * 更新侧边栏配置 */ function updateSidebar(group, tags) { const sidebarPath = path.join(__dirname, '../.vitepress/config/zh.mts'); let sidebarContent = fs.readFileSync(sidebarPath, 'utf8'); // 生成模块链接 const moduleLinks = tags.map(tag => { const moduleName = tag.name.toLowerCase().replace(/\s+/g, '-'); return ` { link: 'openapi/api/${group.outputDir}/${moduleName}', text: '${tag.name} API' }`; }).join(',\n'); // 更新侧边栏配置 const sidebarPattern = new RegExp(`(text: '${group.name}',\\s*items: \\[\\s*\\{[^}]+\\},?\\s*)(\\])`, 's'); const replacement = `$1,\n${moduleLinks}\n $2`; if (sidebarContent.match(sidebarPattern)) { sidebarContent = sidebarContent.replace(sidebarPattern, replacement); fs.writeFileSync(sidebarPath, sidebarContent, 'utf8'); console.log(`侧边栏配置已更新: ${group.name}`); } } /** * 主函数 */ async function main() { console.log('开始自动生成 API 文档...\n'); for (const [key, group] of Object.entries(config.apiGroups)) { console.log(`处理 ${group.name}...`); // 获取 Swagger JSON(统一) const swaggerData = await getSwaggerJson(group); if (!swaggerData) { console.log(`跳过 ${group.name},无法获取数据\n`); continue; } // 基于分组前缀过滤 paths const filterByPrefix = (doc, prefix) => { const cloned = JSON.parse(JSON.stringify(doc)); const filteredPaths = {}; Object.keys(cloned.paths || {}).forEach((p) => { if (p.startsWith(prefix)) filteredPaths[p] = cloned.paths[p]; }); cloned.paths = filteredPaths; return cloned; }; const filtered = filterByPrefix(swaggerData, group.prefix + '/'); // 生成概览文档 const overviewContent = generateOverview(group); const overviewPath = path.join(config.docsDir, group.outputDir, 'index.md'); saveDocument(overviewPath, overviewContent); // 按标签生成模块文档 const tags = filtered.tags || []; const paths = filtered.paths || {}; for (const tag of tags) { const moduleName = tag.name.toLowerCase().replace(/\s+/g, '-'); const moduleContent = generateModuleDoc(group, tag, paths); const modulePath = path.join(config.docsDir, group.outputDir, `${moduleName}.md`); saveDocument(modulePath, moduleContent); } // 更新侧边栏配置 updateSidebar(group, tags); console.log(`${group.name} 处理完成\n`); } console.log('API 文档自动生成完成!'); console.log('\n下一步:'); console.log('1. 启动文档服务: npm run dev'); console.log('2. 访问: http://localhost:6173/wwjcloud/openapi/api/'); } // 运行脚本 if (require.main === module) { main().catch(error => { console.error('脚本执行失败:', error); process.exit(1); }); } module.exports = { config, getSwaggerJson, generateOverview, generateModuleDoc, saveDocument, updateSidebar };