#!/usr/bin/env node /** * API文档生成脚本 * 用于从JSDoc注释生成Apifox可导入的OpenAPI文档 */ const fs = require('fs'); const path = require('path'); // 读取文章路由文件 const articlesRoutePath = path.join(__dirname, '../routes/admin/articles.js'); const routeContent = fs.readFileSync(articlesRoutePath, 'utf8'); // 提取JSDoc注释 function extractJSDocComments(content) { // 匹配包含@api的JSDoc注释块 const jsdocRegex = /\/\*\*[\s\S]*?\*\//g; const comments = content.match(jsdocRegex) || []; const apis = []; comments.forEach((comment, index) => { // 检查是否包含@api标记 if (comment.includes('@api {') || comment.includes('@apiName')) { const api = parseJSDocComment(comment); if (api && api.method && api.path) { apis.push(api); } } }); return apis; } // 解析JSDoc注释 function parseJSDocComment(comment) { const lines = comment.split('\n'); let api = {}; lines.forEach(line => { line = line.trim().replace(/^\*\s*/, ''); // 移除JSDoc的*前缀 if (line.startsWith('@api ')) { // 解析 @api {method} path name 格式 const match = line.match(/@api\s+{(\w+)}\s+(.+?)\s+(.+)/); if (match) { api.method = match[1].toLowerCase(); api.path = match[2]; api.name = match[3]; } } else if (line.startsWith('@apiName ')) { api.name = line.replace('@apiName ', ''); } else if (line.startsWith('@apiGroup ')) { api.group = line.replace('@apiGroup ', ''); } else if (line.startsWith('@apiVersion ')) { api.version = line.replace('@apiVersion ', ''); } else if (line.startsWith('@apiDescription ')) { api.description = line.replace('@apiDescription ', ''); } else if (line.startsWith('@apiParam ')) { if (!api.parameters) api.parameters = []; // 解析参数:@apiParam {type} [name] description const paramMatch = line.match(/@apiParam\s+{([^}]+)}\s+\[?([^\]]+)\]?\s+(.+)/); if (paramMatch) { const param = { type: paramMatch[1], name: paramMatch[2], description: paramMatch[3], required: !line.includes('[') }; api.parameters.push(param); } } else if (line.startsWith('@apiSuccess ')) { if (!api.responses) api.responses = {}; const successMatch = line.match(/@apiSuccess\s+{([^}]+)}\s+(.+)/); if (successMatch) { api.responses[successMatch[1]] = successMatch[2]; } } }); return Object.keys(api).length > 0 ? api : null; } // 生成OpenAPI文档 function generateOpenAPIDoc(apis) { const openapi = { openapi: "3.0.0", info: { title: "飞鸟农业API文档", version: "1.0.0", description: "飞鸟农业平台后端API接口文档" }, servers: [ { url: "http://localhost:3000", description: "开发环境" } ], paths: {}, components: { schemas: { Article: { type: "object", properties: { id: { type: "integer", description: "文章ID" }, title: { type: "string", description: "文章标题" }, subtitle: { type: "string", description: "文章副标题" }, content: { type: "string", description: "文章内容" }, type: { type: "integer", description: "文章类型" }, img: { type: "string", description: "文章图片URL" }, date: { type: "string", format: "date-time", description: "文章日期" }, author: { type: "string", description: "作者" }, category: { type: "integer", description: "用户分类ID" }, crop: { type: "integer", description: "作物分类ID" }, isRecommended: { type: "integer", enum: [0, 1], description: "是否推荐" }, seoKeyword: { type: "string", description: "SEO关键词" }, seoDescription: { type: "string", description: "SEO描述" }, createdAt: { type: "string", format: "date-time", description: "创建时间" }, updatedAt: { type: "string", format: "date-time", description: "更新时间" }, cropInfo: { type: "object", properties: { id: { type: "integer", description: "作物ID" }, name: { type: "string", description: "作物名称" }, level: { type: "integer", description: "作物层级" }, parentId: { type: "integer", description: "父级作物ID" } } } } }, ApiResponse: { type: "object", properties: { status: { type: "boolean", description: "请求状态" }, message: { type: "string", description: "响应消息" }, data: { type: "object", description: "响应数据" }, errors: { type: "array", items: { type: "string" }, description: "错误信息" } } } } } }; // 转换API到OpenAPI格式 apis.forEach(api => { const pathKey = api.path; if (!openapi.paths[pathKey]) { openapi.paths[pathKey] = {}; } const operation = { tags: [api.group || "Default"], summary: api.name, description: api.description || "", parameters: [], responses: { "200": { description: "成功响应", content: { "application/json": { schema: { $ref: "#/components/schemas/ApiResponse" } } } }, "400": { description: "请求参数错误", content: { "application/json": { schema: { $ref: "#/components/schemas/ApiResponse" } } } }, "404": { description: "资源未找到", content: { "application/json": { schema: { $ref: "#/components/schemas/ApiResponse" } } } }, "500": { description: "服务器内部错误", content: { "application/json": { schema: { $ref: "#/components/schemas/ApiResponse" } } } } } }; // 添加参数 if (api.parameters) { api.parameters.forEach(param => { if (param.name.startsWith(':')) { // 路径参数 operation.parameters.push({ name: param.name.substring(1), in: "path", required: true, schema: { type: param.type.toLowerCase() }, description: param.description }); } else { // 查询参数 operation.parameters.push({ name: param.name, in: "query", required: param.required, schema: { type: param.type.toLowerCase() }, description: param.description }); } }); } // 添加请求体(POST/PUT请求) if (['post', 'put', 'patch'].includes(api.method)) { operation.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/Article" } } } }; } openapi.paths[pathKey][api.method] = operation; }); return openapi; } // 主函数 function main() { console.log('正在生成API文档...'); try { // 提取JSDoc注释 const apis = extractJSDocComments(routeContent); console.log(`找到 ${apis.length} 个API接口`); // 生成OpenAPI文档 const openapiDoc = generateOpenAPIDoc(apis); // 保存文档 const outputPath = path.join(__dirname, '../docs/api-docs.json'); const outputDir = path.dirname(outputPath); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } fs.writeFileSync(outputPath, JSON.stringify(openapiDoc, null, 2)); console.log(`API文档已生成: ${outputPath}`); // 同时生成YAML格式(Apifox支持) const yaml = require('js-yaml'); const yamlPath = path.join(__dirname, '../docs/api-docs.yaml'); fs.writeFileSync(yamlPath, yaml.dump(openapiDoc)); console.log(`API文档已生成: ${yamlPath}`); console.log('\n=== 如何在Apifox中导入 ==='); console.log('1. 打开Apifox'); console.log('2. 选择项目 -> 导入'); console.log('3. 选择"OpenAPI"格式'); console.log('4. 导入生成的 api-docs.yaml 或 api-docs.json 文件'); console.log('5. 完成导入后,所有接口注释都会显示在Apifox中'); } catch (error) { console.error('生成API文档失败:', error.message); process.exit(1); } } // 运行脚本 if (require.main === module) { main(); } module.exports = { extractJSDocComments, generateOpenAPIDoc };