2025-08-27 11:24:22 +08:00
|
|
|
|
#!/usr/bin/env node
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* API 文档自动生成脚本
|
|
|
|
|
|
* 从后端 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'),
|
|
|
|
|
|
apiGroups: {
|
2025-08-29 00:10:44 +08:00
|
|
|
|
full: {
|
|
|
|
|
|
name: '全量 API',
|
|
|
|
|
|
swaggerPath: process.env.OPENAPI_FULL_PATH || '/api-json',
|
|
|
|
|
|
outputDir: 'full',
|
|
|
|
|
|
prefix: '',
|
|
|
|
|
|
},
|
|
|
|
|
|
admin: {
|
|
|
|
|
|
name: '管理端 API',
|
|
|
|
|
|
swaggerPath: process.env.OPENAPI_ADMIN_PATH || '/api/admin-json',
|
|
|
|
|
|
outputDir: 'admin',
|
|
|
|
|
|
prefix: '/adminapi',
|
|
|
|
|
|
},
|
|
|
|
|
|
frontend: {
|
|
|
|
|
|
name: '前端 API',
|
|
|
|
|
|
swaggerPath: process.env.OPENAPI_FRONTEND_PATH || '/api/frontend-json',
|
|
|
|
|
|
outputDir: 'frontend',
|
|
|
|
|
|
prefix: '/api',
|
|
|
|
|
|
},
|
|
|
|
|
|
},
|
|
|
|
|
|
// 固定 Token(与后端 swagger.token 保持一致)
|
|
|
|
|
|
token: '9f2a7c1e4b5d8a90c3f6e2b17a4c58d0f1b2c3d4e5f67890ab12cd34ef56a789',
|
2025-08-27 11:24:22 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取 Swagger JSON
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function getSwaggerJson(group) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const url = `${config.backendUrl}${group.swaggerPath}`;
|
|
|
|
|
|
console.log(`获取 ${group.name} API: ${url}`);
|
2025-08-29 00:10:44 +08:00
|
|
|
|
const headers = {};
|
|
|
|
|
|
if (config.token) headers.Authorization = `Bearer ${config.token}`;
|
|
|
|
|
|
const response = await axios.get(url, { headers });
|
2025-08-27 11:24:22 +08:00
|
|
|
|
return response.data;
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error(`获取失败: ${error.message}`);
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成模块文档
|
|
|
|
|
|
*/
|
|
|
|
|
|
function generateModuleDoc(group, tag, paths) {
|
|
|
|
|
|
const moduleName = tag.name;
|
|
|
|
|
|
let markdown = `---
|
2025-08-29 00:10:44 +08:00
|
|
|
|
:title: ${moduleName} API
|
|
|
|
|
|
:description: ${tag.description || `${moduleName} 相关接口`}
|
2025-08-27 11:24:22 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
# ${moduleName} API
|
|
|
|
|
|
|
2025-08-29 00:10:44 +08:00
|
|
|
|
:::: info ${moduleName}
|
2025-08-27 11:24:22 +08:00
|
|
|
|
|
|
|
|
|
|
${tag.description || `${moduleName} 相关接口`}
|
|
|
|
|
|
|
2025-08-29 00:10:44 +08:00
|
|
|
|
::::
|
2025-08-27 11:24:22 +08:00
|
|
|
|
|
|
|
|
|
|
## API 列表
|
|
|
|
|
|
|
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
|
|
// 查找该标签下的接口
|
2025-08-29 00:10:44 +08:00
|
|
|
|
Object.keys(paths).forEach((p) => {
|
|
|
|
|
|
Object.keys(paths[p]).forEach((method) => {
|
|
|
|
|
|
const operation = paths[p][method];
|
2025-08-27 11:24:22 +08:00
|
|
|
|
if (operation.tags && operation.tags.includes(tag.name)) {
|
2025-08-29 00:10:44 +08:00
|
|
|
|
markdown += generateApiDoc(p, method, operation);
|
2025-08-27 11:24:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return markdown;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成单个 API 文档
|
|
|
|
|
|
*/
|
2025-08-29 00:10:44 +08:00
|
|
|
|
function generateApiDoc(pathname, method, operation) {
|
2025-08-27 11:24:22 +08:00
|
|
|
|
const summary = operation.summary || operation.operationId || '未命名接口';
|
|
|
|
|
|
|
|
|
|
|
|
let doc = `### ${summary}\n\n`;
|
2025-08-29 00:10:44 +08:00
|
|
|
|
doc += `**接口**: \`${method.toUpperCase()} ${pathname}\`\n\n`;
|
2025-08-27 11:24:22 +08:00
|
|
|
|
|
|
|
|
|
|
if (operation.description) {
|
|
|
|
|
|
doc += `**描述**: ${operation.description}\n\n`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 请求参数
|
|
|
|
|
|
if (operation.parameters && operation.parameters.length > 0) {
|
|
|
|
|
|
doc += `**参数**:\n\n`;
|
|
|
|
|
|
doc += `| 参数 | 类型 | 必填 | 说明 |\n`;
|
|
|
|
|
|
doc += `|------|------|------|------|\n`;
|
|
|
|
|
|
|
2025-08-29 00:10:44 +08:00
|
|
|
|
operation.parameters.forEach((param) => {
|
2025-08-27 11:24:22 +08:00
|
|
|
|
const required = param.required ? '是' : '否';
|
|
|
|
|
|
const type = param.type || param.schema?.type || 'string';
|
|
|
|
|
|
doc += `| ${param.name} | ${type} | ${required} | ${param.description || ''} |\n`;
|
|
|
|
|
|
});
|
|
|
|
|
|
doc += `\n`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 请求体
|
|
|
|
|
|
if (operation.requestBody) {
|
|
|
|
|
|
doc += `**请求体**:\n\n`;
|
|
|
|
|
|
doc += `\`\`\`json\n`;
|
2025-08-29 00:10:44 +08:00
|
|
|
|
doc += `{}\n`;
|
2025-08-27 11:24:22 +08:00
|
|
|
|
doc += `\`\`\`\n\n`;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 响应
|
|
|
|
|
|
doc += `**响应**:\n\n`;
|
|
|
|
|
|
doc += `\`\`\`json\n`;
|
2025-08-29 00:10:44 +08:00
|
|
|
|
doc += `{"code":200,"message":"success","data":{}}\n`;
|
2025-08-27 11:24:22 +08:00
|
|
|
|
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(`保存失败: ${error.message}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 主函数
|
|
|
|
|
|
*/
|
|
|
|
|
|
async function main() {
|
|
|
|
|
|
console.log('开始自动生成 API 文档...\n');
|
|
|
|
|
|
|
2025-08-29 00:10:44 +08:00
|
|
|
|
for (const [_key, group] of Object.entries(config.apiGroups)) {
|
2025-08-27 11:24:22 +08:00
|
|
|
|
console.log(`处理 ${group.name}...`);
|
|
|
|
|
|
|
|
|
|
|
|
const swaggerData = await getSwaggerJson(group);
|
|
|
|
|
|
if (!swaggerData) {
|
|
|
|
|
|
console.log(`跳过 ${group.name}\n`);
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const tags = swaggerData.tags || [];
|
|
|
|
|
|
const paths = swaggerData.paths || {};
|
|
|
|
|
|
|
|
|
|
|
|
// 为每个标签生成文档
|
|
|
|
|
|
for (const tag of tags) {
|
|
|
|
|
|
const moduleName = tag.name.toLowerCase().replace(/\s+/g, '-');
|
|
|
|
|
|
const content = generateModuleDoc(group, tag, paths);
|
2025-08-29 00:10:44 +08:00
|
|
|
|
const outputPath = path.join(
|
|
|
|
|
|
config.docsDir,
|
|
|
|
|
|
group.outputDir,
|
|
|
|
|
|
`${moduleName}.md`,
|
|
|
|
|
|
);
|
2025-08-27 11:24:22 +08:00
|
|
|
|
saveDocument(outputPath, content);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`${group.name} 完成\n`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('API 文档生成完成!');
|
|
|
|
|
|
console.log('访问: http://localhost:6173/wwjcloud/openapi/api/');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 运行
|
|
|
|
|
|
if (require.main === module) {
|
2025-08-29 00:10:44 +08:00
|
|
|
|
main().catch((error) => {
|
2025-08-27 11:24:22 +08:00
|
|
|
|
console.error('执行失败:', error);
|
|
|
|
|
|
process.exit(1);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = { config, getSwaggerJson, generateModuleDoc };
|